From 687fbda629938f99b610db4c832a61f23cc65e3f Mon Sep 17 00:00:00 2001 From: Will Gordon Date: Sat, 1 Aug 2026 21:15:06 -0400 Subject: [PATCH] fix(sse): scopes CC-relay anthropic-beta to its own requestDefaults Two already-merged agentrouter commits widened usesClaudeCodeProtocol()'s native-Claude system-transform block (billing header + selectBetaFlags-derived anthropic-beta) to also run for generic CC-compatible relay connections, not just real claude traffic and agentrouter's own wire-image mimicry. selectBetaFlags() has no visibility into a relay's own providerSpecificData.requestDefaults, so its header replacement silently wiped out an earlier context-1m append and force-included redact-thinking regardless of the relay's own opt-in. Restores both for plain CC-compatible relays only; real claude/agentrouter traffic is unaffected. Also bumps four stale hardcoded Codex/Claude Code CLI version-string test assertions (0.144.1->0.146.0, 2.1.219->2.1.220) that drifted when the same two commits bumped the version constants without updating their tests, and rebaselines base.ts's frozen file-size cap for this fix's own +35 lines. --- config/quality/file-size-baseline.json | 3 +- open-sse/executors/base.ts | 35 +++++++++++++++++++ tests/unit/executor-codex.test.ts | 6 ++-- tests/unit/executor-default-base.test.ts | 2 +- tests/unit/glm-executor.test.ts | 2 +- .../unit/provider-models-route-codex.test.ts | 4 +-- 6 files changed, 44 insertions(+), 8 deletions(-) diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json index 1856362574..ba4c7b3fbc 100644 --- a/config/quality/file-size-baseline.json +++ b/config/quality/file-size-baseline.json @@ -1,4 +1,5 @@ { + "_rebaseline_2026_08_02_agentrouter_ccbeta_regression_fix": "PR #9173 (cursor-token-renewal) own growth: open-sse/executors/base.ts 1578->1613 (+35). Fixes a real regression from the same two already-merged agentrouter commits documented in _rebaseline_2026_08_02_agentrouter_protocol_dispatch above — usesClaudeCodeProtocol() widened the native-Claude system-transform block (billing header, selectBetaFlags-derived anthropic-beta) to also run for generic CC-compatible relay connections, not just real `claude` traffic and agentrouter's own wire-image mimicry. selectBetaFlags() has no visibility into a relay's own providerSpecificData.requestDefaults, so its Object.assign() silently wiped out an earlier CONTEXT_1M_BETA_HEADER append and force-included redact-thinking-2026-02-12 regardless of the relay's own redactThinking opt-in. Restores both behaviors for `usesClaudeCodeProtocol && !usesCcWireImage(this.provider)` connections only — real claude/agentrouter traffic is untouched. Covered by tests/unit/executor-default-base.test.ts's 'uses CC-compatible connection defaults to append 1M beta' test and tests/unit/cc-compatible-provider.test.ts (both pre-existing, both re-verified passing).", "_rebaseline_2026_08_02_agentrouter_protocol_dispatch": "Reconcile-onto-tip drift surfaced by PR #9173 (cursor-token-renewal): two already-merged, no-PR-branch-left commits on release/v3.8.50 (564c204ef fix(agentrouter): support Claude and Codex protocols; ec150a006 fix(agentrouter): honor alternate protocol in chat pipeline) grew open-sse/executors/base.ts 1562->1578 (Claude/Codex protocol dispatch wiring in the agentrouter executor branch) and open-sse/handlers/chatCore.ts 5020->5028 + tests/unit/chatcore-translation-paths.test.ts 2769->2776 (alternate-protocol chat-pipeline routing + companion test coverage) past their frozen caps, unrelated to this PR's own Cursor renewal changes. Same pattern as the prior release-green rebaselines (fast-gates PR->release do not run check:file-size): no offending branch left to fix in-place. Real sizes per check-file-size.mjs's own split(\"\\n\").length metric.", "_rebaseline_2026_07_24_8470_hyperagent_sticky_thread": "PR #8470 (artickc, fix/hyperagent-tool-loop-thread-sticky) own growth: open-sse/executors/hyperagent.ts 936->1025 (wc -l; check-file-size.mjs counts via split(\"\\n\").length so the gate sees 937->1026, +89, crosses the 1000 cap). Fixes a real bug where a reverse-conversion proxy (text-Intent/JSON to Claude Code native tool_calls) rewrites assistant messages between agentic tool-loop turns, breaking HyperAgent's conversation-prefix fingerprint and cold-starting the thread mid tool-loop. Adds Anthropic tool_use/tool_result flattening to extractMessageText() plus a new rootUserFingerprint()/root-key lookup tier in resolveHyperAgentThreadBinding()/storeHyperAgentThreadAfterTurn() so the thread stays sticky across the tool loop. Cohesive additions inside the existing single-file executor; not extractable without splitting the executor mid-request-flow. Covered by tests/unit/executor-hyperagent.test.ts (19/19, +5 new cases for tool_result/tool_use flattening + root-key stickiness). Pre-merge review flagged a cross-conversation root-key collision risk (tracked in the PR's own mandatory pre-merge checklist, not yet addressed) — unrelated to this file-size ratchet, tracked separately by /fix-prs.", "_rebaseline_2026_07_25_8494_capability_filter_fail_closed": "PR #8494 (fix/capability-filters-fail-closed, #8488) own growth: open-sse/services/combo.ts 3640->3693 (+53) adds a fail-closed guard after filterTargetsByRequestCompatibility() — when every eligible target is excluded by request-capability filtering (vision/tools/etc) instead of quota/health, the combo now returns an explicit `capability_mismatch` 400 (describeCapabilityFilterExhaustion, imported from combo/comboStructure.ts) rather than silently falling through to a generic no-targets error, plus a `compatFilterFailOpen` escape hatch (combo config OR settings) mirrored at both the main/auto and round-robin call sites for symmetry. combo/comboStructure.ts (previously under cap, un-frozen) grows 794->918 (+124) — new home for describeCapabilityFilterExhaustion + providerSupportsEmulatedToolCalling (#5240 emulated tool-calling exemption so fail-closed does not regress prompt-emulation-only combos like all-chatgpt-web). Irreducible orchestration wiring at the existing filter chokepoint (same precedent as #7301's universal-cooldown-retry generalization). Companion test tests/unit/combo-routing-engine.test.ts 3409->3449 (+40, fail-closed/fail-open coverage across both call sites) also rebaselined. Covered by tests/unit/8488-capability-filter-fail-closed.test.ts (new) + 95/95 passing across both files. Structural shrink of combo.ts tracked in #3501.", @@ -343,7 +344,7 @@ "_rebaseline_pr1043_minimax_tts": "Upstream port decolua/9router#1043 (toanalien) own growth: audioSpeech.ts 965->1061 (+96). Adds MiniMax T2A v2 TTS dispatch (handleMinimaxSpeech + hexToBytes helper) — provider entry was already in audioRegistry (format: minimax-tts) but no handler existed, falling through to the OpenAI-compatible default that fails (T2A has custom shape + hex-encoded audio + base_resp envelope). New branch sits next to the other inline provider branches (xiaomi-mimo, coqui, tortoise, aws-polly) — extracting would just create indirection. Covered by tests/unit/minimax-tts-1043.test.ts (3 tests, GREEN: success, base_resp error, invalid-hex).", "_rebaseline_pr4592_exclude_exhausted_auto": "Reconcile #4592 already-merged growth: combo.ts 2991->3036 (+45, terminal-status quota-cutoff exclusion in buildAutoCandidates + opt-in gate). Fast-gate PR->release does not run check:file-size.", "open-sse/executors/antigravity.ts": 1528, - "open-sse/executors/base.ts": 1578, + "open-sse/executors/base.ts": 1613, "open-sse/executors/chatgpt-web.ts": 3241, "open-sse/executors/codex.ts": 1534, "open-sse/executors/cursor.ts": 1560, diff --git a/open-sse/executors/base.ts b/open-sse/executors/base.ts index d30e122d1c..9a987bde0c 100644 --- a/open-sse/executors/base.ts +++ b/open-sse/executors/base.ts @@ -55,6 +55,7 @@ import type { ProviderRequestDefaults } from "../services/providerRequestDefault import { signRequestBody } from "../services/claudeCodeCCH.ts"; import { appendAnthropicBetaHeader, + CLAUDE_CODE_COMPATIBLE_REDACT_THINKING_BETA, CONTEXT_1M_BETA_HEADER, enforceThinkingTemperature, modelHasNativeContext1m, @@ -1216,6 +1217,40 @@ export class BaseExecutor { headers["x-api-key"] = activeCredentials?.apiKey || activeCredentials?.accessToken || ""; } + // The Object.assign() above just replaced "anthropic-beta" wholesale + // with the selectBetaFlags()-derived set, silently wiping out the + // CONTEXT_1M_BETA_HEADER appended earlier from this CC-compatible + // relay's own requestDefaults.context1m (selectBetaFlags has no + // visibility into per-connection requestDefaults — it only reasons + // about the request body shape). Restore it for CC-compatible + // relays (not wire-image/native traffic, which never went through + // that earlier append in the first place). + if (usesClaudeCodeProtocol && !usesCcWireImage(this.provider)) { + if (shouldForwardCcCompatibleContext1m) { + appendAnthropicBetaHeader(headers, CONTEXT_1M_BETA_HEADER); + } + // selectBetaFlags() always includes redact-thinking for an + // "opaque" client (no client-negotiated anthropic-beta) — correct + // for real `claude` traffic and agentrouter's wire-image + // mimicry, both of which must look identical to a genuine Claude + // Code CLI request. A plain CC-compatible relay is neither: + // redactThinking there is an explicit per-connection opt-in + // (providerSpecificData.requestDefaults), not an "opaque client" + // default. Strip it back out unless the relay's own + // requestDefaults opted in (#agentrouter regression: this whole + // block used to run only for real `claude` clients, where this + // distinction didn't exist). + const betaKey = Object.keys(headers).find( + (key) => key.toLowerCase() === "anthropic-beta" + ); + if (betaKey && ccRequestDefaults.redactThinking !== true) { + headers[betaKey] = headers[betaKey] + .split(",") + .map((value) => value.trim()) + .filter((value) => value && value !== CLAUDE_CODE_COMPATIBLE_REDACT_THINKING_BETA) + .join(","); + } + } delete headers["X-Stainless-Helper-Method"]; // OS/arch follow the host running the signed binary. Runtime version diff --git a/tests/unit/executor-codex.test.ts b/tests/unit/executor-codex.test.ts index 771930e1e5..8b07f90cb6 100644 --- a/tests/unit/executor-codex.test.ts +++ b/tests/unit/executor-codex.test.ts @@ -184,10 +184,10 @@ test("CodexExecutor.buildHeaders binds workspace ids and disables SSE accept for assert.equal(standardHeaders.Authorization, "Bearer codex-token"); assert.equal(standardHeaders.Accept, "text/event-stream"); assert.equal(standardHeaders["chatgpt-account-id"], "workspace-1"); - assert.equal(standardHeaders.Version, "0.144.1"); + assert.equal(standardHeaders.Version, "0.146.0"); assert.equal(standardHeaders["Openai-Beta"], "responses=experimental"); assert.equal(standardHeaders["X-Codex-Beta-Features"], "responses_websockets"); - assert.equal(standardHeaders["User-Agent"], "codex-cli/0.144.1 (Windows 10.0.26200; x64)"); + assert.equal(standardHeaders["User-Agent"], "codex-cli/0.146.0 (Windows 10.0.26200; x64)"); assert.equal(compactHeaders.Accept, "application/json"); }); @@ -213,7 +213,7 @@ test("CodexExecutor.buildHeaders honors safe env overrides for Version and User- }, () => { const headers = executor.buildHeaders({ accessToken: "codex-token" }, true); - assert.equal(headers.Version, "0.144.1"); + assert.equal(headers.Version, "0.146.0"); assert.equal(headers["User-Agent"], "custom-codex/9.9.9"); } ); diff --git a/tests/unit/executor-default-base.test.ts b/tests/unit/executor-default-base.test.ts index 110d40a65d..2ae6430d9d 100644 --- a/tests/unit/executor-default-base.test.ts +++ b/tests/unit/executor-default-base.test.ts @@ -1513,6 +1513,6 @@ test("DefaultExecutor.execute does not produce duplicate anthropic-version heade const sentBody = JSON.parse(capturedBody) as { system?: Array<{ text?: string }> }; assert.match( sentBody.system?.[0]?.text ?? "", - /^x-anthropic-billing-header: cc_version=2\.1\.219\.250; cc_entrypoint=cli; cch=[0-9a-f]{5};$/ + /^x-anthropic-billing-header: cc_version=2\.1\.220\.1f2; cc_entrypoint=cli; cch=[0-9a-f]{5};$/ ); }); diff --git a/tests/unit/glm-executor.test.ts b/tests/unit/glm-executor.test.ts index f0b0d9e188..d71407fff3 100644 --- a/tests/unit/glm-executor.test.ts +++ b/tests/unit/glm-executor.test.ts @@ -181,7 +181,7 @@ test("GlmExecutor separates OpenAI-compatible coding headers from Anthropic head assert.equal(anthropicHeaders["anthropic-version"], "2023-06-01"); assert.match(anthropicHeaders["anthropic-beta"], /claude-code-20250219/); assert.equal(anthropicHeaders["anthropic-dangerous-direct-browser-access"], "true"); - assert.match(anthropicHeaders["User-Agent"], /^claude-cli\/2\.1\.219 \(external, sdk-cli\)$/); + assert.match(anthropicHeaders["User-Agent"], /^claude-cli\/2\.1\.220 \(external, sdk-cli\)$/); assert.equal(anthropicHeaders["X-Stainless-Lang"], "js"); assert.equal(anthropicHeaders["X-Stainless-Runtime"], "node"); }); diff --git a/tests/unit/provider-models-route-codex.test.ts b/tests/unit/provider-models-route-codex.test.ts index 8968b435bb..828c8b1b46 100644 --- a/tests/unit/provider-models-route-codex.test.ts +++ b/tests/unit/provider-models-route-codex.test.ts @@ -159,11 +159,11 @@ test("provider models route merges live Codex models with the local catalog then assert.equal(body.discoveredCandidateCount, undefined); assert.deepEqual(seenRequests, [ { - url: "https://chatgpt.com/backend-api/codex/models?client_version=0.144.1", + url: "https://chatgpt.com/backend-api/codex/models?client_version=0.146.0", authorization: "Bearer codex-access-token", workspaceId: "account-123", originator: "codex_cli_rs", - userAgent: "codex-cli/0.144.1 (Windows 10.0.26200; x64)", + userAgent: "codex-cli/0.146.0 (Windows 10.0.26200; x64)", }, { url: "https://raw.githubusercontent.com/openai/codex/refs/heads/main/codex-rs/models-manager/models.json",