Skip to content

fix(copilot): route responses-only models (grok-4.x, gpt-5.x) to /responses API - #2

Closed
MaxMoldmann wants to merge 2437 commits into
masterfrom
fix/copilot-responses-api-routing
Closed

MaxMoldmann wants to merge 2437 commits into
masterfrom
fix/copilot-responses-api-routing

Conversation

@MaxMoldmann

Copy link
Copy Markdown
Owner

Problem

Models like grok-4.5, grok-4.6, grok-4.7, gpt-5.3-codex, gpt-5.5, and other newer Copilot models fail with HTTP 400 unsupported_api_for_model when selected in jcode. The root cause is that Copilot only exposes these models via the OpenAI Responses API (/responses endpoint), not /chat/completions.

From the /models catalog for these models:

{ "supported_endpoints": ["/responses"] }

Fix

Read supported_endpoints from the /models catalog at startup. For any model that lists /responses but not /chat/completions, route requests to /responses instead.

Changed Files

  • crates/jcode-base/src/auth/copilot.rs - Add supported_endpoints: Vec<String> to CopilotModelInfo; add needs_responses_api() method.
  • crates/jcode-provider-copilot-runtime/src/lib.rs - Add responses_model_ids set populated from /models on startup. In stream_request, branch on model_needs_responses_api(): convert messages to Responses API input items and POST to /responses. Add process_responses_sse_stream to parse SSE events.
  • crates/jcode-provider-copilot-runtime/Cargo.toml - Add jcode-provider-openai dependency.
  • crates/jcode-tui/src/tui/app/commands.rs - Add unsupported_api_for_model to is_fatal_model_endpoint_error.

Tests

34/34 copilot-runtime unit tests pass. 7 new needs_responses_api auth tests, 3 new routing tests added.

Live integration test against api.githubcopilot.com:

Check Result
/models lists grok-4.5/4.6/4.7 as responses-only ✓ 13 responses-only models confirmed
/chat/completions for gpt-5.3-codex ✓ HTTP 400 unsupported_api_for_model (root cause)
/responses streaming for gpt-5.3-codex ✓ HTTP 200, text delta received
/responses streaming for grok-4.5 ✓ HTTP 200
/responses streaming for grok-4.6 ✓ HTTP 200
/responses streaming for grok-4.7 ✓ HTTP 200

Affected Models

gpt-5.3-codex, gpt-5.4-mini, gpt-5.5, gpt-5.6-luna, gpt-5.6-sol, gpt-5.6-terra, gpt-6-astra, gpt-6-luna, gpt-6-sol, grok-4.5, grok-4.6, grok-4.7, mai-code-1.1-flash

Closes 1jehuang#1128

…afe config

Preserve contributor ancestry while integrating only 57d5878..bffc2d3 on the current approved-PR candidate. Do not import the old branch tree or unrelated divergent changes.

Keep thought signatures on persisted tool calls and provider resume handles separate from jcode session IDs. Retain Gemini/Antigravity compaction and Gemini schema/client compatibility changes.

Review corrections: do not enable Cursor summary compaction until its prompt truncation is safe. Resolve Gemini config without sticky process-environment exports, preserve the project alias, and invalidate cached runtime state on route/project changes. Apply compaction caps on construction and refresh them before requests while retaining the uncapped model window. Restrict HTTP retries by method/status/quota, honor bounded Retry-After hints, and avoid replaying onboarding.

Validation: provider/schema/message suites, config reload and compaction suites, local HTTP retry fixture, actual runtime cache replacement, and two-turn agent signature/session-identity regression all pass. Shared-target artifacts were rebuilt under the host Cargo gate after a stale dependency caused a false missing-method failure. Live Gemini acceptance remains untested because credentials are unavailable.
* ci: classify PR labels with Jev semantic scope

* ci: pin validated Jev semantic labeler revision

