fix(qwen-web): migrate to v2 chat API with full cookie-jar replay (#3288) - #3723
Conversation
) The legacy /api/chat/completions endpoint was retired upstream and now returns 504 HTML from Alibaba's gateway for every request regardless of credentials, so token refresh could never help (#3288, discussion #2768). - Rewrite QwenWebExecutor to the two-step v2 flow: POST /api/v2/chats/new then POST /api/v2/chat/completions?chat_id=, parsing the phase-based SSE (think -> reasoning, answer -> content) into OpenAI chunks. - Replay the full browser cookie jar (cna + ssxmod_itna/itna2 + token) required by Alibaba's baxia WAF instead of only a Bearer token, plus the bx-v/bx-umidtoken/source anti-bot headers (mirrors grok-web). - Map the WAF/expired HTML page to a clear, actionable auth error instead of surfacing raw 504 HTML to the client. - webSessionCredentials: qwen-web becomes a full-cookie provider; browser login (tokenExtractionConfig) now captures the WAF cookies. - Refresh the model catalog to the current upstream ids (qwen3.7-max, qwen3.7-plus, qwen3.6-plus); legacy ids kept as executor aliases. - New helpers buildQwenCookieHeader/extractQwenToken; 17 unit tests.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Code Review
This pull request migrates the Qwen Web provider to Alibaba's v2 chat API, implementing a two-step flow that creates a chat session before initiating a phase-based SSE stream. It updates the model catalog, captures and replays necessary WAF cookies alongside the bearer token, maps thinking phases to reasoning content, and handles expired sessions gracefully. The review feedback suggests two key improvements: updating foldMessages to fold the entire conversation history to support multi-turn chats, and passing the actual request headers to the jsonResponse helper to preserve them for logging and debugging purposes.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| private foldMessages(messages: Array<{ role: string; content: unknown }>): string { | ||
| let systemContent = ""; | ||
| let userContent = ""; | ||
| for (const m of messages) { | ||
| const text = String(m.content ?? ""); | ||
| if (m.role === "system") { | ||
| systemContent += (systemContent ? "\n\n" : "") + text; | ||
| } else if (m.role === "user") { | ||
| userContent = text; | ||
| } | ||
| } | ||
| return systemContent ? `${systemContent}\n\nUser: ${userContent}` : userContent; | ||
| } |
There was a problem hiding this comment.
The current implementation of foldMessages only captures the last user message and completely ignores any previous user messages or assistant responses in the conversation history. This breaks multi-turn conversations when routed through this single-turn provider.
To preserve conversation context, we should fold the entire history (system, user, and assistant messages) into the single prompt. If there is only a single user message, we can return its content directly to avoid unnecessary prefixes.
private foldMessages(messages: Array<{ role: string; content: unknown }>): string {
if (messages.length === 1 && messages[0].role === "user") {
return String(messages[0].content ?? "");
}
let folded = "";
for (const m of messages) {
const roleName = m.role === "system" ? "System" : m.role === "user" ? "User" : "Assistant";
const text = String(m.content ?? "");
folded += `${roleName}: ${text}\n\n`;
}
return folded.trim();
}| private jsonResponse( | ||
| modelId: string, | ||
| message: Record<string, unknown>, | ||
| finishReason: string, | ||
| url: string, | ||
| transformedBody: unknown | ||
| ) { | ||
| return { | ||
| response: new Response(stream, { | ||
| headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" }, | ||
| }), | ||
| url: CHAT_URL, | ||
| headers: reqHeaders, | ||
| transformedBody: reqBody, | ||
| response: new Response( | ||
| JSON.stringify({ | ||
| id: `chatcmpl-qwen-${Date.now()}`, | ||
| object: "chat.completion", | ||
| created: Math.floor(Date.now() / 1000), | ||
| model: modelId, | ||
| choices: [{ index: 0, message, finish_reason: finishReason }], | ||
| }), | ||
| { headers: { "Content-Type": "application/json" } } | ||
| ), | ||
| url, | ||
| headers: {} as Record<string, string>, | ||
| transformedBody, | ||
| }; | ||
| } |
There was a problem hiding this comment.
The jsonResponse helper currently returns an empty object for headers (headers: {} as Record<string, string>). This loses the actual request headers (such as Authorization and Cookie) that were sent to the upstream API, which are highly valuable for logging, debugging, and audit trails.
We should update jsonResponse to accept the request headers and return them, matching the behavior of the streaming response branch.
private jsonResponse(
modelId: string,
message: Record<string, unknown>,
finishReason: string,
url: string,
headers: Record<string, string>,
transformedBody: unknown
) {
return {
response: new Response(
JSON.stringify({
id: `chatcmpl-qwen-${Date.now()}`,
object: "chat.completion",
created: Math.floor(Date.now() / 1000),
model: modelId,
choices: [{ index: 0, message, finish_reason: finishReason }],
}),
{ headers: { "Content-Type": "application/json" } }
),
url,
headers,
transformedBody,
};
}| message.tool_calls = toolCalls; | ||
| message.content = null; | ||
| } | ||
| return this.jsonResponse(modelId, message, finishReason, completionUrl, msgPayload); |
| return this.jsonResponse( | ||
| modelId, | ||
| { role: "assistant", content: finalText }, | ||
| "stop", | ||
| completionUrl, | ||
| msgPayload | ||
| ); |
Code Review SummaryStatus: 3 Issues Found (already commented) | Recommendation: Address existing feedback before merge Overview
Existing Inline Comments SummaryThe previous review (gemini-code-assist[bot]) has already identified the following issues in CRITICAL (1)
WARNING (2)
Additional Observations
Note: All identified issues are already covered by existing inline comments. Please address those comments before merging. Reviewed by laguna-m.1-20260312:free · 2,351,467 tokens |
|
Review gate: synced with |
* chore(release): open v3.8.23 development cycle * fix(anthropic): strip top_p when temperature is set to avoid 400 (#3691) Integrated into release/v3.8.23 * fix(vertex): support Vertex AI Express-mode API keys (#3690) Integrated into release/v3.8.23 * fix(stream): error on empty Claude SSE instead of synthetic success (#3689) Integrated into release/v3.8.23 * fix(oauth): stop token-refresh invalidation loop + harden proxy resolution (#3692) Integrated into release/v3.8.23 * docs: add FUNDING.yml and Support section to README (#3698) Integrated into release/v3.8.23 * feat: gemini - handle known ratelimits (#3686) Integrated into release/v3.8.23 * fix: stream combo fails over on empty content-filtered response (#3685) (#3702) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(antigravity): preserve gemini-3.1-pro high/low budget tiers (#3696) (#3703) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(auto-combo): add auto-updating model intelligence scoring (#3660) Integrated into release/v3.8.23 * fix(gemini): context-mode fallback for signatureless tool calls (#3688) (#3704) * chore(quality-gate): reconcile file-size baseline (27 files + providerLimits.ts) (#3705) * feat(vertex): dynamic model discovery via Generative Language models API (#3712) Integrated into release/v3.8.23. Vertex dynamic model discovery — surfaces image models (imagen-*, gemini-*-image), embeddings and audio from the live Generative Language catalog, with cached→static fallback and the shared parseGeminiModelsList helper. Validated: parser test 5/5, typecheck:core clean. * fix(combo): gate reasoning token buffer (#3700) Integrated into release/v3.8.23. Makes the #3588 reasoning token buffer safe and configurable: only inflates max_tokens when the model has a known, non-default output cap and the buffered value fits inside it; otherwise preserves/clamps the client limit. Adds the reasoningTokenBufferEnabled kill switch (default ON). Validated: combo-routing-engine 81/81, combo-config 25/25, combo-quality-validator-reasoning 12/12, phase1f 10/10, typecheck:core clean. * refactor(#3501): god-component Phase 1g-1j — client 4062→3408 LOC (-654) (#3717) Phase 1g-1j of #3501: client 4062→3408 LOC. Pure extraction (ProviderPlaygroundPanel, useCommandCodeAuth, useExternalLinkFlow+ExternalLinkModal, useAuthFileHandlers) + loadConnProxies ReferenceError fix + phase1f test path fix. Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> * refactor(#3501): god-component Phase 1k-1m — client 3408→2553 LOC (-855) (#3721) Phase 1k-1m of #3501: client 3408→2553 LOC. Pure extraction (useModelImportHandlers+ImportProgressModal, useModelVisibilityHandlers, ProviderModelsSection). Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> * docs(changelog): restore #3590 bullet lost on the v3.8.20 release branch The fix itself reached main pre-tag via cherry-pick #3591, but its changelog bullet (commit e33fdd4) only ever existed on release/v3.8.20 after the squash-merge. Restored under [3.8.20] per the 2026-06-12 release-branch leftover audit (_tasks/release-audit/release-leftovers-audit-2026-06-12.md). * fix(kiro): resolve quota for IAM Identity Center accounts missing a profileArn (#3722) Integrated into release/v3.8.23 * refactor(#3501): god-component Phase 1n-1s — client 2553→1376 LOC (-1177) (#3725) Phase 1n-1s of #3501: client 2553→1376 LOC. Pure extraction (ConnectionsListPanel, ConnectionsHeaderToolbar, ZedImportCard, BatchTestResultsModal, AdaptaTutorialModal, useApiKeySave + helpers). Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> * feat(model-lockout): settings UI, backend integration, error classification, and success-decay recovery (#3629) Integrated into release/v3.8.23 * refactor(#3501): god-component Phase 1t — client 1376→781 LOC (≤800 TARGET REACHED ✅) (#3727) Phase 1t of #3501: client 1376→781 LOC (≤800 reached). Original god-component 12,882→781 (−94%). Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> * fix: bundle @omniroute/opencode-plugin inside omniroute + add 'setup opencode' CLI command (#3726) Integrated into release/v3.8.23 * feat(vertex): self-tracked USD spend since account added (#3724) Integrated into release/v3.8.23 * fix(qwen-web): migrate to v2 chat API with full cookie-jar replay (#3288) (#3723) Integrated into release/v3.8.23 * fix(sse): make safeLogEvents async — 'await' in a sync function broke every chatHelpers import #3692 added a lazy 'await import(proxyEgress)' for egress-IP visibility inside safeLogEvents, which is a sync function — an ES syntax error. It went unnoticed because typecheck:core does not cover src/sse and no test in the merge gates loaded chatHelpers via tsx; any consumer that did (chat-context-relay and chat-route-coverage suites, integration harnesses) failed at module load with 'await can only be used inside an async function'. safeLogEvents is fire-and-forget logging with an outer try/catch, so making it async (and 'void'-ing the single chat.ts call site) preserves behavior exactly. Validation: tests/unit/chat-context-relay.test.ts + chat-route-coverage.test.ts went from failing-at-load to green (+14 tests destravados). * fix(sse): remove cross-provider credential leak in emergency fallback + combo/proxy audit fixes (#3699) Integrated into release/v3.8.23 * fix(executors): inject MiMoCode anti-abuse marker so free endpoint stops 403ing (#3728) Integrated into release/v3.8.23 * fix(dashboard): repair "Test all models" — toast crash, status icons, auto-hide (#3729) Integrated into release/v3.8.23 * chore(deps): bump actions/upload-artifact from 4 to 7 (#3735) Integrated into release/v3.8.23 — aligns upload-artifact to v7 (already used across ci.yml). * chore(deps): bump actions/cache from 4 to 5 (#3734) Integrated into release/v3.8.23 — actions/cache v4→v5. * chore(deps): bump actions/download-artifact from 4 to 8 (#3733) Integrated into release/v3.8.23 — download-artifact v4→v8. * feat(fallback): add OMNIROUTE_EMERGENCY_FALLBACK env switch (#3741) Adds an OMNIROUTE_EMERGENCY_FALLBACK env switch to disable the emergency budget-exhaustion fallback (reroute to free nvidia/gpt-oss-120b). Default unchanged (enabled). Closes #3739, related #2879. Integrated into release/v3.8.23. * i18n: comprehensive zh-CN translation improvements (#3736) Aligns zh-CN to en (hundreds of entries), translates batch-action labels + settings sidebar menu, adds categoryConfig/endpointTokenSaver keys, resolves __MISSING__ stubs. Sidebar/SidebarTab hardcoded strings replaced with t(). en.json purely additive (8 new sidebar.* keys, 0 removed); cli-i18n gate green. Integrated into release/v3.8.23. * chore(release): v3.8.23 — 2026-06-12 - CHANGELOG: complete v3.8.23 section (28 bullets, 27 commits) - fix(webdav): resolve promise on writeStream finish, not req end — eliminates intermittent 500 on PUT update (writeStream may not have flushed at rename time) - test(autoCombo): stub DB calls from PR #3660 in tieredRotation.test.ts to prevent 5s timeout in vitest (getModelIntelligenceBySource DB init path) - chore(env-sync): add XDG_DATA_HOME + OMNIROUTE_OPENCODE_PLUGIN_DIR to IGNORE_FROM_CODE allowlist (introduced by PR #3726 setup-open-code.mjs, not OmniRoute config vars) - chore(cli): regenerated bin/cli/api-commands/*.mjs (7 new, 27 updated) * fix(model-family): fallback lookup also tries bare model name with dots getNextFamilyFallback normalized dots-to-hyphens ("gemini-3.1-pro-high" → "gemini-3-1-pro-high") but MODEL_FAMILIES keys use the literal dot form. The lookup always missed, returning null for any model whose dots are part of the name rather than a version separator. Fallback: try MODEL_FAMILIES[lookupKey] ?? MODEL_FAMILIES[bareModel] so both naming conventions are covered. Fixes T30 test (pre-existing since v3.8.22). * feat: expose API key cost drilldown + quota % used (#3742) Adds all-time USD cost per API key in the API Key Manager, a per-key deep-link into the Cost Explorer (filtered + grouped by model), URL-param hydration of range/groupBy/apiKeyIds, and a '% used' quota display. Review adjustments: extracted URL-param parsers to a tested module (Rule #18), i18n'd the new strings (en + zh-CN), dropped the redundant webdav-handler entry already on release. Integrated into release/v3.8.23. * feat: add provider display modes — All / Configured / Compact (#3743) Replaces the Providers page configured-only toggle with All/Configured/Compact display modes (Compact = flat deduped grid, no-auth last). Persists the preference and migrates the legacy localStorage key. Rebased onto release/v3.8.23. Integrated into release/v3.8.23. * fix(cache): scope semantic-cache signature to API key (#3740) Adds the api_key_id dimension to generateSignature's SHA-256 hash so two callers with different API keys never receive each other's cached responses. Threads apiKeyId through checkSemanticCache + both write sites; migration 098 clears pre-existing key-less entries; unauthenticated requests stay isolated from keyed ones. 3 TDD tests. Integrated into release/v3.8.23. * fix(responses): apply OpenAI Responses API stream=false spec default (#3708) resolveStreamFlag now applies the stream=false-when-omitted default for sourceFormat=openai-responses (same as the existing claude path), so spec-compliant /v1/responses upstreams that return JSON no longer fall through to the wildcard-Accept heuristic and trigger STREAM_EARLY_EOF / 502. Codex CLI (stream:true) and explicit text/event-stream clients unaffected. Integrated into release/v3.8.23. * chore(release): reconcile CI gates for v3.8.23 - file-size baseline: re-freeze 8 files grown by PRs #3742/#3743/#3740 (cost drilldown, provider display modes, cache key isolation) - ARCHITECTURE.md: update executor count 55→60 (check:docs-counts drift) - .env.example: add OMNIROUTE_EMERGENCY_FALLBACK (#3741, env-doc-sync) - CHANGELOG: add formatted bullets for #3742, #3743, #3708, #3740, model-family-fallback fix; remove duplicate raw ### Fixed section * test: restore assert count to satisfy check:test-masking gate Three test files had net assertion removals after behavior-changing PRs: - chatcore-translation-paths: emergency fallback moved to routing layer (#3699) — add body error assertion + model-name guard - executor-vertex-extended: non-JSON is now Express API key (#3690) — add projects/-path guard to the express-key URL test - stream-utils: empty streams now emit error (#3685) — add code/message/ status/completePayload guards to both passthrough and translate variants All new assertions are meaningful (code enum value, 5xx range, non-empty message, onComplete must-not-fire contract). * fix(ci): move rtl-logical-classes test to ui/ so vitest:ui runner collects it --------- Co-authored-by: Felipe Almeman <4226997+zhiru@users.noreply.github.com> Co-authored-by: NOXX - Commiter <artur1992123@mail.ru> Co-authored-by: Nick Sullivan <142708+TechNickAI@users.noreply.github.com> Co-authored-by: Markus Hartung <mail@hartmark.se> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com> Co-authored-by: Randi <55005611+rdself@users.noreply.github.com> Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> Co-authored-by: Chewji <126886556+Chewji9875@users.noreply.github.com> Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com> Co-authored-by: Felipe Sartori <felipesartori.ti@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Zois Pagoulatos <zpagoulatos@hotmail.com> Co-authored-by: sdfsdfw2 <167810361+sdfsdfw2@users.noreply.github.com> Co-authored-by: Witroch4 <witalo_rocha@hotmail.com>
* chore(release): open v3.8.23 development cycle * fix(anthropic): strip top_p when temperature is set to avoid 400 (diegosouzapw#3691) Integrated into release/v3.8.23 * fix(vertex): support Vertex AI Express-mode API keys (diegosouzapw#3690) Integrated into release/v3.8.23 * fix(stream): error on empty Claude SSE instead of synthetic success (diegosouzapw#3689) Integrated into release/v3.8.23 * fix(oauth): stop token-refresh invalidation loop + harden proxy resolution (diegosouzapw#3692) Integrated into release/v3.8.23 * docs: add FUNDING.yml and Support section to README (diegosouzapw#3698) Integrated into release/v3.8.23 * feat: gemini - handle known ratelimits (diegosouzapw#3686) Integrated into release/v3.8.23 * fix: stream combo fails over on empty content-filtered response (diegosouzapw#3685) (diegosouzapw#3702) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(antigravity): preserve gemini-3.1-pro high/low budget tiers (diegosouzapw#3696) (diegosouzapw#3703) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(auto-combo): add auto-updating model intelligence scoring (diegosouzapw#3660) Integrated into release/v3.8.23 * fix(gemini): context-mode fallback for signatureless tool calls (diegosouzapw#3688) (diegosouzapw#3704) * chore(quality-gate): reconcile file-size baseline (27 files + providerLimits.ts) (diegosouzapw#3705) * feat(vertex): dynamic model discovery via Generative Language models API (diegosouzapw#3712) Integrated into release/v3.8.23. Vertex dynamic model discovery — surfaces image models (imagen-*, gemini-*-image), embeddings and audio from the live Generative Language catalog, with cached→static fallback and the shared parseGeminiModelsList helper. Validated: parser test 5/5, typecheck:core clean. * fix(combo): gate reasoning token buffer (diegosouzapw#3700) Integrated into release/v3.8.23. Makes the diegosouzapw#3588 reasoning token buffer safe and configurable: only inflates max_tokens when the model has a known, non-default output cap and the buffered value fits inside it; otherwise preserves/clamps the client limit. Adds the reasoningTokenBufferEnabled kill switch (default ON). Validated: combo-routing-engine 81/81, combo-config 25/25, combo-quality-validator-reasoning 12/12, phase1f 10/10, typecheck:core clean. * refactor(diegosouzapw#3501): god-component Phase 1g-1j — client 4062→3408 LOC (-654) (diegosouzapw#3717) Phase 1g-1j of diegosouzapw#3501: client 4062→3408 LOC. Pure extraction (ProviderPlaygroundPanel, useCommandCodeAuth, useExternalLinkFlow+ExternalLinkModal, useAuthFileHandlers) + loadConnProxies ReferenceError fix + phase1f test path fix. Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> * refactor(diegosouzapw#3501): god-component Phase 1k-1m — client 3408→2553 LOC (-855) (diegosouzapw#3721) Phase 1k-1m of diegosouzapw#3501: client 3408→2553 LOC. Pure extraction (useModelImportHandlers+ImportProgressModal, useModelVisibilityHandlers, ProviderModelsSection). Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> * docs(changelog): restore diegosouzapw#3590 bullet lost on the v3.8.20 release branch The fix itself reached main pre-tag via cherry-pick diegosouzapw#3591, but its changelog bullet (commit db04ef2) only ever existed on release/v3.8.20 after the squash-merge. Restored under [3.8.20] per the 2026-06-12 release-branch leftover audit (_tasks/release-audit/release-leftovers-audit-2026-06-12.md). * fix(kiro): resolve quota for IAM Identity Center accounts missing a profileArn (diegosouzapw#3722) Integrated into release/v3.8.23 * refactor(diegosouzapw#3501): god-component Phase 1n-1s — client 2553→1376 LOC (-1177) (diegosouzapw#3725) Phase 1n-1s of diegosouzapw#3501: client 2553→1376 LOC. Pure extraction (ConnectionsListPanel, ConnectionsHeaderToolbar, ZedImportCard, BatchTestResultsModal, AdaptaTutorialModal, useApiKeySave + helpers). Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> * feat(model-lockout): settings UI, backend integration, error classification, and success-decay recovery (diegosouzapw#3629) Integrated into release/v3.8.23 * refactor(diegosouzapw#3501): god-component Phase 1t — client 1376→781 LOC (≤800 TARGET REACHED ✅) (diegosouzapw#3727) Phase 1t of diegosouzapw#3501: client 1376→781 LOC (≤800 reached). Original god-component 12,882→781 (−94%). Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> * fix: bundle @omniroute/opencode-plugin inside omniroute + add 'setup opencode' CLI command (diegosouzapw#3726) Integrated into release/v3.8.23 * feat(vertex): self-tracked USD spend since account added (diegosouzapw#3724) Integrated into release/v3.8.23 * fix(qwen-web): migrate to v2 chat API with full cookie-jar replay (diegosouzapw#3288) (diegosouzapw#3723) Integrated into release/v3.8.23 * fix(sse): make safeLogEvents async — 'await' in a sync function broke every chatHelpers import diegosouzapw#3692 added a lazy 'await import(proxyEgress)' for egress-IP visibility inside safeLogEvents, which is a sync function — an ES syntax error. It went unnoticed because typecheck:core does not cover src/sse and no test in the merge gates loaded chatHelpers via tsx; any consumer that did (chat-context-relay and chat-route-coverage suites, integration harnesses) failed at module load with 'await can only be used inside an async function'. safeLogEvents is fire-and-forget logging with an outer try/catch, so making it async (and 'void'-ing the single chat.ts call site) preserves behavior exactly. Validation: tests/unit/chat-context-relay.test.ts + chat-route-coverage.test.ts went from failing-at-load to green (+14 tests destravados). * fix(sse): remove cross-provider credential leak in emergency fallback + combo/proxy audit fixes (diegosouzapw#3699) Integrated into release/v3.8.23 * fix(executors): inject MiMoCode anti-abuse marker so free endpoint stops 403ing (diegosouzapw#3728) Integrated into release/v3.8.23 * fix(dashboard): repair "Test all models" — toast crash, status icons, auto-hide (diegosouzapw#3729) Integrated into release/v3.8.23 * chore(deps): bump actions/upload-artifact from 4 to 7 (diegosouzapw#3735) Integrated into release/v3.8.23 — aligns upload-artifact to v7 (already used across ci.yml). * chore(deps): bump actions/cache from 4 to 5 (diegosouzapw#3734) Integrated into release/v3.8.23 — actions/cache v4→v5. * chore(deps): bump actions/download-artifact from 4 to 8 (diegosouzapw#3733) Integrated into release/v3.8.23 — download-artifact v4→v8. * feat(fallback): add OMNIROUTE_EMERGENCY_FALLBACK env switch (diegosouzapw#3741) Adds an OMNIROUTE_EMERGENCY_FALLBACK env switch to disable the emergency budget-exhaustion fallback (reroute to free nvidia/gpt-oss-120b). Default unchanged (enabled). Closes diegosouzapw#3739, related diegosouzapw#2879. Integrated into release/v3.8.23. * i18n: comprehensive zh-CN translation improvements (diegosouzapw#3736) Aligns zh-CN to en (hundreds of entries), translates batch-action labels + settings sidebar menu, adds categoryConfig/endpointTokenSaver keys, resolves __MISSING__ stubs. Sidebar/SidebarTab hardcoded strings replaced with t(). en.json purely additive (8 new sidebar.* keys, 0 removed); cli-i18n gate green. Integrated into release/v3.8.23. * chore(release): v3.8.23 — 2026-06-12 - CHANGELOG: complete v3.8.23 section (28 bullets, 27 commits) - fix(webdav): resolve promise on writeStream finish, not req end — eliminates intermittent 500 on PUT update (writeStream may not have flushed at rename time) - test(autoCombo): stub DB calls from PR diegosouzapw#3660 in tieredRotation.test.ts to prevent 5s timeout in vitest (getModelIntelligenceBySource DB init path) - chore(env-sync): add XDG_DATA_HOME + OMNIROUTE_OPENCODE_PLUGIN_DIR to IGNORE_FROM_CODE allowlist (introduced by PR diegosouzapw#3726 setup-open-code.mjs, not OmniRoute config vars) - chore(cli): regenerated bin/cli/api-commands/*.mjs (7 new, 27 updated) * fix(model-family): fallback lookup also tries bare model name with dots getNextFamilyFallback normalized dots-to-hyphens ("gemini-3.1-pro-high" → "gemini-3-1-pro-high") but MODEL_FAMILIES keys use the literal dot form. The lookup always missed, returning null for any model whose dots are part of the name rather than a version separator. Fallback: try MODEL_FAMILIES[lookupKey] ?? MODEL_FAMILIES[bareModel] so both naming conventions are covered. Fixes T30 test (pre-existing since v3.8.22). * feat: expose API key cost drilldown + quota % used (diegosouzapw#3742) Adds all-time USD cost per API key in the API Key Manager, a per-key deep-link into the Cost Explorer (filtered + grouped by model), URL-param hydration of range/groupBy/apiKeyIds, and a '% used' quota display. Review adjustments: extracted URL-param parsers to a tested module (Rule diegosouzapw#18), i18n'd the new strings (en + zh-CN), dropped the redundant webdav-handler entry already on release. Integrated into release/v3.8.23. * feat: add provider display modes — All / Configured / Compact (diegosouzapw#3743) Replaces the Providers page configured-only toggle with All/Configured/Compact display modes (Compact = flat deduped grid, no-auth last). Persists the preference and migrates the legacy localStorage key. Rebased onto release/v3.8.23. Integrated into release/v3.8.23. * fix(cache): scope semantic-cache signature to API key (diegosouzapw#3740) Adds the api_key_id dimension to generateSignature's SHA-256 hash so two callers with different API keys never receive each other's cached responses. Threads apiKeyId through checkSemanticCache + both write sites; migration 098 clears pre-existing key-less entries; unauthenticated requests stay isolated from keyed ones. 3 TDD tests. Integrated into release/v3.8.23. * fix(responses): apply OpenAI Responses API stream=false spec default (diegosouzapw#3708) resolveStreamFlag now applies the stream=false-when-omitted default for sourceFormat=openai-responses (same as the existing claude path), so spec-compliant /v1/responses upstreams that return JSON no longer fall through to the wildcard-Accept heuristic and trigger STREAM_EARLY_EOF / 502. Codex CLI (stream:true) and explicit text/event-stream clients unaffected. Integrated into release/v3.8.23. * chore(release): reconcile CI gates for v3.8.23 - file-size baseline: re-freeze 8 files grown by PRs diegosouzapw#3742/diegosouzapw#3743/diegosouzapw#3740 (cost drilldown, provider display modes, cache key isolation) - ARCHITECTURE.md: update executor count 55→60 (check:docs-counts drift) - .env.example: add OMNIROUTE_EMERGENCY_FALLBACK (diegosouzapw#3741, env-doc-sync) - CHANGELOG: add formatted bullets for diegosouzapw#3742, diegosouzapw#3743, diegosouzapw#3708, diegosouzapw#3740, model-family-fallback fix; remove duplicate raw ### Fixed section * test: restore assert count to satisfy check:test-masking gate Three test files had net assertion removals after behavior-changing PRs: - chatcore-translation-paths: emergency fallback moved to routing layer (diegosouzapw#3699) — add body error assertion + model-name guard - executor-vertex-extended: non-JSON is now Express API key (diegosouzapw#3690) — add projects/-path guard to the express-key URL test - stream-utils: empty streams now emit error (diegosouzapw#3685) — add code/message/ status/completePayload guards to both passthrough and translate variants All new assertions are meaningful (code enum value, 5xx range, non-empty message, onComplete must-not-fire contract). * fix(ci): move rtl-logical-classes test to ui/ so vitest:ui runner collects it --------- Co-authored-by: Felipe Almeman <4226997+zhiru@users.noreply.github.com> Co-authored-by: NOXX - Commiter <artur1992123@mail.ru> Co-authored-by: Nick Sullivan <142708+TechNickAI@users.noreply.github.com> Co-authored-by: Markus Hartung <mail@hartmark.se> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com> Co-authored-by: Randi <55005611+rdself@users.noreply.github.com> Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> Co-authored-by: Chewji <126886556+Chewji9875@users.noreply.github.com> Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com> Co-authored-by: Felipe Sartori <felipesartori.ti@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Zois Pagoulatos <zpagoulatos@hotmail.com> Co-authored-by: sdfsdfw2 <167810361+sdfsdfw2@users.noreply.github.com> Co-authored-by: Witroch4 <witalo_rocha@hotmail.com>
* chore(release): open v3.8.23 development cycle * fix(anthropic): strip top_p when temperature is set to avoid 400 (diegosouzapw#3691) Integrated into release/v3.8.23 * fix(vertex): support Vertex AI Express-mode API keys (diegosouzapw#3690) Integrated into release/v3.8.23 * fix(stream): error on empty Claude SSE instead of synthetic success (diegosouzapw#3689) Integrated into release/v3.8.23 * fix(oauth): stop token-refresh invalidation loop + harden proxy resolution (diegosouzapw#3692) Integrated into release/v3.8.23 * docs: add FUNDING.yml and Support section to README (diegosouzapw#3698) Integrated into release/v3.8.23 * feat: gemini - handle known ratelimits (diegosouzapw#3686) Integrated into release/v3.8.23 * fix: stream combo fails over on empty content-filtered response (diegosouzapw#3685) (diegosouzapw#3702) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(antigravity): preserve gemini-3.1-pro high/low budget tiers (diegosouzapw#3696) (diegosouzapw#3703) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(auto-combo): add auto-updating model intelligence scoring (diegosouzapw#3660) Integrated into release/v3.8.23 * fix(gemini): context-mode fallback for signatureless tool calls (diegosouzapw#3688) (diegosouzapw#3704) * chore(quality-gate): reconcile file-size baseline (27 files + providerLimits.ts) (diegosouzapw#3705) * feat(vertex): dynamic model discovery via Generative Language models API (diegosouzapw#3712) Integrated into release/v3.8.23. Vertex dynamic model discovery — surfaces image models (imagen-*, gemini-*-image), embeddings and audio from the live Generative Language catalog, with cached→static fallback and the shared parseGeminiModelsList helper. Validated: parser test 5/5, typecheck:core clean. * fix(combo): gate reasoning token buffer (diegosouzapw#3700) Integrated into release/v3.8.23. Makes the diegosouzapw#3588 reasoning token buffer safe and configurable: only inflates max_tokens when the model has a known, non-default output cap and the buffered value fits inside it; otherwise preserves/clamps the client limit. Adds the reasoningTokenBufferEnabled kill switch (default ON). Validated: combo-routing-engine 81/81, combo-config 25/25, combo-quality-validator-reasoning 12/12, phase1f 10/10, typecheck:core clean. * refactor(diegosouzapw#3501): god-component Phase 1g-1j — client 4062→3408 LOC (-654) (diegosouzapw#3717) Phase 1g-1j of diegosouzapw#3501: client 4062→3408 LOC. Pure extraction (ProviderPlaygroundPanel, useCommandCodeAuth, useExternalLinkFlow+ExternalLinkModal, useAuthFileHandlers) + loadConnProxies ReferenceError fix + phase1f test path fix. Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> * refactor(diegosouzapw#3501): god-component Phase 1k-1m — client 3408→2553 LOC (-855) (diegosouzapw#3721) Phase 1k-1m of diegosouzapw#3501: client 3408→2553 LOC. Pure extraction (useModelImportHandlers+ImportProgressModal, useModelVisibilityHandlers, ProviderModelsSection). Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> * docs(changelog): restore diegosouzapw#3590 bullet lost on the v3.8.20 release branch The fix itself reached main pre-tag via cherry-pick diegosouzapw#3591, but its changelog bullet (commit e33fdd4) only ever existed on release/v3.8.20 after the squash-merge. Restored under [3.8.20] per the 2026-06-12 release-branch leftover audit (_tasks/release-audit/release-leftovers-audit-2026-06-12.md). * fix(kiro): resolve quota for IAM Identity Center accounts missing a profileArn (diegosouzapw#3722) Integrated into release/v3.8.23 * refactor(diegosouzapw#3501): god-component Phase 1n-1s — client 2553→1376 LOC (-1177) (diegosouzapw#3725) Phase 1n-1s of diegosouzapw#3501: client 2553→1376 LOC. Pure extraction (ConnectionsListPanel, ConnectionsHeaderToolbar, ZedImportCard, BatchTestResultsModal, AdaptaTutorialModal, useApiKeySave + helpers). Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> * feat(model-lockout): settings UI, backend integration, error classification, and success-decay recovery (diegosouzapw#3629) Integrated into release/v3.8.23 * refactor(diegosouzapw#3501): god-component Phase 1t — client 1376→781 LOC (≤800 TARGET REACHED ✅) (diegosouzapw#3727) Phase 1t of diegosouzapw#3501: client 1376→781 LOC (≤800 reached). Original god-component 12,882→781 (−94%). Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> * fix: bundle @omniroute/opencode-plugin inside omniroute + add 'setup opencode' CLI command (diegosouzapw#3726) Integrated into release/v3.8.23 * feat(vertex): self-tracked USD spend since account added (diegosouzapw#3724) Integrated into release/v3.8.23 * fix(qwen-web): migrate to v2 chat API with full cookie-jar replay (diegosouzapw#3288) (diegosouzapw#3723) Integrated into release/v3.8.23 * fix(sse): make safeLogEvents async — 'await' in a sync function broke every chatHelpers import diegosouzapw#3692 added a lazy 'await import(proxyEgress)' for egress-IP visibility inside safeLogEvents, which is a sync function — an ES syntax error. It went unnoticed because typecheck:core does not cover src/sse and no test in the merge gates loaded chatHelpers via tsx; any consumer that did (chat-context-relay and chat-route-coverage suites, integration harnesses) failed at module load with 'await can only be used inside an async function'. safeLogEvents is fire-and-forget logging with an outer try/catch, so making it async (and 'void'-ing the single chat.ts call site) preserves behavior exactly. Validation: tests/unit/chat-context-relay.test.ts + chat-route-coverage.test.ts went from failing-at-load to green (+14 tests destravados). * fix(sse): remove cross-provider credential leak in emergency fallback + combo/proxy audit fixes (diegosouzapw#3699) Integrated into release/v3.8.23 * fix(executors): inject MiMoCode anti-abuse marker so free endpoint stops 403ing (diegosouzapw#3728) Integrated into release/v3.8.23 * fix(dashboard): repair "Test all models" — toast crash, status icons, auto-hide (diegosouzapw#3729) Integrated into release/v3.8.23 * chore(deps): bump actions/upload-artifact from 4 to 7 (diegosouzapw#3735) Integrated into release/v3.8.23 — aligns upload-artifact to v7 (already used across ci.yml). * chore(deps): bump actions/cache from 4 to 5 (diegosouzapw#3734) Integrated into release/v3.8.23 — actions/cache v4→v5. * chore(deps): bump actions/download-artifact from 4 to 8 (diegosouzapw#3733) Integrated into release/v3.8.23 — download-artifact v4→v8. * feat(fallback): add OMNIROUTE_EMERGENCY_FALLBACK env switch (diegosouzapw#3741) Adds an OMNIROUTE_EMERGENCY_FALLBACK env switch to disable the emergency budget-exhaustion fallback (reroute to free nvidia/gpt-oss-120b). Default unchanged (enabled). Closes diegosouzapw#3739, related diegosouzapw#2879. Integrated into release/v3.8.23. * i18n: comprehensive zh-CN translation improvements (diegosouzapw#3736) Aligns zh-CN to en (hundreds of entries), translates batch-action labels + settings sidebar menu, adds categoryConfig/endpointTokenSaver keys, resolves __MISSING__ stubs. Sidebar/SidebarTab hardcoded strings replaced with t(). en.json purely additive (8 new sidebar.* keys, 0 removed); cli-i18n gate green. Integrated into release/v3.8.23. * chore(release): v3.8.23 — 2026-06-12 - CHANGELOG: complete v3.8.23 section (28 bullets, 27 commits) - fix(webdav): resolve promise on writeStream finish, not req end — eliminates intermittent 500 on PUT update (writeStream may not have flushed at rename time) - test(autoCombo): stub DB calls from PR diegosouzapw#3660 in tieredRotation.test.ts to prevent 5s timeout in vitest (getModelIntelligenceBySource DB init path) - chore(env-sync): add XDG_DATA_HOME + OMNIROUTE_OPENCODE_PLUGIN_DIR to IGNORE_FROM_CODE allowlist (introduced by PR diegosouzapw#3726 setup-open-code.mjs, not OmniRoute config vars) - chore(cli): regenerated bin/cli/api-commands/*.mjs (7 new, 27 updated) * fix(model-family): fallback lookup also tries bare model name with dots getNextFamilyFallback normalized dots-to-hyphens ("gemini-3.1-pro-high" → "gemini-3-1-pro-high") but MODEL_FAMILIES keys use the literal dot form. The lookup always missed, returning null for any model whose dots are part of the name rather than a version separator. Fallback: try MODEL_FAMILIES[lookupKey] ?? MODEL_FAMILIES[bareModel] so both naming conventions are covered. Fixes T30 test (pre-existing since v3.8.22). * feat: expose API key cost drilldown + quota % used (diegosouzapw#3742) Adds all-time USD cost per API key in the API Key Manager, a per-key deep-link into the Cost Explorer (filtered + grouped by model), URL-param hydration of range/groupBy/apiKeyIds, and a '% used' quota display. Review adjustments: extracted URL-param parsers to a tested module (Rule diegosouzapw#18), i18n'd the new strings (en + zh-CN), dropped the redundant webdav-handler entry already on release. Integrated into release/v3.8.23. * feat: add provider display modes — All / Configured / Compact (diegosouzapw#3743) Replaces the Providers page configured-only toggle with All/Configured/Compact display modes (Compact = flat deduped grid, no-auth last). Persists the preference and migrates the legacy localStorage key. Rebased onto release/v3.8.23. Integrated into release/v3.8.23. * fix(cache): scope semantic-cache signature to API key (diegosouzapw#3740) Adds the api_key_id dimension to generateSignature's SHA-256 hash so two callers with different API keys never receive each other's cached responses. Threads apiKeyId through checkSemanticCache + both write sites; migration 098 clears pre-existing key-less entries; unauthenticated requests stay isolated from keyed ones. 3 TDD tests. Integrated into release/v3.8.23. * fix(responses): apply OpenAI Responses API stream=false spec default (diegosouzapw#3708) resolveStreamFlag now applies the stream=false-when-omitted default for sourceFormat=openai-responses (same as the existing claude path), so spec-compliant /v1/responses upstreams that return JSON no longer fall through to the wildcard-Accept heuristic and trigger STREAM_EARLY_EOF / 502. Codex CLI (stream:true) and explicit text/event-stream clients unaffected. Integrated into release/v3.8.23. * chore(release): reconcile CI gates for v3.8.23 - file-size baseline: re-freeze 8 files grown by PRs diegosouzapw#3742/diegosouzapw#3743/diegosouzapw#3740 (cost drilldown, provider display modes, cache key isolation) - ARCHITECTURE.md: update executor count 55→60 (check:docs-counts drift) - .env.example: add OMNIROUTE_EMERGENCY_FALLBACK (diegosouzapw#3741, env-doc-sync) - CHANGELOG: add formatted bullets for diegosouzapw#3742, diegosouzapw#3743, diegosouzapw#3708, diegosouzapw#3740, model-family-fallback fix; remove duplicate raw ### Fixed section * test: restore assert count to satisfy check:test-masking gate Three test files had net assertion removals after behavior-changing PRs: - chatcore-translation-paths: emergency fallback moved to routing layer (diegosouzapw#3699) — add body error assertion + model-name guard - executor-vertex-extended: non-JSON is now Express API key (diegosouzapw#3690) — add projects/-path guard to the express-key URL test - stream-utils: empty streams now emit error (diegosouzapw#3685) — add code/message/ status/completePayload guards to both passthrough and translate variants All new assertions are meaningful (code enum value, 5xx range, non-empty message, onComplete must-not-fire contract). * fix(ci): move rtl-logical-classes test to ui/ so vitest:ui runner collects it --------- Co-authored-by: Felipe Almeman <4226997+zhiru@users.noreply.github.com> Co-authored-by: NOXX - Commiter <artur1992123@mail.ru> Co-authored-by: Nick Sullivan <142708+TechNickAI@users.noreply.github.com> Co-authored-by: Markus Hartung <mail@hartmark.se> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com> Co-authored-by: Randi <55005611+rdself@users.noreply.github.com> Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> Co-authored-by: Chewji <126886556+Chewji9875@users.noreply.github.com> Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com> Co-authored-by: Felipe Sartori <felipesartori.ti@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Zois Pagoulatos <zpagoulatos@hotmail.com> Co-authored-by: sdfsdfw2 <167810361+sdfsdfw2@users.noreply.github.com> Co-authored-by: Witroch4 <witalo_rocha@hotmail.com>
…egosouzapw#3288) (diegosouzapw#3723) Integrated into release/v3.8.23
* chore(release): open v3.8.23 development cycle * fix(anthropic): strip top_p when temperature is set to avoid 400 (diegosouzapw#3691) Integrated into release/v3.8.23 * fix(vertex): support Vertex AI Express-mode API keys (diegosouzapw#3690) Integrated into release/v3.8.23 * fix(stream): error on empty Claude SSE instead of synthetic success (diegosouzapw#3689) Integrated into release/v3.8.23 * fix(oauth): stop token-refresh invalidation loop + harden proxy resolution (diegosouzapw#3692) Integrated into release/v3.8.23 * docs: add FUNDING.yml and Support section to README (diegosouzapw#3698) Integrated into release/v3.8.23 * feat: gemini - handle known ratelimits (diegosouzapw#3686) Integrated into release/v3.8.23 * fix: stream combo fails over on empty content-filtered response (diegosouzapw#3685) (diegosouzapw#3702) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(antigravity): preserve gemini-3.1-pro high/low budget tiers (diegosouzapw#3696) (diegosouzapw#3703) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(auto-combo): add auto-updating model intelligence scoring (diegosouzapw#3660) Integrated into release/v3.8.23 * fix(gemini): context-mode fallback for signatureless tool calls (diegosouzapw#3688) (diegosouzapw#3704) * chore(quality-gate): reconcile file-size baseline (27 files + providerLimits.ts) (diegosouzapw#3705) * feat(vertex): dynamic model discovery via Generative Language models API (diegosouzapw#3712) Integrated into release/v3.8.23. Vertex dynamic model discovery — surfaces image models (imagen-*, gemini-*-image), embeddings and audio from the live Generative Language catalog, with cached→static fallback and the shared parseGeminiModelsList helper. Validated: parser test 5/5, typecheck:core clean. * fix(combo): gate reasoning token buffer (diegosouzapw#3700) Integrated into release/v3.8.23. Makes the diegosouzapw#3588 reasoning token buffer safe and configurable: only inflates max_tokens when the model has a known, non-default output cap and the buffered value fits inside it; otherwise preserves/clamps the client limit. Adds the reasoningTokenBufferEnabled kill switch (default ON). Validated: combo-routing-engine 81/81, combo-config 25/25, combo-quality-validator-reasoning 12/12, phase1f 10/10, typecheck:core clean. * refactor(diegosouzapw#3501): god-component Phase 1g-1j — client 4062→3408 LOC (-654) (diegosouzapw#3717) Phase 1g-1j of diegosouzapw#3501: client 4062→3408 LOC. Pure extraction (ProviderPlaygroundPanel, useCommandCodeAuth, useExternalLinkFlow+ExternalLinkModal, useAuthFileHandlers) + loadConnProxies ReferenceError fix + phase1f test path fix. Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> * refactor(diegosouzapw#3501): god-component Phase 1k-1m — client 3408→2553 LOC (-855) (diegosouzapw#3721) Phase 1k-1m of diegosouzapw#3501: client 3408→2553 LOC. Pure extraction (useModelImportHandlers+ImportProgressModal, useModelVisibilityHandlers, ProviderModelsSection). Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> * docs(changelog): restore diegosouzapw#3590 bullet lost on the v3.8.20 release branch The fix itself reached main pre-tag via cherry-pick diegosouzapw#3591, but its changelog bullet (commit a6b99843f) only ever existed on release/v3.8.20 after the squash-merge. Restored under [3.8.20] per the 2026-06-12 release-branch leftover audit (_tasks/release-audit/release-leftovers-audit-2026-06-12.md). * fix(kiro): resolve quota for IAM Identity Center accounts missing a profileArn (diegosouzapw#3722) Integrated into release/v3.8.23 * refactor(diegosouzapw#3501): god-component Phase 1n-1s — client 2553→1376 LOC (-1177) (diegosouzapw#3725) Phase 1n-1s of diegosouzapw#3501: client 2553→1376 LOC. Pure extraction (ConnectionsListPanel, ConnectionsHeaderToolbar, ZedImportCard, BatchTestResultsModal, AdaptaTutorialModal, useApiKeySave + helpers). Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> * feat(model-lockout): settings UI, backend integration, error classification, and success-decay recovery (diegosouzapw#3629) Integrated into release/v3.8.23 * refactor(diegosouzapw#3501): god-component Phase 1t — client 1376→781 LOC (≤800 TARGET REACHED ✅) (diegosouzapw#3727) Phase 1t of diegosouzapw#3501: client 1376→781 LOC (≤800 reached). Original god-component 12,882→781 (−94%). Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> * fix: bundle @omniroute/opencode-plugin inside omniroute + add 'setup opencode' CLI command (diegosouzapw#3726) Integrated into release/v3.8.23 * feat(vertex): self-tracked USD spend since account added (diegosouzapw#3724) Integrated into release/v3.8.23 * fix(qwen-web): migrate to v2 chat API with full cookie-jar replay (diegosouzapw#3288) (diegosouzapw#3723) Integrated into release/v3.8.23 * fix(sse): make safeLogEvents async — 'await' in a sync function broke every chatHelpers import diegosouzapw#3692 added a lazy 'await import(proxyEgress)' for egress-IP visibility inside safeLogEvents, which is a sync function — an ES syntax error. It went unnoticed because typecheck:core does not cover src/sse and no test in the merge gates loaded chatHelpers via tsx; any consumer that did (chat-context-relay and chat-route-coverage suites, integration harnesses) failed at module load with 'await can only be used inside an async function'. safeLogEvents is fire-and-forget logging with an outer try/catch, so making it async (and 'void'-ing the single chat.ts call site) preserves behavior exactly. Validation: tests/unit/chat-context-relay.test.ts + chat-route-coverage.test.ts went from failing-at-load to green (+14 tests destravados). * fix(sse): remove cross-provider credential leak in emergency fallback + combo/proxy audit fixes (diegosouzapw#3699) Integrated into release/v3.8.23 * fix(executors): inject MiMoCode anti-abuse marker so free endpoint stops 403ing (diegosouzapw#3728) Integrated into release/v3.8.23 * fix(dashboard): repair "Test all models" — toast crash, status icons, auto-hide (diegosouzapw#3729) Integrated into release/v3.8.23 * chore(deps): bump actions/upload-artifact from 4 to 7 (diegosouzapw#3735) Integrated into release/v3.8.23 — aligns upload-artifact to v7 (already used across ci.yml). * chore(deps): bump actions/cache from 4 to 5 (diegosouzapw#3734) Integrated into release/v3.8.23 — actions/cache v4→v5. * chore(deps): bump actions/download-artifact from 4 to 8 (diegosouzapw#3733) Integrated into release/v3.8.23 — download-artifact v4→v8. * feat(fallback): add OMNIROUTE_EMERGENCY_FALLBACK env switch (diegosouzapw#3741) Adds an OMNIROUTE_EMERGENCY_FALLBACK env switch to disable the emergency budget-exhaustion fallback (reroute to free nvidia/gpt-oss-120b). Default unchanged (enabled). Closes diegosouzapw#3739, related diegosouzapw#2879. Integrated into release/v3.8.23. * i18n: comprehensive zh-CN translation improvements (diegosouzapw#3736) Aligns zh-CN to en (hundreds of entries), translates batch-action labels + settings sidebar menu, adds categoryConfig/endpointTokenSaver keys, resolves __MISSING__ stubs. Sidebar/SidebarTab hardcoded strings replaced with t(). en.json purely additive (8 new sidebar.* keys, 0 removed); cli-i18n gate green. Integrated into release/v3.8.23. * chore(release): v3.8.23 — 2026-06-12 - CHANGELOG: complete v3.8.23 section (28 bullets, 27 commits) - fix(webdav): resolve promise on writeStream finish, not req end — eliminates intermittent 500 on PUT update (writeStream may not have flushed at rename time) - test(autoCombo): stub DB calls from PR diegosouzapw#3660 in tieredRotation.test.ts to prevent 5s timeout in vitest (getModelIntelligenceBySource DB init path) - chore(env-sync): add XDG_DATA_HOME + OMNIROUTE_OPENCODE_PLUGIN_DIR to IGNORE_FROM_CODE allowlist (introduced by PR diegosouzapw#3726 setup-open-code.mjs, not OmniRoute config vars) - chore(cli): regenerated bin/cli/api-commands/*.mjs (7 new, 27 updated) * fix(model-family): fallback lookup also tries bare model name with dots getNextFamilyFallback normalized dots-to-hyphens ("gemini-3.1-pro-high" → "gemini-3-1-pro-high") but MODEL_FAMILIES keys use the literal dot form. The lookup always missed, returning null for any model whose dots are part of the name rather than a version separator. Fallback: try MODEL_FAMILIES[lookupKey] ?? MODEL_FAMILIES[bareModel] so both naming conventions are covered. Fixes T30 test (pre-existing since v3.8.22). * feat: expose API key cost drilldown + quota % used (diegosouzapw#3742) Adds all-time USD cost per API key in the API Key Manager, a per-key deep-link into the Cost Explorer (filtered + grouped by model), URL-param hydration of range/groupBy/apiKeyIds, and a '% used' quota display. Review adjustments: extracted URL-param parsers to a tested module (Rule diegosouzapw#18), i18n'd the new strings (en + zh-CN), dropped the redundant webdav-handler entry already on release. Integrated into release/v3.8.23. * feat: add provider display modes — All / Configured / Compact (diegosouzapw#3743) Replaces the Providers page configured-only toggle with All/Configured/Compact display modes (Compact = flat deduped grid, no-auth last). Persists the preference and migrates the legacy localStorage key. Rebased onto release/v3.8.23. Integrated into release/v3.8.23. * fix(cache): scope semantic-cache signature to API key (diegosouzapw#3740) Adds the api_key_id dimension to generateSignature's SHA-256 hash so two callers with different API keys never receive each other's cached responses. Threads apiKeyId through checkSemanticCache + both write sites; migration 098 clears pre-existing key-less entries; unauthenticated requests stay isolated from keyed ones. 3 TDD tests. Integrated into release/v3.8.23. * fix(responses): apply OpenAI Responses API stream=false spec default (diegosouzapw#3708) resolveStreamFlag now applies the stream=false-when-omitted default for sourceFormat=openai-responses (same as the existing claude path), so spec-compliant /v1/responses upstreams that return JSON no longer fall through to the wildcard-Accept heuristic and trigger STREAM_EARLY_EOF / 502. Codex CLI (stream:true) and explicit text/event-stream clients unaffected. Integrated into release/v3.8.23. * chore(release): reconcile CI gates for v3.8.23 - file-size baseline: re-freeze 8 files grown by PRs diegosouzapw#3742/diegosouzapw#3743/diegosouzapw#3740 (cost drilldown, provider display modes, cache key isolation) - ARCHITECTURE.md: update executor count 55→60 (check:docs-counts drift) - .env.example: add OMNIROUTE_EMERGENCY_FALLBACK (diegosouzapw#3741, env-doc-sync) - CHANGELOG: add formatted bullets for diegosouzapw#3742, diegosouzapw#3743, diegosouzapw#3708, diegosouzapw#3740, model-family-fallback fix; remove duplicate raw ### Fixed section * test: restore assert count to satisfy check:test-masking gate Three test files had net assertion removals after behavior-changing PRs: - chatcore-translation-paths: emergency fallback moved to routing layer (diegosouzapw#3699) — add body error assertion + model-name guard - executor-vertex-extended: non-JSON is now Express API key (diegosouzapw#3690) — add projects/-path guard to the express-key URL test - stream-utils: empty streams now emit error (diegosouzapw#3685) — add code/message/ status/completePayload guards to both passthrough and translate variants All new assertions are meaningful (code enum value, 5xx range, non-empty message, onComplete must-not-fire contract). * fix(ci): move rtl-logical-classes test to ui/ so vitest:ui runner collects it --------- Co-authored-by: Felipe Almeman <4226997+zhiru@users.noreply.github.com> Co-authored-by: NOXX - Commiter <artur1992123@mail.ru> Co-authored-by: Nick Sullivan <142708+TechNickAI@users.noreply.github.com> Co-authored-by: Markus Hartung <mail@hartmark.se> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com> Co-authored-by: Randi <55005611+rdself@users.noreply.github.com> Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> Co-authored-by: Chewji <126886556+Chewji9875@users.noreply.github.com> Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com> Co-authored-by: Felipe Sartori <felipesartori.ti@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Zois Pagoulatos <zpagoulatos@hotmail.com> Co-authored-by: sdfsdfw2 <167810361+sdfsdfw2@users.noreply.github.com> Co-authored-by: Witroch4 <witalo_rocha@hotmail.com>
What
Migrates the Qwen Web (Tongyi) provider (
qwen-web) from the retired v1 chat API to the current v2 flow, fixing #3288 and the follow-up in discussion #2768.Why
The legacy endpoint
https://chat.qwen.ai/api/chat/completionswas retired upstream. It now returns504HTML from Alibaba's gateway (alibaba-ga) for every request — verified with no credentials at all — so no amount of token refreshing could ever work. The reporter (@r3c0nshade) tried fresh tokens from multiple accounts and Chrome profiles; all failed because the endpoint itself is dead.Probe evidence (2026-06-12, plain HTTP):
POST /api/chat/completions(old executor)text/html, gatewayalibaba-ga, for every requestPOST /api/v2/chat/completionstext/html— Aliyun WAF JS challengePOST /api/v2/chats/newtext/html— same WAF challengeGET /api/modelsqwen3.7-plus/max,qwen3.6-plusChanges
open-sse/executors/qwen-web.ts— rewritten to the two-step v2 flow:POST /api/v2/chats/new→POST /api/v2/chat/completions?chat_id=. Parses the phase-based SSE (think/thinking_summary→reasoning_content,answer/null → content) into OpenAI chunks for both streaming and non-streaming. Maps the WAF/expired HTML page to a clear, actionable auth error (401) instead of surfacing raw 504 HTML.cna,ssxmod_itna,ssxmod_itna2,token, ...), not just a Bearer token. NewbuildQwenCookieHeader/extractQwenTokenhelpers inwebCookieAuth.ts, mirroring howgrok-webreplays its anti-bot cookies. Also sends thebx-v/bx-umidtoken/source: webheaders.webSessionCredentials.ts—qwen-webbecomes a full-cookie provider (kind: "cookie",acceptsFullCookieHeader: true) with an updated placeholder instructing users to paste the full Cookie header.tokenExtractionConfig.ts— browser login now captures the WAF cookies (cna,ssxmod_itna,ssxmod_itna2) alongside the token.providerRegistry.ts+freeModelCatalog.data.tsupdated to the current upstream ids (qwen3.7-max,qwen3.7-plus,qwen3.6-plus), no longerdiscontinued. Legacy ids (qwen-plus,qwen3-max, ...) still resolve via the executor'sMODEL_ALIASES.Tests (Rule #18 — TDD)
The fix is grounded in two independent reference implementations (gpt4free
Qwen.py, Chat2APIqwen-ai.ts) and pinned by 17 new unit tests:executor-qwen-web.test.ts(11): v2 two-step flow + payload shape, full cookie-jar + bearer replay on every call, anti-bot headers, think→reasoning mapping, WAF-504-HTML → clean auth error, streaming chunk transform, bare-token back-compat, legacy-id alias mapping, registry/catalog assertions.web-cookie-auth.test.ts(6):buildQwenCookieHeader/extractQwenToken.Gates:
lint0 errors,typecheck:coreclean,check:any-budget:t11pass,check:cyclespass, 110 related unit tests green. The two pre-existing vitest failures (free-pool-tab,AutoComboCatalog) and the docs-sync version mismatch are unrelated to this change (confirmed via clean-tree baseline-compare).Residual validation note
Unit tests validate the request shape, SSE parsing, and error handling deterministically — the established validation path for OmniRoute's web-cookie executors. The one thing CI cannot exercise is whether Alibaba's live WAF accepts a replayed cookie jar from a real logged-in session (true of every web-cookie provider). A final live confirmation with a real
qwen-websession (VPS or browser) is recommended before declaring #3288 fully closed end-to-end.Closes #3288.