Repository navigation
fix(stream): accept the buffer size glm.ts has been passing since #12179 - #12925
Merged
diegosouzapw merged 3 commits intoSep 10, 2026
Merged
diegosouzapw merged 3 commits into
diegosouzapw merged 3 commits into
Conversation
This was referenced Sep 7, 2026
Merged
…aper-inference#12179 createSSETransformStreamWithLogger, which takes 15. Two things followed. The visible one: `open-sse/executors/glm.ts(244,5) TS2554` fails the API Route Typecheck gate, and that gate is "reject new errors not in the frozen baseline" -- so it fails on every open PR in the repo, not only on changes that touch this file. The quiet one: the argument was dropped, and createSSEStream hard-coded `{ highWaterMark: 16384 }` on both queues. The 64 KB the call site asks for never reached the stream, so the perf change that hunk describes has not been in effect at all. Adds `streamBufferBytes` to StreamOptions, defaulting to the 16384 every provider used before it was configurable, threads it through the convenience wrapper as a 16th parameter, and uses it for both queues. glm.ts is unchanged: its existing call now compiles and takes effect. The default is a parameter default, not `|| DEFAULT`, so a deliberate 0 -- a queue that applies backpressure immediately -- is honoured rather than silently replaced.
…le exit Two follow-ups on the rebased branch. glm.ts now passes the budget. The tip had already cleared the TS2554 by deleting the 16th argument, leaving a comment saying the highWaterMark stays at the helper default — so the queue budget cheaper-inference#12179 asked for was still not applied, and this PR's new parameter had no caller. Wiring the call site is what turns the capability into the fix. The test file also no longer hangs. Every stream it builds arms a 10s idle watchdog via setInterval in createSSEStream's start, and nothing cancelled them, so node:test kept waiting on a non-empty event loop long after the assertions passed. Cancelling each readable in an after hook runs the TransformStream's cancel handler, which clears the timer: the file reports in ~7s instead of running until the runner is killed.
diegosouzapw
force-pushed
the
fix/glm-stream-buffer-arity
branch
from
September 10, 2026 21:25
5324bc0 to
6824c48
Compare
diegosouzapw
merged commit Sep 10, 2026
af49d49
into
cheaper-inference:release/v3.8.51
3 of 7 checks passed
This was referenced Sep 11, 2026
diegosouzapw
added a commit
that referenced
this pull request
Sep 11, 2026
Four base-reds that appeared on the tip today, none owned by any open PR: - providers-constants-split / provider-node-reserved-prefix: GreenPT (#13024) and EURouter (#13025) move the totals 238 -> 240 and 408 -> 410. - provider-translate-path-golden: same two providers, regenerated with UPDATE_GOLDEN=1; the snapshot delta is purely additive. - glm-sse-transform-arity: #12925 declared the buffer-size slot GLM had been filling since #12179. The invariant is unchanged — the value must land in a real parameter, never a dropped positional — so the guard now checks it from the helper side and requires the named constant at the call site instead of a magic literal. - injection-guard-scan-bound-3932: #13104 split the 16 KB budget between head and tail, so 'outside the window' is now the middle of an oversized body, not its end. The 16 KB ceiling these cases protect is untouched. Co-authored-by: diegosouzapw <[email protected]>
7 tasks done
diegosouzapw
added a commit
to initguru/OmniRoute
that referenced
this pull request
Sep 16, 2026
…ng parameter createSSETransformStreamWithLogger gained a new requestedThinking parameter inserted before customToolNames. glm.ts's translateSseResponse still called it with the pre-existing positional argument list, so the new parameter silently absorbed the old customToolNames slot, and the GLM_STREAM_BUFFER_BYTES tuning value (cheaper-inference#12925) landed on requestToolIdentityMap instead of streamBufferBytes — a TS2345 (number is not assignable to Map<...> | null) caught by check:open-sse-typecheck, and a real loss of GLM's 64KB stream buffer budget. Insert an explicit `undefined` for requestedThinking to restore the original alignment. Co-authored-by: diegosouzapw <[email protected]>
diegosouzapw
added a commit
that referenced
this pull request
Sep 17, 2026
…ive thinking (#12905) * fix(thinking): recognize adaptive thinking + parse/scrub DSML tool-call markers Two defects combined to break DeepSeek-V4-Flash turns and raise 502 empty_response on Claude Code autocompact. Defect 1 — DSML tool-call markers leaked as visible content: DeepSeek-V4-Flash occasionally emits tool calls in a non-standard DSML text format using full-width pipes instead of the OpenAI tool_calls JSON. Two shapes appear in production call logs: - complete block: <|DSML|:Read><path>...</path></|DSML|:Read> - stray closers (truncated call): </|DSML|parameter></|DSML|invoke> </|DSML|tool_calls>, sometimes trailing a system-prompt echo The openai-compatible path never parsed these, so the markers leaked to the client as visible content and the turn ended incomplete. Fix: add open-sse/utils/dsmlToolCalls.ts — parseDsmlToolCalls() converts complete DSML blocks into OpenAI tool_calls and strips stray closing markers from content (streaming-safe via a holdback for partial openers). Wire it into the response translator before extractXmlInvokeBlocks so DSML and XML invoke tool calls share the same pending queue. Defect 2 — adaptive thinking silently suppressed: A prior inline === 'enabled' check on body.thinking.type silently suppressed adaptive (the intent Claude Code actually sends), so reasoning was dropped. The model then emitted DSML tool-call markers as plain text, producing an incomplete stop finish. Fix: use hasActiveClaudeThinking() (which recognizes enabled AND adaptive) to set requestedThinking, thread it through stream.ts and translator state, and gate thinking block emission on state.requestedThinking so upstream reasoning_content only relays when the client opted in. Tests: 29/29 (6 dsml-tool-calls, 5 thinking-active-claude-adapter, 3 translator-resp-dsml-integration, 15 translator-resp-openai-to-claude incl. requestedThinking suppression regression). typecheck:core clean. * fix(sse): strip echoed system-prompt preamble + preserve large analysis/summary blocks DeepSeek-V4 and similar models echo the OMNIROUTE_SYSTEM_INSTRUCTION_APPEND directive (appended to the system tail by claude-to-openai.ts) and whole chunks of the system prompt (<analysis>/<system-reminder>/<summary> blocks, prose reproductions of the superpowers skill section) verbatim at the START of their reply — the 'system message leak' persisting after the request-side fix. Add two streaming-safe preamble strippers in directivePreambleStripper.ts: - createDirectivePreambleStripper(directive): drops a leading reproduction of the exact configured directive across arbitrary SSE chunk boundaries. - createSystemPreambleStripper(): removes <analysis>/<system-reminder>/ <summary> echo blocks and known prose heads (Phase B) from the very start of a stream, only while the stream is still a preamble. Wire both into openai-to-claude.ts content-delta path: chain the exact-directive stripper then the system-echo stripper before DSML/XML-invoke parsing, so a leading system echo is dropped before it reaches the client. Preserve large blocks (>= SYSTEM_ECHO_THRESHOLD=1000 chars) and blocks with no trailing content — these are the model's real response (e.g. a Claude Code autocompact summary), not a short system-echo. Stops the autocompact empty-response regression where a whole-summary <analysis> block was stripped to empty (3a8515). Regression: origin's markdown-boundary feature (bufferedPrefix / splitMarkdownBoundary, commit 1b39873) is preserved — preamble strip runs before the markdown buffer rehydration, and the scrubbed content flows into the existing DSML/XML-invoke/markdown pipeline unchanged. TDD: tests/unit/directive-preamble-strip.test.ts (7 cases), system-preamble-strip.test.ts (12 cases incl. 3a8515 regression), system-preamble-wiring.test.ts (3 integration cases); group F regression 24/24 green; typecheck:core 0 errors. * fix(sse): gate thinking block on requestedThinking + synthesize text block for reasoning-only responses Reasoning-content (thinking) blocks were emitted unconditionally to Claude-format clients, leaking reasoning to thinking-opt-out clients (Claude Code sends thinking:{type:"disabled"}) — the operator reported 'reasoning is exposed'. On reasoning-only upstream responses (GLM-5.2 autocompact pattern), the unconditional thinking block also caused either a 502 'no content block' at flush, or — after a text-block fallback — an autocompact 'empty response' rejection that looped the session forever. Streaming translator (openai-to-claude.ts): - Compute hasReasoning outside the emission gate; accumulate into state._reasoningAccum always (so fix B can fire). - Gate only the thinking-block EMISSION on requestedThinking === true. - FIX B at finish: when no text block was started and requestedThinking !== true, synthesize a text content block from _reasoningAccum so autocompact can extract the summary (no 502, compact applies). - Skip fix B when requestedThinking === true to avoid double-exposure (thinking block + text block both carrying reasoning). Non-streaming translator (responseTranslator.ts): - Thread requestedThinking through translateNonStreamingResponse into convertOpenAINonStreamingToClaude. - suppressThinking = requestedThinking === false: drop the thinking block when content is present (no leak); relay reasoning as a text block when the response is reasoning-only (no 502). requestedThinking === undefined keeps the legacy 'always a thinking block' relay. chatCore.ts: pass hasActiveClaudeThinking(body) to the non-stream translate call (inline, since the shared const is in the stream branch's temporal dead zone here). Tests: 25/25 (5 gate-restore, 1 gate-502-repro, 4 nonstream-leak, 15 resp-openai-to-claude incl. requestedThinking suppression regression). Group E (22) + F (14) regression-free. typecheck:core clean. * fix(sse): restore requestToolIdentityMap in the Codex CLI responses-translation path The needsResponsesTranslation branch (openai-responses -> openai, used when the client also speaks Responses) silently dropped the requestToolIdentityMap argument to createSSETransformStreamWithLogger when the requestedThinking parameter was added, reverting the #7936 tool-identity round-trip fix for that branch. The sibling needsTranslation branch was updated correctly; restore the same argument here. Co-authored-by: diegosouzapw <[email protected]> * docs(changelog): add the 3 fragments documented in the PR body The PR body already writes out the changelog.d/ entries for the DSML parser (Group F), the directive-preamble stripper (Group E), and the reasoning-gate thinking-leak fix (Group G), but none of the files existed in the diff. Add them so the release aggregator picks them up. Co-authored-by: diegosouzapw <[email protected]> * fix(sse): realign GLM's positional call after the new requestedThinking parameter createSSETransformStreamWithLogger gained a new requestedThinking parameter inserted before customToolNames. glm.ts's translateSseResponse still called it with the pre-existing positional argument list, so the new parameter silently absorbed the old customToolNames slot, and the GLM_STREAM_BUFFER_BYTES tuning value (#12925) landed on requestToolIdentityMap instead of streamBufferBytes — a TS2345 (number is not assignable to Map<...> | null) caught by check:open-sse-typecheck, and a real loss of GLM's 64KB stream buffer budget. Insert an explicit `undefined` for requestedThinking to restore the original alignment. Co-authored-by: diegosouzapw <[email protected]> * fix(sse): make the system-preamble stripper opt-in and flush it at stream end `createSystemPreambleStripper()` was wired DEFAULT-ON and unconditional in the openai→claude streaming translator, unlike the exact-directive stripper right above it, which only runs when the operator configured OMNIROUTE_SYSTEM_INSTRUCTION_APPEND. Cause: the system-echo stripper recognises its openers by English-prose heuristics ("# Skill usage", "# Verification Process", <analysis>/<summary>/<system-reminder>), so leaving it always-on made it mutate the response payload of EVERY openai→claude stream. A legitimate reply opening with "# Skill usage: how to write one\n\nHere is the guide." lost that whole section. It is now gated on OMNIROUTE_STRIP_SYSTEM_PREAMBLE=1, mirroring the directive stripper's opt-in. Second cause, same feature: neither stripper was ever flushed. Both buffer while a construct is still undecided — a directive prefix that never completes, an <analysis> block that never closes — and nothing released that buffer at the end of the stream. A reply consisting of an unterminated echo block therefore reached the client as an EMPTY message: the answer was held in the buffer and discarded with the stripper. Both strippers now expose flush(), the finish handler calls it for both, and the released text is emitted as a text block. A construct that WAS finally classified as an echo is not resurrected (the drop is final). Tests: tests/unit/system-preamble-gate-and-flush.test.ts pins the default-off contract, the opted-in behaviour, the flush for both strippers (unit + wiring), and the no-resurrection guard. system-preamble-wiring.test.ts now opts in explicitly, since it exercises the stripping path. * fix(sse): thread the client's thinking intent into the non-streaming path The streaming and non-streaming translators disagreed on the default meaning of `requestedThinking`, so the SAME request produced different shapes depending on `stream`. Cause: chatCore computes the client's intent (hasActiveClaudeThinking) and threads it into the SSE translator, which relays reasoning as a thinking block only when it is explicitly `true` — but NO caller ever passed it to translateNonStreamingResponse(). The non-streaming OpenAI→Claude conversion therefore only ever saw `undefined`, its legacy "always relay a thinking block" default, and leaked reasoning to a client that had opted out with `thinking: {"type":"disabled"}`. The streaming plumbing also coerced an omitted value into an explicit `false`, hiding the divergence behind two different spellings of "no intent". Fix (least destructive of the options): do NOT flip either gate — both encode a deliberate, regression-tested contract — but give the non-streaming path the same input the streaming path already has. runNonStreamingProviderLeg owns the client body (`sourceBody`), so it computes the intent with the very same helper and passes it down through translateNonStreamingClientResponse. `undefined` keeps its documented back-compat relay for callers that cannot express intent (issue-7856 / issue-6623), and stream.ts no longer defaults the parameter to `false`, so "absent" now means the same thing in both signatures. No content is lost by the suppression: a reasoning-ONLY response is still relayed as an ordinary text block (no empty response, no 502) — exactly what the streaming finish handler does. Tests: tests/unit/nonstream-requested-thinking-parity.test.ts drives the real provider leg with thinking disabled / enabled / adaptive. --------- Co-authored-by: Jihyun Son <[email protected]> Co-authored-by: diegosouzapw <[email protected]>
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…aper-inference#12179 (cheaper-inference#12925) Rebased onto the tip and completed, per the maintainer's call to finish the wiring rather than merge the capability alone. What changed since your version: The tip had already cleared the TS2554 by deleting the 16th argument, leaving a comment that the highWaterMark stays at the helper default. So the base-red you found is gone, but the 64 KB cheaper-inference#12179 asked for was still not applied and your new parameter had no caller. glm.ts now passes it, which is what turns the capability into the fix. Your test file also hung the runner: every stream createSSEStream builds arms a 10s idle watchdog via setInterval in start, and nothing cancelled them, so node:test waited on a non-empty event loop long after the assertions passed. Cancelling each readable in an after hook runs the cancel handler that clears the timer — the file now reports in about 7 seconds. Worth knowing for future stream tests. Your five assertions are unchanged and all pass. Reading the writable's desiredSize to measure the queue budget the stream was actually built with, rather than standing in for it, is the detail that makes this testable at all — and the 0-budget case pinning `??` against `||` is the kind of thing that silently rots otherwise. Thank you also for separating your own red checks from the base's and reporting what you found there. That is how cheaper-inference#12919's identical failures got explained instead of chased.
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…ive thinking (cheaper-inference#12905) * fix(thinking): recognize adaptive thinking + parse/scrub DSML tool-call markers Two defects combined to break DeepSeek-V4-Flash turns and raise 502 empty_response on Claude Code autocompact. Defect 1 — DSML tool-call markers leaked as visible content: DeepSeek-V4-Flash occasionally emits tool calls in a non-standard DSML text format using full-width pipes instead of the OpenAI tool_calls JSON. Two shapes appear in production call logs: - complete block: <|DSML|:Read><path>...</path></|DSML|:Read> - stray closers (truncated call): </|DSML|parameter></|DSML|invoke> </|DSML|tool_calls>, sometimes trailing a system-prompt echo The openai-compatible path never parsed these, so the markers leaked to the client as visible content and the turn ended incomplete. Fix: add open-sse/utils/dsmlToolCalls.ts — parseDsmlToolCalls() converts complete DSML blocks into OpenAI tool_calls and strips stray closing markers from content (streaming-safe via a holdback for partial openers). Wire it into the response translator before extractXmlInvokeBlocks so DSML and XML invoke tool calls share the same pending queue. Defect 2 — adaptive thinking silently suppressed: A prior inline === 'enabled' check on body.thinking.type silently suppressed adaptive (the intent Claude Code actually sends), so reasoning was dropped. The model then emitted DSML tool-call markers as plain text, producing an incomplete stop finish. Fix: use hasActiveClaudeThinking() (which recognizes enabled AND adaptive) to set requestedThinking, thread it through stream.ts and translator state, and gate thinking block emission on state.requestedThinking so upstream reasoning_content only relays when the client opted in. Tests: 29/29 (6 dsml-tool-calls, 5 thinking-active-claude-adapter, 3 translator-resp-dsml-integration, 15 translator-resp-openai-to-claude incl. requestedThinking suppression regression). typecheck:core clean. * fix(sse): strip echoed system-prompt preamble + preserve large analysis/summary blocks DeepSeek-V4 and similar models echo the OMNIROUTE_SYSTEM_INSTRUCTION_APPEND directive (appended to the system tail by claude-to-openai.ts) and whole chunks of the system prompt (<analysis>/<system-reminder>/<summary> blocks, prose reproductions of the superpowers skill section) verbatim at the START of their reply — the 'system message leak' persisting after the request-side fix. Add two streaming-safe preamble strippers in directivePreambleStripper.ts: - createDirectivePreambleStripper(directive): drops a leading reproduction of the exact configured directive across arbitrary SSE chunk boundaries. - createSystemPreambleStripper(): removes <analysis>/<system-reminder>/ <summary> echo blocks and known prose heads (Phase B) from the very start of a stream, only while the stream is still a preamble. Wire both into openai-to-claude.ts content-delta path: chain the exact-directive stripper then the system-echo stripper before DSML/XML-invoke parsing, so a leading system echo is dropped before it reaches the client. Preserve large blocks (>= SYSTEM_ECHO_THRESHOLD=1000 chars) and blocks with no trailing content — these are the model's real response (e.g. a Claude Code autocompact summary), not a short system-echo. Stops the autocompact empty-response regression where a whole-summary <analysis> block was stripped to empty (3a8515). Regression: origin's markdown-boundary feature (bufferedPrefix / splitMarkdownBoundary, commit f03d99a) is preserved — preamble strip runs before the markdown buffer rehydration, and the scrubbed content flows into the existing DSML/XML-invoke/markdown pipeline unchanged. TDD: tests/unit/directive-preamble-strip.test.ts (7 cases), system-preamble-strip.test.ts (12 cases incl. 3a8515 regression), system-preamble-wiring.test.ts (3 integration cases); group F regression 24/24 green; typecheck:core 0 errors. * fix(sse): gate thinking block on requestedThinking + synthesize text block for reasoning-only responses Reasoning-content (thinking) blocks were emitted unconditionally to Claude-format clients, leaking reasoning to thinking-opt-out clients (Claude Code sends thinking:{type:"disabled"}) — the operator reported 'reasoning is exposed'. On reasoning-only upstream responses (GLM-5.2 autocompact pattern), the unconditional thinking block also caused either a 502 'no content block' at flush, or — after a text-block fallback — an autocompact 'empty response' rejection that looped the session forever. Streaming translator (openai-to-claude.ts): - Compute hasReasoning outside the emission gate; accumulate into state._reasoningAccum always (so fix B can fire). - Gate only the thinking-block EMISSION on requestedThinking === true. - FIX B at finish: when no text block was started and requestedThinking !== true, synthesize a text content block from _reasoningAccum so autocompact can extract the summary (no 502, compact applies). - Skip fix B when requestedThinking === true to avoid double-exposure (thinking block + text block both carrying reasoning). Non-streaming translator (responseTranslator.ts): - Thread requestedThinking through translateNonStreamingResponse into convertOpenAINonStreamingToClaude. - suppressThinking = requestedThinking === false: drop the thinking block when content is present (no leak); relay reasoning as a text block when the response is reasoning-only (no 502). requestedThinking === undefined keeps the legacy 'always a thinking block' relay. chatCore.ts: pass hasActiveClaudeThinking(body) to the non-stream translate call (inline, since the shared const is in the stream branch's temporal dead zone here). Tests: 25/25 (5 gate-restore, 1 gate-502-repro, 4 nonstream-leak, 15 resp-openai-to-claude incl. requestedThinking suppression regression). Group E (22) + F (14) regression-free. typecheck:core clean. * fix(sse): restore requestToolIdentityMap in the Codex CLI responses-translation path The needsResponsesTranslation branch (openai-responses -> openai, used when the client also speaks Responses) silently dropped the requestToolIdentityMap argument to createSSETransformStreamWithLogger when the requestedThinking parameter was added, reverting the cheaper-inference#7936 tool-identity round-trip fix for that branch. The sibling needsTranslation branch was updated correctly; restore the same argument here. Co-authored-by: diegosouzapw <[email protected]> * docs(changelog): add the 3 fragments documented in the PR body The PR body already writes out the changelog.d/ entries for the DSML parser (Group F), the directive-preamble stripper (Group E), and the reasoning-gate thinking-leak fix (Group G), but none of the files existed in the diff. Add them so the release aggregator picks them up. Co-authored-by: diegosouzapw <[email protected]> * fix(sse): realign GLM's positional call after the new requestedThinking parameter createSSETransformStreamWithLogger gained a new requestedThinking parameter inserted before customToolNames. glm.ts's translateSseResponse still called it with the pre-existing positional argument list, so the new parameter silently absorbed the old customToolNames slot, and the GLM_STREAM_BUFFER_BYTES tuning value (cheaper-inference#12925) landed on requestToolIdentityMap instead of streamBufferBytes — a TS2345 (number is not assignable to Map<...> | null) caught by check:open-sse-typecheck, and a real loss of GLM's 64KB stream buffer budget. Insert an explicit `undefined` for requestedThinking to restore the original alignment. Co-authored-by: diegosouzapw <[email protected]> * fix(sse): make the system-preamble stripper opt-in and flush it at stream end `createSystemPreambleStripper()` was wired DEFAULT-ON and unconditional in the openai→claude streaming translator, unlike the exact-directive stripper right above it, which only runs when the operator configured OMNIROUTE_SYSTEM_INSTRUCTION_APPEND. Cause: the system-echo stripper recognises its openers by English-prose heuristics ("# Skill usage", "# Verification Process", <analysis>/<summary>/<system-reminder>), so leaving it always-on made it mutate the response payload of EVERY openai→claude stream. A legitimate reply opening with "# Skill usage: how to write one\n\nHere is the guide." lost that whole section. It is now gated on OMNIROUTE_STRIP_SYSTEM_PREAMBLE=1, mirroring the directive stripper's opt-in. Second cause, same feature: neither stripper was ever flushed. Both buffer while a construct is still undecided — a directive prefix that never completes, an <analysis> block that never closes — and nothing released that buffer at the end of the stream. A reply consisting of an unterminated echo block therefore reached the client as an EMPTY message: the answer was held in the buffer and discarded with the stripper. Both strippers now expose flush(), the finish handler calls it for both, and the released text is emitted as a text block. A construct that WAS finally classified as an echo is not resurrected (the drop is final). Tests: tests/unit/system-preamble-gate-and-flush.test.ts pins the default-off contract, the opted-in behaviour, the flush for both strippers (unit + wiring), and the no-resurrection guard. system-preamble-wiring.test.ts now opts in explicitly, since it exercises the stripping path. * fix(sse): thread the client's thinking intent into the non-streaming path The streaming and non-streaming translators disagreed on the default meaning of `requestedThinking`, so the SAME request produced different shapes depending on `stream`. Cause: chatCore computes the client's intent (hasActiveClaudeThinking) and threads it into the SSE translator, which relays reasoning as a thinking block only when it is explicitly `true` — but NO caller ever passed it to translateNonStreamingResponse(). The non-streaming OpenAI→Claude conversion therefore only ever saw `undefined`, its legacy "always relay a thinking block" default, and leaked reasoning to a client that had opted out with `thinking: {"type":"disabled"}`. The streaming plumbing also coerced an omitted value into an explicit `false`, hiding the divergence behind two different spellings of "no intent". Fix (least destructive of the options): do NOT flip either gate — both encode a deliberate, regression-tested contract — but give the non-streaming path the same input the streaming path already has. runNonStreamingProviderLeg owns the client body (`sourceBody`), so it computes the intent with the very same helper and passes it down through translateNonStreamingClientResponse. `undefined` keeps its documented back-compat relay for callers that cannot express intent (issue-7856 / issue-6623), and stream.ts no longer defaults the parameter to `false`, so "absent" now means the same thing in both signatures. No content is lost by the suppression: a reasoning-ONLY response is still relayed as an ordinary text block (no empty response, no 502) — exactly what the streaming finish handler does. Tests: tests/unit/nonstream-requested-thinking-parity.test.ts drives the real provider leg with thinking disabled / enabled / adaptive. --------- Co-authored-by: Jihyun Son <[email protected]> Co-authored-by: diegosouzapw <[email protected]>
AndresDiagoM
added a commit
to AndresDiagoM/OmniRoute
that referenced
this pull request
Oct 19, 2026
…helper clientTools now sits before streamBufferBytes, so the trailing buffer-size slot GLM fills (cheaper-inference#12925) stays last and GLM's call matches the helper arity. GLM passes null for clientTools; the translate call in streamingTail drops its placeholder undefined.
diegosouzapw
added a commit
that referenced
this pull request
Oct 22, 2026
…ng from every provider (#17118) (#17119) * fix(sse): hand Anthropic-format clients their declared tool-name casing from every provider Claude-format responses assembled by openai-to-claude, gemini-to-claude and the non-streaming responseTranslator all ended in restoreClaudeToolName(), whose canonical upgrade (read -> Read, #7926/#9008/#10392/#11085) assumes the client is Claude Code. OpenCode's @ai-sdk/anthropic provider declares lowercase tools, so a combo falling back from native claude to Antigravity, Codex or Gemini (or any of those used directly over /v1/messages) answered with tool_use.name "Read" for a tool declared as "read", and every tool call failed client-side ("No tool named Read"). Lift the #12721 passthrough rule into a shared module and apply it on the translate paths too: alias ledger first, then the CLIENT request's declared spelling (exact, then case-insensitive), then the legacy canonical upgrade only for names the request never declared. Claude Code declares "Read", so it keeps getting "Read" back when an OpenAI-style upstream downcases it; OpenCode gets "read" back from the same upstream. The client's tools[] is threaded into the translate-mode stream state and into translateNonStreamingResponse. * docs(changelog): add fragment for #17119 * fix(sse): keep streamBufferBytes the last parameter of the transform helper clientTools now sits before streamBufferBytes, so the trailing buffer-size slot GLM fills (#12925) stays last and GLM's call matches the helper arity. GLM passes null for clientTools; the translate call in streamingTail drops its placeholder undefined. * fix(sse): carry declared tools through Claude passthrough (#17118) * refactor(sse): split declaredToolCasing helpers under the complexity gate findDeclaredToolName (cyclomatic 17, cognitive 18) and restoreDeclaredToolName (cognitive 17) were over the new-code limit of 15. Extract declaredNameOf() for the per-entry tools[] spelling and aliasLedgerName() for the _toolNameMap precedence; behaviour is unchanged and the 17118/17119 suites still pass. --------- Co-authored-by: diegosouzapw <[email protected]>
DarknessKiller
added a commit
to DarknessKiller/OmniRoute
that referenced
this pull request
Oct 25, 2026
…1f05` - New UI Changes are not applied Squashed commit of the following: commit fb2808905d16c0ed51ec54ee206f798465001f05 Author: Diego Rodrigues de Sa e Souza <[email protected]> Date: Sat Oct 24 23:02:31 2026 -0300 fix(cli): resolve the systray entry and unwrap its constructor (#17328) Resolve the systray runtime entry and unwrap its constructor in the CLI tray. Evidence: focused tests pass (tests/unit/cli/tray-failure-diagnostics-17315.test.ts, tests/unit/cli/tray-runtime-entry-17315.test.ts, tests/unit/cli/tray-runtime-windows-esm-import.test.ts), validated in one combined board of 9 PRs on origin/release/v3.8.53 with typecheck:core, check:open-sse-typecheck and ESLint clean; file-size OK; changelog integrity OK against origin/release/v3.8.53. ⚠️ base-red inherited: #17219 commit 033f012fc9b25e302debe59e9669b749d3fcd992 Author: Diego Rodrigues de Sa e Souza <[email protected]> Date: Sat Oct 24 23:02:17 2026 -0300 fix(ci): restore strict core types without runtime changes (#17857) Restore strict core types for the stream/cache helpers without runtime changes. Evidence: focused tests pass (tests/unit/compute-finish-reason-coverage.test.ts, tests/unit/proxy-dispatcher-cloud-reap.test.ts, tests/unit/semantic-cache-secret-storage-14484.test.ts), validated in one combined board of 9 PRs on origin/release/v3.8.53 with typecheck:core, check:open-sse-typecheck and ESLint clean; file-size OK; changelog integrity OK against origin/release/v3.8.53. ⚠️ base-red inherited: #17219 commit 3fc7d04a36d4e361daf8dc95540fe26dff104f0f Author: Diego Rodrigues de Sa e Souza <[email protected]> Date: Sat Oct 24 23:02:06 2026 -0300 fix(ci): reject dependencies that drift from candidate lockfile (#17854) Reject CI installs whose dependencies drift from the candidate lockfile. Evidence: focused tests pass (tests/unit/build/verify-ci-install.test.mjs), validated in one combined board of 9 PRs on origin/release/v3.8.53 with typecheck:core, check:open-sse-typecheck and ESLint clean; file-size OK; changelog integrity OK against origin/release/v3.8.53. ⚠️ base-red inherited: #17219 commit 7d3a4787130c23e7eb5fa5483d9f468bbd061f5e Author: Diego Rodrigues de Sa e Souza <[email protected]> Date: Sat Oct 24 23:01:54 2026 -0300 fix(ci): provision verified workflow scanners for release observers (#17846) Provision verified, pinned scanners for the Release-Green observer jobs. Evidence: focused tests pass (tests/unit/release-green-scanner-setup.test.mjs), validated in one combined board of 9 PRs on origin/release/v3.8.53 with typecheck:core, check:open-sse-typecheck and ESLint clean; file-size OK; changelog integrity OK against origin/release/v3.8.53. ⚠️ base-red inherited: #17219 commit 0d96ef1748dd765a289186b0fb31e3555117358d Author: Diego Rodrigues de Sa e Souza <[email protected]> Date: Sat Oct 24 23:01:41 2026 -0300 fix(sse): surface midstream keepalive read failures (#17292) (#17341) Signal upstream read failures after partial output in the early-stream keepalive path. Evidence: focused tests pass (tests/unit/early-stream-midstream-failure-17292.test.ts, tests/unit/earlyStreamKeepalive.test.ts), validated in one combined board of 9 PRs on origin/release/v3.8.53 with typecheck:core, check:open-sse-typecheck and ESLint clean; file-size OK; changelog integrity OK against origin/release/v3.8.53. ⚠️ base-red inherited: #17219 Fixes #17292 commit 4fa23c64d7263f8eb346fad521e20e930e8a3439 Author: Diego Rodrigues de Sa e Souza <[email protected]> Date: Sat Oct 24 23:01:30 2026 -0300 fix(sse): preserve provider-native model IDs (#16823) (#17336) Preserve known provider-native model IDs through model resolution. Evidence: focused tests pass (tests/unit/16823-native-model-aliases.test.ts, tests/unit/16823-ollama-tags-persist-dispatch.test.ts), validated in one combined board of 9 PRs on origin/release/v3.8.53 with typecheck:core, check:open-sse-typecheck and ESLint clean; file-size OK; changelog integrity OK against origin/release/v3.8.53. ⚠️ base-red inherited: #17219 commit b69588a1e0585e658602a730f4f2dab7955cd219 Author: Diego Rodrigues de Sa e Souza <[email protected]> Date: Sat Oct 24 23:01:18 2026 -0300 fix(api): explain the Windows EBUSY/EPERM failure of the in-app npm update (#17335) Explain the Windows EBUSY/EPERM failure of the in-app npm update with an actionable message. Evidence: focused tests pass (tests/unit/system-update-install-error-windows-ebusy.test.ts), validated in one combined board of 9 PRs on origin/release/v3.8.53 with typecheck:core, check:open-sse-typecheck and ESLint clean; file-size OK; changelog integrity OK against origin/release/v3.8.53. ⚠️ base-red inherited: #17219 commit 3c90c0636832ec3b1c7f7d7871fa17933c721211 Author: Diego Rodrigues de Sa e Souza <[email protected]> Date: Sat Oct 24 23:01:05 2026 -0300 fix(sse): preserve explicit adaptive thinking effort (#17333) Preserve an explicitly requested adaptive thinking effort instead of overriding it. Evidence: focused tests pass (tests/unit/16662-explicit-adaptive-effort.test.ts, tests/unit/thinking-budget.test.ts), validated in one combined board of 9 PRs on origin/release/v3.8.53 with typecheck:core, check:open-sse-typecheck and ESLint clean; file-size OK; changelog integrity OK against origin/release/v3.8.53. ⚠️ base-red inherited: #17219 commit 5e146a4a75f4bf8bf7c62575615fcc12979f980d Author: Diego Rodrigues de Sa e Souza <[email protected]> Date: Sat Oct 24 23:00:55 2026 -0300 fix(usage): preserve cache reads after compatibility zeros (#17297) (#17327) Preserve cache-read token counts when a compatibility layer reports zeros. Evidence: focused tests pass (tests/unit/issue-17297-cache-read-zero-compat.test.ts), validated in one combined board of 9 PRs on origin/release/v3.8.53 with typecheck:core, check:open-sse-typecheck and ESLint clean; file-size OK; changelog integrity OK against origin/release/v3.8.53. ⚠️ base-red inherited: #17219 Fixes #17297. commit 8f2120868bff087673e402c517e1caa7320dca12 Author: Felipe Marques <[email protected]> Date: Fri Oct 23 01:09:15 2026 -0300 feat(audio): add ElevenLabs sound effects and music generation (#17287) commit 6c928202d71d2ce7e0b94802ac6dfe0aa1278b48 Author: Fouad Salkini <[email protected]> Date: Thu Oct 22 23:21:03 2026 +0300 feat(api): add OpenRouter-format prices to /v1/models entries (#17259) commit 3f83d37acefc3a5414d21530586e46d7dd31c814 Merge: 76f4325ab9 daca9902e8 Author: DarknessKiller <[email protected]> Date: Mon Oct 26 02:02:33 2026 +0800 Merge commit 'daca9902e859bc1aa95ba276e3ef200ba7f5d142' of https://github.com/diegosouzapw/OmniRoute into release/v3.8.53-mrdarknessk commit daca9902e859bc1aa95ba276e3ef200ba7f5d142 Author: Diego Rodrigues de Sa e Souza <[email protected]> Date: Thu Oct 22 14:37:22 2026 -0300 docs(i18n): re-translate GUARDRAILS mirrors (66 locales) (#17950) commit 34e3d1f99834f1ca4ca4724eb872ca5e9722c383 Author: Diego Rodrigues de Sa e Souza <[email protected]> Date: Thu Oct 22 14:34:24 2026 -0300 refactor(security): quantifier-free edge strip in combo step slugs (CodeQL #1165) (#17906) slugify() in src/lib/combos/steps.ts strips the edge separator with /^-|-$/g instead of /^-+|-+$/g. The preceding replace already collapses every separator run into one dash, so the output is identical; tests/unit/combo-step-id-slug.test.ts pins the id shape. commit da030357292857aed4e2e38d727f92c471e7aba9 Author: leoplife <[email protected]> Date: Thu Oct 22 14:30:50 2026 -0300 feat(config): refresh OpenCode Zen free models (#17345) commit d63ab780f8aa7d1efd704a365fd8835728924788 Author: Fabio Junior Gruchovski <[email protected]> Date: Thu Oct 22 14:30:32 2026 -0300 fix: honor explicit combo selection in MCP and A2A (#17188) (#17235) commit dc648dfce195861964d2d0d0f5b4731a37cd00b6 Author: Diego Rodrigues de Sa e Souza <[email protected]> Date: Thu Oct 22 13:53:45 2026 -0300 fix(ci): accept Dependabot's [email protected] sign-off in check:ai-attribution (#17876) Dependabot ends every bump commit with Signed-off-by: dependabot[bot] <[email protected]>; the exemption only matched the noreply co-author address, so Lint and Fast Quality Gates failed on every Dependabot PR. The exemption now accepts that exact name with either exact address; look-alike names with the same address still fail. commit 0482755c5e4faeeb23736b9e56ec293d84809efa Author: Diego Rodrigues de Sa e Souza <[email protected]> Date: Thu Oct 22 08:47:26 2026 -0300 docs(i18n): ENVIRONMENT — carry the CodeBuddy ∅ (∞) default in source + 66 mirrors (base-red fix for cli-env-quality-docs-mirrors) (#17929) commit b7a61c8d107ddc9771696e8c12be86736e778629 Author: Diego Rodrigues de Sa e Souza <[email protected]> Date: Thu Oct 22 08:42:36 2026 -0300 docs(i18n): re-translate CLAUDE.md mirrors (66 locales) — protected surface, owner approval required (#17910) commit 00f340bc211603cb6083ceca22fe093fe13bbaac Author: Diego Rodrigues de Sa e Souza <[email protected]> Date: Thu Oct 22 08:22:59 2026 -0300 docs(i18n): re-translate the ENVIRONMENT mirrors after #17074 (#17919) commit cce4f5bc2579f4a4cb66471901a0fd835d6dd1a3 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 12:53:17 2026 +0200 fix(batch): preserve CSV source when remapping input (#17222) commit 4bb1c22b055046a266a2b89ac699e1ca58673d6e Author: MeTony <[email protected]> Date: Thu Oct 22 12:52:52 2026 +0200 fix(tools): keep client-declared filePath in the Read shim (#17294) (#17295) commit b68c33211e99aa24ce3ec04a1a68280b0bd8395d Author: Fabio Junior Gruchovski <[email protected]> Date: Thu Oct 22 07:34:49 2026 -0300 Fix/17043 claude observed quota reset release (#17107) commit 24f32794f925e25afdfe7800b4206c6dbaceb8fd Author: Paco Cartones <[email protected]> Date: Thu Oct 22 12:32:30 2026 +0200 fix(batch): discard file reads after input format changes (#17221) commit c9d9a2ef06fc15d95498fb4ef7f222597bf2edbb Author: Rafa Martins <[email protected]> Date: Thu Oct 22 07:32:13 2026 -0300 fix(sse): stop quota-share block from crashing the proxy and cooling the shared connection (#17132) (#17214) commit 0a4a06eebb4b38d3698ad4578274016d45b412af Author: Aldo <[email protected]> Date: Thu Oct 22 04:32:01 2026 -0600 fix(translator): contain malformed upstream payloads (#17198) commit 4f8e62017d58cdfa985f4b956a9684090dfa0c16 Author: Diego Rodrigues de Sa e Souza <[email protected]> Date: Thu Oct 22 07:21:42 2026 -0300 docs(i18n): RESILIENCE_GUIDE — fix the lock-scope code span, carry the #16319 contract block in source + 66 mirrors (base-red fix) (#17921) commit f5452822a84a6c76ed5059087b475946373e351c Author: Mayank Jaiswal <[email protected]> Date: Thu Oct 22 15:27:35 2026 +0530 test(api): lock env master key bypass in model catalog auth (#17340) commit f2070a84fde166e90d58ba0a95f619bda0e66099 Author: r2jaff <[email protected]> Date: Thu Oct 22 12:57:25 2026 +0300 test(sse): pin spec reporter in the Perplexity stream boundary test (#17321) The test spawns its fixture with node --test and asserts on spec reporter output ("ℹ tests 8"), but did not pass --test-reporter. Node 22 defaults to the tap reporter when stdout is not a TTY, so the assertion failed on Node 22 (supported by engines: >=22.22.2) while passing on Node 24, where spec is the default. Pin the reporter the way tests/unit/helpers/runIsolatedBoundaryFixture.ts already does. Co-authored-by: Claude <[email protected]> commit 141844081819e333a4b251e0a9b62acc014f2b7c Author: Calvin Chen <[email protected]> Date: Thu Oct 22 17:57:15 2026 +0800 fix(compression): keep Responses tool output and injected messages intact (#17310) * fix(compression): treat trailing Responses tool outputs as the current turn in lite An OpenAI Responses API tool turn carries no assistant message: the call and its output arrive as top-level input items, which the adapter turns into a synthesized role:"tool" message. lite derived the current turn from the last assistant message and defaulted to protecting nothing when there was none, so the newest function_call_output was truncated and the agent kept re-reading the same file. Trailing Responses tool outputs now count as the current turn. Adds isResponsesToolOutputMessage() to the body adapter for the check. * fix(compression): restore engine-injected messages into Responses input The Responses adapter maps input[] items to chat messages and maps the result back by source index. A message an engine injects has no source item — CCR prepends its protocol instruction — so the restore loop skipped it and the instruction never reached the provider. The model then saw [CCR retrieve] markers with no protocol telling it how to expand them. Injected messages are now restored as new input items ahead of the mapped ones. * refactor(compression): share the Responses input restore tail to clear the cognitive-complexity ratchet Extract finalizeResponsesInput() for the two identical string-input + injected-items branches in restore(), add a test for the string-input path and a changelog fragment. Co-authored-by: diegosouzapw <[email protected]> --------- Co-authored-by: diegosouzapw <[email protected]> commit 90d22e0c826db242a9485b37f1867caea61cc2b3 Author: Mayank Jaiswal <[email protected]> Date: Thu Oct 22 15:27:04 2026 +0530 fix(sse): honor Retry-After hint and jitter intra-URL retry backoff (#17306) * fix(sse): honor Retry-After hint and jitter intra-URL retry backoff * chore(changelog): add fragment for retry-after hint and jitter intra-URL retry Co-authored-by: diegosouzapw <[email protected]> --------- Co-authored-by: diegosouzapw <[email protected]> commit 6a9b1e2efd1f58eb5bad4f3b3435f2ea87c9b331 Author: Junior-HJ <[email protected]> Date: Thu Oct 22 04:56:54 2026 -0500 fix(cursor): bridge Grep when the declared pattern carries a length bound (#17298) * fix(sse): bridge Cursor Grep when the declared pattern declares a length bound * chore(sse): prefix the changelog fragment with its PR number commit 5ad89354d140b5f34bdfb91109d6c24fb949ae69 Author: Fouad Salkini <[email protected]> Date: Thu Oct 22 12:56:44 2026 +0300 fix(models): stop the context-window reconcile from blocking the event loop (#17296) * fix(models): stop the context-window reconcile from blocking the event loop Each auto:discovery override write bumped the model catalog cache version, so the next getCatalogWindow call in the same pass rebuilt the synced vision verdict map from every synced catalog. On ~6,300 synced models with ~1,400 divergent windows a pass blocked the event loop for ~10.7 s, and since #11016 a pass runs after every synced catalog write. The pass now writes without invalidating and bumps the catalog version once at the end, and it skips rewriting an override that already holds the discovered window. * chore(changelog): name the reconcile-stall fragment after #17296 * chore(changelog): link the reconcile-stall fragment to #17296 * refactor(models): split reconcileContextWindows into small helpers to clear the cognitive-complexity ratchet Behavior is unchanged: the per-entry decision moves into reconcileOne with isAlreadyPinned and dropStaleAutoPin, so the new-code cognitive-complexity gate reports 0 violations for contextWindowResolver.ts. Changelog fragment notes the refreshedAt semantics. Co-authored-by: diegosouzapw <[email protected]> --------- Co-authored-by: diegosouzapw <[email protected]> commit 7e8f8f8bfa68e74064044357e70195c21d1851b0 Author: Nick Carter <[email protected]> Date: Thu Oct 22 05:56:34 2026 -0400 fix(sse): preserve restored thinking in active tool cycles (#17288) * fix(sse): preserve restored thinking in active tool cycles * docs(changelog): add fragment for preserving restored thinking in active tool cycles Co-authored-by: diegosouzapw <[email protected]> --------- Co-authored-by: diegosouzapw <[email protected]> commit f4cf8441786800478603fdde52b9f0bd74bb3136 Author: Dizzle <[email protected]> Date: Thu Oct 22 11:56:24 2026 +0200 fix(calls): measure content presence on streamed replies (#17277) a streamed reply was always logged as empty because the reader read the envelope as a body; the summary is now measured like any other body commit 9bbc2e37558328ef1dc7bec16c93651ca6b8a3fd Author: Stanislav <[email protected]> Date: Thu Oct 22 12:56:13 2026 +0300 fix(usage): release the captured provider request body when its dispatch settles (#17269) * fix(usage): release the captured provider request body when its dispatch settles The Capture object returned by createPreparedRequestLogger lives in an AsyncLocalStorage store, so sockets and streams spawned during the dispatch keep the store — and its latest.bodyString — alive after the request is done. For agent traffic with base64 screenshots that string is 3-10 MB, which remained reachable forever: heap climbed ~4-8 MB/h (day averages 847 -> 1314 MB over three days) until the pressure guard. runWithCapture now calls an optional Capture.release() in its finally, so the retained body is dropped as soon as the dispatch settles; body(fallback) already falls back to the transformed body once latest is gone. Follow-up to #16752. Measured on a self-hosted instance over 25.8 h after the patch: heap plateaued at 241-388 MB (0.5 MB/h), RSS 790-1048 MB, zero guard trips. * chore: rename changelog fragment with the PR number (#17269) * fix(usage): reject late captures from settled dispatches * fix(usage): keep the captured wire body readable after dispatch, release it from the ALS store instead Capture.release() cleared latest() when the dispatch settled, but chatCore reads providerRequestCapture.latest()/body() after that point (signature-failure diagnostics, call-log request body). AsyncLocalStorage now holds a small holder whose capture field is nulled in the finally of runWithCapture: sockets and streams that outlive the dispatch no longer pin the Capture and its multi-megabyte body, while chatCore keeps the Capture through its own closure. The settled-dispatch guard stays. Tests now assert the post-dispatch read still returns the wire body and the holder is empty. Co-authored-by: diegosouzapw <[email protected]> --------- Co-authored-by: diegosouzapw <[email protected]> commit 6b2926286f69cd1872333510e1569dfc3ff84c1b Author: aminHZDEV <[email protected]> Date: Thu Oct 22 13:26:03 2026 +0330 fix(api): list only callable models in a restricted key's /v1/models (#17261) * fix(api): list only callable models in a restricted key's /v1/models A key restricted to cc/* listed other providers' claude-* rows (dva/claude-opus-5-high, ddgw/claude-haiku-4-5) that every chat request refused with 403. The catalog filter also checks a row's bare root (#781), and with preferClaudeCodeForUnprefixedClaudeModels on, the permission check expands a bare claude-* name to cc/<name>, which cc/* matches. Dispatch checks only the requested id. - The root fallback now also needs a bare allowlist entry matching the root, which is the case #781 was added for (allowedModels: ["gpt-4o"]). - isModelAllowedForKey judges no-think/<provider>/<model> as the real model, as the chat handler strips the prefix before its key check. The key's catalog now keeps the no-think/cc/ variants it can call. * docs(changelog): add fragment for #17261 * docs(changelog): credit @aminHZDEV in the #17261 fragment commit 3414466b4ba187e9bd93c21153c6449b6934dcf9 Author: aminHZDEV <[email protected]> Date: Thu Oct 22 13:25:53 2026 +0330 fix(pricing): price claude-sonnet-5-5 on the cc provider (#17260) * fix(pricing): price claude-sonnet-5-5 on the cc provider The cc table priced claude-opus-5-5 but had no claude-sonnet-5-5 entry, so getPricingForModel("cc", "claude-sonnet-5-5") returned null. Use the shared Sonnet 5 tier ($2 / $10, cache read $0.20), which Anthropic lists for Sonnet 5.5. * docs(changelog): add fragment for #17260 * docs(changelog): credit @aminHZDEV in the #17260 fragment commit 6e12c409f7278004475eee697450f5c57b76bba9 Author: Calvin Chen <[email protected]> Date: Thu Oct 22 17:55:43 2026 +0800 fix(compression): compact Anthropic tool_result content in headroom e… (#17258) * fix(compression): compact Anthropic tool_result content in headroom engine * docs(changelog): add fragment for headroom tool_result compaction Co-authored-by: diegosouzapw <[email protected]> --------- Co-authored-by: diegosouzapw <[email protected]> commit 62ecd3deaf46b894574ced9b3df4cd38216d5081 Author: Patryk Kopyciński <[email protected]> Date: Thu Oct 22 11:55:32 2026 +0200 fix(translator): hoist nested tool_result.content cache_control on the openai-compat and openai→claude paths (#17257) * fix(translator): hoist tool_result.content cache_control to block level for openai-compat downstream re-translation An OpenAI `role:"tool"` message carrying `cache_control` on its content blocks reached Anthropic as `tool_result.content[i].cache_control`, which is hard rejected: [400] messages.N.content.0.tool_result.content.0.cache_control: cache_control may not be specified within `tool_result.content`. Instead, place it directly on `tool_result` filterToOpenAIFormat() early-returned tool messages untouched, so nested markers survived into openai-to-claude's tool_result block. The existing prepareClaudeRequest hoist (6d45da2050) could not catch this: it only runs for targetFormat === "claude", while this path is sourceFormat=openai → targetFormat=openai into an openai-compatible gateway that itself re-translates to Anthropic. Two surgical changes: - filterToOpenAIFormat strips nested markers off tool content blocks and, in preserve mode, hoists the first one onto the tool MESSAGE. - openai-to-claude emits that message-level marker on the `tool_result` block. Covered by tests/unit/openai-tool-message-cache-control-400.test.ts (5 cases across strip and preserve modes). Mutation-checked: reverting either change turns 4/5 red. * chore: drop replay scratch file * fix(translator): hoist nested tool_result.content cache_control in openai-to-claude itself Replaying the captured 157-message live body showed the previous commit only covered the openai→openai (openai-compatible passthrough) leg. The DIRECT openai→claude leg still emitted 2 nested markers, because the Claude-side hoist from 6d45da2050 lives on another branch line and is not an ancestor of this branch — and that path never calls filterToOpenAIFormat. Add hoistToolResultCacheControl() in openai-to-claude and apply it at BOTH tool_result construction sites (the role:"tool" branch and the user-turn tool_result part). An explicitly provided block-level marker wins over a nested one; a tool_result with no marker gains no cache_control key. Replay of the live body is now clean on all four combinations: openai->openai preserve=false/true 0 nested markers openai->claude preserve=false/true 0 nested markers Mutation-checked: neutering the helper returns the direct leg to 2 markers. Suite grows 5 -> 9 cases. Targeted regression run (414 cache/translator/ openai/claude/tool files): 3334 pass, 1 fail — kiro-tool-call-validation, pre-existing and reproduced with these changes stashed out. * refactor(translator): rename to hoistNestedToolResultCacheControl to avoid shadowing claudeHelper's body-level guard openai-to-claude.ts and claudeHelper.ts both exported hoistToolResultCacheControl with different signatures (content-level vs body-level). No cross-import so it compiled, but the name collision is a readability trap. The openai-to-claude one operates on a single tool_result's content array, so name it accordingly. * docs(changelog): add fragment for #17257 commit 0212597367e2a169cb341f4b3dc6cc03e668c856 Author: Edo Sulai <[email protected]> Date: Thu Oct 22 16:55:21 2026 +0700 fix(antigravity): keep Claude tool results before merged user text (#17256) * fix(antigravity): keep Claude tool responses before merged user text * style(antigravity): prettier-format the Claude tool-result order change and add its changelog fragment Co-authored-by: diegosouzapw <[email protected]> --------- Co-authored-by: diegosouzapw <[email protected]> commit 4801197b0f063fce8c7bcf5eaf9c67265b32e09e Author: Paco Cartones <[email protected]> Date: Thu Oct 22 11:55:10 2026 +0200 fix(dashboard): ignore obsolete initial provider loads (#17253) * fix(dashboard): ignore obsolete initial provider loads * docs(changelog): record #17253 commit 90c00f61a6a4045e52e04e5f60aae9d0c6fcc269 Author: Daniil Karpenko <[email protected]> Date: Thu Oct 22 13:55:00 2026 +0400 fix(capabilities): resolve compat vision overrides for prefix-addressed custom nodes (#17252) * fix(capabilities): resolve compat vision overrides for prefix-addressed custom nodes #15086 made the canonical capability resolver read `modelCompatOverrides.supportsVision`, but the lookup is keyed by the provider id `PUT /api/provider-models` received — normally a custom node's internal id (`openai-compatible-responses-<uuid>`). The catalog addresses the same node by its user-defined prefix (`catalog.ts`'s `alias = prefix || providerIdToAlias[providerId] || providerId`), and so do prefix-addressed combo steps, so the operator's vision flag stayed invisible on that path: an image request to a prefix-addressed target was rejected as vision-incapable even though the model does accept images. `getProviderNodeIdByPrefixSync()` adds the missing reverse index (prefix -> node id, built from the same per-DB memo as the existing id -> prefix lookup), and the compat vision lookup consults the mapped node id LAST so a direct key match always wins and nothing that resolved before can change. Verified with a regression test that fails on the base tip (4 of 5 cases) and passes with the fix; the node-id form, the prefix form and the catalog-shaped string form now agree, an explicit `false` still flows through, and unrelated providers keep their own verdict. * chore(changelog): add fragment for #17252 commit e7f22bae0b952854bdfb9225dd38186ccba42237 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 11:54:50 2026 +0200 test(cursor): cover keyed mutex successor cleanup (#17251) * test(cursor): cover keyed mutex successor cleanup * docs(changelog): record #17251 commit 07b78c97345385bc2c189dc1c401fdf4286b9444 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 11:34:50 2026 +0200 test(batch): cover JSONL sampling boundaries (#17249) * test(batch): cover JSONL sampling boundaries * docs(changelog): record #17249 commit e3aa3151fb028618fe4c885a887b48216f79820d Author: Paco Cartones <[email protected]> Date: Thu Oct 22 11:34:39 2026 +0200 test(batch): exercise forbidden CSV mapping paths (#17248) * test(batch): exercise forbidden CSV mapping paths * docs(changelog): record #17248 commit 10b0f292ad3f9c833f520e3ebc7592f436fe156b Author: Paco Cartones <[email protected]> Date: Thu Oct 22 11:34:29 2026 +0200 fix(sse): preserve refusal deltas in buffered OpenAI SSE (#17246) * fix(sse): preserve refusal deltas in buffered OpenAI SSE * docs(changelog): record #17246 commit 9f32603b28613500e36d26c86d6d80af83fdf32d Author: Paco Cartones <[email protected]> Date: Thu Oct 22 11:34:19 2026 +0200 fix(sse): ignore non-object events in buffered OpenAI SSE (#17245) * fix(sse): ignore non-object events in buffered OpenAI SSE * docs(changelog): record #17245 commit 8586d1d4786a9c413f262883da8fc1a4a03df270 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 11:34:07 2026 +0200 fix(batch): require content in CSV mappings (#17244) * fix(batch): require content in CSV mappings * docs(changelog): record #17244 commit 639cf25fa0de37120a8a20f0f169ccca6fc6deef Author: Paco Cartones <[email protected]> Date: Thu Oct 22 11:33:57 2026 +0200 fix(proxy): preserve malformed percent escapes in subscription labels (#17242) * fix(proxy): preserve malformed percent escapes in subscription labels * docs(changelog): record #17242 commit f233d031e01ea3211a532c8202ac63ea0d43b080 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 11:33:45 2026 +0200 fix(proxy): preserve default ports in subscription URIs (#17241) * fix(proxy): preserve default ports in subscription URIs * docs(changelog): record #17241 commit d77ebe1010e15a9ef58a6e509820e9069b3e148e Author: Paco Cartones <[email protected]> Date: Thu Oct 22 11:33:34 2026 +0200 fix(batch): preserve quoted multiline CSV headers (#17240) * fix(batch): preserve quoted multiline CSV headers * docs(changelog): record #17240 commit 847931fba1002a8fbd63619a5053a83083b94f49 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 11:33:23 2026 +0200 fix(sse): parse native Gemini events in buffered responses (#17239) * fix(sse): parse native Gemini events in buffered responses * docs(changelog): record #17239 commit 9085a2a4079abff697be677573d092dc1c43da62 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 11:33:13 2026 +0200 fix(dashboard): preserve URL continuation in copied cURL commands (#17238) * fix(dashboard): preserve URL continuation in copied cURL commands * docs(changelog): record #17238 commit dab9ce1d28f1ddf9c535692f7fb10808932f3d94 Author: Edo Sulai <[email protected]> Date: Thu Oct 22 16:33:03 2026 +0700 fix(api): honor native model aliases in audio transcriptions (#17232) * fix(api): honor native model aliases in audio transcriptions * refactor(api): keep audio alias dispatch within complexity budget * test(audio): prove native alias through HTTP multipart * fix(ci): dispose compression timer and scope mirror provenance checks * chore(changelog): name the audio alias fragment after PR #17232 and credit the author Co-authored-by: diegosouzapw <[email protected]> --------- Co-authored-by: diegosouzapw <[email protected]> commit 90843005a09e738d2c5ac3a402b86bf8c009217f Author: Fabio Junior Gruchovski <[email protected]> Date: Thu Oct 22 06:32:52 2026 -0300 docs(mcp): correct tool-cardinality activation note (#17185) (#17231) * docs(mcp): correct tool-cardinality activation note (#17185) * test(mcp): cover default manifest and combined tool-filter precedence (#17185) * docs(mcp): remove stale activation claim from cardinality tests (#17185) * docs(changelog): register PR #17231 release fragment commit 040561f4b128530ce03f0ec0f20c8a8f180a1ec0 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 11:32:39 2026 +0200 test(sse): drain call logs before thinking test cleanup (#17224) commit 2e18ee71d0e001610122467282fa4d6751bccfd3 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 11:32:29 2026 +0200 test(cache): wait for loaded provider data before asserting stats (#17220) * test(cache): wait for provider data before asserting stats * docs(changelog): reference cache wait PR 17220 * docs(changelog): file the cache page wait fragment under maintenance Co-authored-by: diegosouzapw <[email protected]> --------- Co-authored-by: diegosouzapw <[email protected]> commit 7695ba8f3c487bf952e010ffd9970b6f097e7c10 Author: Praveen K Palaniswamy <[email protected]> Date: Thu Oct 22 05:32:18 2026 -0400 fix(cli): make restart return by starting the new server detached (#17168) (#17218) * fix(cli): make restart return by starting the new server detached (#17168) omniroute restart stopped the daemon and then called runServe() without daemon: true, so it took the foreground supervisor path and never returned to the shell, which made it unusable from scripts and scheduled tasks. Pass daemon: true so the restarted server detaches like serve --daemon. runRestartCommand() now accepts injectable stop/sleep/serve deps (same pattern as runStopCommand) so the regression test never touches a real server. Update the stale update.mjs comment that described restart as foreground. * chore(changelog): add #17218 fragment commit 3fef1092a9684e09b123cc01152871a0e0bf985f Author: Rafa Martins <[email protected]> Date: Thu Oct 22 06:32:07 2026 -0300 fix(executors): normalize Bedrock tool_use ids to ^[a-zA-Z0-9_-]+$ with pairing preserved (#17143) (#17217) * fix(executors): normalize Bedrock tool_use ids to ^[a-zA-Z0-9_-]+$ with pairing preserved (#17143) Bedrock validates tool-use ids against ^[a-zA-Z0-9_-]+$ and 400s the whole request when replaying history carrying anything else. The Bedrock executor only trimmed ids, forwarding VS Code / IDE-bridge ids with dots, colons and slashes verbatim, so any conversation with such tool history failed with 'messages.N.content.0.tool_use.id: String should match pattern'. normalizeToolUseId now replaces out-of-pattern characters deterministically with a digest of the original: tool_use and tool_result go through the same helper so the pair normalizes to the same string, distinct originals stay distinct, and already-valid ids pass through exactly unchanged. * fix(executors): preserve Bedrock tool IDs across normalization collisions --------- Co-authored-by: diegosouzapw <[email protected]> commit 80dae14fec596bdaa8c5aa01bb288e921c10f715 Author: Rafa Martins <[email protected]> Date: Thu Oct 22 06:31:57 2026 -0300 chore(db): remove unreachable default-settings seeding from runMigrations (#17186) (#17216) The post-migration guard checked appliedRecords.some(name startsWith '051_'), but record names are the filename regex's SECOND capture group (text after the numeric prefix), so the branch could never execute and orphaned insertDefaultDatabaseSettings() plus its DEFAULT_DATABASE_SETTINGS import. The defaults it would have seeded are already supplied by merge-on-read (optimizationSettings.ts), so removal keeps behavior exactly; a tombstone comment records why the guard was dead. Pinned by an invariant test over the real migrations directory. commit 6abb163824a3bb35d79d2e744a010aa75840e48e Author: Paco Cartones <[email protected]> Date: Thu Oct 22 11:31:46 2026 +0200 fix(batch): upload complete input files instead of preview samples (#17213) * fix(batch): preserve complete input files during upload * test(batch): allow time for large CSV upload regression * docs(changelog): reference batch upload PR 17213 commit 2a2d98d25f12df2f6e07212663d52a2ad6eb70d1 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 11:31:36 2026 +0200 fix(batch): reject array-valued Anthropic params in JSONL validation (#17211) * fix(batch): reject array-valued Anthropic params in JSONL validation * docs(changelog): reference batch params fix PR commit e8ba277f9d251fb2281658ead86662862c016d2c Author: seathatflowsinourveins <[email protected]> Date: Thu Oct 22 05:31:26 2026 -0400 fix(executors): preserve explicit Codex reasoning context (#17210) * fix(executors): preserve explicit Codex reasoning context Preserve explicit caller reasoning context at the Codex HTTP/WebSocket wire allowlist. Keep unsupported reasoning siblings filtered and document the model/mode boundary. * chore(changelog): number the fixes fragment for #17210 commit df53b3d1156c375776b6859df06e924115c0b2ce Author: Rafa Martins <[email protected]> Date: Thu Oct 22 06:31:16 2026 -0300 fix(sse): stop injecting outputDimensionality into Gemini embeddings (#17158) (#17208) * fix(sse): stop injecting outputDimensionality into Gemini embeddings (#17158) Google's OpenAI-compatible embeddings endpoint honors the `dimensions` field and rejects the camelCase Gemini-native `outputDimensionality` with 400 "Unknown name", so every sized Gemini embedding request failed and only the default 3072-dim output stayed reachable. The native embedContent transport already maps `dimensions` to snake_case `output_dimensionality` itself, so the translation is unnecessary on both paths. Forward `dimensions` untouched and refresh the stale combo schema comment. * docs(changelog): add fragment for the Gemini embeddings outputDimensionality fix Co-authored-by: diegosouzapw <[email protected]> --------- Co-authored-by: diegosouzapw <[email protected]> commit bdcd6c0088f7a68bb0d18b258fcdc1d6cd44c9ce Author: Paco Cartones <[email protected]> Date: Thu Oct 22 09:51:26 2026 +0200 test(providers): cover the Microsoft Designer web retirement helpers (#17207) Pin the retired provider id matching, the 410 error raised for a retired provider and the guard that recognises exactly that error. Co-authored-by: pacocartones <[email protected]> commit 9106ec32df86651cf76acb6e5a3a95ca43046aa6 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 09:51:16 2026 +0200 test(dashboard): cover the hidden sidebar group normalisation (#17206) Pin the normalisation of the hidden sidebar groups setting: non-arrays give none, only known ids are kept, duplicates collapse and the canonical order wins. Co-authored-by: pacocartones <[email protected]> commit 3dbced5dc1d68016bb906c956a0f7ea26248b880 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 09:51:06 2026 +0200 test(cli): cover buildHealthcheckPath (#17205) Pin that the Node bin directory is appended to the healthcheck PATH unless it is already an exact entry, using path.delimiter so the cases hold on Windows. Co-authored-by: pacocartones <[email protected]> commit 57f487b4780694c634c3f94d7fc1b34585a242e9 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 09:50:56 2026 +0200 test(providers): cover the custom model metadata precedence (#17204) Pin that a custom model overlays only the fields it stores (null counts, undefined does not), and how discovered and custom model lists merge by id. Co-authored-by: pacocartones <[email protected]> commit 6f626bde79b91b2e4b7ffc9ec305d227df7c5dd3 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 09:50:46 2026 +0200 test(analytics): cover sortProviderDailyUsageRows (#17203) Pin the request-count table sort: string columns by localeCompare, numeric columns as numbers, ascending and descending, and the input array left in order. Co-authored-by: pacocartones <[email protected]> commit 0f76b4e3a1376d66eaa411df1a0607940ded9de8 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 09:50:36 2026 +0200 test(proxy): cover normalizeProxyHostForLog (#17202) Pin the proxy log host normalisation: trim, one pair of IPv6 brackets removed, lowercase, and null for anything that is not a non-empty string. Co-authored-by: pacocartones <[email protected]> commit d4b9ca8547b5c20a3a0347fd9bcdda48ce4623f6 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 09:50:25 2026 +0200 test(rate-limit): cover the connection override mapping (#17201) Pin how a per-connection override maps onto Bottleneck options (concurrency, minimum time, a per-minute reservoir), that zero or invalid values fall through, and how the override map is loaded. Co-authored-by: pacocartones <[email protected]> commit 823c9c33dfa78dcebe1e8f97ba96f5bf1204c509 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 09:50:15 2026 +0200 test(trae): cover parseTraeApiHost and resolveTraeApiHost (#17200) Pin that only a plain https origin under trae.ai is accepted for the token refresh host, that look-alike hosts, credentials, ports and other protocols are rejected, and that the default region is the fallback. Co-authored-by: pacocartones <[email protected]> commit 80482f49763d08694814422da4d8cdc93aa7bfff Author: Paco Cartones <[email protected]> Date: Thu Oct 22 09:50:05 2026 +0200 test(qwen): cover the thinking tool_choice sanitiser (#17199) Pin when Qwen thinking is active, which tool_choice values are incompatible with it, and that only that combination is rewritten to auto with a warning that names the provider. Co-authored-by: pacocartones <[email protected]> commit 0f1505f7fdad297c6e2905311baabee31a3da5bb Author: Paco Cartones <[email protected]> Date: Thu Oct 22 09:49:53 2026 +0200 test(fallback): cover isRequestScoped400 (#17194) Pin which 400 responses are request-scoped and so do not rotate accounts: parameter, context overflow and malformed request texts, while credential and throttling texts and other statuses are not. Co-authored-by: pacocartones <[email protected]> commit 68e1572858053bff2c4ff8cca3d6329e042e6abb Author: Paco Cartones <[email protected]> Date: Thu Oct 22 09:49:43 2026 +0200 test(sse): cover normalizeFinalOpenAIStreamChunk (#17193) Pin the final stream chunk normalisation: numeric chunk and tool-call ids become strings, aliased tool names are restored, and the result reports changes and the finish reason of the first choice. Co-authored-by: pacocartones <[email protected]> commit 0568c16789c323b0fb39f468afb77c48d8378314 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 09:49:33 2026 +0200 test(proxy): cover sanitizeTransportError (#17192) Pin the allowlist of the rethrown transport error: the caller's message, an allowlisted code or the fallback, the cause code, a short errorCode and a finite statusCode, and nothing else. Co-authored-by: pacocartones <[email protected]> commit f5c4fa29c13b125342bc2198d449e5a8c37f4de9 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 09:49:23 2026 +0200 test(fallback): cover the shared-wallet 402 helpers (#17191) Pin which providers and bodies count as a shared-wallet credit exhaustion, the usage balance signal, and the generic credit check that also accepts it. The behaviour is otherwise covered only through the auth integration test. Co-authored-by: pacocartones <[email protected]> commit 4531993a1362b8a5b1d2da62c91c9d7205f9b070 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 09:49:13 2026 +0200 test(claude-code): cover the thinking display summary rule (#17190) Pin when a compatible request gets display summarized: from the connection setting or the Copilot marker, never for disabled thinking, and how an explicit display interacts with each. Co-authored-by: pacocartones <[email protected]> commit b42e271cdddc28cac91ffad7c7db436835650e86 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 09:49:03 2026 +0200 test(gemini): cover the pasted cookie normalisation (#17189) Pin how a bare value, a cookie header and a flat or wrapped JSON export are normalised, and when the validation variant strips the Cookie prefix or returns null for a missing PSID. Co-authored-by: pacocartones <[email protected]> commit b93849a12d1b7cc8c63daf911044436d613e7c89 Author: 小妍儿 ✨ <[email protected]> Date: Thu Oct 22 15:48:53 2026 +0800 fix(cursor): preserve structured model entitlement errors (#17182) * fix(cursor): project typed Connect errors and scope weekly model denials * fix(cursor): preserve buffered stream errors and JSON whitespace * fix(cursor): classify pre-output stream failures at readiness boundary * refactor(cursor): extract JSON whitespace predicate * fix(cursor): preserve known-type legacy debug error details * docs(changelog): record Cursor entitlement error fix for #17182 * fix(cursor): keep generic ActionRequired quota cues alongside typed error details Typed or debug ErrorDetails replaced the envelope message, dropping generic ActionRequiredError quota cues and changing otherwise equivalent refusals from 429 to 502. Keep public details first and unchanged. Append the complete sanitized message only within the remaining title+detail budget; omit placeholders, duplicates and over-budget messages rather than truncating either text. Weekly model entitlement still takes precedence as 404. Public text keeps the existing allowlist, credential redaction and original length limits. Verified all three focused test files in default and private TMPDIR runs (126/126 each), including full-budget, one-character-over and exact-fit regressions for typed/debug details. All ten external probe files passed, including the independent legacy safety probe (8/8). The full Cursor run passed 769 tests with 13 skips after an unchanged retry of one subprocess SIGKILL timing assertion. Complexity/dead-code ratchets, core/open-sse/API typechecks, lint, mutation coverage and changed-file formatting passed. commit 0eaf0b3d320161e38c140a0c1f885fb7e896c727 Author: Samar Nathani <[email protected]> Date: Thu Oct 22 13:18:42 2026 +0530 fix(cli): gate cmd.exe arg escaping on the resolved spawn shell (#17178) On Windows the launch call sites (launch, launch-codex, fcc-claude and run) cmd.exe-escaped pass-through arguments unconditionally. That escaping is only correct on the `shell: true` path (npm .cmd shims, #8837); the native-installer path spawns a real claude.exe/codex.exe with `shell: undefined` (#9454), where the escaped text reached the binary literally (e.g. `^^^"-p^^^"`) and every launch was silently mangled with exit code 0. Add one shared quoteShellArgsForSpawn(args, platform, useShell) helper and gate every spawn/plan site on the resolved spec's `shell`, so the `run` dry-run plan keeps matching what actually spawns. Fixes #17177 commit e6d2cbf76e81d4d6a4ca3408bbdcd4834dfc99b9 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 09:48:33 2026 +0200 test(db): cover the API key usage-limit field helpers (#17176) Pin the usage-limit flag (true, 1 and the string 1 only), the positive finite USD limits, the snake_case then camelCase read of a row, and the SQL assignments an update appends. Co-authored-by: pacocartones <[email protected]> commit 5bb022b5e46146a79b2036277cae2c29de70d0e6 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 09:48:23 2026 +0200 test(translator): cover conversationSignatureFields (#17175) Pin which fields enter a tool conversation signature: the tool-call fields that are present, the item type unless it is message, and never role or content. Co-authored-by: pacocartones <[email protected]> commit a3614d34aca43e03bc61ca2c9cb0a7ccd61a606e Author: Paco Cartones <[email protected]> Date: Thu Oct 22 09:48:13 2026 +0200 test(cli): cover the container config write refusal message (#17174) Pin the refusal prefix that callers use to recognise it, the tool label and target path, the host command with its placeholder, and the override hint of the refusing surface. Co-authored-by: pacocartones <[email protected]> commit c3df8a0719f75d8f855f7ca2db0e5f4785d47ff7 Author: Paco Cartones <[email protected]> Date: Thu Oct 22 09:48:03 2026 +0200 test(oauth): cover resolveDashboardPort and buildSshLocalForward (#17173) Pin the dashboard port resolution (explicit port, https 443, http or missing 80) and the ssh local-forward command: one -L flag per port, duplicates collapsed, host filled in and the user placeholder left. Co-authored-by: pacocartones <[email protected]> commit ef008b34ee95230d8db848511c5306e7468fb63c Author: Paco Cartones <[email protected]> Date: Thu Oct 22 09:47:53 2026 +0200 test(combo): cover isRetryAfterEligibleStatus (#17172) Pin the statuses allowed to carry a retry-after suffix on an all-targets-failed combo response: 429 and 503 only, never a config-class status such as 422, and nothing that is not a real number. Co-authored-by: pacocartones <[email protected]> commit 686769758c62154d17d2f2d7402d3795fc886930 Author: Mike Ng <[email protected]> Date: Thu Oct 22 15:21:47 2026 +0800 fix(cache): version and canonicalize response control identities (#17161) * fix(cache): version and canonicalize response control identities * docs(cache): add response identity changelog fragment * fix(cache): reuse the semanticCache import for canonical JSON and pin the schema:2 plain-text keys Re-export canonicalJson from src/lib/semanticCache so the open-sse cache manager adds no new open-sse to src boundary edge (ratchet stays at 774), and align the #15816 plain-text key test to the intentional schema:2 identity while asserting the legacy payload is no longer reused. Co-authored-by: diegosouzapw <[email protected]> --------- Co-authored-by: diegosouzapw <[email protected]> commit fa885d4699deb386484e91eff203935cdbeab078 Author: opensource-elearning <[email protected]> Date: Thu Oct 22 12:51:37 2026 +0530 perf(resilience): memoize resolveResilienceSettings by settings object identity (#17153) * perf(resilience): memoize resolveResilienceSettings by settings object identity resolveResilienceSettings() is called on every request from chatCore.ts:417 and performs 13 normalizations + a full legacy fallback rebuild per call. The settings object comes from getCachedSettings() which uses a TTLCache with a 5s TTL that returns the SAME object reference on cache hits. This change adds a WeakMap cache keyed by that exact object reference, so repeated requests within the 5s window reuse the fully resolved ResilienceSettings object. Cold: 13 normalizations + fallback + object construction Warm (within 5s): single WeakMap get + return (zero normalizations) No behavior change: settings are immutable per request, and the cache auto-invalidates when settings are updated (TTLCache evicts old object, new object created, WeakMap key differs). Tests: resilience-stream-recovery-feature-flags, providerCooldownTracker, stream-throughput-watchdog, auto-routing-settings-resolve, cli-resilience-commands all pass. * test(resilience): cover settings memoization identity and invalidation * docs(resilience): note the memoized settings are shared and env defaults frozen per object; add changelog fragment Co-authored-by: diegosouzapw <[email protected]> --------- Co-authored-by: diegosouzapw <[email protected]> commit cd8706b36f5f22240a58806585433282d30b0b1c Author: Dizzle <[email protected]> Date: Thu Oct 22 09:21:27 2026 +0200 fix(syntx): cap SYNTX media download and data-URL decode at 50 MiB (#17152) SYNTX media fetched by URL and the three data-URL decodes loaded unbounded content into memory; the download now enforces a 50 MiB limit (413) and a client abort (499), and the data-URL decodes reject oversized payloads before allocating (400) commit dbd6492ba9cec6b33bc9788f449cceab21e2dee7 Author: opensource-elearning <[email protected]> Date: Thu Oct 22 12:51:17 2026 +0530 perf(logging): build the HH:MM:SS clock prefix without a locale formatter (#17149) * perf(logging): build the HH:MM:SS clock prefix without a locale formatter getTimeString() in streamHandler.ts and usageTracking.ts called toLocaleTimeString('en-US', ...) for every stream completion and every usage line. Intl formatting dominates that helper; the value it returns is a fixed zero-padded HH:MM:SS, so it can be built directly. Measured on Node 24.21.0, 100k iterations x 11 alternating rounds, production shape (a fresh new Date() inside each call): toLocaleTimeString median 63.19 us/call padStart median 0.30 us/call saved 62.89 us/call Two calls per request (one usage line, one stream completion log) is ~0.13 ms/request; ~126 s per million requests. Output is unchanged: compared the two implementations across 518,400 second-resolution instants spanning 6 dates (incl. a leap day and both DST boundaries) with zero value mismatches, and every value still matches the strict CLOCK_TOKEN_RE /^\d{2}:\d{2}:\d{2}$/ the console interceptor uses. Also drops an unused catch binding in usageTracking.ts that the lint gate reported, and prunes its now-orphaned suppression entry. New test pins the log prefix shape and fails on a 12-hour-clock regression. * test(logging): cover clock boundaries and interceptor parsing * chore(changelog): add the clock-prefix fragment with credit Co-authored-by: diegosouzapw <[email protected]> --------- Co-authored-by: diegosouzapw <[email protected]> commit 679c736c59960ad4a90ce9cd3fe19566cabdd34b Author: opensource-elearning <[email protected]> Date: Thu Oct 22 12:51:05 2026 +0530 perf(build): enable incremental TypeScript for the open-sse workspace typecheck (#17147) * perf(build): enable incremental TypeScript for the open-sse workspace typecheck open-sse/tsconfig.json was the one typecheck config left without incremental state after #16342 covered the four root configs. The open-sse gate therefore re-checked the whole workspace (~26k files, checkJs on JS too) on every invocation. Adds incremental + tsBuildInfoFile, reusing the existing .gitignore:306 '.tsbuildinfo-*' rule, so no build artefacts are tracked. Measured with node scripts/check/check-open-sse-typecheck.mjs on Node 24.21.0 / tsc 6.0.3, 5 runs each: cold (no build-info) 25.6s warm (build-info) 6.3s Correctness: a warm run still emits every diagnostic. Verified by injecting a deliberate TS2322 into the workspace, confirming it is reported on both the first and second run, then fixing it and confirming a clean exit. The gate reports openSseTypecheckErrors=0 against the frozen baseline in both states. * chore(changelog): name the open-sse incremental typecheck fragment after its PR with credit Co-authored-by: diegosouzapw <[email protected]> --------- Co-authored-by: diegosouzapw <[email protected]> commit 2b99af7bd9e98b0f37513c90fb479f865b02310f Author: Devulapalli Naga Sri Vaishnavi <[email protected]> Date: Thu Oct 22 12:50:53 2026 +0530 Preserve tool calls in textual reasoning chunks (#17144) * fix(responses): preserve tool calls in textual reasoning chunks * chore(changelog): name the fragment with the PR number and add the contributor credit (#17144) Co-authored-by: diegosouzapw <[email protected]> --------- Co-authored-by: diegosouzapw <[email protected]> commit 36613be7064a505c55a3a7ee356776c706488b5d Author: Saeb Amini <[email protected]> Date: Thu Oct 22 17:20:40 2026 +1000 Add Max Output Tokens override to Custom Models section (#17140) * Add max-output-tokens override editor to custom models list * Add maxOutputTokenOverride field to CompatModelRow type * Add maxOutputTokenOverrideInvalid translation key * feat(dashboard): translate the max-output-tokens override error in all locales, add UI test and changelog fragment (#17140) Adds providers.maxOutputTokenOverrideInvalid to the 66 locale catalogs, a vitest UI test covering load / number / blank-clears / invalid-no-PUT for the Custom Models editor, and the features fragment. Co-authored-by: diegosouzapw <[email protected]> --------- Co-authored-by: diegosouzapw <[email protected]> commit 5b77da523f0c6f34958cb38a71375b6400d1f137 Author: Dizzle <[email protected]> Date: Thu Oct 22 09:20:29 2026 +0200 fix(sse): reject shut-down models regardless of id letter case (#17135) The lifecycle lookup key ignores letter case on the model side like the snapshot did, so non-lowercase spellings of a shut-down model id get the record verdict (410, which then cites the canonical record id) instead of being routed. commit 6e4b353549145d6bfd8d677398df8d5e70af9aa6 Author: Dizzle <[email protected]> Date: Thu Oct 22 09:20:16 2026 +0200 fix(sse): add the compat-rejected last-resort fallback to the universal combo dispatch (#17120) the round-robin-only last-resort tier was missing from the shared dispatch loop, so a priority combo whose kept targets were all skipped answered 503 without reconsidering a compat-rejected-but-healthy target; the rejected set is now computed at resolution, threaded into the loop, and probed only when no kept target was ever attempted — each new case fails without the change. commit 20683172beed44ccf29e86863c3217e5c6eb0808 Author: Andres Felipe Diago Matta <[email protected]> Date: Thu Oct 22 02:20:04 2026 -0500 fix(sse): hand Anthropic-format clients their declared tool-name casing from every provider (#17118) (#17119) * fix(sse): hand Anthropic-format clients their declared tool-name casing from every provider Claude-format responses assembled by openai-to-claude, gemini-to-claude and the non-streaming responseTranslator all ended in restoreClaudeToolName(), whose canonical upgrade (read -> Read, #7926/#9008/#10392/#11085) assumes the client is Claude Code. OpenCode's @ai-sdk/anthropic provider declares lowercase tools, so a combo falling back from native claude to Antigravity, Codex or Gemini (or any of those used directly over /v1/messages) answered with tool_use.name "Read" for a tool declared as "read", and every tool call failed client-side ("No tool named Read"). Lift the #12721 passthrough rule into a shared module and apply it on the translate paths too: alias ledger first, then the CLIENT request's declared spelling (exact, then case-insensitive), then the legacy canonical upgrade only for names the request never declared. Claude Code declares "Read", so it keeps getting "Read" back when an OpenAI-style upstream downcases it; OpenCode gets "read" back from the same upstream. The client's tools[] is threaded into the translate-mode stream state and into translateNonStreamingResponse. * docs(changelog): add fragment for #17119 * fix(sse): keep streamBufferBytes the last parameter of the transform helper clientTools now sits before streamBufferBytes, so the trailing buffer-size slot GLM fills (#12925) stays last and GLM's call matches the helper arity. GLM passes null for clientTools; the translate call in streamingTail drops its placeholder undefined. * fix(sse): carry declared tools through Claude passthrough (#17118) * refactor(sse): split declaredToolCasing helpers under the complexity gate findDeclaredToolName (cyclomatic 17, cognitive 18) and restoreDeclaredToolName (cognitive 17) were over the new-code limit of 15. Extract declaredNameOf() for the per-entry tools[] spelling and aliasLedgerName() for the _toolNameMap precedence; behaviour is unchanged and the 17118/17119 suites still pass. --------- Co-authored-by: diegosouzapw <[email protected]> commit 4aa9958bc2966f9d38d325e92f9d709d05a1e51b Author: Dizzle <[email protected]> Date: Thu Oct 22 09:19:53 2026 +0200 fix(sse): a second 429 with a shorter cooldown no longer shortens the active cooldown (#17116) markRateLimited overwrote the expiry unconditionally, so a second 429 with a shorter cooldown reopened the combo target early; the assignment now keeps the later expiry commit 2d43b5c83b41970490bdb405615726c9eb41e045 Author: Davy Massoneto <[email protected]> Date: Thu Oct 22 04:19:41 2026 -0300 fix: preserve capped Claude output when fitting thinking budget (#17110) * fix: preserve capped Claude output when fitting thinking budget * docs: add changelog for Claude thinking budget fix commit ce826c8c21edd72872c40a80e2d625b1d9a9b884 Author: Junior-HJ <[email protected]> Date: Thu Oct 22 02:19:12 2026 -0500 fix(codebuddy): harden gateway compliance and tool schema contracts (#17080) * fix(codebuddy): harden gateway compliance and tool schema contracts * style(test): format codebuddy gateway hardening test with prettier commit 5d315a9562064a6eec61cb2fd7832f65db01e991 Author: opensource-elearning <[email protected]> Date: Thu Oct 22 12:49:02 2026 +0530 feat(sse): let the quota-refusal egress scope cover every provider (#17074) * feat(sse): let the quota-refusal egress scope cover every provider A 429 received through a proxy pool member set that member aside only for the IP-bucketed family (opencode-go, opencode-cli, plus opencode-zen when its flag is on). Every other provider kept egressing through the member that had just answered 429, so a rate limit could not rotate the address — worst for the free and keyless providers, whose quota is per IP because there is no key to bucket by. rotatesEgressOnQuotaRefusal() widens that write to every provider when the operator sets OMNIROUTE_EGRESS_QUOTA_ROTATION_PROVIDERS=all, and keeps the IP-bucketed family as the only default. The clear-on-success arm, the sibling-connection lockout and every existing default are unch…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This currently fails CI on every open PR in the repo, including ones that touch nothing near it.
#12179gave GLM a 64 KB stream buffer by adding a 16th argument tocreateSSETransformStreamWithLogger:The signature takes 15. Two things followed.
The visible one.
open-sse/executors/glm.ts(244,5): error TS2554: Expected 2-15 arguments, but got 16.The API Route Typecheck gate is "reject new errors not covered by the frozen baseline", so it reports1 new/regressed TypeScript errorand fails — on any PR, since nobody's branch removes it. I found it while auditing my own open PRs: #12896 fails the identical 8 checks after a clean rebase ontorelease/v3.8.51, with a test that passes locally, and #12919 (not mine) fails the same 8.The quiet one. The extra argument was simply dropped, and
createSSEStreamhard-coded the queue budget:So the 64 KB that call site asks for never reached the stream. The perf change that hunk describes has not been in effect since it landed — the comment above it has been describing something that does not happen.
The change
StreamOptionsgainsstreamBufferBytes, defaulting to the16384every provider used before it was configurable; the convenience wrapper takes it as a 16th parameter and forwards it; both queues use it.glm.tsis unchanged — its existing call now compiles and takes effect, which is the smallest edit that makes the original intent true rather than deleting it.The default is a parameter default rather than
|| DEFAULT, so a deliberate0— a queue that applies backpressure immediately — is honoured instead of silently replaced. There is a test for exactly that, because||here is the easy way to write it and the wrong one.Verification
tsc -p tsconfig.typecheck-api.json: 299 → 298 errors. The one removed isglm.tsTS2554, the one the gate names. Nothing new appears; the remainingstream.tsTS2353 aboutcancelis in the frozen baseline (it sat at line 2986 before this change and 2998 after — same error, shifted by the added lines).tests/unit/sse-stream-buffer-bytes.test.ts: 5 passed. ATransformStream's writable queue starts withdesiredSize === highWaterMark, so the assertions measure the queue that was actually built rather than standing in for it.Each claim checked by breaking it:
16384|| DEFAULTstreamBufferBytes: 0testI have not touched the buffer size for any other provider: without the option, every stream still gets 16384.