* ci: allow manually labeling an existing PR
Configure root reasoning effort independently for light and deep swarm modes.
* ci: label PRs after current-head Greptile reviews

* ci: isolate trusted review concurrency and pin verified action
Preserve current master and original PR ancestry. The two formatting fixes are already present; apply only the three missing workflow fixes. Duplicate-rejecting YAML parsing passes for every workflow, and negative controls reproduce each old duplicate env failure. Other workflow semantics are unchanged.
Apply only the two scoped contribution commits while preserving current master and original PR ancestry. Keep fallback branch and automatic pull policies unchanged.
Preserve current master and original author history. Resolve terminal test overlap by retaining tmux extended-key coverage and checking the Kitty push sequence specifically rather than rejecting modifyOtherKeys. Candidate is pending combined validation.
… fixes

The exact scoped three-fix patch is already present on current master. Preserve original contribution ancestry without replacing newer code. Current candidate tests and public-interface validation follow.
…ixes

The scoped three commits are patch-equivalent and all changed blobs matched current master before the other approved integrations. Preserve original PR ancestry without replacing newer code.
…ixes

The branch fixes were previously integrated as c6a2a2d and 343e483. Preserve the current tree, including subsequent PDF and focus-revision support. Validated harness API, bridge, and SDK tests serially plus TypeScript typechecking.
zipadoodlez and others added 29 commits September 23, 2026 10:10
…g#1400)

* fix(tui): give drag-edge autoscroll its own step, not the wheel momentum path

Holding the mouse at the top or bottom edge of the chat pane while
drag-selecting routed through scroll_copy_selection_pane ->
enqueue_mouse_scroll, the mouse-wheel momentum path. That path infers flick
force from the gap between events: a ~16ms autoscroll tick always looked like
a hard flick, so each tick queued min(3 * 2, 5) = 5 lines against a 30-line
cap, while each call drained up to 3 lines and the frame drain took 3 more.
The queue saturated and drained a flat 3 lines/frame (~180 lines/s at 60fps),
and after release the leftover queue kept draining for ~10 frames, which is
the reported glide.

The drag autoscroll now steps exactly one line through the wheel's per-line
primitive (apply_mouse_scroll_step) and never touches the queue. Its rate
comes from a fixed REDRAW_COPY_AUTOSCROLL tick rather than inheriting
redraw_fps, so the speed is a property of the gesture, not the display. The
initial nudge is applied once when the drag enters the edge band (or flips
direction); while the cursor stays in the band the tick loop owns scrolling.

Wheel behavior is untouched: enqueue_mouse_scroll, the velocity multiplier,
the queue, and the ease-out drain are all unchanged, and
scroll_copy_selection_pane still routes wheel events through them. Only the
programmatic caller changed.

Fixes 1jehuang#1332

* test(tui): cover the drag-edge autoscroll rate and release

Regression test for the reported bug: one tick moves exactly one line (never a
velocity-scaled wheel notch, and never accelerating while held), the tick
cadence is pinned to REDRAW_COPY_AUTOSCROLL instead of the refresh rate, and
the view does not drift after release (the old momentum glide).

* refactor(tui): trim comments around the autoscroll fix

Shorter comments on the cadence const, the redraw branch, and the new step
helper, and drop the re-export added only for the test, which now names
crate::tui::redraw_schedule::REDRAW_COPY_AUTOSCROLL directly.

* fix(tui): keep the display cadence while dragging at a pane edge

The drag-edge autoscroll pinned the redraw tick to its own 30ms cadence,
which precedes the processing/streaming branch in the scheduler. Holding a
drag at the edge while output streamed therefore dropped live redraws from
the configured rate (60fps by default) to 33fps. Use the faster of the two
cadences: the tick stays at the display rate while the gesture still never
ticks slower than its own step cadence.

Also clear the armed edge autoscroll when copy-selection mode exits. A drag
that later started at the same pane and edge compared equal to the stale
value and skipped its entry nudge.

