From 6fd3f61fd2dff7ef83a85529c1f0cf010029cb2b Mon Sep 17 00:00:00 2001 From: Will Gordon Date: Tue, 4 Aug 2026 09:40:06 -0400 Subject: [PATCH] fix(sse): reconciles rebase-onto-tip drift for 9006 Two categories of inherited base-branch breakage surfaced when rebasing onto release/v3.8.50's latest tip, both confirmed unrelated to this PR's own diff: - check:file-size: base.ts and chat.ts drifted further past their frozen caps via already-merged commits (7163081f5 and others) that didn't rebaseline after growing them. Documented and bumped in file-size-baseline.json. - chat-helpers.test.ts: two gpt-5.5 routing assertions predate #9275 (fix(routing): bare model ids route to codex first), which deliberately made gpt-5.5 route to codex unconditionally, regardless of which other providers are active. Confirmed via #9275's own commit message and code comments this is intentional, not a regression; verified reproducible on the raw base tip alone, with no changes from this PR involved. Updated both assertions and their names to match the new, intentional default. --- config/quality/file-size-baseline.json | 5 +++-- tests/unit/chat-helpers.test.ts | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json index 4fec1559ee..36aeac94ed 100644 --- a/config/quality/file-size-baseline.json +++ b/config/quality/file-size-baseline.json @@ -1,4 +1,5 @@ { + "_rebaseline_2026_08_04_9006_reconcile_onto_tip": "PR #9006 (fix/vertex-claude-catalog-dispatch) rebase-onto-tip reconciliation, 5 days after the PR's own _rebaseline_2026_07_30_9006 entry below. Two further inherited drifts, neither this PR's own growth (its own diff still touches neither open-sse/executors/base.ts nor src/sse/handlers/chat.ts): (1) src/sse/handlers/chat.ts 1846->1847 (+1), same root cause as the original entry (fast-gates PR->release does not run check:file-size) — another already-merged PR added one more line since. (2) open-sse/executors/base.ts 1578->1623 (+45): commit 7163081f5 fix(agentrouter): retry on 400 content-blocked + burst guard (#9323), merged directly to release/v3.8.50 between this PR's last sync and now, grew base.ts without updating its baseline entry. No offending branch left to fix in either case; verified via git diff against upstream/release/v3.8.50 that this PR's own commits do not touch either file.", "_rebaseline_2026_07_30_9006_vertex_claude_catalog_dispatch": "PR #9006 (fix/vertex-claude-catalog-dispatch): three files, two causes. (1) src/sse/handlers/chat.ts 1845->1846 (+1): NOT this PR's own growth — this PR never touches chat.ts at all. Measured 1846 (split(\"\\n\").length) at this PR's own merge-base (before any of its 11 commits), so the drift was already inherited from already-merged PRs on release/v3.8.50 (fast-gates PR->release do not run check:file-size, same root cause as _rebaseline_2026_07_25_v3849_basered_filesize and _rebaseline_2026_07_02_5798_release_green) — no offending branch left to fix. (2) src/sse/services/auth.ts 2508->2512 (+4 net, after extraction — see below) and open-sse/handlers/chatCore.ts 5020->5023 (+3, comment-only): genuine own growth. auth.ts adds Vertex 403 PERMISSION_DENIED disambiguation (Google's google.rpc.ErrorInfo proto distinguishes a connection-wide cause — SERVICE_DISABLED, or IAM_PERMISSION_DENIED against a project-level resource — from a model-specific one scoped to a .../models/ resource), added mid-PR after a quality-gate reviewer flagged the plan's originally-accepted \"Vertex 403 always -> per-model lockout\" trade-off. The actual classification logic (~40 lines) was EXTRACTED into a new leaf module src/sse/services/vertexErrorClassifier.ts (mirrors the googApiKeyAuth.ts precedent, _rebaseline_2026_07_14_7034_goog_api_key), leaving only the irreducible call-site wiring in the frozen file: a 1-line import plus widening the existing #3027 per-model-403 guard condition. chatCore.ts's +3 is a pure comment expansion (no functional change) clarifying that the adjacent effort-suffix strip is no longer unconditional for every provider, requested by a separate quality-gate code-reviewer finding; not extractable (it's a comment). Auth.ts's disambiguation logic covered by 3 new test cases in tests/unit/vertex-passthrough-model-lockout.test.ts (SERVICE_DISABLED, IAM_PERMISSION_DENIED+model-resource, IAM_PERMISSION_DENIED+project-resource) plus a 4th regression test for a multi-detail-body correlation bug (reason and resource must be read from the SAME ErrorInfo detail, not independently regexed across the whole body) found by an adversarial quality-gate pass and fixed before merge.", "_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": 1623, "open-sse/executors/chatgpt-web.ts": 3241, "open-sse/executors/codex.ts": 1534, "open-sse/executors/cursor.ts": 1560, @@ -401,7 +402,7 @@ "src/shared/components/RequestLoggerV2.tsx": 1629, "src/shared/components/analytics/charts.tsx": 1035, "src/shared/services/cliRuntime.ts": 1122, - "src/sse/handlers/chat.ts": 1846, + "src/sse/handlers/chat.ts": 1847, "src/sse/services/auth.ts": 2512, "tests/unit/account-fallback-service.test.ts": 1572, "tests/unit/provider-validation-specialty.test.ts": 2980, diff --git a/tests/unit/chat-helpers.test.ts b/tests/unit/chat-helpers.test.ts index 4841a3de47..c1eb85afaa 100644 --- a/tests/unit/chat-helpers.test.ts +++ b/tests/unit/chat-helpers.test.ts @@ -160,7 +160,12 @@ test("resolveModelOrError routes Codex native compact gpt-5.5 requests to Codex" assert.equal(result.model, "gpt-5.5"); }); -test("resolveModelOrError keeps non-Codex gpt-5.5 Responses requests on OpenAI", async () => { +test("resolveModelOrError routes bare gpt-5.5 Responses requests to Codex regardless of client user-agent", async () => { + // #9275: gpt-5.5 is now in CODEX_NATIVE_UNPREFIXED_MODELS — bare-id requests + // always route to codex, even from a non-Codex-CLI client, so the Codex CLI + // default is honored deterministically instead of racing other providers + // that also catalog the id. Prefix the model id (e.g. openai/gpt-5.5) to opt + // into a different provider. const result = await resolveModelOrError( "gpt-5.5", { model: "gpt-5.5", input: "hello" }, @@ -168,7 +173,7 @@ test("resolveModelOrError keeps non-Codex gpt-5.5 Responses requests on OpenAI", { "user-agent": "OpenAI/Node" } ); - assert.equal(result.provider, "openai"); + assert.equal(result.provider, "codex"); assert.equal(result.model, "gpt-5.5"); }); @@ -187,7 +192,12 @@ test("resolveModelOrError routes bare gpt-5.5 to Codex medium when Codex is the assert.equal(result.targetFormat, "openai-responses"); }); -test("resolveModelOrError keeps bare gpt-5.5 on OpenAI when OpenAI is the only active account", async () => { +test("resolveModelOrError routes bare gpt-5.5 to Codex even when OpenAI is the only active account", async () => { + // #9275: the codex-first default for gpt-5.5 is unconditional — it does not + // fall back to whichever OTHER provider happens to be active. If codex has + // no active connection, execution surfaces a "No active credentials" error + // with candidate-prefix hints (see handleNoCredentials, #9275) rather than + // silently routing to openai here. await seedConnection("openai"); const result = await resolveModelOrError( @@ -197,7 +207,7 @@ test("resolveModelOrError keeps bare gpt-5.5 on OpenAI when OpenAI is the only a { "user-agent": "OpenAI/Node" } ); - assert.equal(result.provider, "openai"); + assert.equal(result.provider, "codex"); assert.equal(result.model, "gpt-5.5"); });