Addresses the greptile review on 1jehuang#1400.

* revert(tui): keep the drag-edge autoscroll cadence fixed

Reverts the display-cadence change from the previous commit. Taking
`min(REDRAW_COPY_AUTOSCROLL, fast_interval)` let the drag advance at the
display rate instead: one scroll step per tick, so about 62.5 lines per
second at a 60fps setting and 125 at 120fps, against the 33.3 lines per
second the gesture is defined by.

Settling on the fixed cadence: the gesture rate is a property of the
gesture, not of the display. The cost is that a held edge drag ticks at
33fps while output streams, which is the trade accepted in the review on
1jehuang#1400. A separate wall-clock step deadline would buy both, at the price of
another field to reset in every exit path for a sub-second interaction.

Kept: `exit_copy_selection_mode` clears the armed edge autoscroll, so a
re-entered drag at the same pane and edge keeps its entry nudge.
…ity (1jehuang#1399)

* style: attribute palette overrides by role identity, not color proximity

`remap_literal_using` matched every buffer color against the nearest
overridden role default in Oklab space, so a single override repainted
unrelated roles: `/colors dim` also moved `tool`, `border`, `md_dim`, and the
`user_bg`/`selection_bg` backgrounds.

Only a color that *is* a role's default is now attributed to that role (named
colors keep their explicit mapping). Ad hoc `rgb(...)` literals carry no role
and are left alone, which is the trade: configurable means role-tagged, so give
a shade a role if it should follow `/colors`. No proximity guess, no bleed, and
an override can never touch another role's output.

Deletes `remap_literal_using`, `remap_literal_with`, `remap_literal`,
`adapt_color`, `match_target`, the legacy `adapt_buffer_for_palette`, and the
literal-coverage tests. `FAMILY_RADIUS` and `role_for_rendered` stay as
frame-measurement tooling for the harmony topology test; `palette_literals.rs`
stays as the light-contrast corpus. Default palettes are unchanged.

Part of 1jehuang#1397. Supersedes 1jehuang#1394.

* test(style): serialize palette and theme tests on one shared lock

The palette and theme-mode test modules each held their own TEST_LOCK while
mutating the same process-global palette and THEME_MODE, so a theme test could
flip Light between a palette test's Dark setup and its assertion.
light_theme_interaction mutated both globals with no lock at all.

One crate-level STYLE_TEST_LOCK now covers every test that touches either
global, held through setup, assertion, and restore.

---------

Co-authored-by: zipadoodlez <zipadoodlez@users.noreply.github.com>
…GUI clients

- jcode-base: Claude /limit-reset contract (read-only at-wall offer lookup,
  profile-pinned organization, confirmed claim, cache and cooldown invalidation)
- jcode-base: account-scoped OpenAI reset preparation and GUI review details
- protocol/daemon: invalidate_anthropic_usage, and both invalidations are now
  lightweight one-shot control requests
- harness API/SDKs: invalidate_usage request for Rust and TypeScript
- CLI: jcode usage --json reports redeemable banked resets per login
paused_jcode_shell_command is only used by tests, and escape_shell_single_quotes
is always called fully qualified, so both imports warned on macOS and tripped
the zero-warning budget in the macOS Build & Test job.
After the CLI launcher bundle was dropped, only tests call it, so macOS builds
reported it as dead code and exceeded the zero-warning budget.
…on id

Reload handoff re-execs clients with --resume <id>. When the id existed only
server-side, find_session_by_name_or_id fell through to a title scan that
parsed every session file (~2600 files, 1.1GB), stalling client startup ~15s.
Generated ids can never match a short name or title, so bail early.
Jcode talks to provider APIs directly (Claude via the native Anthropic
OAuth/API runtime) rather than shelling out to vendor CLIs. Drop the
deprecated jcode-provider-claude-cli-runtime crate, MultiProvider's claude
slot and use_claude_cli flag, and the ClaudeSubprocess provider choice.

--provider claude-subprocess remains a hidden alias for claude, and
JCODE_USE_CLAUDE_CLI is now ignored with a warning. test_api and the real
provider smoke script now exercise the direct Anthropic runtime.
…the aws CLI

Enable aws-config's credentials-login feature so ProfileFileCredentialsProvider
reads `aws login` (login_session) profiles natively, and drop the
`aws configure export-credentials` subprocess.
Voice sends wait on classification. Batches share identical state and are
independent, so run them with try_join_all instead of sequential round trips.
Streaming events went through block_in_place, handing the worker core to a
fresh blocking thread per delta. With Tokio's default 512-thread, 10 s
keep-alive pool and glibc's per-thread arenas, one desktop connection grew
the bridge to 69 threads and ~123 MB of anonymous memory.

- Only enter block_in_place for the event kinds that read session files.
- Bound the blocking pool (8) with a 2 s keep-alive.
- Cap glibc arenas at 2 and trim after heavy requests, large frames, and
  client disconnect.
- Release oversized frame buffers instead of pinning their capacity.
- Cap session-scan stat workers at 4.
…ead of ACP

Grok Build no longer spawns `grok agent stdio`. It calls the Grok CLI chat
proxy (https://cli-chat-proxy.grok.com/v1, OpenAI-compatible) directly through
the OpenAI-compatible runtime, so Jcode owns tool execution.

- Auth: the xAI OIDC session in $GROK_HOME/auth.json (default ~/.grok/auth.json).
  Only the https://auth.x.ai::<grok-cli client id> entry is used. expires_at
  (or the JWT exp claim) is honoured, the token is refreshed via refresh_token
  with a 60s skew, and a 401 forces one refresh-and-replay. The token is re-read
  for each request.
- Identity: requests present the official Grok CLI headers (User-Agent
  grok-cli/<ver>, X-XAI-Token-Auth: xai-grok-cli, x-grok-client-version,
  x-grok-client-identifier: grok-shell, x-grok-client-surface, and per-turn
  x-grok-model-override / x-grok-conv-id / x-grok-req-id). These were taken
  from the Grok CLI 1.0.41 binary. JCODE_GROK_CLI_VERSION overrides the version.
- Login: native xAI OAuth device flow (Grok CLI client id) in both the CLI and
  the TUI. It writes to the shared Grok credential store. The managed Grok
  binary download and the ACP bin/tests/dependency have been removed.
- Route ids (grok-build:<model>, grok-build-acp) and /model selection are
  unchanged.
…afe batch

Typesafe latency is bimodal and sticky per connection (~150ms or 2-12s).
Voice insert/send waits on Jev, so race duplicates on separate clients
after 300/700/1500ms and take the first valid answer. Primary failures
still return immediately and are never duplicated. Typesafe direct accepts
all 26 voice questions in one request, and batches now run concurrently.

Measured live: unhedged median ~5s, hedged median ~0.7-1.1s.
Managed Jcode Cloud hosts authorize a fresh key for each connection and
publish their host keys through the control plane. SshConnectOptions can
now supply that identity exclusively, pin known_hosts without consulting
user or system files, and ignore user SSH config. Adds an ssh_prompt
example that creates a session and runs one prompt over SSH.
Agents restarted the bridge by hand from bash (kill, then setsid nohup).
When the agent's own session went through that bridge, the kill cut its
connection, the turn was interrupted before the relaunch ran, and every
Desktop panel was stranded on a dead socket.

reload-bridge (in selfdev and desktop_selfdev) runs the restart as a
daemon-owned background task that survives the caller disconnecting. It
preflights the new binary, records the old bridge's command line from its
peer PID, stops it, starts the new one, verifies the socket accepts, and
relaunches the old command line if the new bridge exits or never accepts.
Prompts now steer agents to it instead of manual restarts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Grok not usable on GitHub Copilot subscription