Compare commits

...

88 Commits

Author SHA1 Message Date
diegosouzapw
189c867c51 Merge remote-tracking branch 'origin/release/v3.8.50' into test/9178-opencode-plugin-bare-key-suite 2026-08-08 00:43:00 -03:00
Markus Hartung
acfb844852 fix(db): resolve migration version 135 numbering collision (#9745)
Two files both claimed migration version 135:
135_connection_runtime_state.sql (#9449, landed 2026-08-07) and
135_migrate_model_capability_max_token.sql (#8908, landed 2026-08-05).
#9449 branched before #8908 merged and never got renumbered before
landing on release/v3.8.50.

This is not cosmetic: getMigrationFiles() throws "Migration version
collision detected" the moment ANY code path first touches the
database (getDbInstance() -> runMigrations()), which means a
completely fresh install/deploy from this branch cannot even boot —
confirmed live against a freshly built container while testing
unrelated live-verification tooling.

Renumbered the later-landing file to 140 (the next free slot) and
added the matching isSchemaAlreadyApplied("140") retroactive guard in
migrationRunner.ts, so a DB that already ran this migration under the
old 135 number isn't treated as needing a fresh application. This
matches the established pattern already used for the prior 135/136 ->
137/138 renumber in the same file (also caused by the same recurring
branch-before-merge numbering race).

Test plan:
- TDD: new tests/unit/migration-135-numbering-collision.test.ts (2/2)
  — spins up a hermetic fresh DB and confirms getDbInstance() applies
  every real on-disk migration without throwing, plus confirms both
  formerly-135 migrations' effects are present. Confirmed failing
  (reproducing the exact live crash) with the pre-fix colliding
  filenames restored, passing after the rename.
- npm run typecheck:core — clean
- npm run lint — clean
- npm run check:file-size — clean (migrationRunner.ts rebaselined
  1084->1094 for the new guard case)
- Full migration-runner + migration-numbering test suites (64 tests
  across 6 files) — all pass, no regressions
2026-08-08 00:42:29 -03:00
diegosouzapw
7a463bc5f9 Merge remote-tracking branch 'origin/release/v3.8.50' into test/9178-opencode-plugin-bare-key-suite 2026-08-07 23:58:26 -03:00
Diego Rodrigues de Sa e Souza
ee0f4298ca feat(plugins): expose client request headers in plugin context (#9570) (#9668)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:54:32 -03:00
Markus Hartung
0082ac5113 fix(openrouter): scope model failures per-model instead of poisoning the whole connection (#9635)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:54:29 -03:00
Arnav Rastogi
e224165689 fix(providers): mint a Zed LLM token for zed-hosted model discovery (#9628)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:54:26 -03:00
stanley
552f2e1563 fix(backend): stop reasoning replay placeholder from self-poisoning (#9573) (#9610)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:54:23 -03:00
vinogradovnet
8cb51b7922 feat(compression): add Russian language pack (#9581)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:54:20 -03:00
Maxim Seshuk
8da0aa5ba8 fix(standalone): don't await the WebDAV check before Next's listener (#9580)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:54:17 -03:00
Maxim Seshuk
7a369ba6e3 feat(audio): add Soniox STT + TTS provider (#9579)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:54:14 -03:00
backryun
e60d7f81b8 fix(types): preserve streaming PII choice keys (#9566)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:54:11 -03:00
backryun
e62516364c fix(types): validate Vision Bridge combo names (#9565)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:54:08 -03:00
backryun
c4f37ab01b fix(types): preserve sanitized tool array contracts (#9564)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:54:05 -03:00
backryun
250ea5e849 fix(types): validate Azure OpenAI base URLs (#9563)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:54:03 -03:00
backryun
8b4240831b fix(types): narrow chatCore local contracts (#9562)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:54:00 -03:00
backryun
4da8ef47c3 fix(types): align stream failure callback contracts (#9561)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:57 -03:00
Shixi Li
0a1f84f83f fix(classify): honor upstream retry windows on Gemini free-tier 429s (#9513)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:54 -03:00
nguyenha935
20a4ab6f55 fix(api): stop rejecting long chat histories by default (#9494)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:51 -03:00
Bob.Hou
765fd71aea feat(lib): make MODELS_DEV_SYNC_ENABLED actually control the sync (#9483)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:47 -03:00
Bob.Hou
29d97ac328 fix(sse): take the Antigravity output ceiling from the model, not a constant (#9482)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:44 -03:00
dependabot[bot]
1f77f75b69 deps: bump undici from 8.9.0 to 8.10.0 (#9472)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:41 -03:00
Poid-ZA
d22839626b chore(db): raise sqlite cache_size/mmap_size defaults (#9467)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:38 -03:00
dependabot[bot]
cd97ce5c2c chore(deps): bump docker/login-action from 4.5.2 to 4.6.0 (#9462)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:35 -03:00
dependabot[bot]
656526bc8d chore(deps): bump github/codeql-action/init from 4.37.3 to 4.37.4 (#9461)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:33 -03:00
dependabot[bot]
9f9ff3911a chore(deps): bump github/codeql-action/analyze from 4.37.3 to 4.37.4 (#9459)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:30 -03:00
dependabot[bot]
7b7b22cec4 chore(deps): bump github/codeql-action from 4.37.3 to 4.37.4 (#9458)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:27 -03:00
小妍儿 ✨
6aa9d90067 fix(ui): preserve request log position (#9452)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:24 -03:00
Bob.Hou
217ac4c829 feat(warmup): proactive Claude warmup scheduler (#8848) (#9449)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:21 -03:00
Diego Rodrigues de Sa e Souza
2f5df569b5 feat(api): add plugins marketplace install API (#6752) (#9445)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:18 -03:00
Diego Rodrigues de Sa e Souza
348102a114 feat(api): add response content encoding verification (#6736) (#9444)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:15 -03:00
Wahyu Hidayatulloh Pamungkas
e6bf92ad65 fix(usage): stop double-counting cache-read tokens in Command Code executor (#9438)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:12 -03:00
Pixma
99be4474b6 fix(providers): correct Codex GPT-5.6 context limits (#9432)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:08 -03:00
dependabot[bot]
6287caa5b3 deps: bump the npm_and_yarn group across 1 directory with 2 updates (#9427)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:05 -03:00
Diego Rodrigues de Sa e Souza
babc75f058 feat(chatgpt-web): harden prompt-emulated tool contract for thinking models (#7679) (#9422)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:02 -03:00
Chewji
062d48b1ad fix(combo): keep operator-defined model order for deterministic strategies (#9420)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:53:00 -03:00
Diego Rodrigues de Sa e Souza
74cf860ccc feat(providers): make video_url passthrough configurable per provider/model (#9248) (#9419)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:52:57 -03:00
Diego Rodrigues de Sa e Souza
915b383a71 fix(qoder): surface qodercli stderr in error message instead of generic 502 (#9319) (#9412)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:52:54 -03:00
Bob.Hou
7857e0ac6d refactor(sse): move the thinking-budget helpers out of base.ts (#9381)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:52:51 -03:00
Bob.Hou
f22b81c2d2 fix(sse): drop the localDb barrel imports from chat and auth (#9380)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:52:48 -03:00
Bob.Hou
c32818e738 test(quota): wait for the hot-path consumption instead of sleeping (#9365)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:52:45 -03:00
Milan Soni
696435efa8 fix(routing): normalize time remaining in reset-window strategy (#9330) (#9353)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:52:42 -03:00
Bob.Hou
a57e4ab873 fix(auth): let an agy request find the connection it authorized (#9340)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:52:38 -03:00
Diego Rodrigues de Sa e Souza
9dd6251361 feat(dashboard): render provider API key registration links (#9270) (#9336)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:52:35 -03:00
Diego Rodrigues de Sa e Souza
e3769ba709 feat(providers): accept JSON cookie objects in normalizeSessionCookieHeader (#9284) (#9335)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:52:32 -03:00
Diego Rodrigues de Sa e Souza
7fb3b7558a feat(providers): support max reasoning effort for opencode-zen DeepSeek models (#9318) (#9334)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:52:29 -03:00
SAMUEL AUGUSTO GUIMARAES LOPES
236aad07c7 fix(mcp): stop DB init logging from corrupting the stdio JSON-RPC stream (#9281)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:52:26 -03:00
SAMUEL AUGUSTO GUIMARAES LOPES
41d2e4e7a1 test(compression): lock in stacked RTK+Caveman savings on redundant tool_result content (#9278)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:52:23 -03:00
小妍儿 ✨
12e5c83692 fix(chat): resolve stored combo names before image-model validation (#8986) (#9027)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:52:20 -03:00
小妍儿 ✨
305a9d5f37 docs(troubleshooting): document the chat_admission_busy 503 and how to tune heavyweight chat concurrency (#9021)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:52:17 -03:00
小妍儿 ✨
9dc8cdbcb1 fix(kiro): keep interleaved tool results grouped without dropping assistant text (#8903) (#8931)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:52:13 -03:00
小妍儿 ✨
c73af2761e fix(providers): expose dual-auth actions for CodeBuddy CN (#8921)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:52:10 -03:00
小妍儿 ✨
3f4f2000b6 fix(proxy): isolate registry credentials from autofill (#8883)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:52:06 -03:00
小妍儿 ✨
124f64a6c0 fix(cli): default Codex wire API to responses (#8876)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:52:03 -03:00
小妍儿 ✨
5d71f47815 fix(opencode): complete generated model limits (#8869)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
2026-08-07 20:51:59 -03:00
Diego Rodrigues de Sa e Souza
6c22f8d4c3 fix(api): specialty model catalog ignores hidden OpenRouter model flags (#9293)
The specialty model catalog loops (image, rerank, audio, moderation, video,
music) in catalog.ts reduced OpenRouter model IDs to only the final path
segment via .split("/").pop() before calling getModelIsHidden(), so stored
hidden flags with full provider-relative paths (e.g. openrouter+google/chirp-3)
were never matched.

Fix: introduce a shared getSpecialtyModelRelativeId helper that strips only
the provider prefix (like the embedding loop already did), and apply it to
all 6 affected specialty loops. Also add a hidden-model guard to the live
OpenRouter catalog path that had no such check at all.

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:09:17 -03:00
Diego Rodrigues de Sa e Souza
a651ffa66a fix(backend): use accumulated responseBody for provider payload to avoid stale dashboard log viewer data (#9315)
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:09:12 -03:00
Diego Rodrigues de Sa e Souza
df64220087 fix(web-search): bind each search provider attempt to its connection proxy (#9201)
* fix(web-search): bind each search provider attempt to its connection proxy (#9201)

The search path resolved credentials but never resolved the connection
proxy, so the upstream fetch always egressed directly. The connection-test
path already used the proxy correctly, proving the gap was in the
data-plane transport binding.

- Resolve the connection proxy before each upstream attempt using the
  existing resolveProxyForConnection(connectionId, apiKeyId, providerId)
  precedence chain, then wrap the fetch in runWithProxyContext so the
  patched globalThis.fetch routes through the configured proxy.
- Resolve and bind the alternate connection proxy independently during
  failover, so the primary account's context never leaks into the fallback.
- Carry connectionId and apiKeyId through SearchHandlerOptions into the
  route and executeWebSearch callers.
- Add connectionId to all saveCallLog entries in tryProvider, so the
  regular call log identifies the account.
- Emit a sanitized logProxyEvent per real upstream search attempt with
  provider, connection ID, proxy level, status, duration, and target
  origin/path (no query, API key, or proxy credentials).
- Cover both POST /v1/search and executeWebSearch() consumers (MCP,
  internal, skills) since both bypassed the same proxy binding.

* fix(sse): extract search proxy binding into leaf module to fit file-size cap

Move the per-attempt proxy resolution, proxied fetch, sanitized proxy-event
emission, and response handling for web search providers out of
open-sse/handlers/search.ts into a new open-sse/handlers/search/searchProxy.ts,
so the provider-dispatch chokepoint (tryProvider) stays a thin wiring call and
search.ts fits back under the frozen file-size cap (1536 lines).

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:09:08 -03:00
Diego Rodrigues de Sa e Souza
09e4c150c1 fix(credential-health): scheduler never retries failed connections due to static interval comparison (#9289)
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:09:00 -03:00
Diego Rodrigues de Sa e Souza
6d99a01a0b fix(catalog): cache getModelsDevPricing() to prevent OOM at startup (#9300)
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:08:56 -03:00
Diego Rodrigues de Sa e Souza
d92e984fec fix(providers): bump qwen-web SPA version header from 0.2.66 to 0.2.81 (#9304)
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:08:52 -03:00
Diego Rodrigues de Sa e Souza
85e518b7f4 fix(qoder): include actionable CLI_QODER_BIN hint in connection test when qodercli is not found (#9277)
When the Qoder CLI (qodercli) is not detected by getCliRuntimeStatus after
an OmniRoute restart (e.g. restricted launch context on Windows where
APPDATA/PATH are not inherited), the connection test showed only the
non-actionable 'Local CLI runtime is not installed'. Now it surfaces the
same buildQoderCliNotFoundHint guidance already used in the executor path,
telling the user to set CLI_QODER_BIN to the absolute path of qodercli.

Closes #9277

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:08:48 -03:00
Diego Rodrigues de Sa e Souza
3be585ef41 fix(providers): use prefix regex for web search fallback detector to catch versioned tool types (#9279)
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:08:44 -03:00
Diego Rodrigues de Sa e Souza
2e71558a0f fix(providers): modal.com validation returns clear error when Base URL is missing (#9102)
Modal (modal.com) is bring-your-own-deploy and requires a Base URL pointing to the
user's OpenAI-compatible Modal app. The connect-connection form labels the Base URL
override field as Optional, but the modal validator does not handle the empty case:
when no Base URL is set it passes normalizeBaseUrl('') into validateOpenAILikeProvider,
which builds an empty probe URL and trips parseOutboundUrl, leaking the raw guard
message 'Invalid outbound URL: '.

Fix: guard the empty/whitespace baseUrl case in the modal specialty validator and
return a clear, actionable error message explaining that a Base URL is required.
Add a regression test asserting the fix.

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:08:39 -03:00
Diego Rodrigues de Sa e Souza
771d3e363a fix: make antigravity and agy equivalent in credential selection (#9204)
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:08:35 -03:00
Diego Rodrigues de Sa e Souza
0bc72cfd65 fix(providers): manual Vision capable override does not affect Combo routing (#9195)
Three linked bugs prevented the Custom Models 'Vision capable' toggle from
affecting Combo routing, causing 400 capability_mismatch on image requests
sent through Combos targeting a custom vision model.

Bug #1 (catalog, dead guard): modelType === 'chat' was always false for
chat models because modelType was only assigned 'embedding', 'rerank',
'image', or 'audio'. Changed the guard to !modelType || modelType ===
'chat' so getCustomVisionCapabilityFields() fires for custom chat models.

Bug #2 (catalog, synced-first ordering): When a model appeared in both
syncedAvailableModels (from discovery) and customModels, the custom row
was skipped entirely, losing the vision override. Now merge vision fields
into the existing synced entry when the custom model has an explicit
supportsVision boolean.

Bug #3 (routing capabilities): getResolvedModelCapabilities() /
resolveVisionCapability() had no path to consult the customModels
supportsVision flag. Added a sync DB lookup helper and a new
customVisionOverride parameter so the dashboard toggle affects Combo
routing.

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:08:30 -03:00
Diego Rodrigues de Sa e Souza
46e5dfdc8f fix(lmarena): emit Uint8Array SSE chunks instead of strings to satisfy shared pipeline contract (#9237)
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:08:26 -03:00
Diego Rodrigues de Sa e Souza
41d16c9bb4 fix(api/analytics): stop charging :free models at arbitrary fallback price (#9054)
resolveModelPricing() in analytics route fell back to
Object.keys(providerPricing)[0] when a model had no pricing
entry. For OpenRouter, the defaults layer always contributes
an 'auto' record as the first key, so every :free model was
charged at that arbitrary rate in the analytics dashboard.

Fix: short-circuit :free models to return null before the
last-resort fallback, and remove the Object.keys(...)[0]
arbitrary-substitution fallback.

Closes #9054

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:08:22 -03:00
Diego Rodrigues de Sa e Souza
cfeea5dc5b fix(cli): enable systray2 on Windows for Norton-friendly tray (#8609)
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:08:17 -03:00
Diego Rodrigues de Sa e Souza
6aac7b0c8f fix(opencode): propagate vision capability from live catalog into opencode.json (#8960)
The opencode config generator fetched the live /v1/models catalog but only
extracted context_length for new model entries, discarding capabilities
(capabilities.vision, input_modalities, etc.) that OpenCode uses to gate
clipboard/image input. Newly discovered vision-capable models were presented
as text-only, causing OpenCode to reject attachments before sending the HTTP
request.

- Add input_modalities/output_modalities to CatalogModelEntry
- Add deriveOpenCodeCapabilities() helper mapping catalog capabilities to
  OpenCode fields (attachment, reasoning, temperature, tool_call) with
  explicit user override precedence
- Replace the existing round-trip-only flag loop in buildModelEntry() with
  the new helper so catalog-derived values fill in for new models

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:08:13 -03:00
Diego Rodrigues de Sa e Souza
bf8277ad46 fix(ui): normalize Free Pool API response payload to read from data.proxies (#9046)
The GET /api/settings/free-proxies route returns { success, data: { proxies, total, ... } }
since #6909, but FreePoolTab.loadData() was reading data.items and data.total from the
top-level JSON — both undefined, causing the proxy table to always show as empty
despite synced stats rendering correctly from the separate /stats endpoint.

Fix: normalize the payload with body?.data ?? body fallback so both the current
nested contract (data.proxies) and any legacy top-level shape work.

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:08:08 -03:00
Diego Rodrigues de Sa e Souza
c50c783549 fix(proxies): resolveProxyForConnection now returns the proxy name, so the dashboard badge shows the name instead of the hostname (#8995)
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:08:04 -03:00
Diego Rodrigues de Sa e Souza
ebddc51575 fix(providers): gate premium opencode-zen/opencode-go models behind an API key (#8681)
* fix(providers): gate premium opencode-zen/opencode-go models behind an API key (#8681)

Root cause: the free/noauth opencode provider (and opencode-zen/opencode-go)
expose the full upstream model list including PREMIUM models (gpt-5, claude-*,
gemini-*, kimi-k2.6, etc.). With a keyless connection, the executor sends no
Authorization header and upstream returns 401 'Missing API key' for any
premium model — which is the exact string the client shows.

Fix: add a request-time gate in OpencodeExecutor.execute() that detects
keyless connections + premium models and returns a clear 402 error with
message 'This model requires an opencode API key — add one in Settings →
Providers.' instead of proxying the raw upstream 401.

Free models (known free catalog +  suffix) continue to work keyless
(deepseek-v4-flash-free, big-pickle, etc.). Users with a valid opencode API
key keep premium access. opencode-go has no free tier — all models require
a key.

* fix(providers): use a free opencode model in the #7993 proxy-routing test

The #8681 keyless-premium gate short-circuits 'grok-code' (a premium
model) with 402 before any fetch happens, so the proxy-egress assertion
never saw a request. Swap to 'deepseek-v4-flash-free' (already applied
to the sibling opencode-proxy-rotation-4954.test.ts in this same PR)
so the test again exercises the proxy-routing path it targets.

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:07:59 -03:00
Diego Rodrigues de Sa e Souza
4299085da1 fix(db): stream DB backup export instead of buffering entire file into memory (#9045)
The GET /api/db-backups/export route used fs.readFileSync + new Response(buffer) which buffered the entire database backup into memory — for a 280MB DB this spiked RSS to ~1.5GB (5.3x the DB size), causing timeouts on constrained machines.

Fix: stream the backup file as a ReadableStream response body using fs.createReadStream + ReadableStream, keeping peak RSS under 0.5x the DB size. Includes cleanup on stream completion, error, and client abort.

Also: changed fs.copyFileSync to await fs.promises.copyFile in node:sqlite, bun, and sql.js adapters so the backup() call does not block the event loop during a large DB copy.

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:07:55 -03:00
Diego Rodrigues de Sa e Souza
48b17ff2b7 fix(build): remove misleading open-sse/package.json facade and add workspace typecheck gate (#8781)
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:07:50 -03:00
Diego Rodrigues de Sa e Souza
a76bee9f3e fix(providers): switch Antigravity quota RPCs to iterate ANTIGRAVITY_RUNTIME_BASE_URLS (#8965)
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:07:46 -03:00
Diego Rodrigues de Sa e Souza
d12c3b37da fix: resolve two macOS-only test/script failures in unit suite (#8577)
bin/restore-policies.sh used readarray (bash 4+), which fails on macOS
bash 3.2. Replace with a compatible while-read loop.

machineId.test.ts disableWindowsRegistryStrategy() did not neutralize
the macOS ioreg strategy, so mocked os.hostname() was never reached
on macOS and both ladder tests failed. Stub execSync for ioreg commands
so the fallback chain reaches os.hostname() as intended.

Production src/shared/utils/machineId.ts is correct and unchanged.

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:07:42 -03:00
Diego Rodrigues de Sa e Souza
1b83b337b3 fix(cli): fall back to node:sqlite when better-sqlite3 constructor throws at runtime (#8826)
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:07:38 -03:00
Diego Rodrigues de Sa e Souza
7be4e55e7e fix(opencode-zen): add current free-tier models to registry to enable combo context pre-filtering (#8841)
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:07:34 -03:00
Diego Rodrigues de Sa e Souza
904e8af09a fix(opencode): prefix provider id with opencode- for auth login command (#8830)
The bundled @omniroute/opencode-plugin registers its provider under
'opencode-omniroute' (the 'opencode-' prefix is required by OpenCode
>=1.17.8's native-adapter gate on model providerID). But the CLI
instructed 'opencode auth login --provider omniroute' — the unprefixed
id — so OpenCode reported 'Unknown provider "omniroute"' because it
resolves --provider against the exact provider id the plugin registered.

Add resolveOpenCodeAuthProviderId() helper that idempotently adds the
'opencode-' prefix when absent, and use it everywhere the CLI builds
or prints the --provider argument: resolveOpenCodeAuthSpawn args,
runOpenCodeAuth ENOENT message, and runSetupOpenCodeCommand 'Run
manually'/'Next step' messages. Update the plugin README and test
assertions to match.

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 18:07:30 -03:00
diegosouzapw
24635f7d48 Merge origin/release/v3.8.50 (post-#9688) — base wins on babysit/sweep duplicated fixes; baseline reconcile belongs to #9616 2026-08-07 11:17:03 -03:00
diegosouzapw
0b8c6b3a8c Revert "fix(pr): rename duplicate migration 134_proxy_logs_egress_ip to 139"
This reverts commit 1312e1a917.
2026-08-07 04:18:11 -03:00
diegosouzapw
255cc8b02a docs(changelog): fragment for #9614 2026-08-07 03:21:11 -03:00
diegosouzapw
1312e1a917 fix(pr): rename duplicate migration 134_proxy_logs_egress_ip to 139 2026-08-07 01:26:34 -03:00
diegosouzapw
9f76c63559 fix(pr): update file-size baseline for base-red drift after merging 48 base commits 2026-08-07 01:22:10 -03:00
diegosouzapw
a1a04877c9 fix(pr): remove YAML frontmatter from changelog fragment (validator expects bare bullet) 2026-08-07 01:07:56 -03:00
diegosouzapw
d1bfa7a3c0 fix(pr): fix changelog fragment format, login-bootstrap test assertions, and VM_DEPLOYMENT_GUIDE fabricated env vars 2026-08-07 00:52:53 -03:00
diegosouzapw
985e8ba44c Merge remote-tracking branch 'origin/test/9178-opencode-plugin-bare-key-suite' into babysit/pr-9614 2026-08-07 00:01:45 -03:00
diegosouzapw
56c8ecab44 test(cli): realign opencode-plugin suite to the bare-key static-catalog contract
#9178 (fix #9175) dropped the provider prefix from static-catalog model
dict keys — the correct production behavior (OC's getModel looks models up
by bare id), live-validated in the PR — but the subpackage's own suite was
not swept: 21 tests in config-shim.test.ts + provider-id-routing.test.ts
still asserted the prefixed keys, breaking opencode-plugin CI on every
living-release-PR run since the merge.

- Lookups opencode-omniroute/<raw-id> -> <raw-id>; omniroute/<combo> -> <combo>.
- #7976 anti-double-prefix invariant kept (the negative assert on the
  OC-gate-prefixed key stays).
- Obsolete comment above the raw-model dict write rewritten to describe
  the #9175 contract it contradicted.
- Subpackage lockfile synced to the already-bumped 0.2.1.

Validation: full subpackage suite hermetic — 287/287 pass (was 21 failing).
2026-08-06 10:41:32 -03:00
271 changed files with 12745 additions and 1235 deletions

View File

@@ -345,8 +345,11 @@ ALLOW_API_KEY_REVEAL=false
# OMNIROUTE_CHAT_HEAVY_TOOL_COUNT=64
# Conservative string-size token estimate that classifies a request as heavyweight. Default 32000.
# OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS=32000
# Hard message-count cap; excess receives compact-required 413. Default 800.
# OMNIROUTE_CHAT_HARD_MAX_MESSAGES=800
# Optional opt-in hard message-count cap; excess receives compact-required 413 before
# compression can run. Unset/0 (the default) means no history cap: heap growth is bounded
# by OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT and the heap-pressure shed instead. Set a positive
# value only on memory-constrained deployments that need a hard ceiling.
# OMNIROUTE_CHAT_HARD_MAX_MESSAGES=0
# Hard cap (bytes) for a non-streaming upstream response buffered fully into memory
# (#5152). Past this the upstream reader is cancelled and the request fails fast
@@ -794,6 +797,16 @@ PROVIDER_LIMITS_SYNC_SPACING_MS=1500
# Disable the proactive recovery scheduler entirely (default: false).
# OMNIROUTE_DISABLE_CONNECTION_RECOVERY=false
# Proactive Claude warmup scheduler (#8848): fires a trivial request to opted-in
# OAuth connections on a cron schedule (America/Los_Angeles) so accounts do not
# hit the 5-hour sliding window cold. Off by default — set ENABLED=1 and flip
# per-connection flags in settings.claudeWarmup.connections to activate.
# Used by: src/lib/warmupScheduler.ts.
# OMNIROUTE_WARMUP_ENABLED=false
# OMNIROUTE_WARMUP_CRON="0 7 * * *"
# OMNIROUTE_WARMUP_CONCURRENCY=3
# OMNIROUTE_WARMUP_MODEL=
# Background job interval for budget reset checks (ms). Default: 600000 (10m).
# Used by: src/lib/jobs/budgetResetJob.ts. Floor: 10000.
#OMNIROUTE_BUDGET_RESET_JOB_INTERVAL_MS=600000
@@ -1527,6 +1540,15 @@ APP_LOG_TO_FILE=true
# ═══════════════════════════════════════════════════════════════════════════════
# 19. MODEL SYNC (Dev)
# ═══════════════════════════════════════════════════════════════════════════════
# Enable the models.dev capability sync. Default: false (opt-in only).
# Also settable from Dashboard > Settings > AI. This variable wins over that
# setting whenever it is set to anything non-empty, in either direction, so a
# deployment can pin the sync on or off without depending on database state
# surviving a rebuild. Leave it unset to let the dashboard toggle decide.
# On: 1, true, yes or on (any casing). Any other value is off.
# Used by: src/lib/modelsDevSync.ts
# MODELS_DEV_SYNC_ENABLED=false
# Development-time model catalog sync interval in seconds.
# Used by: src/lib/modelsDevSync.ts
# Default: 86400 (24 hours)

View File

@@ -22,10 +22,10 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
- uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
with:
languages: javascript-typescript
queries: security-extended
- uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
- uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
with:
category: "/language:javascript-typescript"

View File

@@ -137,13 +137,13 @@ jobs:
uses: docker/setup-buildx-action@v4
- name: Login to Docker Hub
uses: docker/login-action@v4.5.2
uses: docker/login-action@v4.6.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v4.5.2
uses: docker/login-action@v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -237,13 +237,13 @@ jobs:
uses: docker/setup-buildx-action@v4
- name: Login to Docker Hub
uses: docker/login-action@v4.5.2
uses: docker/login-action@v4.6.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v4.5.2
uses: docker/login-action@v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -372,7 +372,7 @@ jobs:
- name: Upload Trivy SARIF to Security tab
if: needs.prepare.outputs.version != 'main'
continue-on-error: true
uses: github/codeql-action/upload-sarif@v4.37.3
uses: github/codeql-action/upload-sarif@v4.37.4
with:
sarif_file: trivy-results.sarif
category: trivy-image

View File

@@ -271,6 +271,10 @@ jobs:
# covered by typecheck:core's curated allowlist. See check-dashboard-typecheck.mjs.
- name: Typecheck (dashboard)
run: npm run check:dashboard-typecheck
# #8781: open-sse workspace typecheck gate — the workspace imports @/ which
# escapes to src/ via undeclared path aliases. See check-open-sse-typecheck.mjs.
- name: Typecheck (open-sse)
run: npm run check:open-sse-typecheck
# WS4.2 (v3.8.49 plan): TypeScript 7 native-compiler SHADOW — advisory only.
# TS7 went GA 2026-07-08 with 8-12x type-check speedups; its Compiler API only
# arrives in 7.1, so typescript-eslint / type-coverage / Stryker stay on 6.x

View File

@@ -30,7 +30,7 @@ omniroute setup opencode --auth
# 3. Restart OpenCode — /models lists the full live catalog
```
The `--auth` flag runs `opencode auth login --provider omniroute` automatically.
The `--auth` flag runs `opencode auth login --provider opencode-omniroute` automatically.
Use `--base-url` to point at a non-default OmniRoute address:
```sh
@@ -84,7 +84,7 @@ Peer dep: `@opencode-ai/plugin` (managed by your OpenCode install).
```
```sh
opencode auth login --provider omniroute
opencode auth login --provider opencode-omniroute
# prompts for the OmniRoute API key, writes to ~/.local/share/opencode/auth.json
```
@@ -164,8 +164,8 @@ Then in `~/.config/opencode/opencode.json` reference each directory by absolute
Paths are relative to `~/.config/opencode/`. Each entry now resolves to a distinct module file, so OC loads them as two separate plugin instances. Authenticate each:
```sh
opencode auth login --provider omniroute
opencode auth login --provider omniroute-preprod
opencode auth login --provider opencode-omniroute
opencode auth login --provider opencode-omniroute-preprod
```
Each entry gets its own provider id, its own model picker entry, its own slot in `auth.json`, and its own TTL cache. Closures are isolated per plugin instance — no cross-talk.

View File

@@ -4403,11 +4403,11 @@ export function buildStaticProviderEntry(
entry.release_date = raw.release_date;
}
// OC's static-catalog reader parses each key on `/` and rejects the
// entire provider block if ANY key resolves to a parsed providerID that
// has no corresponding provider block. So bare keys (no `/`) MUST be
// prefixed with the resolved providerId. Already-prefixed keys
// (e.g. `cc/claude-opus-4-7`) are left as-is to avoid double-prefixing.
// #9175: OC's `getModel` looks the model up by BARE id — the part after
// the first `/` in the user's request — so a dict key with an embedded
// provider prefix (`<providerId>/<raw-id>`) is unreachable. Keys are the
// raw id verbatim; ids that already contain `/` (e.g. `cc/claude-opus-4-7`)
// keep it because the slash is part of the upstream model id itself.
models[raw.id] = entry;
}

View File

@@ -227,7 +227,7 @@ test("config: with valid auth.json + apiKey + baseURL → mutates input.provider
// Stripped per-model shape: name + cap flags + modalities + (optional)
// cost. OC's SDK static schema accepts only `limit.{context,output}` —
// `limit.input` is NOT in the SDK shape and gets dropped silently.
const claude = entry.models["opencode-omniroute/claude-sonnet-4-6"];
const claude = entry.models["claude-sonnet-4-6"];
assert.ok(claude, "claude model surfaced");
assert.equal(claude.name, "claude-sonnet-4-6");
assert.equal(claude.attachment, true);
@@ -248,7 +248,7 @@ test("config: with valid auth.json + apiKey + baseURL → mutates input.provider
// Combo surfaces under bare key + LCD'd
// (gemini's reasoning=false → combo reasoning=false).
const combo = entry.models["omniroute/claude-tier"];
const combo = entry.models["claude-tier"];
assert.ok(combo, "combo surfaced under bare key");
assert.equal(combo.name, "Claude Tier");
assert.equal(combo.reasoning, false, "LCD: any member reasoning=false → combo reasoning=false");
@@ -471,10 +471,10 @@ test("config: combos fetcher throws → emit models-only catalog (no combos in m
assert.ok(entry);
const ids = Object.keys(entry.models).sort();
assert.deepEqual(ids, [
"opencode-omniroute/claude-sonnet-4-6",
"opencode-omniroute/gemini-3-flash",
"claude-sonnet-4-6",
"gemini-3-flash",
]);
assert.equal(entry.models["omniroute/claude-tier"], undefined, "no combo entry");
assert.equal(entry.models["claude-tier"], undefined, "no combo entry");
assert.ok(
logger.entries.some((e) => String(e[0]).includes("/api/combos fetch failed")),
"combos-fetch breadcrumb emitted"
@@ -723,7 +723,7 @@ test("buildStaticProviderEntry: stripped per-model shape matches sibling @omniro
}
// Sanity: claude entry has all expected stripped fields.
const claude = block.models["opencode-omniroute/claude-sonnet-4-6"];
const claude = block.models["claude-sonnet-4-6"];
assert.equal(typeof claude.name, "string");
assert.equal(typeof claude.attachment, "boolean");
assert.equal(typeof claude.reasoning, "boolean");
@@ -748,8 +748,8 @@ test("buildStaticProviderEntry: hidden combos are excluded", () => {
"https://or.example/v1",
"sk-test"
);
assert.equal(block.models["omniroute/claude-tier"], undefined);
assert.ok(block.models["opencode-omniroute/claude-sonnet-4-6"]);
assert.equal(block.models["claude-tier"], undefined);
assert.ok(block.models["claude-sonnet-4-6"]);
});
// ────────────────────────────────────────────────────────────────────────────
@@ -765,7 +765,7 @@ test("buildStaticProviderEntry: emits modalities.input from raw.input_modalities
"https://or.example/v1",
"sk-test"
);
const claude = block.models["opencode-omniroute/claude-sonnet-4-6"];
const claude = block.models["claude-sonnet-4-6"];
assert.deepEqual(claude.modalities?.input, ["text", "image"]);
assert.deepEqual(claude.modalities?.output, ["text"]);
});
@@ -779,7 +779,7 @@ test("buildStaticProviderEntry: never emits limit.input (OC SDK rejects it)", ()
"https://or.example/v1",
"sk-test"
);
const claude = block.models["opencode-omniroute/claude-sonnet-4-6"];
const claude = block.models["claude-sonnet-4-6"];
assert.equal((claude.limit as Record<string, unknown>).input, undefined);
assert.equal(typeof claude.limit?.context, "number");
assert.equal(typeof claude.limit?.output, "number");
@@ -807,7 +807,7 @@ test("buildStaticProviderEntry: emits cost when enrichment carries pricing", ()
"sk-test",
enrichment
);
const claude = block.models["opencode-omniroute/claude-sonnet-4-6"];
const claude = block.models["claude-sonnet-4-6"];
assert.equal(claude.cost?.input, 3);
assert.equal(claude.cost?.output, 15);
assert.equal(claude.cost?.cache_read, 0.3);
@@ -828,8 +828,8 @@ test("buildStaticProviderEntry: emits release_date when raw carries it; omits wh
"https://or.example/v1",
"sk-test"
);
assert.equal(block.models["opencode-omniroute/claude-with-date"].release_date, "2026-02-19");
assert.equal(block.models["opencode-omniroute/gemini-3-flash"].release_date, undefined);
assert.equal(block.models["claude-with-date"].release_date, "2026-02-19");
assert.equal(block.models["gemini-3-flash"].release_date, undefined);
});
test("buildStaticProviderEntry: combo modalities = intersection of members (LCD)", () => {
@@ -858,7 +858,7 @@ test("buildStaticProviderEntry: combo modalities = intersection of members (LCD)
"https://or.example/v1",
"sk-test"
);
const combo = block.models["omniroute/mixed-tier"];
const combo = block.models["mixed-tier"];
assert.ok(combo, "combo emitted under slug key");
// claude has text+image, text-only has text → intersection drops image.
assert.deepEqual(combo.modalities?.input, ["text"]);
@@ -967,10 +967,10 @@ test("config: enrichment fetched + name overlaid on raw-model entries", async ()
"opencode-omniroute"
];
assert.ok(entry);
assert.equal(entry.models["opencode-omniroute/claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
assert.equal(entry.models["opencode-omniroute/gemini-3-flash"].name, "Gemini 3 Flash");
assert.equal(entry.models["claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
assert.equal(entry.models["gemini-3-flash"].name, "Gemini 3 Flash");
// Combo names still come from /api/combos — enrichment overlay does NOT touch combos.
assert.equal(entry.models["omniroute/claude-tier"].name, "Claude Tier");
assert.equal(entry.models["claude-tier"].name, "Claude Tier");
assert.equal(enrichmentFetcher.callCount(), 1);
});
@@ -1000,7 +1000,7 @@ test("config: features.enrichment=false skips enrichment fetch + keeps raw-id na
assert.ok(entry);
assert.equal(enrichmentFetcher.callCount(), 0, "enrichment fetch suppressed by feature flag");
assert.equal(
entry.models["opencode-omniroute/claude-sonnet-4-6"].name,
entry.models["claude-sonnet-4-6"].name,
"claude-sonnet-4-6",
"raw id retained"
);
@@ -1027,7 +1027,7 @@ test("config: enrichment fetcher throws → soft-fail (warn + raw-id static cata
];
assert.ok(entry, "static block still published on enrichment failure");
assert.equal(
entry.models["opencode-omniroute/claude-sonnet-4-6"].name,
entry.models["claude-sonnet-4-6"].name,
"claude-sonnet-4-6",
"raw id retained"
);
@@ -1229,11 +1229,11 @@ test("config: diskCache hydrates stale snapshot when /v1/models throws", async (
"opencode-omniroute"
];
assert.ok(
entry.models["opencode-omniroute/claude-sonnet-4-6"],
entry.models["claude-sonnet-4-6"],
"stale snapshot hydrated into static block"
);
assert.equal(
entry.models["opencode-omniroute/claude-sonnet-4-6"].name,
entry.models["claude-sonnet-4-6"].name,
"Claude Sonnet 4.6 (cached)",
"stale enrichment also reused"
);
@@ -1281,7 +1281,7 @@ test("config: cached rawEnrichment from earlier provider hook is reused (no refe
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
"opencode-omniroute"
];
assert.equal(entry.models["opencode-omniroute/claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
assert.equal(entry.models["claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
});
// ─────────────────────────────────────────────────────────────────────
@@ -1332,12 +1332,12 @@ test("config: providerTag (default-on) prepends '<provider> - ' to enriched raw-
];
assert.ok(entry);
assert.equal(
entry.models["opencode-omniroute/claude-sonnet-4-6"].name,
entry.models["claude-sonnet-4-6"].name,
"Claude - Claude Sonnet 4.6"
);
assert.equal(entry.models["opencode-omniroute/gemini-3-flash"].name, "Gemini - Gemini 3 Flash");
assert.equal(entry.models["gemini-3-flash"].name, "Gemini - Gemini 3 Flash");
// Combos stay untouched — `Combo: ` prefix already conveys multi-upstream.
assert.equal(entry.models["omniroute/claude-tier"].name, "Claude Tier");
assert.equal(entry.models["claude-tier"].name, "Claude Tier");
});
test("config: providerTag=false suppresses the suffix", async () => {
@@ -1364,7 +1364,7 @@ test("config: providerTag=false suppresses the suffix", async () => {
"opencode-omniroute"
];
assert.equal(
entry.models["opencode-omniroute/claude-sonnet-4-6"].name,
entry.models["claude-sonnet-4-6"].name,
"Claude Sonnet 4.6",
"enriched name kept, provider tag suppressed"
);
@@ -1396,7 +1396,7 @@ test("config: providerTag falls back to UPPER(alias) when providerDisplayName mi
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
"opencode-omniroute"
];
assert.equal(entry.models["opencode-omniroute/claude-sonnet-4-6"].name, "CC - Claude Sonnet 4.6");
assert.equal(entry.models["claude-sonnet-4-6"].name, "CC - Claude Sonnet 4.6");
});
test("config: providerTag skipped entirely when neither providerDisplayName nor providerAlias set", async () => {
@@ -1423,7 +1423,7 @@ test("config: providerTag skipped entirely when neither providerDisplayName nor
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
"opencode-omniroute"
];
assert.equal(entry.models["opencode-omniroute/claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
assert.equal(entry.models["claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
});
test("config: providerTag is idempotent — second hook call doesn't double-suffix", async () => {
@@ -1451,7 +1451,7 @@ test("config: providerTag is idempotent — second hook call doesn't double-suff
"opencode-omniroute"
];
assert.equal(
entryA.models["opencode-omniroute/claude-sonnet-4-6"].name,
entryA.models["claude-sonnet-4-6"].name,
"Claude - Claude Sonnet 4.6"
);
@@ -1462,7 +1462,7 @@ test("config: providerTag is idempotent — second hook call doesn't double-suff
"opencode-omniroute"
];
assert.equal(
entryB.models["opencode-omniroute/claude-sonnet-4-6"].name,
entryB.models["claude-sonnet-4-6"].name,
"Claude - Claude Sonnet 4.6"
);
});
@@ -1516,7 +1516,7 @@ test("buildStaticProviderEntry: nested combo-ref context is the bottleneck acros
);
// Pre-fix: Parent would advertise 200_000 (only raw-big counted).
// Post-fix: Parent should advertise 8_000 (TinyCombo bottleneck).
const parent = block.models["omniroute/parent"];
const parent = block.models["parent"];
assert.ok(parent, "Parent combo must be in the static catalog");
assert.equal(parent.limit?.context, 8_000);
});

View File

@@ -111,7 +111,9 @@ test("#6859: createOmniRouteProviderHook end-to-end — catalog keys/providerID
// `opencode-omniroute`. Confirmed against the issue's own curl repro
// (`model: "opencode-omniroute/hermes-smart-stack"` → "No active
// credentials for provider: opencode-omniroute").
test("#7976: buildStaticProviderEntry keys bare-slug combo ids with the unprefixed omnirouteProviderId (no double OC-gate prefix)", () => {
// #9175 tightened this further: OC's `getModel` looks models up by BARE id,
// so combo dict keys now carry NO prefix at all (not even `omniroute/`).
test("#7976/#9175: buildStaticProviderEntry keys combos by bare slug (no prefix at all — never the OC-gate providerId)", () => {
const resolved = resolveOmniRoutePluginOptions({ providerId: "omniroute" });
assert.equal(resolved.providerId, "opencode-omniroute");
assert.equal(resolved.omnirouteProviderId, "omniroute");
@@ -131,7 +133,7 @@ test("#7976: buildStaticProviderEntry keys bare-slug combo ids with the unprefix
"sk-test"
);
assert.deepEqual(Object.keys(block.models), ["omniroute/hermes-smart-stack"]);
assert.deepEqual(Object.keys(block.models), ["hermes-smart-stack"]);
assert.equal(
block.models["opencode-omniroute/hermes-smart-stack"],
undefined,

1
_tasks Symbolic link
View File

@@ -0,0 +1 @@
/home/diegosouzapw/dev/proxys/OmniRoute/_tasks

View File

@@ -218,6 +218,26 @@ function registerPluginInOpenCodeConfig({
* a clear "could not run opencode" message instead of a hard import
* failure.
*/
/**
* Resolve the provider id used for `opencode auth login --provider <id>`.
*
* The bundled @omniroute/opencode-plugin registers its provider under
* `opencode-<id>` (the `opencode-` prefix is required by OpenCode >=1.17.8's
* native-adapter gate). The auth login command must use the prefixed form
* because OpenCode resolves `--provider <id>` against the provider id the
* plugin actually registered.
*
* Idempotent: if the id already starts with `opencode-`, it passes through
* unchanged. This protects users who manually worked around the bug with
* `--provider opencode-omniroute`.
*
* @param {string} providerId
* @returns {string}
*/
export function resolveOpenCodeAuthProviderId(providerId) {
return providerId.startsWith("opencode-") ? providerId : `opencode-${providerId}`;
}
/**
* Pure resolver for the `opencode auth login` spawn descriptor. Extracted so the
* platform-branching logic is unit-testable without mocking child_process or
@@ -231,21 +251,23 @@ function registerPluginInOpenCodeConfig({
*/
export function resolveOpenCodeAuthSpawn(providerId, platform = process.platform) {
const isWin = platform === "win32";
const authProviderId = resolveOpenCodeAuthProviderId(providerId);
return {
command: isWin ? "opencode.cmd" : "opencode",
args: ["auth", "login", "--provider", providerId],
args: ["auth", "login", "--provider", authProviderId],
options: { stdio: "inherit", shell: isWin },
};
}
export function runOpenCodeAuth(providerId) {
const authProviderId = resolveOpenCodeAuthProviderId(providerId);
const { command, args, options } = resolveOpenCodeAuthSpawn(providerId);
const res = spawnSync(command, args, options);
if (res.error) {
// ENOENT = opencode is not on PATH
if (res.error.code === "ENOENT") {
printInfo(
`opencode CLI not found on PATH. Run \`opencode auth login --provider ${providerId}\` manually after installing OpenCode.`
`opencode CLI not found on PATH. Run \`opencode auth login --provider ${authProviderId}\` manually after installing OpenCode.`
);
return 1;
}
@@ -343,7 +365,8 @@ export async function runSetupOpenCodeCommand(opts = {}) {
if (wantsAuth) {
if (nonInteractive) {
printInfo(`Skipping \`opencode auth login\` (non-interactive mode).`);
printInfo(`Run manually: opencode auth login --provider ${providerId}`);
const authProviderId = resolveOpenCodeAuthProviderId(providerId);
printInfo(`Run manually: opencode auth login --provider ${authProviderId}`);
} else {
printHeading("Authenticating with OpenCode");
const authExit = runOpenCodeAuth(providerId);
@@ -352,8 +375,9 @@ export async function runSetupOpenCodeCommand(opts = {}) {
}
}
} else {
const authProviderId = resolveOpenCodeAuthProviderId(providerId);
printInfo(
`Next step: opencode auth login --provider ${providerId} (pass --auth to do this automatically)`
`Next step: opencode auth login --provider ${authProviderId} (pass --auth to do this automatically)`
);
}

View File

@@ -17,7 +17,7 @@ export const SYSTRAY_VERSION = "2.1.4";
const SYSTRAY_SPEC = `${SYSTRAY_PACKAGE}@${SYSTRAY_VERSION}`;
export function resolveSystrayBinName(platform: NodeJS.Platform): string | null {
if (platform === "win32") return null;
if (platform === "win32") return "tray_windows_release.exe";
if (platform === "darwin") return "tray_darwin_release";
return "tray_linux_release";
}
@@ -45,7 +45,6 @@ export function chmodSystrayBinAt(runtimeRoot: string, platform: NodeJS.Platform
}
export async function loadSystray(): Promise<(new (...args: unknown[]) => unknown) | null> {
if (process.platform === "win32") return null; // Windows uses tray.ps1 instead
ensureRuntimeDir();
if (!isInstalled()) {
try {

View File

@@ -130,7 +130,7 @@ async function openSqliteDatabase(dbPath, options = {}) {
try {
return new loaded.Database(dbPath, options);
} catch (error) {
throw createSqliteNativeError(error);
return openWithSyncDriverFallback(dbPath, options, error);
}
}

View File

@@ -167,6 +167,10 @@ export function getAutostartStatus() {
linger: tryReadLingerEnabled(),
};
}
if (process.platform === "win32") {
const winMechanism = isAutostartEnabled() ? "vbs-startup" : null;
return { enabled: isAutostartEnabled(), mechanism: winMechanism };
}
return { enabled: isAutostartEnabled(), mechanism: null };
}

View File

@@ -1,5 +1,4 @@
import { isTraySupported, initSystrayUnix, killSystrayUnix } from "./traySystray.mjs";
import { initWinTray, killWinTray } from "./trayWindows.mjs";
let active = null;
@@ -10,15 +9,17 @@ export async function initTray({ port, onQuit, onOpenDashboard, onShowLogs }) {
const ctx = { port, onQuit, onOpenDashboard, onShowLogs };
// initSystrayUnix is async: it lazily installs/loads systray2 from the runtime
// dir (trayRuntime.ts) rather than from node_modules. (#4605)
active = process.platform === "win32" ? initWinTray(ctx) : await initSystrayUnix(ctx);
// Use systray2 on all platforms including Windows — the tarball ships
// tray_windows_release.exe, avoiding the Norton/AVG IDP.HELU.PSE85 heuristic
// that fires on temp-dir PowerShell scripts. (#8609)
active = await initSystrayUnix(ctx);
return active;
}
export function killTray() {
if (!active) return;
try {
if (process.platform === "win32") killWinTray(active);
else killSystrayUnix(active);
killSystrayUnix(active);
} catch {}
active = null;
}

View File

@@ -3,7 +3,7 @@
import { spawn } from "node:child_process";
import { existsSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { fileURLToPath, pathToFileURL } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@@ -43,7 +43,15 @@ export async function startMcpCli(rootDir = ROOT) {
}
// `tsx` loader is only required for local `.ts` fallback; JS entry works without it.
const loaderArgs = mcpEntry.endsWith(".ts") ? ["--import", "tsx"] : [];
const tsxLoaderArgs = mcpEntry.endsWith(".ts") ? ["--import", "tsx"] : [];
// Preload the stdout/stderr console guard before mcpEntry's own module graph evaluates —
// DB init (a side effect of createMcpServer()'s tool registration) logs via plain
// console.log, and by the time any code inside mcpEntry itself could redirect it, that
// module's own (hoisted) imports have already run. Loading the guard first, in a separate
// module, is the only point early enough to guarantee it never leaks into the JSON-RPC
// stream on stdout.
const consoleGuard = pathToFileURL(join(__dirname, "mcpStdioConsoleGuard.mjs")).href;
const loaderArgs = ["--import", consoleGuard, ...tsxLoaderArgs];
await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [...loaderArgs, mcpEntry], {

View File

@@ -0,0 +1,16 @@
// Preloaded (via `node --import`) before open-sse/mcp-server/server.ts and its entire
// import graph evaluate. The stdio MCP transport uses stdout exclusively for JSON-RPC
// messages, but DB init (getDbInstance(), triggered as a side effect of evaluating the
// server's module graph — e.g. tool registration reading compression settings) logs via
// plain console.log. A redirect placed *inside* server.ts (even at the top of its first
// executed function) is too late: static imports are hoisted and fully evaluated before
// any of that function's own code runs, so earlier console.log calls during import-time
// side effects already escaped to the real stdout by then. Redirecting here, in a module
// that loads before server.ts is even requested, is the only point early enough to
// guarantee no startup output leaks into the JSON-RPC stream and corrupts it client-side
// (e.g. Claude Desktop: "Unexpected token 'D', \"[DB] Changi\"... is not valid JSON").
import { Console } from "node:console";
const stderrConsole = new Console({ stdout: process.stderr, stderr: process.stderr });
console.log = stderrConsole.log.bind(stderrConsole);
console.warn = stderrConsole.warn.bind(stderrConsole);

View File

@@ -43,6 +43,19 @@ if (isVersionFastPath(process.argv)) {
process.exit(0);
}
// MCP stdio transport uses stdout exclusively for JSON-RPC messages. Redirect
// console.log/warn to stderr before anything else runs — including the tsx/esm and
// polyfill imports below, since those (and their transitive module graphs, e.g. DB
// init) can themselves log during evaluation. Redirecting after those imports let
// early output leak straight into the JSON-RPC stream and corrupt it client-side
// (e.g. Claude Desktop: "Unexpected token 'D', \"[DB] Changi\"... is not valid JSON").
if (process.argv.includes("--mcp")) {
const { Console } = await import("node:console");
const stderrConsole = new Console({ stdout: process.stderr, stderr: process.stderr });
console.log = stderrConsole.log.bind(stderrConsole);
console.warn = stderrConsole.warn.bind(stderrConsole);
}
// Register tsx so dynamic imports of .ts source files (referenced as .js per
// TypeScript conventions) resolve correctly. The build never emits .js for
// src/lib/cli-helper/, so tsx handles the .ts → .js resolution at runtime.
@@ -58,16 +71,6 @@ await import("../open-sse/utils/setupPolyfill.ts");
const { registerAliasResolver } = await import("./aliasResolver.mjs");
await registerAliasResolver(ROOT);
// MCP stdio transport uses stdout exclusively for JSON-RPC messages.
// Redirect console.log/warn to stderr early (before loadEnvFile and DB init)
// so no startup output corrupts the protocol.
if (process.argv.includes("--mcp")) {
const { Console } = await import("node:console");
const stderrConsole = new Console({ stdout: process.stderr, stderr: process.stderr });
console.log = stderrConsole.log.bind(stderrConsole);
console.warn = stderrConsole.warn.bind(stderrConsole);
}
// Electron persists secrets (JWT_SECRET, API_KEY_SECRET, STORAGE_ENCRYPTION_KEY) to
// `<DATA_DIR>/server.env` (electron/main.js), never `.env`. Migrating an existing
// install (storage.sqlite + server.env) to the CLI left those secrets undiscoverable —

View File

@@ -39,7 +39,8 @@ snap="$(ops_find_snapshot "$ID")"
# Policy definition tables present in BOTH the snapshot and the live DB. GLOB
# keeps `_` literal; we drop usage counters / logs so accounting isn't rewound.
readarray -t tables < <(
tables=()
while IFS= read -r t; do tables+=("$t"); done < <(
sqlite3 "$snap/storage.sqlite" \
"SELECT name FROM sqlite_master WHERE type='table' AND name GLOB 'api_key*' \
AND name NOT GLOB '*counter*' AND name NOT GLOB '*_log*' ORDER BY name;"

View File

@@ -0,0 +1 @@
- **feat(api):** add response content encoding verification — confirms Next.js compress:true and documents stripStaleForwardingHeaders behavior ([#6736](https://github.com/diegosouzapw/OmniRoute/issues/6736))

View File

@@ -0,0 +1 @@
- **feat(api):** add plugins marketplace install endpoint with checksum verification ([#6752](https://github.com/diegosouzapw/OmniRoute/issues/6752))

View File

@@ -0,0 +1 @@
- **feat(chatgpt-web):** harden prompt-emulated tool contract for thinking models (#7679 — thanks @horacecar)

View File

@@ -0,0 +1 @@
- **feat(providers):** make video_url passthrough configurable per provider/model via compat override ([#9248](https://github.com/diegosouzapw/OmniRoute/issues/9248)) — thanks @HellFiveOsborn

View File

@@ -0,0 +1 @@
- **feat(dashboard):** render a conditional "Get API key" link on the provider detail page, surfaced from the existing `notice.apiKeyUrl` / `notice.signupUrl` catalog metadata (e.g. `pioneer`, `jina`, `together`). The link opens in a new tab and is hidden when neither URL is present, so existing providers are unaffected. Tracks the notice field in `ProviderCatalogMetadata` ([#9270](https://github.com/diegosouzapw/OmniRoute/pull/9270))

View File

@@ -0,0 +1 @@
- **feat(providers):** accept JSON cookie objects in normalizeSessionCookieHeader (#9284 — thanks @AIB1TAL0S)

View File

@@ -0,0 +1 @@
- **feat(providers):** support max reasoning effort for opencode-zen DeepSeek models (#9318)

View File

@@ -0,0 +1 @@
- feat(plugins): expose client request headers in plugin onRequest/onResponse context (#9570)

View File

@@ -0,0 +1 @@
- **feat(audio):** Soniox STT + TTS provider (`sx`) — async speech-to-text (`stt-async-v5`, `stt-async-v4`) and real-time text-to-speech (`tts-rt-v1`) ([#9579](https://github.com/diegosouzapw/OmniRoute/pull/9579))

View File

@@ -0,0 +1,2 @@
- fix(tests): make machineId tests macOS-compatible by stubbing ioreg in test helper (#8577)
- fix(scripts): replace bash 4+ readarray with compatible while-read loop in restore-policies.sh (#8577)

View File

@@ -0,0 +1 @@
- fix(cli): enable systray2 on Windows for Norton-friendly tray (#8609)

View File

@@ -0,0 +1 @@
- fix(providers): gate premium opencode-zen/opencode-go models behind an API key (#8681)

View File

@@ -0,0 +1 @@
- fix(build): remove misleading open-sse/package.json facade and add workspace typecheck gate (#8781)

View File

@@ -0,0 +1 @@
- fix(cli): fall back to node:sqlite when better-sqlite3 constructor throws at runtime (#8826)

View File

@@ -0,0 +1 @@
- fix(opencode): prefix provider id with "opencode-" for auth login command (#8830)

View File

@@ -0,0 +1 @@
- fix(opencode-zen): add current free-tier models to registry to enable combo context pre-filtering (#8841)

View File

@@ -0,0 +1 @@
- **fix(opencode):** generate schema-complete model limits so OpenCode accepts catalog entries without an explicit output cap ([#8869](https://github.com/diegosouzapw/OmniRoute/pull/8869)) — thanks @xiaoyaner0201

View File

@@ -0,0 +1 @@
- **fix(cli):** default omitted Codex CLI wire API settings to Responses and clear stale Chat state after reset ([#8876](https://github.com/diegosouzapw/OmniRoute/pull/8876)) — thanks @xiaoyaner0201

View File

@@ -0,0 +1 @@
- **fix(proxy):** isolate new proxy credential fields from browser and password-manager autofill after form reset ([#8883](https://github.com/diegosouzapw/OmniRoute/pull/8883)) — thanks @xiaoyaner0201

View File

@@ -0,0 +1 @@
- **fix(providers):** expose both OAuth Connect and manual API-key actions for dual-auth providers such as CodeBuddy CN ([#8921](https://github.com/diegosouzapw/OmniRoute/pull/8921)) — thanks @Llliao1113

View File

@@ -0,0 +1 @@
- fix(opencode): propagate vision capability from live catalog into opencode.json (#8960)

View File

@@ -0,0 +1 @@
- fix(providers): switch Antigravity quota RPCs to iterate ANTIGRAVITY_RUNTIME_BASE_URLS (#8965)

View File

@@ -0,0 +1 @@
- fix(proxies): resolveProxyForConnection now returns the proxy name, so the dashboard badge shows the name instead of the hostname (#8995)

View File

@@ -0,0 +1 @@
- fix(db): stream DB backup export instead of buffering entire file into memory (#9045)

View File

@@ -0,0 +1 @@
- fix(ui): normalize Free Pool API response payload to read from data.proxies (#9046)

View File

@@ -0,0 +1 @@
- fix(api/analytics): stop charging :free models at arbitrary fallback price (#9054)

View File

@@ -0,0 +1 @@
- fix(providers): modal.com validation returns clear error when Base URL is missing, instead of leaking "Invalid outbound URL" (#9102)

View File

@@ -0,0 +1,2 @@
- fix(catalog): repair dead guard and synced-first ordering for custom model Vision capable override (#9195)
- fix(routing): consult customModels supportsVision flag in Combo vision filter (#9195)

View File

@@ -0,0 +1 @@
- fix(web-search): bind each search provider attempt to its connection proxy (#9201)

View File

@@ -0,0 +1 @@
- fix(auth): make antigravity and agy equivalent in credential selection (#9204)

View File

@@ -0,0 +1 @@
- fix(lmarena): emit Uint8Array SSE chunks instead of strings to satisfy shared pipeline contract (#9237)

View File

@@ -0,0 +1 @@
- fix(qoder): include actionable CLI_QODER_BIN hint in connection test when qodercli is not found (#9277)

View File

@@ -0,0 +1 @@
- **fix(providers):** the web search fallback detector in `webSearchFallback.ts` used an exact `Set` (`web_search`, `web_search_preview`) that missed Anthropic's date-suffixed server-tool variant `web_search_20250305` (sent by Claude Code 2.1.220+). Changed to prefix regex `/^web_search/`, matching the two other detectors in the codebase, so the fallback intercepts versioned web search tools for OpenAI-compatible upstreams ([#9279](https://github.com/diegosouzapw/OmniRoute/pull/9279))

View File

@@ -0,0 +1 @@
- fix(credential-health): scheduler never retries failed connections due to static interval comparison (#9289)

View File

@@ -0,0 +1 @@
- fix(api): specialty model catalog ignores hidden OpenRouter model flags (#9293)

View File

@@ -0,0 +1 @@
- fix(catalog): cache getModelsDevPricing() to prevent OOM at startup (#9300)

View File

@@ -0,0 +1 @@
- fix(providers): bump qwen-web SPA version header from 0.2.66 to 0.2.81 (#9304)

View File

@@ -0,0 +1 @@
- fix(backend): use accumulated responseBody for provider payload in dashboard log viewer to avoid stale data from truncated SSE events (#9315)

View File

@@ -0,0 +1 @@
- fix(qoder): surface qodercli stderr in error message instead of generic 502 (#9319)

View File

@@ -0,0 +1 @@
- **fix(providers):** Codex GPT-5.6 model metadata reports the 1M context window and 922K input limit ([#9431](https://github.com/diegosouzapw/OmniRoute/issues/9431)).

View File

@@ -0,0 +1 @@
- fix(api): make the 800-message chat history cap opt-in so long conversations reach compression instead of a terminal 413 (#9494)

View File

@@ -0,0 +1 @@
- **fix(standalone):** multipart uploads (`POST /v1/audio/transcriptions`) no longer hang — the WebDAV wrapper hands non-WebDAV requests to Next synchronously instead of losing the start of a streaming body ([#9580](https://github.com/diegosouzapw/OmniRoute/pull/9580))

View File

@@ -0,0 +1 @@
- **test(cli):** OpenCode plugin suite realigned to the bare-key static-catalog contract from #9178/#9175 (21 tests were red on every opencode-plugin CI run; 287/287 after) ([#9614](https://github.com/diegosouzapw/OmniRoute/pull/9614))

View File

@@ -1356,24 +1356,11 @@
"count": 1
}
},
"src/sse/handlers/chat.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/sse/handlers/chatHelpers.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/sse/services/auth.ts": {
"no-restricted-imports": {
"count": 1
},
"no-restricted-syntax": {
"count": 1
}
},
"src/sse/services/model.ts": {
"no-restricted-imports": {
"count": 2

View File

@@ -1,4 +1,5 @@
{
"_rebaseline_2026_08_08_migration_135_collision": "fix(db): resolve migration version 135 numbering collision — #9449's 135_connection_runtime_state.sql and #8908's 135_migrate_model_capability_max_token.sql both claimed version 135 (#9449 branched before #8908 merged and never got renumbered before landing on release/v3.8.50), which threw 'Migration version collision detected' the moment ANY code touched the database — a fresh install/deploy from this tip cannot even boot. Renumbered the later-landing file to 140 (next free slot) and added the matching isSchemaAlreadyApplied('140') retroactive guard, matching the established pattern already used for the prior 135/136 -> 137/138 renumber in the same file. Own growth: src/lib/db/migrationRunner.ts 1084->1094 (+10, the new case block) — irreducible, matches the existing per-case guard pattern exactly. Covered by tests/unit/migration-135-numbering-collision.test.ts (2/2), confirmed failing (reproducing the exact live crash) against the pre-fix colliding filenames, passing after.",
"_rebaseline_2026_08_02_9259_rolling_rpm": "PR #9259 (issue #8733) own growth: open-sse/services/rateLimitManager.ts baseline 1060->1167 (+107; final source 1153). The existing withRateLimit chokepoint now composes process-local rolling RPM leases with Bottleneck admission, releases pre-dispatch leases on queue timeout/abort/connection disable, preserves caller abort reasons, and wires 429/header state into the extracted rollingRpmGate.ts. The remaining growth is irreducible lifecycle wiring at the dispatch boundary plus the real watchdog test hooks needed to verify queued-wedge recovery; moving it further would obscure lease ownership and Bottleneck cleanup. Covered by the focused rate-limit manager/sliding-window suite (33/33); distributed multi-instance coordination remains explicitly out of scope.",
"_rebaseline_2026_07_24_8470_hyperagent_sticky_thread": "PR #8470 (artickc, fix/hyperagent-tool-loop-thread-sticky) own growth: open-sse/executors/hyperagent.ts 936->1025 (wc -l; check-file-size.mjs counts via split(\"\\n\").length so the gate sees 937->1026, +89, crosses the 1000 cap). Fixes a real bug where a reverse-conversion proxy (text-Intent/JSON to Claude Code native tool_calls) rewrites assistant messages between agentic tool-loop turns, breaking HyperAgents conversation-prefix fingerprint and cold-starting the thread mid tool-loop. Adds Anthropic tool_use/tool_result flattening to extractMessageText() plus a new rootUserFingerprint()/root-key lookup tier in resolveHyperAgentThreadBinding()/storeHyperAgentThreadAfterTurn() so the thread stays sticky across the tool loop. Cohesive additions inside the existing single-file executor; not extractable without splitting the executor mid-request-flow. Covered by tests/unit/executor-hyperagent.test.ts (19/19, +5 new cases for tool_result/tool_use flattening + root-key stickiness). Pre-merge review flagged a cross-conversation root-key collision risk (tracked in the PRs own mandatory pre-merge checklist, not yet addressed) — unrelated to this file-size ratchet, tracked separately by /fix-prs.",
"_rebaseline_2026_07_25_8494_capability_filter_fail_closed": "PR #8494 (fix/capability-filters-fail-closed, #8488) own growth: open-sse/services/combo.ts 3640->3693 (+53) adds a fail-closed guard after filterTargetsByRequestCompatibility() — when every eligible target is excluded by request-capability filtering (vision/tools/etc) instead of quota/health, the combo now returns an explicit `capability_mismatch` 400 (describeCapabilityFilterExhaustion, imported from combo/comboStructure.ts) rather than silently falling through to a generic no-targets error, plus a `compatFilterFailOpen` escape hatch (combo config OR settings) mirrored at both the main/auto and round-robin call sites for symmetry. combo/comboStructure.ts (previously under cap, un-frozen) grows 794->918 (+124) — new home for describeCapabilityFilterExhaustion + providerSupportsEmulatedToolCalling (#5240 emulated tool-calling exemption so fail-closed does not regress prompt-emulation-only combos like all-chatgpt-web). Irreducible orchestration wiring at the existing filter chokepoint (same precedent as #7301's universal-cooldown-retry generalization). Companion test tests/unit/combo-routing-engine.test.ts 3409->3449 (+40, fail-closed/fail-open coverage across both call sites) also rebaselined. Covered by tests/unit/8488-capability-filter-fail-closed.test.ts (new) + 95/95 passing across both files. Structural shrink of combo.ts tracked in #3501.",
@@ -160,312 +161,6 @@
"_rebaseline_2026_06_20_4389_thinking_toolchoice": "Re-baseline base.ts 1387->1399 (#4389): tool_choice-forced thinking guard at the existing Claude wire-image injection chokepoint (effThinking gate avoids the Anthropic 400 when tool_choice forces a tool). Cohesive guard; structural shrink tracked in #3501.",
"_rebaseline_2026_07_18_6979_codex_test": "PR #6979 own growth: executor-codex.test.ts 1340->1347 (+7 = generalized ensureThinkingBudget assertion added to the existing codex thinking-budget cases). antigravity-test bump 942->977 REVERTED here: #7408's test split dropped that file to 888, so this PR's +35 fits under the original 942 frozen cap.",
"cap": 1000,
"frozen": {
"_rebaseline_2026_07_02_5816_qoder": "PR #5816 (@AgentKiller45, qoder PAT via qodercli): qoderCli.ts 666->989, new-above-cap frozen (owner-approved baseline freeze). The growth is the legitimate PAT job-token exchange + quota parsing CLI transport (the pure-JS Cosy path 500'd on every PAT request); extracting the spawn/parse helpers now would just add indirection to a contributor PR mid-merge. Test frozen also raised for this PR's coverage growth: providers-page-utils.test.ts 1052->1092. Additionally clears an inherited base-red from the already-merged #5933 (codex json_schema->text.format): translator-openai-responses-req.test.ts 1097->1172 (+75 regression tests, no offending branch left). All remain frozen (cannot grow further); release captain's rebaseline-at-release supersedes.",
"open-sse/services/qoderCli.ts": 989,
"_rebaseline_pr1043_minimax_tts": "Upstream port decolua/9router#1043 (toanalien) own growth: audioSpeech.ts 965->1061 (+96). Adds MiniMax T2A v2 TTS dispatch (handleMinimaxSpeech + hexToBytes helper) — provider entry was already in audioRegistry (format: minimax-tts) but no handler existed, falling through to the OpenAI-compatible default that fails (T2A has custom shape + hex-encoded audio + base_resp envelope). New branch sits next to the other inline provider branches (xiaomi-mimo, coqui, tortoise, aws-polly) — extracting would just create indirection. Covered by tests/unit/minimax-tts-1043.test.ts (3 tests, GREEN: success, base_resp error, invalid-hex).",
"_rebaseline_2026_07_23_8266_alibaba_media": "#8266 (@backryun) own growth: imageRegistry.ts 821->979 (+158) — Alibaba-family media models (Qwen image/video, Bailian, Wan) added to the image/video registry. Registry model data, not extractable logic; frozen at new size.",
"open-sse/config/imageRegistry.ts": 979,
"open-sse/config/providerRegistry.ts": 4731,
"open-sse/executors/antigravity.ts": 1813,
"open-sse/executors/base.ts": 1540,
"open-sse/executors/chatgpt-web.ts": 3206,
"open-sse/executors/claude-web.ts": 1057,
"open-sse/executors/codex.ts": 1541,
"open-sse/executors/cursor.ts": 1577,
"open-sse/executors/deepseek-web.ts": 1148,
"_rebaseline_2026_06_22_4644_deepseek_web_tools": "PR #4644 (BugsBag/robust deepseek-web tool-call parsing): open-sse/executors/deepseek-web.ts 1117->1125 (+8). The new agentic tool-call path emits surrounding text + reasoning before tool_calls and swaps to the dedicated deepseekWebTools.ts parser; the +8 lines are cohesive wiring at the existing transformSSE chokepoint (the parser itself lives in the new deepseekWebTools.ts file, already under cap). The PR's own fast-gate (PR->release) does not run check:file-size, so this surfaced only at release reconcile. Covered by tests/unit/deepseek-web-tools-variants.test.ts + deepseek-web-tools-execute.test.ts.",
"_rebaseline_2026_06_23_4712_deepseek_web_tool_results": "PR for #4712 (deepseek-web drops role:tool): open-sse/executors/deepseek-web.ts 1125->1148 (+23). messagesToPrompt() now folds role:\"tool\" results into the single-prompt transcript (recovering the tool name from the preceding assistant tool_calls by tool_call_id) instead of silently dropping them; the lines are cohesive wiring inside the existing function. Covered by tests/unit/deepseek-web-tool-result-prompt-4712.test.ts.",
"_rebaseline_2026_06_28_5237_impersonation_ua_refresh": "PR #5237 (refresh impersonation UAs): grok-web.ts 1871->1873 (+2), muse-spark-web.ts 1284->1302 (+18), perplexity-web.ts 1013->1032 (+19). Net semantic change in each file is a single User-Agent constant (Chrome 147->149 for grok/muse; perplexity kept at Firefox 148 to stay matched with the firefox_148 TLS profile — the contributor's 152 bump was reverted to avoid a UA-vs-JA3 mismatch, #2459). The growth is Prettier reflow that lint-staged unavoidably applies to these grandfathered long-line files the moment they are touched; not extractable. src/sse/services/auth.ts 2336->2401 in the same reconcile is #5222's antigravity-LRU-retry growth that merged via --admin without a baseline bump.",
"open-sse/executors/duckduckgo-web.ts": 925,
"open-sse/executors/grok-web.ts": 1873,
"open-sse/executors/hyperagent.ts": 937,
"open-sse/executors/muse-spark-web.ts": 1396,
"open-sse/executors/perplexity-web.ts": 1032,
"open-sse/handlers/audioSpeech.ts": 1061,
"open-sse/handlers/chatCore.ts": 5125,
"open-sse/handlers/imageGeneration.ts": 3777,
"open-sse/handlers/responseSanitizer.ts": 1139,
"open-sse/handlers/search.ts": 1546,
"open-sse/handlers/sseParser.ts": 830,
"open-sse/handlers/videoGeneration.ts": 1275,
"_rebaseline_2026_07_22_8010_codex_responses_engine": "PR #8010 (@JxnLexn) own growth: open-sse/mcp-server/schemas/tools.ts 1497->1505 (+8 = threading the new \"codex-responses\" literal into the compressionConfigureInput strategy/autoTriggerMode Zod enums and setCompressionEngineInput engine enum, mirroring the existing rtk/omniglyph enum entries; no new tool). open-sse/services/compression/strategySelector.ts 1043->1054 (+11 = one new `if (mode === \"codex-responses\")` dispatch branch in runCompression that delegates 100% to the new codexResponsesEngine.apply, mirroring the existing rtk single-mode dispatch, plus threading config.codexResponsesConfig.preserveToolNames into the shared adaptBodyForCompression call at the 3 existing call sites). src/lib/db/compression.ts (untracked, new-file cap 800) 794->845 (+51 = normalizeCodexResponsesConfig, mirroring the existing normalizeRtkConfig normalizer, plus registering \"codex-responses\" in the COMPRESSION_MODES/STACKED_PIPELINE_ENGINE_IDS/SINGLE_MODE_ENGINE sets and the getCompressionSettings load/save switch) — added to the baseline at its current size. All three are cohesive dispatch/normalizer wiring at existing chokepoints (mirroring the prior compression-mode rebaselines #6534/#6556), not extractable without hiding the mode-dispatch boundary. Covered by tests/unit/compression/codex-responses.test.ts (6) + omniglyph-registries.test.ts/types.test.ts (22, updated for the new mode).",
"_rebaseline_2026_07_22_8034_compression_exclusions_persistence": "#8034 (compression exclusions) own growth: src/lib/db/compression.ts 845->850 (+5 = threading the new compressionExclusions field through the existing getCompressionSettings/saveCompressionSettings load/save switch over the shared key_value compression namespace — no new table, no raw SQL). Mirrors the prior compression-field rebaselines (#8010 codex-responses normalizer at the same chokepoint); the load/save switch is a single dispatch boundary, not extractable without hiding it. Covered by the PR's 8 node:test + 3 vitest cases.",
"src/lib/db/compression.ts": 866,
"open-sse/mcp-server/schemas/tools.ts": 1505,
"open-sse/mcp-server/server.ts": 1555,
"open-sse/mcp-server/tools/advancedTools.ts": 1120,
"_rebaseline_2026_06_27_5193_antigravity_basered": "Base-red (pre-existing release drift, fast-gate PR->release skips check:file-size): accountFallback.ts 1773->1777 and src/app/api/providers/[id]/test/route.ts 924->940 were already over their frozen caps on release/v3.8.39 independent of any antigravity change. Owner chose to rebaseline (keep the documented issue-reference comments #1846/#1449/#347 etc.) rather than accept the contributor comment-stripping in #5200/#5198. Reverted #5200 to restore the comments; bumped these two frozen caps to the actual base sizes. No logic change.",
"_rebaseline_2026_07_22_8213_gemini_tpm_quota_cooldown_wait": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: open-sse/services/accountFallback.ts 1857->1932 on the merged tip (release 1892 incl #8050 +35, plus this PR own growth +40); measured against the PR own merge-base was 1857->1898 (+41 — the release tip separately carries an unrelated +34 from #8050's antigravity 404 model-not-found lockout scoping, which this PR's branch does not include and this entry does not cover). Own growth is the Gemini TPM-ceiling classification + cooldown-wait wiring feeding into the combo cooldown-wait state machine (rate-limit wedge recovery) introduced by this PR's commit series. Irreducible additions at the existing account-fallback/model-lockout chokepoint. Covered by the PR's own gemini-rate-limit-tracker and TPM-ceiling benchmark test additions.",
"_rebaseline_2026_07_23_8252_combo_400_advance": "#8252 (@RaviTharuma) own growth: accountFallback.ts 1932->1940 (+8) + combo.ts 3604->3630 (+26) — advance combo on model-scoped 400s wrapped as invalid/Bad-Request. Irreducible wiring at existing account-fallback + combo dispatch chokepoints. Covered by combo-model-scoped-400-advance.test.ts.",
"open-sse/services/accountFallback.ts": 1940,
"open-sse/services/adobeFireflyClient.ts": 1958,
"open-sse/services/batchProcessor.ts": 915,
"open-sse/services/browserBackedChat.ts": 850,
"open-sse/services/claudeCodeCompatible.ts": 1202,
"_rebaseline_pr4592_exclude_exhausted_auto": "Reconcile #4592 already-merged growth: combo.ts 2991->3036 (+45, terminal-status quota-cutoff exclusion in buildAutoCandidates + opt-in gate). Fast-gate PR->release does not run check:file-size.",
"_rebaseline_2026_06_24_headroom_strategy": "Headroom-aware connection selection (dario technique): combo.ts 3168->3180 (+12 = a new `else if (strategy === \"headroom\")` dispatch branch in handleComboChat that delegates to orderTargetsByHeadroom + its log line, plus the import). The actual logic lives OUT of the god-file: the pure ranker rankByHeadroom/computeHeadroom is the new leaf open-sse/services/combo/headroomRanking.ts (91 LOC, <cap) and the async orderer orderTargetsByHeadroom is appended to the existing open-sse/services/combo/quotaStrategies.ts (<cap) next to its sibling reset-aware/reset-window orderers (reuses their connection-expansion machinery). headroom = 1 - max(util_5h, util_7d) from getSaturation (src/lib/quota/saturationSignals.ts), prefers the connection with the most free capacity. Only the dispatch wiring is irreducible at the existing combo strategy chokepoint (mirrors the reset-aware/reset-window/context-optimized branches); not extractable without hiding the call site. fill-first stays default; all existing strategies untouched. Covered by tests/unit/combo-headroom-ranking.test.ts (pure helper) + tests/unit/combo-headroom-strategy.test.ts (orderer, saturation injected). Structural shrink of combo.ts tracked in #3501.",
"_rebaseline_2026_06_24_quota_share_strategy": "Dedicated quota-share strategy (Phase 3 #9): combo.ts 3180->3190 (+10 = one new `else if (strategy === \"quota-share\")` dispatch branch in handleComboChat that delegates 100% to selectQuotaShareTarget + its log line, plus the import). All the new logic lives OUT of the god-file in two new leaves under open-sse/services/combo/: quotaShareInflight.ts (in-flight counter with TTL/lease, ~150 LOC <cap) and quotaShareStrategy.ts (per-model bucket gating via isBucketSaturated + DRR proportional to weight + P2C over in-flight, ~240 LOC <cap). Only the dispatch wiring is irreducible at the existing combo strategy chokepoint (mirrors the headroom/reset-aware/reset-window/context-optimized branches); not extractable without hiding the call site. ZERO existing strategy cases were modified — only this branch was added, and the qtSd/ combos switched from fill-first to quota-share in src/lib/quota/quotaCombos.ts. Covered by tests/unit/quota-share-strategy.test.ts (gating, DRR fairness, P2C in-flight, fail-open, activation). Structural shrink of combo.ts tracked in #3501.",
"_rebaseline_2026_06_24_task_aware_routing": "Task-aware routing strategy (port PR #2045, OmniRoute #4945): combo.ts 3190->3225 (+35) = one new `else if (strategy === \"task-aware\")` dispatch branch delegating 100% to selectTaskAwareTarget + its imports/log lines. All scoring/classification logic lives OUT of the god-file in the new leaf open-sse/services/taskAwareRouting.ts (553 LOC <cap). Only the dispatch wiring is irreducible at the existing combo strategy chokepoint (mirrors quota-share/headroom/reset-aware branches). ZERO existing strategy cases modified. Covered by tests/unit/combo-task-aware.test.ts (35 tests). Structural shrink of combo.ts tracked in #3501.",
"_rebaseline_2026_07_22_8213_combo_cooldown_wait_recording": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: open-sse/services/combo.ts 3548->3604 (+56, entirely this PR's diff — no other commit touched this file between the PR's merge-base and the release tip). Fixes combo cooldown-wait state recording so a bogus 503 is no longer crystallized when the cooldown-wait vars reset every setTry, adds an OpenAI-format SSE error frame path for combo-exhausted rejections (capturing request body + attempted models), and gives an abandoned per-target dispatch its own timeout instead of leaking a permanent 'pending' dashboard entry. Irreducible additions at the existing handleComboChat dispatch/retry chokepoint (mirrors the prior quota-share/headroom/task-aware strategy-branch precedents already frozen in this file). Covered by the PR's own combo-config + Gemini TPM-ceiling benchmark test additions.",
"open-sse/services/combo.ts": 3630,
"_rebaseline_2026_06_26_fidelity_gate_extraction": "Milestone-B fidelity-gate wiring residual: bodyToText+gateAdvance extracted to fidelityGateStep.ts (889->854, -35), but the StackOptions.fidelityGate field, the `const fidelityGate` reads at the two stacked-loop dispatch chokepoints, and the import of FidelityGateConfig are irreducible wiring that cannot leave strategySelector without an architectural refactor of the pre-existing stacked pipeline. Net: 889->854 (+6 vs the pre-Milestone-B frozen 848). Covered by tests/unit/compression/*.test.ts (940 pass).",
"_rebaseline_2026_06_28_5243_risk_gate_prepass": "PR #5243 (compression risk-gate pre-pass) own growth: open-sse/services/compression/strategySelector.ts 854->899 (+45). The three exported entry points (applyCompression/applyStackedCompression/applyStackedCompressionAsync) become thin wrappers over pure-extracted private bodies (runCompression/runStackedCompression/runStackedCompressionAsync) so the risk-gate mask->run->restore wrapper sits strictly OUTSIDE the per-step loop — a single universal integration point. The wrapper logic itself (resolveRiskGate/withRiskGate) lives in the new riskGate/strategyWrap.ts (<cap); the residual growth is the duplicated thin-wrapper signatures + the extracted bodies' dispatch boundary, guarded by a byte-identical parity test (riskGateIntegration). Default off (DEFAULT_COMPRESSION_CONFIG unchanged). Not extractable without hiding the dispatch boundary, mirroring prior compression rebaselines. Structural shrink tracked in #3501.",
"_rebaseline_2026_06_29_5286_memoization": "PR #5286 own growth: strategySelector.ts 899->960 (+61 = the opt-in result-memoization branches in applyCompression/applyCompressionAsync — principal+determinism gate, makeMemoKey lookup/store with model+supportsVision folded into the key, recompute-with-memo-off). Default off (memoizeCompressionResults), so zero behavior change. The memo helpers live in the leaf resultMemo.ts (<cap); the chokepoint wiring here is not extractable. Structural shrink of this hot-path file tracked in #3501.",
"open-sse/services/compression/strategySelector.ts": 1060,
"_rebaseline_2026_07_10_gcf_v3_2_decode": "PR #6838 own growth: new vendored file open-sse/services/compression/engines/headroom/gcf/decode_generic.ts frozen at 880 (> 800 cap). It is the vendored GCF generic-profile decoder (spec v3.2 nested flattening plus the prototype-pollution / hasOwnProperty hardening added in this PR's Gemini review). Kept as one file faithful to upstream gcf-typescript so re-vendoring stays a clean copy rather than a re-split each cycle (sibling generic.ts/scalar.ts stay < cap; extraction would also fragment the file's frozen eslint no-explicit-any suppressions). Round-trip + prototype-pollution regression coverage in tests/unit/compression/headroom-smartcrusher.test.ts. Frozen: only shrinks from here.",
"open-sse/services/compression/engines/headroom/gcf/decode_generic.ts": 880,
"open-sse/services/rateLimitManager.ts": 1035,
"_rebaseline_2026_06_29_4038_cas_guard": "PR (#4038) own growth: tokenRefresh.ts 2103->2181 (+78 = the compare-and-swap guard on the refresh persist — runWithCasGuard/getActiveCasGuard AsyncLocalStorage pair mirroring runWithOnPersist, casGuardShouldSkipPersist that rereads the row right before persisting and skips the write when a concurrent writer already rotated the refresh_token past the one presented, plus getCasGuardStats counters). Fixes the sibling-rotation-revert → token-family-revocation storm. Gated behind an active guard (opt-in; no guard => byte-identical). Wiring lives at the two persist chokepoints inside getAccessToken; the comparison reuses wasRefreshTokenRotated from refreshSerializer. Not extractable without splitting the refresh hot path.",
"_rebaseline_2026_07_09_6126_clinepass_dual_auth": "PR #6126 (@hajilok, dual-auth ClinePass) own growth: tokenRefresh.ts 2181->2182 (+1 = a single `case \"clinepass\":` fallthrough label added to the existing `case \"cline\":` in _getAccessTokenInternal's provider switch, so clinepass token refresh dispatches to the already-shared refreshClineToken() instead of silently falling through to the generic OAuth refresh). Irreducible 1-line switch-case wiring at the existing chokepoint; the header-building logic for the same feature was extracted to a new leaf src/shared/utils/clineAuth.ts::buildClinepassHeaders() (well under cap) to avoid growing open-sse/executors/default.ts. Covered by tests/unit/clinepass-provider.test.ts.",
"_rebaseline_2026_07_09_6363_kiro_external_idp": "PR #6363 (@artickc, Kiro external IdP) own growth: tokenRefresh.ts 2182->2249 (+67 = the external_idp refresh branch inside refreshKiroToken — standard public-client OAuth2 refresh_token grant against the org IdP tokenEndpoint via buildExternalIdpRefreshParams/isExternalIdpAuthMethod from the new leaf open-sse/services/kiroExternalIdp.ts, with invalid_grant/invalid_client -> unrecoverable_refresh_error mapping). Cohesive addition at the existing refreshKiroToken chokepoint. Covered by tests/unit/kiro-external-idp.test.ts.",
"open-sse/services/tokenRefresh.ts": 2249,
"open-sse/services/usage.ts": 3454,
"open-sse/translator/request/openai-to-gemini.ts": 906,
"open-sse/translator/request/openai-to-kiro.ts": 912,
"_rebaseline_2026_07_22_8211_gemini_malformed_tool_choice": "PR #8211 (hartmark, fix/gemini-malformed-function-call-tool-choice) own growth: open-sse/translator/response/gemini-to-openai.ts 771->821 (+50, entirely this PR's diff — no other commit touched this file between the PR's merge-base and the release tip). Adds MALFORMED_FUNCTION_CALL/UNEXPECTED_TOOL_CALL handling inside geminiToOpenAIResponse(): synthesizes a `malformed_tool_call` tool_calls entry so finish_reason normalizes to the standard \"tool_calls\" instead of an unrecognized raw enum value that OpenAI-compatible clients (e.g. OpenClaw) silently ignore, and always synthesizes (rather than skipping when a real tool call already exists) so a malformed attempt alongside a real one in the same turn is not silently discarded. Irreducible cohesive addition at the existing candidate/finishReason translation chokepoint (mirrors the 9router#2462 raw-finish-reason precedent immediately below it in the same function). Covered by the PR's own tests/unit test additions for both the malformed-only and malformed-plus-real-call cases.",
"open-sse/translator/response/gemini-to-openai.ts": 821,
"_rebaseline_2026_07_22_7936_namespace_roundtrip": "#7936 (@RCrushMe, Responses-Chat namespace round-trip identity seam) own growth: open-sse/translator/response/openai-responses.ts 1092->1125 (+33) and open-sse/utils/stream.ts 2814->2869 (+55) — threading the namespace-identity seam through the Responses↔Chat translation + stream paths so tool-call namespaces survive the round-trip. Cohesive translation/stream wiring at existing chokepoints, frozen at new size.",
"_rebaseline_2026_07_22_8210_openrouter_midstream_error": "PR #8210 (hartmark, fix/openrouter-midstream-error-surfacing) own growth: open-sse/translator/response/openai-responses.ts 1137->1163 (+26) measured on the merged tip (release 1137 + this PR own growth). Adds a single new branch inside openaiToOpenAIResponsesResponse() that detects an OpenRouter-style mid-stream aggregator error (HTTP 200 SSE chunk with empty choices + a top-level error object) and surfaces it as state.upstreamError instead of silently falling through to the no-op/awaitingTrailingUsage path, which previously masked the failure as a false empty-success completion and skipped combo fallback. Irreducible call-site addition at the existing chunk-dispatch chokepoint (mirrors the Gemini-to-OpenAI translator's #4177 precedent for the same class of upstream error surfacing). Note: this baseline entry does NOT cover the separate pre-existing +11 drift already on the release tip from #8081/#8162 (1125->1136, unrelated reasoning-placeholder-stripping fix merged after this PR branched) — that drift belongs to the maintainer's rebaseline, not this PR.",
"open-sse/translator/response/openai-responses.ts": 1163,
"open-sse/utils/cursorAgentProtobuf.ts": 1521,
"_rebaseline_2026_07_23_8143_empty_catch_logging": "#8143 (@chirag127) own growth: open-sse/utils/stream.ts 2869->2887 (+18) — replacing empty catch blocks in the SSE stream subsystem with console.debug logging (Rule #6 silent-swallow fix, issues #8138-#8142). Cohesive logging additions at the existing catch chokepoints, not extractable; frozen at new size. Covered by tests/unit/stream-handler-catch-logging-8143.test.ts.",
"open-sse/utils/stream.ts": 2887,
"src/app/(dashboard)/dashboard/HomePageClient.tsx": 1385,
"src/app/(dashboard)/dashboard/analytics/ComboHealthTab.tsx": 1031,
"src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx": 3120,
"src/app/(dashboard)/dashboard/cache/media/MediaPageClient.tsx": 1105,
"src/app/(dashboard)/dashboard/cache/page.tsx": 845,
"src/app/(dashboard)/dashboard/cli-code/components/CodexToolCard.tsx": 900,
"src/app/(dashboard)/dashboard/cloud-agents/page.tsx": 931,
"_rebaseline_2026_07_15_7070_combos_memo": "PR #7070 (perf/p1-memo) own growth: src/app/(dashboard)/dashboard/combos/page.tsx 4655->4656 (+1 = React.memo wrapping of ComboCard). Covered by tests/unit/ui/combos-page-smoke.test.tsx.",
"src/app/(dashboard)/dashboard/combos/page.tsx": 4656,
"src/app/(dashboard)/dashboard/costs/CostOverviewTab.tsx": 1495,
"src/app/(dashboard)/dashboard/costs/quota-share/components/PoolWizard.tsx": 1022,
"src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": 2615,
"_rebaseline_2026_07_22_8213_health_unblock_model_cooldowns": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: src/app/(dashboard)/dashboard/health/page.tsx 1094->1165 (+71, entirely this PR's diff — no other commit touched this file between the PR's merge-base and the release tip). Adds handleUnblockAll/handleUnblockOne dashboard actions (DELETE /api/resilience/model-cooldowns) so an operator can manually clear a Gemini TPM-wedge model lockout surfaced by this PR's cooldown-wait fixes, instead of waiting out the ceiling. Irreducible UI wiring at the existing health-page action chokepoint. Covered by the PR's own dashboard/resilience test additions.",
"src/app/(dashboard)/dashboard/health/page.tsx": 1165,
"src/app/(dashboard)/dashboard/playground/components/tabs/ApiTab.tsx": 847,
"src/app/(dashboard)/dashboard/providers/[id]/ProviderDetailPageClient.tsx": 804,
"src/app/(dashboard)/dashboard/providers/[id]/components/ConnectionRow.tsx": 958,
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx": 967,
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 1288,
"src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts": 986,
"src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderModels.ts": 155,
"src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderSettings.ts": 264,
"src/app/(dashboard)/dashboard/providers/[id]/providerPageHelpers.ts": 1054,
"src/app/(dashboard)/dashboard/providers/components/onboarding/ProviderOnboardingWizard.tsx": 948,
"src/app/(dashboard)/dashboard/providers/page.tsx": 1927,
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1201,
"src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx": 819,
"src/app/(dashboard)/dashboard/settings/components/ComboDefaultsTab.tsx": 903,
"src/app/(dashboard)/dashboard/settings/components/CompressionSettingsTab.tsx": 974,
"src/app/(dashboard)/dashboard/settings/components/MemorySkillsTab.tsx": 898,
"src/app/(dashboard)/dashboard/settings/components/PricingTab.tsx": 1019,
"src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": 1464,
"src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": 1183,
"src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": 1629,
"src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx": 1924,
"src/app/(dashboard)/dashboard/usage/components/BudgetTab.tsx": 1028,
"src/app/(dashboard)/dashboard/usage/components/EvalsTab.tsx": 2148,
"src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx": 1127,
"src/app/api/oauth/[provider]/[action]/route.ts": 970,
"src/app/api/providers/[id]/models/route.ts": 2593,
"src/app/api/providers/[id]/test/route.ts": 940,
"src/app/api/usage/analytics/route.ts": 948,
"src/app/api/v1/models/catalog.ts": 1615,
"src/lib/cloudflaredTunnel.ts": 935,
"src/lib/db/apiKeys.ts": 1662,
"src/lib/db/core.ts": 1825,
"src/lib/db/migrationRunner.ts": 1125,
"src/lib/db/models.ts": 1259,
"src/lib/db/providers.ts": 1107,
"src/lib/db/proxies.ts": 1177,
"src/lib/db/settings.ts": 1155,
"src/lib/db/usageAnalytics.ts": 925,
"src/lib/evals/evalRunner.ts": 961,
"src/lib/memory/retrieval.ts": 1171,
"src/lib/modelsDevSync.ts": 934,
"src/lib/providers/validation.ts": 4523,
"src/lib/resilience/settings.ts": 841,
"src/lib/tailscaleTunnel.ts": 1202,
"src/lib/usage/callLogs.ts": 997,
"src/lib/usage/providerLimits.ts": 1006,
"src/lib/usage/usageHistory.ts": 988,
"_rebaseline_2026_06_27_5193_5203_antigravity_oauthmodal": "Antigravity remote-login own growth: OAuthModal.tsx 960->969 (gate units). #5193 (+~4: remote paste instruction shown for all remote incl. Google + its rationale comment) and #5203 (+~5: handleManualSubmit credential-blob branch + button guard; submit logic extracted to oauthBlobSubmit.ts to minimize). Frozen set to the SUM so either merge order passes. Cohesive at the existing manual-submit chokepoint.",
"_rebaseline_2026_07_18_7399_xai_oauth_modal": "PR #7399 (xAI OAuth PKCE) own growth: OAuthModal.tsx 993->998 (+5 = provider entry + PKCE flow branch wiring at the existing provider-switch chokepoint; the provider logic itself lives in src/lib/oauth/providers/xai-oauth.ts, new leaf). Third irreducible wiring bump on this modal (969->989->993->998); structural shrink tracked in #3501.",
"_rebaseline_2026_07_19_6636_codex_session_json": "#6636 own growth: OAuthModal.tsx 998->1030 (gate units, split(\"\\n\").length incl. trailing newline; +32 = session-JSON paste branch for handleManualSubmit plus a shared submitCodexAccessToken() helper extracted from the pre-existing bare-JWT branch, mirroring the #5203 oauthBlobSubmit.ts extraction precedent; the normalizer logic itself lives in the new src/lib/oauth/utils/codexSessionImport.ts leaf module, not here). Fourth irreducible wiring bump on this modal (969->989->993->998->1030); structural shrink tracked in #3501.",
"_rebaseline_2026_07_19_7546_ghe_copilot_modal": "PR #7546 (GHE Copilot OAuth provider) own growth: OAuthModal.tsx 1030->1056 (gate units). Adds a gheUrl input state, routes ghe-copilot through the existing device-code branch, and threads gheUrl into the device-code request/poll extraData at the existing provider-switch chokepoints (+~24 lines, cohesive with the same pattern as #7399/#6636). The standalone GHE enterprise-URL config step JSX (originally +31 lines inline) was extracted to the new src/shared/components/oauthModal/GheConfigStep.tsx leaf component to minimize the bump; what remains is the irreducible provider-branch wiring. Fifth bump on this modal (969->989->993->998->1030->1056); structural shrink tracked in #3501.",
"_rebaseline_2026_07_21_8027_grok_cli_auth_json_paste": "PR #8027 (RaviTharuma, fix(grok-cli) #7610) own growth: OAuthModal.tsx 1080->1100 (gate units). Requires the full ~/.grok/auth.json (with refresh_token) on the paste-import path instead of a bare JWT, at the existing paste-token chokepoint (renamed tab label, updated instructions/placeholder, textarea for the auth.json blob, inline error surface). The validation logic itself (parseGrokCliPasteToken, previously an inline ~75-line function) was extracted to the new src/lib/oauth/utils/grokCliAuthJson.ts leaf module — mirroring the #6636/#7546 extraction precedent — so only the irreducible UI wiring remains here. Sixth bump on this modal (969->989->993->998->1030->1056->1100); structural shrink tracked in #3501.",
"src/shared/components/OAuthModal.tsx": 1100,
"_rebaseline_2026_07_22_8213_requestloggerdetail_unblock_ui": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: src/shared/components/RequestLoggerDetail.tsx 799->941 (+142, entirely this PR's diff — no other commit touched this file between the PR's merge-base and the release tip; crosses the general 800-line new-file cap so is frozen here for the first time). Adds a collapsible section header (open/expand-less toggle) plus per-log-entry unblock (`unblocking`/`cleared` state, isCombo503 detection) so the request-logger detail panel surfaces the same Gemini TPM cooldown-wait / model-lockout unblock action introduced by this PR at the individual-request level (mirrors the health-page bulk unblock action added in the same PR). Covered by the PR's own dashboard/resilience test additions.",
"src/shared/components/RequestLoggerDetail.tsx": 941,
"src/shared/components/RequestLoggerV2.tsx": 1629,
"src/shared/components/analytics/charts.tsx": 1558,
"_rebaseline_2026_07_10_6318_omp_letta": "PR #6318 (@hamsa0x7, omp+letta CLI integrations) own growth: cliTools.ts (+53 = 2 registry entries incl. omp docsUrl) and cliRuntime.ts (+18 = runtime-detection wiring for the 2 new tools). Cohesive registry/wiring growth at the existing chokepoints; scope reduced from the original 5 tools (pi/codewhale/jcode shipped separately).",
"src/shared/constants/cliTools.ts": 916,
"src/shared/constants/pricing.ts": 1662,
"src/shared/constants/providers.ts": 3276,
"src/shared/constants/sidebarVisibility.ts": 1198,
"src/shared/services/cliRuntime.ts": 1128,
"src/shared/validation/schemas.ts": 2523,
"_rebaseline_2026_06_28_5275_correlation_id_extract": "Extraction of the safe CorrelationId subset of #5275 (hartmark) — request correlation id stored in call_logs (migration 109) and returned via the X-Correlation-Id response header, WITHOUT the combo/resilience or build/lazy-loading changes (those stay in #5275). Own growth: callLogs.ts 975->985 (correlation_id column on CallLogSummaryRow + read/map), usageHistory.ts 983->988 (correlationId metadata normalize), chat.ts 1575->1632 (withCorrelationId response wiring + combo-failure log carrying correlationId), chatHelpers.ts new 811 (withCorrelationId helper + reqId threading; was 791<cap pre-feature). Cohesive request/logging chokepoint wiring; structural shrink of chat.ts tracked in #3501.",
"_rebaseline_2026_07_09_6678_routing_strategy_9router": "#6678 (SeaXen) — 9router-parity Routing Strategy settings card + per-provider/combo sticky-round-robin override. Own growth: ProviderDetailPageClient.tsx 784->786 (single ProviderAccountRoutingCard mount + import), auth.ts 2448->2458 (providerStrategies override resolution: fallbackStrategy/stickyRoundRobinLimit per-provider cascade in getProviderCredentials). Both additive, zero unrelated refactor; new UI/logic lives in new files (ProviderAccountRoutingCard.tsx, RoutingStrategyCard.tsx, rrState.ts::resolveComboStickyRoundRobinLimit). chat.ts value below reflects the current release tip (grown by other concurrent PRs, e.g. #6640), not this PR own change.",
"_rebaseline_2026_07_22_8213_chat_abandoned_target_abort": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: src/sse/handlers/chat.ts 1794->1860 (+66, measured against the PR's own merge-base — the release tip separately carries an unrelated -5 net shrink from #8013's antigravity callable-catalog alignment, which this PR's branch does not include and this entry does not cover). Adds resolveDispatchClientRawRequest(): merges a per-target modelAbortSignal into clientRawRequest.signal (via mergeAbortSignals) so a combo target abandoned by comboTargetTimeoutMs actually observes its own abort and reaches its cleanup path, instead of hanging forever inside withRateLimit/acquireAccountSemaphore and leaking a permanent 'pending' dashboard entry (live incident, log id 1784418258231-14961a). Also wires combo-exhausted rejection logging to capture request body + attempted models via the new rejectedRequestUsage helper. Irreducible additions at the existing chat dispatch chokepoint. Covered by the PR's own combo-config + integration test additions.",
"_rebaseline_2026_07_23_8127_grok_weekly_quota": "#8127 (@apoapostolov) own growth: src/sse/handlers/chat.ts 1861->1865 (+4) — weekly quota tracking for grok-web wires a quota-fetch hook at the existing dispatch chokepoint. Thin wiring mirroring adjacent provider-quota branches; not extractable. Covered by tests/unit/grok-quota-fetcher.test.ts.",
"src/sse/handlers/chat.ts": 1865,
"_rebaseline_2026_07_20_7779_routingcombo_thread": "PR #7779 own growth: chatHelpers.ts 876->877 (+1, thread routingComboId into executeChatWithBreaker for compression-combo assignment). Frozen so it can only shrink.",
"src/sse/handlers/chatHelpers.ts": 878,
"src/sse/services/auth.ts": 2475,
"open-sse/executors/default.ts": 890,
"open-sse/translator/request/openai-responses.ts": 902,
"open-sse/executors/kiro.ts": 944,
"open-sse/translator/request/openai-to-claude.ts": 823,
"tests/unit/account-fallback-service.test.ts": 1572,
"tests/unit/provider-validation-specialty.test.ts": 2980,
"open-sse/executors/huggingchat.ts": 813,
"_rebaseline_2026_07_01_v3843_release_5609": "Rebaseline v3.8.43 (PR #5609 release reconciliation). DRIFT dos 109 commits do ciclo: 8 god-files existentes cresceram (ApiManagerPageClient 2983->3017, combos/page 4594->4608, AddApiKeyModal 868->869, providerPageHelpers 974->996, chat.ts 1635->1647, auth.ts 2401->2403, batchProcessor 828->915, combo.ts 3368->3387) + 2 novos acima do cap (huggingchat.ts 813, tests web-cookie-providers-new 827) + 4 test files cresceram. Modularizacao deferida (blast-radius mid-release); congelado no estado atual p/ o proximo ciclo ratchetar daqui.",
"src/lib/providers/validation/webProvidersA.ts": 809,
"src/lib/tokenHealthCheck.ts": 832,
"_rebaseline_2026_07_09_6587_kiro_api_key_auth": "PR #6587 (@strangersp) own growth for Kiro long-lived API-key auth, merged onto v3.8.47 tip: openai-to-kiro.ts 890->912 (+22, auth-header selection for API-key-vs-OAuth-token connections), providerLimits.ts 998->1000 (+2, API-key auth-type branch), translator-openai-to-kiro.test.ts 1234->1257 (+23), providers-page-utils.test.ts 1109->1107 (net -2 after merging with parallel release drift; connectionMatchesProviderCard api_key coverage added), provider-validation-specialty.test.ts 2856->2980 (+124 net after merge with parallel release drift; this PR also removed the file's `@typescript-eslint/no-explicit-any` eslint-suppression entry by fixing all `any` usages, adding typed replacements). Cohesive additive feature growth, well tested; not extractable without splitting the existing chokepoints mid-merge.",
"_rebaseline_2026_07_19_7787_ic2_localdb_reexports": "PR #7787 (IC2 raw connections cache + lazy-decrypt) own growth: localDb.ts 805->807 (gate units, +2). localDb.ts is the re-export-only layer (hard rule #2 — no logic); the PR adds 4 new db/readCache re-exports (touchConnectionLastUsed, getCachedRawProviderConnections, getCachedProviderConnectionById, getCachedProviderNodes) required by existing barrel importers. Irreducible for a re-export list; frozen so it can only shrink.",
"_rebaseline_2026_07_20_7819_autocandidateoverrides_reexport": "PR for #7819 (Level 1+2: read-only auto/* candidate transparency + per-API-key exclusions) own growth: localDb.ts 807->808 (+1). Adds a single `export * from \"./db/autoCandidateOverrides\"` barrel re-export (hard rule #2 — no logic) for the new DB module backing per-apiKey candidate exclusions. Irreducible for a re-export list; frozen so it can only shrink.",
"src/lib/localDb.ts": 808,
"_rebaseline_2026_07_12_v3847_mergeprs_tail": "v3.8.47 /merge-prs tail (owner-approved): src/lib/localDb.ts NEW>800 (799->805, +6 re-exports countFreeProxies + recordFreeProxySyncErrors/clearFreeProxySyncErrors/getFreeProxySyncErrors + FreeProxySyncErrors type for #6909 free-pool relay-repair; re-export-only per Hard Rule #2, not extractable).",
"_rebaseline_2026_07_21_8034_compression_exclusions_sidebar": "#8034 (compression exclusions dashboard tab) own growth: sections.ts 796->806 (+10, one new COMPRESSION_CONTEXT_GROUP sidebar item linking /dashboard/compression/exclusions). The file was already 796/800 before this PR (organic growth from prior sidebar entries), so a single new nav item pushed it 6 lines over cap. Freezing at 806 (cannot grow further); the sidebar item array is data, not extractable logic.",
"_rebaseline_2026_07_23_8219_cache_ttl_settings_sidebar": "#8219 (@oyi77) own growth: sections.ts 806->813 (+7) — configurable model-catalog cache-TTL settings adds a new sidebar nav entry + its visibility wiring. Sidebar item array is data, not extractable logic; frozen at new size.",
"src/shared/constants/sidebarVisibility/sections.ts": 813,
"_rebaseline_2026_07_22_8056_headroom_minrows": "#8056 (@RaviTharuma, persist Headroom minRows) own growth: src/lib/db/compression.ts 850->866 (+16 HeadroomConfig+DEFAULT_HEADROOM_CONFIG+normalize/store in get/updateCompressionSettings) and open-sse/services/compression/strategySelector.ts 1054->1060 (+6 merge settings.headroom into stacked stepConfig). Cohesive settings-persistence + stacked-merge wiring at existing chokepoints, frozen at new size.",
"_rebaseline_2026_07_22_8081_reasoning_placeholder_guard": "#8081 (@Dingding-leo) own growth: openai-responses.ts 1125->1137 (+12) restructuring the reasoning-placeholder guard so it skips only the empty content block and still emits finish_reason/tool_calls in the same chunk. Cohesive translator wiring; frozen at new size.",
"open-sse/services/usage/antigravity.ts": 802,
"_rebaseline_2026_07_22_fusion_8013_8098_antigravity": "Fusion of #8013 (backryun, catalog/IDE-CLI-split rewrite) + #8098 (nguyenha935, protocol-fidelity/fail-closed/credits/tool-cloaking): open-sse/services/usage/antigravity.ts NEW 802 (>cap 800, +2 — #8098 credits/tier usage service on #8013's profile-aware headers). Test growth (models-catalog-route 1605->1608, provider-models-route 1752->1757 from #8013 Gemini 3.6 catalog) tracked in testFrozen.",
"_rebaseline_2026_07_22_8050_model_lockout_exact_family": "#8050 (@AndrianBalanescu) own growth: accountFallback.ts 1864->1892 (+28) — exact-vs-family model-lockout scoping (getModelLockKey/isModelLocked/clearModelLock/getModelLockoutInfo) so an Antigravity 404 for one bare model no longer hijacks the whole family cooldown. Cohesive lockout logic; frozen at new size."
},
"testCap": 1000,
"testFrozen": {
"_rebaseline_2026_06_27_5193_antigravity_test": "#5193 own test growth: oauth-providers-config.test.ts 870->873 (+3: antigravity projectId assertion + 50ms tick for the now fire-and-forget onboarding, matching the no-PKCE/no-openid flow).",
"_rebaseline_2026_07_02_5928_base_red": "web-cookie-providers-new.test.ts 845->850: #5928 (test(security) Kimi Web URL host parse, CodeQL #689) grew the file +5 lines and merged into release/v3.8.44 WITHOUT rebaselining, leaving a fast-gates base-red that blocked every subsequent PR->release. Test growth is legitimate (a security regression test); maintainer absorbs the drift here. Frozen at 850.",
"_rebaseline_2026_07_09_6126_clinepass_dualauth": "#6126 (ClinePass dual-auth) own test growth: oauth-providers-config.test.ts 842->845 (+3: clinepass key/config/required-fields entries reusing the Cline WorkOS flow config, needed after registering clinepass in the oauth.ts PROVIDERS enum).",
"_rebaseline_2026_07_12_v3847_mergeprs_tail": "v3.8.47 /merge-prs tail: translator-openai-responses-req.test.ts 1172->1195 (+23 = #6807 reasoning-summary-for-effort-only regression tests). Frozen only shrinks.",
"_rebaseline_2026_07_22_8056_headroom_minrows": "#8056 (@RaviTharuma, persist Headroom minRows) own growth: src/lib/db/compression.ts 850->866 (+16 HeadroomConfig+DEFAULT_HEADROOM_CONFIG+normalize/store in get/updateCompressionSettings) and open-sse/services/compression/strategySelector.ts 1054->1060 (+6 merge settings.headroom into stacked stepConfig). Cohesive settings-persistence + stacked-merge wiring at existing chokepoints, frozen at new size.",
"_rebaseline_2026_07_22_8123_agy_live_model_sync": "#8123 (safeer/@adevwithpurpose) own test growth: provider-models-route.test.ts 1757->1783 (+26) — live AGY model discovery assertions (isDiscoverableAgyModelId + filterUserCallableAntigravityModels), composing with the #8013 fusion's antigravity discovery rewrite.",
"_rebaseline_2026_07_22_8213_combo_config_cooldown_wait_tests": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: tests/unit/combo-config.test.ts 880->940 (+60, entirely this PR's diff — testFrozen add covering isComboCooldownWaitEligible (gating cooldown-wait to auto/quota-share strategies with the feature enabled) and resolveComboTargetTimeoutMsForCombo (raising the per-target timeout floor to cover the cooldown-wait budget + buffer for eligible strategies, fixing the 120s default cutting off a 130s wait early and returning a synthetic 524)). Covered by the new assertions themselves.",
"_rebaseline_2026_07_23_8122_codex_image_edits": "#8122 (@xiaoyaner0201) own growth: tests/unit/image-generation-handler.test.ts 2019->2029 (+10) — new coverage for Codex reference image edits (POST /v1/images/edits) plus the sanitizeImageProviderError/redactSensitiveErrorText hardening it introduces. Test-only growth at the existing handler test file.",
"_rebaseline_2026_07_25_8510_adobe_firefly_reference_images_tests": "#8510 (artickc, feat/adobe-firefly-reference-images) own test growth: tests/unit/adobe-firefly.test.ts 711->871 (+159, entirely this PR's diff — new referenceBlobs upload/dispatch coverage for handleAdobeFireflyImageGeneration, resolveAdobeSourceImageIds, and the storage-upload wire contract). Route-level /v1/images/edits coverage (credentials/rate-limit/4-ref-cap branches added to route.ts) lives in the new tests/unit/8510-adobe-firefly-edits-route.test.ts instead of growing this file further.",
"_rebaseline_basered_codebuddy_cn": "Base-red fix (#4664 CodeBuddy CN): oauth-providers-config.test.ts 867->870 (+3) to align the EXPECTED provider list/config with the codebuddy-cn provider that #4664 added to the registry without updating this test (it asserts 'exactly once').",
"_rebaseline_pr4613_compatible_provider_groups": "Reconcile #4613 already-merged growth: providers-page-utils.test.ts 1004->1052 (+48, buildCompatibleProviderGroups partition unit test). Fast-gate PR->release does not run check:file-size, so this surfaced post-merge.",
"tests/integration/chat-pipeline.test.ts": 1598,
"tests/integration/chatcore-compression-integration.test.ts": 1114,
"tests/unit/account-fallback-service.test.ts": 1563,
"tests/unit/batch_api.test.ts": 1324,
"tests/unit/cc-compatible-provider.test.ts": 1217,
"tests/unit/chatcore-translation-paths.test.ts": 2876,
"tests/unit/chatgpt-web.test.ts": 3148,
"tests/unit/combo-routing-engine.test.ts": 3457,
"tests/unit/db-migration-runner.test.ts": 1499,
"tests/unit/deepseek-web.test.ts": 1092,
"tests/unit/executor-codex.test.ts": 1339,
"tests/unit/executor-default-base.test.ts": 1519,
"tests/unit/grok-web.test.ts": 2437,
"tests/unit/image-generation-handler.test.ts": 2029,
"tests/unit/model-sync-route.test.ts": 1016,
"tests/unit/models-catalog-route.test.ts": 1636,
"tests/unit/perplexity-web.test.ts": 1355,
"tests/unit/provider-models-route.test.ts": 1787,
"tests/unit/provider-validation-specialty.test.ts": 2985,
"tests/unit/providers-page-utils.test.ts": 1106,
"tests/unit/response-sanitizer.test.ts": 1063,
"tests/unit/route-edge-coverage.test.ts": 1241,
"tests/unit/search-handler-extended.test.ts": 1071,
"tests/unit/sse-auth.test.ts": 1610,
"tests/unit/stream-utils.test.ts": 2445,
"tests/unit/token-refresh-service.test.ts": 1378,
"tests/unit/translator-openai-responses-req.test.ts": 1194,
"tests/unit/translator-openai-to-gemini.test.ts": 1619,
"tests/unit/translator-openai-to-kiro.test.ts": 1275,
"tests/unit/translator-resp-gemini-to-openai.test.ts": 1234,
"tests/unit/usage-service-hardening.test.ts": 1483,
"tests/unit/vscode-token-routes.test.ts": 1256,
"tests/unit/executor-antigravity.test.ts": 1098
},
"_rebaseline_2026_06_09": "Re-baseline consciente pre-release v3.8.19: 9 arquivos cresceram durante o ciclo (features mergeadas: RequestLoggerV2 +281 request-logger rework, stream +101, combo +73, chatCore +45, catalog +32 fable-5/catalog-flag, callLogs +4, accountFallback +2, usageHistory novo 840) + core.ts +7 (fix resetAllDbModuleState, PR 3536). A catraca segue valendo destes valores — proximo crescimento falha. Decisao: encolher (esp. RequestLoggerV2/chatCore) e a issue #3501 ficam para o ciclo seguinte.",
"_rebaseline_2026_06_11_phase1f": "Phase 1f (#3501): ProviderDetailPageClient.tsx 4948→4062 (-886 LOC); 3 novos hooks extraídos. useProviderConnections.ts=954 acima do cap=800 — justificado: extração direta do god-component (zero lógica nova), própria redução do cliente supera o custo. useProviderSettings.ts=263 e useProviderModels.ts=154 já abaixo do cap.",
"_rebaseline_2026_06_12_review_issues": "Re-baseline consciente do /review-issues v3.8.23: 27 arquivos com crescimento herdado (v3.8.22 nunca reconciliado) + fixes deste round (combo.ts #3685, openai-to-gemini.ts #3688, tokenRefresh.ts #3692, validation/proxies de outras merges). providerLimits.ts (941) adicionado como frozen (split coeso de usage). Shrink endereçado separadamente pelo #3501.",
"_rebaseline_2026_06_12_phase1g1j": "Phase 1g-1j (#3501): ProviderDetailPageClient.tsx 4063→3409 (extraídos ProviderPlaygroundPanel, useCommandCodeAuth, useExternalLinkFlow+ExternalLinkModal, useAuthFileHandlers — zero lógica nova). models/route.ts 2344→2426: drift do #3712 (vertex dynamic model discovery) reconciliado aqui.",
"_rebaseline_2026_06_12_phase1n1s": "Phase 1n-1s (#3501): ProviderDetailPageClient.tsx 2554→1376 (extraídos ConnectionsListPanel, ConnectionsHeaderToolbar, ZedImportCard, BatchTestResultsModal, AdaptaTutorialModal + hooks/useApiKeySave + 4 helper closures→providerPageHelpers.ts). providerPageHelpers.ts 822→897 justificado: recebe 4 closures do god-component (getApiLabel/getApiDefaultPath/getApiPath/getHeaderIconProviderId), zero lógica nova, cliente encolhe mais do que helpers crescem.",
"_rebaseline_2026_06_12_phase1t": "Phase 1t (#3501): ProviderDetailPageClient.tsx 1377→782 — META ≤800 ATINGIDA (extraídos ProviderPageHeader, CompatibleNodeCard, ProviderModalsPanel, EmptyConnectionsPlaceholder, UpstreamProxyCard, SearchProviderCard + hooks useConnectionGate/useProviderNodeActions). Drift concorrente reconciliado: ResilienceTab/sse-chat/sse-auth/accountFallback/combo (merges #3629 model-lockout etc.).",
"_rebaseline_2026_06_21_v3833_usage_quota_trio": "usage.ts 3414->3450 (+36) do trio de PRs de quota/usage do owner mergeados nesta rodada: #4493 (parse de quota reset numeric-string como Unix sec/ms), #4494 (janela semanal de code-review do Codex + additional_rate_limits fallback) e #4512 (mensagem clara de auth-expired p/ Kiro social-auth). Medido o valor real (wc -l 3449 + 1) apos os 3 cherry-picks; #4493/#4494 cresceram o arquivo sem bump (fast-gate PR->release nao roda check:file-size). Crescimento coeso em message/parse chokepoints existentes; shrink estrutural rastreado em #3501.",
"_rebaseline_2026_06_21_v3833_mine_batch1": "Reconcile de reds latentes de PRs do owner mergeados nesta rodada (fast-gate PR->release NAO roda check:file-size): src/shared/constants/pricing.ts 1623->1632 (+9, #4488 default pricing Qwen coder-model no provider qw), tests/integration/chat-pipeline.test.ts 1669->1671 (+2) e tests/unit/vscode-token-routes.test.ts 1208->1212 (+4) ambos do #4500 (skip disabled providers in combo fallback — regressao de teste cobrindo 404-vs-400). Valores medidos reais (wc+1). Crescimento de feature/teste coeso; shrink estrutural rastreado em #3501.",
"_rebaseline_2026_06_21_v3833_mine_final": "Reconcile dos reds latentes do lote de PRs do owner desta rodada (fast-gate PR->release NAO roda check:file-size): src/app/.../api-manager/ApiManagerPageClient.tsx 2909->2979 (+70, #4505 inline show/hide toggle p/ API keys), src/app/.../cli-code/components/CodexToolCard.tsx 894->900 (+6, #4504 enable Apply/Reset quando CLI instalado), src/app/.../usage/components/ProviderLimits/index.tsx 1069->1121 (+52, #4495 dropdown filter per-provider no quota dashboard), src/shared/constants/pricing.ts 1632->1662 (+30, #4508 default cost rows Antigravity Gemini 3.5 Flash + gemini-pro-agent). Valores reais (wc+1). NAO ratchetei chatCore.ts p/ baixo (5085<5125 passa por shrink) p/ nao quebrar PRs em voo da sessao paralela do stack #3501. Crescimento de feature coeso; shrink estrutural rastreado em #3501.",
"_rebaseline_2026_06_21_v3833_r3_contrib": "Reconcile de reds latentes de PRs de contribuidores desta rodada (fast-gate PR->release NAO roda check:file-size): src/shared/constants/providers.ts 3243->3254 (+11, #4522 authHint + enriquecimento freeNote/apiHint na entry bazaarlink), open-sse/services/combo.ts 2649->2657 (+8, #4530 passar maxCooldownMs nos 3 call sites de recordModelLockoutFailure + #4524 campos account/combo/latency no payload do webhook telegram). Valores reais (wc+1). Crescimento coeso; shrink estrutural rastreado em #3501.",
"_rebaseline_2026_06_12_v3823_new_features": "Re-baseline v3.8.23 pós-merge de #3742 (cost drilldown: ApiManagerPageClient.tsx +21, CostOverviewTab.tsx +14, providerLimits.ts +2, usage.ts +53) + #3743 (provider display modes: ProviderDetailPageClient.tsx +2, providerPageHelpers.ts +42, providers.ts +2) + #3740 (semantic cache key isolation: chat.ts +3). Crescimento justificado por features novas mergeadas no ciclo.",
"_rebaseline_2026_06_13_combo_quota_audit": "Re-baseline consciente do audit combo+quota (PR #3779): combo.ts 5054→5131 (+77). Crescimento = 5 fixes TDD + estratégia complexity-aware 2026 (W1 clampComboDepth + threading de maxDepth em 6 assinaturas/dispatch/DAG; W2 extração shouldSkipForPredictedTtft; W4 scoreAutoTargets exportado + param manifestHint). A parte limpa-extraível do W4 (construção do hint inline, ~30 linhas) FOI extraída para autoCombo/complexityRouter.ts (buildComplexityRoutingHint) — este +76 é o resíduo irredutível (edição de assinaturas/threading, não bloco movível). Shrink estrutural de combo.ts segue com #3501.",
"_rebaseline_2026_06_13_v3824_3776": "Re-baseline v3.8.24 pós #3776 (strict-mode controls Claude Code default models: ApiManagerPageClient.tsx 2701→2909 = UI de famílias bloqueáveis cc/* + chips; apiKeys.ts 1490→1633 = blocked_models deny-list + candidatos de permissão claude-code; schemas.ts 2515→2519 = reformatação Prettier + reasoningTokenBufferEnabled restaurado) + carry-over base.ts 1205→1218 do #3780 (enforceThinkingTemperature no chokepoint, drift de baseline não bumpado no merge). Crescimento de feature; sem god-component novo.",
"_rebaseline_2026_06_13_3786_agy_fallback": "Re-baseline #3786 (agy Pro-family upstream-id fallback chain): antigravity.ts 1572→1649 (+77). Crescimento = split de execute() em driver + executeOnce(modelIdOverride) para retentar ids alternativos no 400 (gemini-3.1-pro-high→gemini-pro-agent→gemini-3-pro-high), threading do override em transformRequest/cleanModelName. Lógica coesa de retry no executor — não é bloco movível (chama this.executeOnce). A parte pura (ANTIGRAVITY_PRO_FALLBACK_CHAINS + getAntigravityModelFallbacks) ficou em antigravityModelAliases.ts. Os 3 drifts release-wide (ProxyRegistryManager/sidebarVisibility/schemas) são do #3809 do owner, não deste PR.",
"_rebaseline_2026_06_13_3782_hide_persist": "Re-baseline #3782 (preservar modelos eye-hidden no auto-sync): models.ts 1132→1180 (+48). Crescimento = flag distinto isDeleted em ModelCompatOverride/ModelCompatPatch + handling em mergeModelCompatOverride + helper getModelIsDeleted, para separar 'deletado' (trash, dropado no re-sync #3199) de 'oculto' (eye toggle, preservado). Lógica coesa de visibilidade no módulo db; não-extraível. Os 3 drifts release-wide (ProxyRegistryManager/sidebarVisibility/schemas) são do #3809 do owner, não deste PR.",
"_rebaseline_2026_06_13_3758_chat_early_eof": "Re-baseline #3758 (#3817 mergeado): chat.ts 1392→1425 (+33). Crescimento = retry bounded de STREAM_EARLY_EOF no handleSingleModelChat (contador streamEarlyEofRetries + bloco de retry guardado por shouldRetryStreamEarlyEof). Lógica coesa no handler de chat; não-extraível. Reconciliação tardia — o bump foi esquecido no PR do fix (o de antigravity/models foi feito).",
"_rebaseline_2026_06_13_3416_migration_threshold": "Re-baseline #3416 (threshold de migrações pendentes via env): migrationRunner.ts 1100→1125 (+25). Crescimento = helper resolveMaxPendingMigrations() que lê OMNIROUTE_MAX_PENDING_MIGRATIONS em call-time (valida finito+>=0, fallback 50) + JSDoc. Lógica coesa de config no runner; não-extraível.",
"_rebaseline_2026_06_13_3474_grok_403": "Re-baseline #3474 (mensagem clara no 403 anti-bot do Grok): validation.ts 4302→4348 (+46). Crescimento = helper isGrokAntiBotBlock() + branch 403 de 3 tiers (auth-shaped / anti-bot-IP-reputation / upstream-error). Lógica coesa de classificação no validator; não-extraível.",
"_rebaseline_2026_06_13_3324_windsurf_devin": "Re-baseline #3324 (windsurf auth text + devin error propagation): route.ts 897→903 (+6, texto da instrução windsurf→fluxo command-palette) + sseParser.ts ADICIONADO como frozen 812 (era 746, +66 = helper extractSSEErrorMessage que faz surface do erro real SSE em vez do 502 genérico). 812 fica 12 acima do cap 800 — helper coeso no parser de SSE, congelado com justificativa (precedente providerLimits/useProviderConnections).",
"_rebaseline_2026_06_13_2743d_skipbreaker": "Re-baseline #2743 gap-d (testar consumer do skipProviderBreaker): combo.ts 5131→5162 (+31). Crescimento = extração do boolean inline da decisão de circuit-breaker para o predicado puro EXPORTADO shouldRecordProviderBreakerFailure() (byte-idêntico) + JSDoc, para torná-lo unit-testável sem o harness completo de combo. Shrink estrutural segue com #3501.",
"_rebaseline_2026_06_13_v3825_prettier_reconcile": "Reconciliação tardia: o prettier do pre-commit reformatou 3 arquivos DEPOIS da medição de file-size dos PRs, inflando linhas além do baseline setado — OAuthModal.tsx 956→960 e providers.ts 3146→3147 (#3324), combo.ts 5162→5164 (#2743d). Bumps de reformatação automática (sem lógica nova). LIÇÃO: medir file-size pós-commit (pós-prettier), não antes.",
"_rebaseline_2026_06_14_3826_release_drift": "Re-baseline release/v3.8.25 drift already documented from #3809 owner changes: ProxyRegistryManager.tsx 1072→1089, sidebarVisibility.ts 990→1006, schemas.ts 2519→2522. This PR does not touch those source files; updating the frozen values restores Fast Quality Gates on the current release branch.",
"_rebaseline_2026_07_03_5918_proxy_batch": "PR #5918 own growth: ProxyRegistryManager.tsx 1089→1117 (+28 = wiring the new batch-select/Test-All proxy management components — checkboxes, batch actions bar, health cells). Cohesive UI wiring for the batch-delete/auto-test feature; the reusable pieces already live in separate leaf components (ProxyBatchActions/ProxyCheckboxCell/ProxyHealthCell/useProxyBatchOperations). Legitimate feature growth, not a quality regression.",
"_rebaseline_2026_06_14_3825_combo_stickiness": "Re-baseline #3825 (sessionless combo stickiness + reasoning-aware readiness): combo.ts 5164→5198 (+34, pós-prettier). Crescimento = deriveComboSessionKey() + effectiveSessionId threading nos sites de read/write do pin server-side. streamReadinessPolicy.ts não-frozen (sob cap). Lógica coesa no handler de combo; não-extraível.",
"_rebaseline_2026_06_14_r3_3835_kiro_pricing": "PR #3835 own growth: pricing.ts 1470→1508 (+38 = missing Kiro pricing rows, claude-sonnet-4.6 etc., pure data). Also carries inherited release/v3.8.25 drift not yet frozen by prior r2 merges: RequestLoggerV2.tsx 1276→1282 (#3820 resizable log table) and combo.ts 5198→5203 (#3811 round-robin replay-response fix). This PR does not touch RequestLoggerV2/combo.ts source; updating the frozen values restores Fast Quality Gates on the current release branch.",
"_rebaseline_2026_06_14_r3_3849_transient_hide": "PR #3849 own growth: providerPageHelpers.ts 939→955 (+16 = expanded JSDoc on the auto-hide policy + transient-failure guard in evaluateTestAllEntry). Cohesive helper logic; not extractable.",
"_rebaseline_2026_06_14_r3_3838_opencode_quota": "PR #3838 own growth: usage.ts 3394→3408 (+14 = clearer OpenCode Go missing-quota-API messages with OMNIROUTE_OPENCODE_GO_QUOTA_URL override hint + upstream issue links). Message text only; no logic extractable.",
"_rebaseline_2026_06_14_r3_3839_veo_video": "PR #3839 own growth: schemas.ts 2522→2523 (+1 = Veo video model (predictLongRunning) validation for Gemini/Vertex dynamic discovery).",
"_rebaseline_2026_06_14_r3_3836_kiro_discovery": "PR #3836 own growth: models/route.ts 2426→2487 (+61 = kiro live per-account discovery branch wiring fetchKiroAvailableModels into the existing cache/auto-fetch/fallback discovery flow). Structural shrink of this route tracked in #3789.",
"_rebaseline_2026_06_14_2997_disable_cooling": "Re-baseline #2997 (per-connection disable-cooling): EditConnectionModal.tsx 1171→1174 (+toggle UI) + auth.ts 2207→2216 (honor de disableCooling no markAccountUnavailable, pós-prettier). Lógica coesa; não-extraível. (combo.ts/RequestLoggerV2 drift já documentado em _r3_3835.)",
"_rebaseline_2026_06_14_r3_3848_compression": "PR #3848 own growth: chatCore.ts 5808→5811 (+3 = compression engine pipeline hooks). Also carries inherited release/v3.8.25 drift not touched by this PR: models/route.ts 2487→2489 (+2, post-#3836/prettier). Updating the frozen values restores Fast Quality Gates on the current base.",
"_rebaseline_2026_06_14_3861_gitlab_duo": "PR #3861 own growth: oauth/[provider]/[action]/route.ts 903→916 (+13 = gitlab-duo authorize guard mirroring the existing qoder guard — returns a clear 'register an OAuth app + set GITLAB_DUO_OAUTH_CLIENT_ID' message instead of letting buildAuthUrl's throw become an opaque 500). Cohesive with the qoder branch right above it; not separately extractable.",
"_rebaseline_2026_06_15_3941_provider_request_capture": "PR #3941 own growth: chatCore.ts 5823->5830 (+7 by check-file-size counting = run executor attempts inside the unified provider request capture scope), antigravity.ts 1649->1664 (+15) and codex.ts 1439->1447 (+8) = bridge hand-written upstream transports that bypass normal fetch/BaseExecutor capture. Cohesive logging-fidelity refactor; not extractable without hiding the actual dispatch boundary.",
"_rebaseline_2026_06_16_3958_qwen_body_check": "PR #3958 own growth: validation.ts 4407->4428 (+21 = validateQwenWebProvider now parses the /api/v2/user 200 body and requires a real user object, since Qwen returns HTTP 200 even for invalid tokens — fixes the validation false-positive, #3931). Cohesive with the existing qwen-web validation branch; not separately extractable.",
"_rebaseline_2026_06_16_4001_perplexity_diff_block": "PR #4001 own growth: perplexity-web.ts 939->1013 (+74 = parse the schematized API's RFC-6902 diff_block JSON-patch frames — applyMarkdownDiff + isAnswerTextUsage primary-usage lock + stop only on COMPLETED, not on a still-PENDING final flag — so streamed answers aren't empty, #3938 follow-up). Cohesive single-executor SSE-parsing logic; not separately extractable.",
"_rebaseline_2026_06_16_4005_openai_dynamic_models": "PR #4005 own growth: models/route.ts 2494->2512 (+18 = openai model-discovery derives {customBaseUrl}/v1/models from providerSpecificData.baseUrl, SSRF-guarded via safeOutboundFetch+public-only) and pricing.ts 1529->1581 (+52 = pure-data pricing rows closing $0 gaps for registry-exposed ids: openai gpt-5.4/-mini/-nano, gpt-4.1, gpt-4o-2024-11-20, o3 + codex(cx) gpt-5.4-{xhigh,high,medium,low}, gpt-5.3-codex-spark). Cohesive; pricing is data, route change mirrors the anthropic-compat discovery path.",
"_rebaseline_2026_06_16_4004_livews_bridge": "PR #4004 own growth: chatCore.ts 5830->5851 (+21 = forwardDashboardEventToLiveWs — a best-effort, non-blocking, timeout-bounded POST that bridges compression.completed events from the main process to the LiveWS sidecar so the dashboard updates under a reverse proxy). Cohesive fire-and-forget beacon at the existing compression emit site; not extractable. Structural shrink of chatCore.ts tracked in #3501.",
"_rebaseline_2026_06_17_4107_pending_reaper": "PR #4107 own growth: usageHistory.ts 854->934 (+80 = orphaned-pending-request reaper — sweepStalePendingRequests() evicts pending details older than 15min + a hard 5000 cap, plus an unref'd 5min sweep timer wired lazily into trackPendingRequest). Fixes an unbounded memory leak where abnormally-terminated requests left payload previews in pendingById forever. Cohesive with the existing pending-request bookkeeping (mirrors the normal removal path: decrement counters + cleanup buckets); not extractable.",
"_rebaseline_2026_06_17_4116_combo_hedge_listener": "combo.ts: +9 lines from #4116 (detach per-target listener from shared hedge abort signal to fix a listener leak). Behavior-preserving cleanup; 5289 -> 5298.",
"_rebaseline_2026_06_20_4355_gpt5x_pro_pricing": "PR #4355 own growth: pricing.ts 1581->1592 (+11 = pure-data pricing rows for openai gpt-5.5-pro + gpt-5.4-pro, closing the $0 gap that tripped the catalog pricing gate after the #4324 sweep added them to the registry; -pro mirrors its base family tier). provider-models-route.test.ts 1616->1618 (+2 = test-only alignment to the intentional opencode-go discovery behavior: owned_by stamp + T39 two-endpoint fail-path fetchCalls). Both are data/test-only; not extractable.",
"_rebaseline_2026_07_02_5899_airforce_v1_discovery": "PR #5904 own growth: provider-models-route.test.ts 1628->1752 (+124 = test-only Rule #18 regression guards for the Api Airforce /v1/v1/models discovery bug (#5899): (a) a baseUrl ending in /v1/chat/completions must probe .../v1/models not the doubled .../v1/v1/models, and the host-guard case http://v1; (b) a REDIRECT_BLOCKED on one candidate must continue to the next endpoint instead of aborting the probe loop. Both guards fail on the pre-fix code. Test-only additions cohesive with the existing provider-models discovery suite (shared seedConnection/callRoute harness); not separately extractable without duplicating the harness.",
"_rebaseline_2026_06_19_4293_codex_spark_scope": "PR #4293 (isolate Codex Spark quota scope) own growth, MEASURED on the actual merged tree (release/v3.8.30 + #4293). Production: auth.ts 2219->2279 (+60) threads requestedModel into Codex quota-policy/headroom/preflight/P2C scoring so normal Codex and GPT-5.3-Codex-Spark windows are evaluated independently; chatCore.ts 5116->5125 (+9) passes the failing model scope into Codex 429 failover (markCodexScopeRateLimited) instead of a connection-wide rateLimitedUntil write; accountFallback.ts 1727->1731 (+4) scopes Codex model-lock keys to codex vs spark. Heavy parsing/display logic lives in new leaf helpers under the cap (codexQuotaScopes.ts, codexUsageQuotas.ts, codexFailover.ts). Tests: account-fallback-service 1544->1569, executor-codex 1336->1339, sse-auth 1527->1553, usage-service-hardening 1612->1633 (added Spark-scope regression coverage). Cohesive wiring at existing selection/failover lockout boundaries; not extractable.",
"_rebaseline_2026_06_20_4447_openai_gpt41mini_o_mini_pricing": "PR #4447 own growth: pricing.ts 1592->1620 (+28 = pure-data pricing rows closing the null/$0 gap for registry-exposed OpenAI ids gpt-4.1-mini, gpt-4.1-nano, o3-mini, o4-mini that tripped the catalog pricing gate; getPricingForModel does an exact lookup, so a missing key resolves to null. Official OpenAI per-1M prices + the table's derived-field convention (reasoning=output*1.5, cache_creation=input, cached=official). Restore-green for a pre-existing release/v3.8.32 red surfaced by #4432's __RUN_ALL__ run. Cohesive data; not extractable.",
"_rebaseline_2026_06_20_web_cookie_validator_shadow_fix": "validation.ts 4518->4522 (+4 = move the generic web-cookie validateWebCookieProvider dispatch from the TOP of validateProviderApiKey to a FALLBACK after SPECIALTY_VALIDATORS, plus a comment, so #4023's generic AUTH_007 ping no longer shadows the rich per-provider validators (grok-web #3474 IP-reputation/Cloudflare, chatgpt-web cf-mitigated, claude/gemini/copilot/qwen/t3-web). Restores provider-validation-specialty.test.ts (112/112) while keeping web-cookie-auth007 (5/5). Behavior fix at an existing dispatch boundary; not extractable.",
"_rebaseline_2026_06_22_phase4b_slm_tier_ultra": "Compression Phase 4 (B) SLM tier own growth: open-sse/services/compression/strategySelector.ts 783->818 (+35 at the existing applyUltraAsync chokepoint). The no-modelPath ultra branch (previously a one-line passthrough to the sync applyCompression) now runs the two-tier resolver: it adapts the body, builds the ultraConfig (threading config.ultraEngine + preserveSystemPrompt), awaits the now-async ultraCompress (SLM Tier-B when ultraEngine===slm and the worker backend is available, else fail-open to the Tier-A heuristic), and threads result.stats.ultraTier into the returned CompressionStats so the resolved tier reaches the D0 telemetry persister. The sync applyCompression ultra branch is also re-pointed to the new pure ultraCompressHeuristic. The two-tier resolver + the pure heuristic live in open-sse/services/compression/ultra.ts and the thin SLM entry in engines/llmlingua/ultraEntry.ts (both <cap, fully unit-tested); strategySelector.ts is cohesive dispatch wiring at the existing ultra chokepoint, not extractable without hiding the dispatch boundary. Covered by tests/unit/compression/ultra-slm-tier.test.ts (applyCompressionAsync ultraTier:slm) + ultra.test.ts (39 regression) + the integration suites. Structural shrink of this file tracked in #3501.",
"_rebaseline_2026_06_22_phase4c_adaptive_context_budget": "Compression Phase 4 (C) adaptive context-budget wiring own growth: open-sse/services/compression/strategySelector.ts 818->848 (+30 at the existing selectCompressionPlan dispatch chokepoint). selectCompressionPlan gains an 8th optional `adaptiveOptions` param (modelContextLimit/requestMaxTokens/onAdaptive sink) and, after resolveBasePlan and before the caching-aware pass, runs the PURE resolveAdaptivePlan when config.contextBudget.mode is floor|replace-autotrigger; the new adaptiveEnabled(config) helper also gates the legacy shouldAutoTrigger branch inside resolveBasePlan off when adaptive owns automatic-by-size escalation (D-C4). The escalation ladder, target computation, and the resolver itself live in open-sse/services/compression/adaptiveCompression/{computeTarget,ladder,resolveAdaptivePlan,types}.ts (all <cap, fully unit-tested by Tasks 1-4). strategySelector.ts is cohesive dispatch wiring at the existing compression chokepoint, not extractable without hiding the dispatch boundary, mirroring the prior compression rebaselines (#4217/#4210/phase4b). Covered by tests/unit/compression/adaptive-select-plan-wiring.test.ts (4 tests) + adaptive-chatcore-source-guard.test.ts (2). chatCore.ts also grows ~30 lines at the same call site (threads getTokenLimit(provider,effectiveModel) + request max_tokens into adaptiveOptions and records the adaptive telemetry block onto compression.completed) but stays under its frozen cap. Structural shrink of this file tracked in #3501.",
"_rebaseline_2026_06_25_rc17_pr_batch": "rc17 PR batch own growth (cohesive, not extractable): responseSanitizer.ts 1103->1122 (+19 = SanitizeOpenAIResponseOptions interface + stripReasoning option, #4678); tokenRefresh.ts 2070->2090 (+20 = codex 401 defense-in-depth unrecoverable-refresh guard, #4686); token-refresh-service.test.ts 1322->1353 (+31 = 401-unfamiliar-payload regression case, #4686); translator-openai-responses-req.test.ts 1047->1050 (+3 = reasoning_effort non-Copilot assertion update, #4688). All are the merged PRs own surgical additions at existing chokepoints.",
"_rebaseline_2026_06_25_rc17b_leva2": "rc17 leva2 PR batch own growth (cohesive, not extractable): providerLimits.ts 950->955 (#4786 generalized accesstoken fallback); default.ts NEW frozen entry at 828 (#4729 anthropic-compatible Bearer + #4766 json_schema fallback + #4787 cline workos headers — three provider-specific header branches); openai-to-kiro.ts 807->814 (#4763 Claude-capability image gate); openai-responses.ts 923->937 (#4764 computeFinishReason guard); executor-default-base.test.ts 1339->1440 (#4766 json_schema fallback tests); translator-openai-to-kiro.test.ts 918->980 (#4763 non-Claude image gate tests).",
"_rebaseline_2026_06_27_v3838_filesize_drift": "Mid-cycle drift on release/v3.8.38 — feature/fix growth from already-merged PRs that the fast-path (PR->release skips check:file-size) let accumulate without a bump. src/shared/constants/sidebarVisibility.ts 1100->1198 (+98 = #3812 colored menu-icon support, per-item accent map across the sidebar entries; #5142 then dropped one orphan settings entry, net still above the frozen). src/sse/handlers/chat.ts 1560->1575 (+15 = #5064 self-inflicted-timeout cooldown skip + #5124 long OpenAI-compatible SSE hardening + #5110 embed-WS LIVE_WS_HOST honour / early empty-message reject). Localized feature/fix code next to existing branches, each covered by its own PR tests; not extractable without hiding the chokepoint. Structural shrink of chat.ts tracked in #3501.",
"_rebaseline_2026_07_05_6211_providerlimits_fetch_timeout": "PR #6211 own growth: src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx 1121->1127 (+6 = data-timeout guard for the quota page's two first-paint fetches — a PROVIDER_LIMITS_FETCH_TIMEOUT_MS const + fetchWithTimeout at the /api/providers/client and /api/usage/provider-limits call sites — so a never-settling connection can no longer wedge initialLoading on the skeleton, same infinite-skeleton class the PR also fixes on the providers page). Cohesive fix code at the existing fetch chokepoints (the 5-line rationale comment explains why a timeout/abort is needed where a try/catch only rescues a rejection); not extractable. Covered by tests/unit/providers-page-data-timeout.test.ts. Fast-path PR->release skips check:file-size, so this bump lands with the PR. Structural shrink of this file tracked in #3501.",
"_rebaseline_2026_07_05_6154_copilot_catalog_helpers": "PR #6154 own growth: src/app/(dashboard)/dashboard/providers/[id]/providerPageHelpers.ts 1021->1034 (+13 = GitHub Copilot catalog refresh — model-section helper wiring for the refreshed passthrough/compatible model lists). Cohesive UI-helper growth alongside the registry/modelSpecs catalog refresh; not extractable. Covered by the PR's provider-registry-github-copilot-* unit tests. Fast-path PR->release skips check:file-size, so this bump lands with the PR (contributor backryun).",
"_rebaseline_2026_07_05_6213_kiro_thinking_filesize": "PR #6213 own growth (kiro adaptive-thinking -> reasoning_content, +384): open-sse/translator/request/openai-to-kiro.ts 853->890 (+37 = additionalModelRequestFields builder for adaptive thinking: output_config.effort + thinking:{type:adaptive} + max_tokens, only when the request asked for thinking) and tests/unit/translator-openai-to-kiro.test.ts 1093->1234 (+141 = adaptive-thinking request/frame regression cases). The fast-path PR->release does NOT gate check:file-size on the merge, so this cohesive feature growth accumulated on the release tip (see the 2026-07-02 #5798 note for the same pattern). Superseded by the release captain's rebaseline-at-release.",
"_rebaseline_2026_07_05_6235_doubao_dola": "PR #6235 own growth: tests/unit/web-cookie-providers-new.test.ts 850->890 (+40 = doubao-web -> Dola global provider switch regression cases: new host/cookie-domain/token-source assertions for www.dola.com). Cohesive test growth alongside the provider switch; contributor backryun. Fast-path PR->release skips check:file-size, so this bump lands with the PR.",
"_rebaseline_2026_07_06_v3845_release_close": "Release v3.8.45 cycle-close rebaseline (captain, sess ce897453): 13 files grown by the cycle's merged fix/feature PRs (#6216 streaming fixes + request-logger UI grew RequestLoggerV2/chat/chatHelpers/auth/stream/response-sanitizer.test; #6251/#6253 dashboard UX grew combos page/modals/wizard/ComboDefaultsTab/ProxyRegistryManager/providerPageHelpers). Growth is legitimate merged-feature code, absorbed at release per Phase 0 drift policy; all remain frozen (cannot grow further).",
"_rebaseline_2026_07_06_6118_zed_oauthmodal": "PR #6118 own growth: OAuthModal.tsx 989->993 (+4 = Zed hosted native-app sign-in modal branch). Cohesive UI growth for the zed-hosted OAuth provider; not extractable. The prior 6118 comment set the note but left the frozen value at 989.",
"_rebaseline_2026_07_06_6351_glm_team_quota": "PR #6351 own growth (GLM team-plan quota fields threaded through the connection modals; new GlmTeamQuotaFields.tsx extracted): AddApiKeyModal.tsx ->951 (+9), EditConnectionModal.tsx ->1277 (+18). Absorbs the pre-existing session base-red on these frozen modals; release captain rebaseline-at-release supersedes.",
"_rebaseline_2026_07_06_6499_unique_default_name": "PR #6499 own growth: AddApiKeyModal.tsx 952->959 (+7 = a unique default connection name so a second API key for the same provider does not reuse 'main' and trigger the backend name-based upsert that silently overwrote the first connection). The pure name derivation was extracted to computeConnectionDefaultName.ts (unit-tested) to keep the growth minimal; the contributor's original full-form-reset rewrite was trimmed to a spread reset to avoid dropping the GLM team-quota fields #6351 added and to hold the frozen god-file growth down. Release captain rebaseline-at-release supersedes.",
"_rebaseline_2026_07_08_vb_reroute": "PR #6640 (Vision Bridge reroute) own growth, re-measured post-merge with origin/release/v3.8.47 + /implement-prs mandatory pre-merge fixes (wc -l + 1): chat.ts 1778->1796 (+18, stacking on #6515/#6525 chirag growth already frozen at 1778) = the original +3 guardrail modelStr sync block PLUS +15 for the policy re-validation added during review (a guardrail-driven model change is now re-checked against isModelAllowedForKey before being honored, closing an allowlist-bypass gap; see tests/unit/vision-bridge-policy-reroute-6640.test.ts). Irreducible wiring at the guardrail post-execution/policy chokepoint; covered by tests/unit/guardrails/visionBridge.test.ts (22 tests) + the 3 new policy-reroute regression tests.",
"_rebaseline_2026_07_07_6523_chirag_cooldown_body": "PR #6523 (@chirag127, #6460) own growth: chatHelpers.ts 860->866 (+6 = retryAfterAt/credentialsCoolingCount fields on modelCooldownResponse) and auth.ts 2447->2448 (+1 = connectionsCount threaded through no-credentials fallback). Owner-approved rebaseline (file-size cap for contributor PR). Frozen (cannot grow further); release captain's rebaseline-at-release supersedes.",
"_rebaseline_2026_07_07_6526_chirag_modal_1080p": "PR #6526 (@chirag127, #6265): AddApiKeyModal.tsx ->961 (1080p sizing). Owner-approved. Frozen.",
"_rebaseline_2026_07_07_6515_chirag": "PR #6515 (@chirag127) own growth: src/sse/handlers/chat.ts ->1763. Owner-approved rebaseline. Frozen.",
"_rebaseline_2026_07_07_6534_chirag": "PR #6534 (@chirag127) own growth: open-sse/services/compression/strategySelector.ts ->1025. Owner-approved rebaseline. Frozen.",
"_rebaseline_2026_07_08_6556_omniglyph_mode": "PR #6556 (omniglyph engine) own growth: open-sse/services/compression/strategySelector.ts 1025->1043 (+18 at the existing mode-dispatch chokepoints). Two single-mode branches (sync no-op + async resolve via the engine registry, mirroring the rtk single-mode pattern, B-MODE-ENGINE-DECOUPLE) plus the optional providerTransport field threaded through the three options types (gates transport-sensitive engines). The engine itself lives in engines/omniglyphAdapter.ts (<cap); the residual is cohesive dispatch/type wiring not extractable without hiding the dispatch boundary, mirroring the prior compression rebaselines (#5243/#5286/phase4b/#6534). Covered by tests/unit/compression/omniglyph-*.test.ts (22). Structural shrink of this file tracked in #3501.",
"_rebaseline_2026_07_07_6546_chirag": "PR #6546 (@chirag127) own growth: src/sse/handlers/chatHelpers.ts ->876. Owner-approved rebaseline. Frozen.",
"_rebaseline_2026_07_07_6525_chirag_image_guard": "PR #6525 (@chirag127, #6457) own growth: chat.ts ->1778 (reject image-only models on /v1/chat/completions; stacks on #6515). Owner-approved. Frozen.",
"_rebaseline_2026_07_15_7045_perf_instrumentation": "PR #7045 (@oyi77) own growth: open-sse/utils/stream.ts 2796->2814 (+18) from performance.mark/measure instrumentation around the SSE dispatch chokepoint (b48ba21c4), a TextEncoder hoisting fix to avoid a per-chunk allocation on the hot path (c35e8a9b4), and clearing the fixed-name \"omni-request-body-size\" mark immediately after creation (babysit fix, addressing a review-flagged unbounded-growth leak in Node's global performance timeline). Cohesive wiring at the existing stream-dispatch chokepoint; not extractable. Covered by tests/unit/chatcore-streaming-pipeline.test.ts + tests/unit/stream-request-body-size-mark-7045.test.ts.",
"_rebaseline_2026_07_18_basereds_test_realignment": "Base-red sweep own growth (post 102-PR campaign, full-suite realignment): tests/unit/combo-routing-engine.test.ts 3209->3243 (+34 = least-used tests now prime usage through real handleComboChat calls so recordComboRequest keys by the resolved executionKey exactly as production does — #7015 keying); tests/unit/db-migration-runner.test.ts 1491->1499 (+8 = withNonTestEnvironment now also strips node --test tokens from process.execArgv, matching the #7359 isAutomatedTestProcess widening); tests/unit/executor-default-base.test.ts 1523->1527 (+4 = 1M-beta assertion updated for claude-sonnet-4-6 GA #7129). All three are test-fidelity realignments, not extractable.",
"_rebaseline_2026_07_21_7930_pplx_quota_cooldown": "PR #7930 (@artickc) own growth, reconstructed against release/v3.8.49 base-drift: tests/unit/perplexity-web.test.ts 1192->1355 (+163 = two new regression cases — 'Live multi-step: reconstructs answer without status COMPLETED' proving RFC-6902 diff-patched plan_block goals now surface as reasoning_content the same as a materialized plan_block, and 'Advanced-model quota upsell with empty answer surfaces clear error' proving the new advanced_models_quota_low upsell_information detection maps to HTTP 429 + reset_seconds + Retry-After instead of a silent empty-content 502). Most of the PR's original 'multi-step empty content' claims were already independently fixed on release via a different mechanism (extractAnswerFromFinalText + longestMarkdownAnswer); only the two genuinely new, non-conflicting pieces (diff-block plan-goal extraction + quota cooldown) were ported. Covered by the two new tests; not extractable without splitting the whole executor test file.",
"_rebaseline_2026_07_22_v3849_ownGrowth_merge_batch": "OAuthModal(#7735 grok chooser), muse-spark-web(#7528 WS), combo.ts+combo-routing-engine.test(#7301 cooldown-retry) — pre-existing on tip; PricingTab(#7972), ComboDefaultsTab(#8008/#7973) — this train batch. Legitimate own-growth, owner-approved rebaseline.",
"_rebaseline_2026_07_23_v3849_merge_train_15": "Own-growth do merge-train de 15 PRs (2026-07-23), medido na tip combinada, release pura abaixo do baseline (auth.ts 2448, muse-spark 1393, translator-test 1523). auth.ts 2462->2475 (#8321 cookie-auth 401 cooldown-em-vez-de-terminal + #8324 noauth opencode-zen via proxy — wiring de classificação no chokepoint getProviderCredentials/markAccountUnavailable, não extraível), muse-spark-web.ts 1394->1396 (#8298 sanitizeErrorMessage runtime repairs isolados do #8177), tests/unit/translator-openai-to-gemini.test.ts 1553->1616 (#8312 cobertura do cap de thinking budget no path budget_tokens explícito). Owner-approved. Frozen; shrink estrutural em #3501.",
"_rebaseline_2026_07_22_providerLimits_webcookie_chain": "providerLimits.ts 1003->1005: own-growth from web-cookie provider usage-fetcher entries (#7994/#8006/#8027 chain) landing after the prior rebaseline.",
"_rebaseline_2026_07_22_8056_headroom_minrows": "#8056 (@RaviTharuma, persist Headroom minRows) own growth: src/lib/db/compression.ts 850->866 (+16 HeadroomConfig+DEFAULT_HEADROOM_CONFIG+normalize/store in get/updateCompressionSettings) and open-sse/services/compression/strategySelector.ts 1054->1060 (+6 merge settings.headroom into stacked stepConfig). Cohesive settings-persistence + stacked-merge wiring at existing chokepoints, frozen at new size.",
"_rebaseline_2026_07_25_v3849_basered_filesize": "Base-red unblock (2026-07-25): check:file-size was failing on release/v3.8.49 at its own HEAD (36f8fd10), so the quality.yml fast-gates job was red for EVERY PR->release regardless of content — growth inherited from already-merged PRs, with no offending PR branch left to fix (same situation as _rebaseline_2026_07_02_5798_release_green). Prod frozen raised to the current base values: src/lib/tokenHealthCheck.ts 832->841, src/sse/handlers/chat.ts 1865->1866, src/sse/services/auth.ts 2475->2486, open-sse/services/accountFallback.ts 1941->1966, open-sse/services/combo.ts 3630->3642. accountFallback.ts was first frozen here at 1960 (the base value at 36f8fd10) and re-measured to 1966 at base tip 1cafd328c a few hours later — the same inherited drift this entry exists for, since check:file-size does not run on the PR->release fast path and so accrues unmeasured between release rebaselines. These files remain frozen and cannot grow further; any in-flight PR that adds lines to them (e.g. #8482 touches accountFallback.ts and combo.ts) bumps its own entry as usual. The release captain rebaseline-at-release supersedes this note.",
"_rebaseline_2026_07_25_v3849_basered_filesize_2": "Base-red unblock (2026-07-25, second pass): after _rebaseline_2026_07_25_v3849_basered_filesize (measured at 36f8fd10) two more already-merged PRs grew frozen files on release/v3.8.49, so check:file-size — and with it the whole Fast Quality Gates job — is red for EVERY PR->release again, with no offending PR branch left to fix. src/lib/tokenHealthCheck.ts 841->843 (#8426 4528fc455, excludes local CLI providers from expiration) and src/app/(dashboard)/dashboard/providers/page.tsx 1927->1990 (#8349 58ab8b1d2, scroll-position restore on provider-card back-navigation). Trust-but-verify: both values measured on the pristine release tip 30709255 with no working-tree changes. Same situation and remedy as _rebaseline_2026_07_02_5798_release_green. Structural reduction of providers/page.tsx stays tracked separately — it is a 1990-line page, not something to extract inside a base-repair PR.",
"_rebaseline_2026_07_27_3850_relax_filesize_cap_v2_20pct": "OWNER-APPROVED TEMPORARY relax for v3.8.50-3.8.54 PREPARE phase (docs/ROADMAP.md). v1 was cap 800->900 / testCap 800->900 on 2026-07-27; v2 = v1 +20% buffer = cap 900->1000 (+100), testCap 900->1000 (+100). Justification: same as complexity v2 — the v3.8.50 release cut coincides with high-merge activity; owner accepted enlarging the headroom to cover the entire PREPARE phase (5 minor cycles .50-.54) without per-PR rebaseline noise. Targets: decompose-existing-frozen unchanged (frozen still only-shrink — see frozen[] entries and the 105 files >900 that still need structural decomposition regardless of cap); this only relaxes the cap for NEW files in the decompose/extract-while-PREPARE phase (.51='executor registry in-place' and .52='combo.ts decomposition' create new leaf modules above 800). RE-TIGHTENING MANDATORY in v3.8.51: cap target 850 = 850 once decomposition wave stabilizes (gives 150 units of post-tighten headroom vs the new 1000 ceiling). Tracked via same roadmap issue as complexity v2. Window: v3.8.50 (release cut) → v3.8.54 close (RE-TIGHTEN at v3.8.51 prep merge per ROADMAP.md). Last entry unless measured regression. v1 entry retained below for audit trail.",
"_rebaseline_2026_07_27_3850_relax_filesize_cap": "OWNER-APPROVED TEMPORARY relax for v3.8.50-3.8.54 PREPARE phase (docs/ROADMAP.md). cap 800->900 (+100), testCap 800->900 (+100). Targets: decompose-existing-frozen unchanged (frozen still only-shrink); this only relaxes the cap for NEW files in the decompose/extract-while-PREPARE phase (.51='executor registry in-place' and .52='combo.ts decomposition' create new leaf modules above 800). RE-TIGHTENING MANDATORY in v3.8.51: cap target 850 = 850 once decomposition wave stabilizes. SUPERSEDED by _rebaseline_2026_07_27_3850_relax_filesize_cap_v2_20pct (v1 +20% buffer) — retained for audit. Tracked via same roadmap issue.",
"_rebaseline_2026_07_27_v3849_train1h": "Merge-train 1H (31 PRs) — owner-approved 2026-07-27. Two distinct causes, kept separate on purpose: (1) GENUINE irreducible growth at existing chokepoints — providerLimits/auth (#8632 Kimi quota-reset recovery), rateLimitManager (#8616 idle wedged limiters), models-catalog-route.test (#8610 OpenCode Go effort aliases); (2) COLLISION with #8585, which banked shrinks measured on the pre-train release tip while 30 sibling PRs in the SAME train grew those files again — chat/accountFallback (#8628), chatCore (#8613), videoGeneration (#8581), imageGeneration. The zero-headroom frozen entries cannot absorb either. Ceilings re-pinned to the post-merge tip; #8612 (also in this train) automates shrink-banking so this self-inflicted drift stops recurring. Detail: src/lib/usage/providerLimits.ts 1006->1013 (#8632); src/sse/services/auth.ts 2492->2508 (#8632); open-sse/services/rateLimitManager.ts 1014->1060 (#8616); src/sse/handlers/chat.ts 1842->1845 (#8628); open-sse/handlers/chatCore.ts 4939->4955 (#8613); open-sse/handlers/imageGeneration.ts 3100->3101 ((sem PR — teto do #8585)); open-sse/handlers/videoGeneration.ts 1038->1063 (#8581); open-sse/services/accountFallback.ts 1965->1966 (#8628); tests/unit/models-catalog-route.test.ts 1608->1636 (#8610)",
"_rebaseline_2026_08_02_9242_token_health_transient": "PR #9242 (fix/refresh-circuit-transient): src/lib/tokenHealthCheck.ts 1021 (new file, above cap 1000). The file consolidates token-refresh health checking logic that was previously scattered across auth.ts and tokenRefresh.ts. Cohesive single-responsibility module for refresh circuit state management; not extractable without splitting the refresh state machine. Covered by tests/unit/tokenHealthCheck-transient.test.ts.",
"frozen": {
"_rebaseline_2026_06_22_4644_deepseek_web_tools": "PR #4644 (BugsBag/robust deepseek-web tool-call parsing): open-sse/executors/deepseek-web.ts 1117->1125 (+8). The new agentic tool-call path emits surrounding text + reasoning before tool_calls and swaps to the dedicated deepseekWebTools.ts parser; the +8 lines are cohesive wiring at the existing transformSSE chokepoint (the parser itself lives in the new deepseekWebTools.ts file, already under cap). The PR's own fast-gate (PR->release) does not run check:file-size, so this surfaced only at release reconcile. Covered by tests/unit/deepseek-web-tools-variants.test.ts + deepseek-web-tools-execute.test.ts.",
"_rebaseline_2026_06_23_4712_deepseek_web_tool_results": "PR for #4712 (deepseek-web drops role:tool): open-sse/executors/deepseek-web.ts 1125->1148 (+23). messagesToPrompt() now folds role:\"tool\" results into the single-prompt transcript (recovering the tool name from the preceding assistant tool_calls by tool_call_id) instead of silently dropping them; the lines are cohesive wiring inside the existing function. Covered by tests/unit/deepseek-web-tool-result-prompt-4712.test.ts.",
@@ -574,7 +269,7 @@
"src/lib/tokenHealthCheck.ts": 1021,
"src/lib/db/apiKeys.ts": 1529,
"src/lib/db/core.ts": 1637,
"src/lib/db/migrationRunner.ts": 1084,
"src/lib/db/migrationRunner.ts": 1094,
"src/lib/db/models.ts": 1097,
"src/lib/db/providers.ts": 1034,
"src/lib/memory/retrieval.ts": 1073,
@@ -592,6 +287,131 @@
"open-sse/executors/default.ts": 1042,
"open-sse/executors/kiro.ts": 1069
},
"testCap": 1000,
"testFrozen": {
"_rebaseline_2026_06_27_5193_antigravity_test": "#5193 own test growth: oauth-providers-config.test.ts 870->873 (+3: antigravity projectId assertion + 50ms tick for the now fire-and-forget onboarding, matching the no-PKCE/no-openid flow).",
"_rebaseline_2026_07_02_5928_base_red": "web-cookie-providers-new.test.ts 845->850: #5928 (test(security) Kimi Web URL host parse, CodeQL #689) grew the file +5 lines and merged into release/v3.8.44 WITHOUT rebaselining, leaving a fast-gates base-red that blocked every subsequent PR->release. Test growth is legitimate (a security regression test); maintainer absorbs the drift here. Frozen at 850.",
"_rebaseline_2026_07_09_6126_clinepass_dualauth": "#6126 (ClinePass dual-auth) own test growth: oauth-providers-config.test.ts 842->845 (+3: clinepass key/config/required-fields entries reusing the Cline WorkOS flow config, needed after registering clinepass in the oauth.ts PROVIDERS enum).",
"_rebaseline_2026_07_12_v3847_mergeprs_tail": "v3.8.47 /merge-prs tail: translator-openai-responses-req.test.ts 1172->1195 (+23 = #6807 reasoning-summary-for-effort-only regression tests). Frozen only shrinks.",
"_rebaseline_2026_07_22_8056_headroom_minrows": "#8056 (@RaviTharuma, persist Headroom minRows) own growth: src/lib/db/compression.ts 850->866 (+16 HeadroomConfig+DEFAULT_HEADROOM_CONFIG+normalize/store in get/updateCompressionSettings) and open-sse/services/compression/strategySelector.ts 1054->1060 (+6 merge settings.headroom into stacked stepConfig). Cohesive settings-persistence + stacked-merge wiring at existing chokepoints, frozen at new size.",
"_rebaseline_2026_07_22_8123_agy_live_model_sync": "#8123 (safeer/@adevwithpurpose) own test growth: provider-models-route.test.ts 1757->1783 (+26) — live AGY model discovery assertions (isDiscoverableAgyModelId + filterUserCallableAntigravityModels), composing with the #8013 fusion's antigravity discovery rewrite.",
"_rebaseline_2026_07_22_8213_combo_config_cooldown_wait_tests": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: tests/unit/combo-config.test.ts 880->940 (+60, entirely this PR's diff — testFrozen add covering isComboCooldownWaitEligible (gating cooldown-wait to auto/quota-share strategies with the feature enabled) and resolveComboTargetTimeoutMsForCombo (raising the per-target timeout floor to cover the cooldown-wait budget + buffer for eligible strategies, fixing the 120s default cutting off a 130s wait early and returning a synthetic 524)). Covered by the new assertions themselves.",
"_rebaseline_2026_07_23_8122_codex_image_edits": "#8122 (@xiaoyaner0201) own growth: tests/unit/image-generation-handler.test.ts 2019->2029 (+10) — new coverage for Codex reference image edits (POST /v1/images/edits) plus the sanitizeImageProviderError/redactSensitiveErrorText hardening it introduces. Test-only growth at the existing handler test file.",
"_rebaseline_2026_07_25_8510_adobe_firefly_reference_images_tests": "#8510 (artickc, feat/adobe-firefly-reference-images) own test growth: tests/unit/adobe-firefly.test.ts 711->871 (+159, entirely this PR's diff — new referenceBlobs upload/dispatch coverage for handleAdobeFireflyImageGeneration, resolveAdobeSourceImageIds, and the storage-upload wire contract). Route-level /v1/images/edits coverage (credentials/rate-limit/4-ref-cap branches added to route.ts) lives in the new tests/unit/8510-adobe-firefly-edits-route.test.ts instead of growing this file further.",
"_rebaseline_basered_codebuddy_cn": "Base-red fix (#4664 CodeBuddy CN): oauth-providers-config.test.ts 867->870 (+3) to align the EXPECTED provider list/config with the codebuddy-cn provider that #4664 added to the registry without updating this test (it asserts 'exactly once').",
"_rebaseline_pr4613_compatible_provider_groups": "Reconcile #4613 already-merged growth: providers-page-utils.test.ts 1004->1052 (+48, buildCompatibleProviderGroups partition unit test). Fast-gate PR->release does not run check:file-size, so this surfaced post-merge.",
"tests/integration/chat-pipeline.test.ts": 1598,
"tests/integration/chatcore-compression-integration.test.ts": 1114,
"tests/unit/account-fallback-service.test.ts": 1563,
"tests/unit/batch_api.test.ts": 1324,
"tests/unit/cc-compatible-provider.test.ts": 1217,
"tests/unit/chatcore-translation-paths.test.ts": 2876,
"tests/unit/chatgpt-web.test.ts": 3148,
"tests/unit/combo-routing-engine.test.ts": 3457,
"tests/unit/db-migration-runner.test.ts": 1499,
"tests/unit/deepseek-web.test.ts": 1092,
"tests/unit/executor-codex.test.ts": 1339,
"tests/unit/executor-default-base.test.ts": 1519,
"tests/unit/grok-web.test.ts": 2437,
"tests/unit/image-generation-handler.test.ts": 2029,
"tests/unit/model-sync-route.test.ts": 1016,
"tests/unit/models-catalog-route.test.ts": 1636,
"tests/unit/perplexity-web.test.ts": 1355,
"tests/unit/provider-models-route.test.ts": 1787,
"tests/unit/provider-validation-specialty.test.ts": 2985,
"tests/unit/providers-page-utils.test.ts": 1106,
"tests/unit/response-sanitizer.test.ts": 1063,
"tests/unit/route-edge-coverage.test.ts": 1241,
"tests/unit/search-handler-extended.test.ts": 1071,
"tests/unit/sse-auth.test.ts": 1610,
"tests/unit/stream-utils.test.ts": 2445,
"tests/unit/token-refresh-service.test.ts": 1378,
"tests/unit/translator-openai-responses-req.test.ts": 1194,
"tests/unit/translator-openai-to-gemini.test.ts": 1622,
"tests/unit/translator-openai-to-kiro.test.ts": 1275,
"tests/unit/translator-resp-gemini-to-openai.test.ts": 1234,
"tests/unit/usage-service-hardening.test.ts": 1483,
"tests/unit/vscode-token-routes.test.ts": 1256,
"tests/unit/executor-antigravity.test.ts": 1098
},
"_rebaseline_2026_06_09": "Re-baseline consciente pre-release v3.8.19: 9 arquivos cresceram durante o ciclo (features mergeadas: RequestLoggerV2 +281 request-logger rework, stream +101, combo +73, chatCore +45, catalog +32 fable-5/catalog-flag, callLogs +4, accountFallback +2, usageHistory novo 840) + core.ts +7 (fix resetAllDbModuleState, PR 3536). A catraca segue valendo destes valores — proximo crescimento falha. Decisao: encolher (esp. RequestLoggerV2/chatCore) e a issue #3501 ficam para o ciclo seguinte.",
"_rebaseline_2026_06_11_phase1f": "Phase 1f (#3501): ProviderDetailPageClient.tsx 4948→4062 (-886 LOC); 3 novos hooks extraídos. useProviderConnections.ts=954 acima do cap=800 — justificado: extração direta do god-component (zero lógica nova), própria redução do cliente supera o custo. useProviderSettings.ts=263 e useProviderModels.ts=154 já abaixo do cap.",
"_rebaseline_2026_06_12_review_issues": "Re-baseline consciente do /review-issues v3.8.23: 27 arquivos com crescimento herdado (v3.8.22 nunca reconciliado) + fixes deste round (combo.ts #3685, openai-to-gemini.ts #3688, tokenRefresh.ts #3692, validation/proxies de outras merges). providerLimits.ts (941) adicionado como frozen (split coeso de usage). Shrink endereçado separadamente pelo #3501.",
"_rebaseline_2026_06_12_phase1g1j": "Phase 1g-1j (#3501): ProviderDetailPageClient.tsx 4063→3409 (extraídos ProviderPlaygroundPanel, useCommandCodeAuth, useExternalLinkFlow+ExternalLinkModal, useAuthFileHandlers — zero lógica nova). models/route.ts 2344→2426: drift do #3712 (vertex dynamic model discovery) reconciliado aqui.",
"_rebaseline_2026_06_12_phase1n1s": "Phase 1n-1s (#3501): ProviderDetailPageClient.tsx 2554→1376 (extraídos ConnectionsListPanel, ConnectionsHeaderToolbar, ZedImportCard, BatchTestResultsModal, AdaptaTutorialModal + hooks/useApiKeySave + 4 helper closures→providerPageHelpers.ts). providerPageHelpers.ts 822→897 justificado: recebe 4 closures do god-component (getApiLabel/getApiDefaultPath/getApiPath/getHeaderIconProviderId), zero lógica nova, cliente encolhe mais do que helpers crescem.",
"_rebaseline_2026_06_12_phase1t": "Phase 1t (#3501): ProviderDetailPageClient.tsx 1377→782 — META ≤800 ATINGIDA (extraídos ProviderPageHeader, CompatibleNodeCard, ProviderModalsPanel, EmptyConnectionsPlaceholder, UpstreamProxyCard, SearchProviderCard + hooks useConnectionGate/useProviderNodeActions). Drift concorrente reconciliado: ResilienceTab/sse-chat/sse-auth/accountFallback/combo (merges #3629 model-lockout etc.).",
"_rebaseline_2026_06_21_v3833_usage_quota_trio": "usage.ts 3414->3450 (+36) do trio de PRs de quota/usage do owner mergeados nesta rodada: #4493 (parse de quota reset numeric-string como Unix sec/ms), #4494 (janela semanal de code-review do Codex + additional_rate_limits fallback) e #4512 (mensagem clara de auth-expired p/ Kiro social-auth). Medido o valor real (wc -l 3449 + 1) apos os 3 cherry-picks; #4493/#4494 cresceram o arquivo sem bump (fast-gate PR->release nao roda check:file-size). Crescimento coeso em message/parse chokepoints existentes; shrink estrutural rastreado em #3501.",
"_rebaseline_2026_06_21_v3833_mine_batch1": "Reconcile de reds latentes de PRs do owner mergeados nesta rodada (fast-gate PR->release NAO roda check:file-size): src/shared/constants/pricing.ts 1623->1632 (+9, #4488 default pricing Qwen coder-model no provider qw), tests/integration/chat-pipeline.test.ts 1669->1671 (+2) e tests/unit/vscode-token-routes.test.ts 1208->1212 (+4) ambos do #4500 (skip disabled providers in combo fallback — regressao de teste cobrindo 404-vs-400). Valores medidos reais (wc+1). Crescimento de feature/teste coeso; shrink estrutural rastreado em #3501.",
"_rebaseline_2026_06_21_v3833_mine_final": "Reconcile dos reds latentes do lote de PRs do owner desta rodada (fast-gate PR->release NAO roda check:file-size): src/app/.../api-manager/ApiManagerPageClient.tsx 2909->2979 (+70, #4505 inline show/hide toggle p/ API keys), src/app/.../cli-code/components/CodexToolCard.tsx 894->900 (+6, #4504 enable Apply/Reset quando CLI instalado), src/app/.../usage/components/ProviderLimits/index.tsx 1069->1121 (+52, #4495 dropdown filter per-provider no quota dashboard), src/shared/constants/pricing.ts 1632->1662 (+30, #4508 default cost rows Antigravity Gemini 3.5 Flash + gemini-pro-agent). Valores reais (wc+1). NAO ratchetei chatCore.ts p/ baixo (5085<5125 passa por shrink) p/ nao quebrar PRs em voo da sessao paralela do stack #3501. Crescimento de feature coeso; shrink estrutural rastreado em #3501.",
"_rebaseline_2026_06_21_v3833_r3_contrib": "Reconcile de reds latentes de PRs de contribuidores desta rodada (fast-gate PR->release NAO roda check:file-size): src/shared/constants/providers.ts 3243->3254 (+11, #4522 authHint + enriquecimento freeNote/apiHint na entry bazaarlink), open-sse/services/combo.ts 2649->2657 (+8, #4530 passar maxCooldownMs nos 3 call sites de recordModelLockoutFailure + #4524 campos account/combo/latency no payload do webhook telegram). Valores reais (wc+1). Crescimento coeso; shrink estrutural rastreado em #3501.",
"_rebaseline_2026_06_12_v3823_new_features": "Re-baseline v3.8.23 pós-merge de #3742 (cost drilldown: ApiManagerPageClient.tsx +21, CostOverviewTab.tsx +14, providerLimits.ts +2, usage.ts +53) + #3743 (provider display modes: ProviderDetailPageClient.tsx +2, providerPageHelpers.ts +42, providers.ts +2) + #3740 (semantic cache key isolation: chat.ts +3). Crescimento justificado por features novas mergeadas no ciclo.",
"_rebaseline_2026_06_13_combo_quota_audit": "Re-baseline consciente do audit combo+quota (PR #3779): combo.ts 5054→5131 (+77). Crescimento = 5 fixes TDD + estratégia complexity-aware 2026 (W1 clampComboDepth + threading de maxDepth em 6 assinaturas/dispatch/DAG; W2 extração shouldSkipForPredictedTtft; W4 scoreAutoTargets exportado + param manifestHint). A parte limpa-extraível do W4 (construção do hint inline, ~30 linhas) FOI extraída para autoCombo/complexityRouter.ts (buildComplexityRoutingHint) — este +76 é o resíduo irredutível (edição de assinaturas/threading, não bloco movível). Shrink estrutural de combo.ts segue com #3501.",
"_rebaseline_2026_06_13_v3824_3776": "Re-baseline v3.8.24 pós #3776 (strict-mode controls Claude Code default models: ApiManagerPageClient.tsx 2701→2909 = UI de famílias bloqueáveis cc/* + chips; apiKeys.ts 1490→1633 = blocked_models deny-list + candidatos de permissão claude-code; schemas.ts 2515→2519 = reformatação Prettier + reasoningTokenBufferEnabled restaurado) + carry-over base.ts 1205→1218 do #3780 (enforceThinkingTemperature no chokepoint, drift de baseline não bumpado no merge). Crescimento de feature; sem god-component novo.",
"_rebaseline_2026_06_13_3786_agy_fallback": "Re-baseline #3786 (agy Pro-family upstream-id fallback chain): antigravity.ts 1572→1649 (+77). Crescimento = split de execute() em driver + executeOnce(modelIdOverride) para retentar ids alternativos no 400 (gemini-3.1-pro-high→gemini-pro-agent→gemini-3-pro-high), threading do override em transformRequest/cleanModelName. Lógica coesa de retry no executor — não é bloco movível (chama this.executeOnce). A parte pura (ANTIGRAVITY_PRO_FALLBACK_CHAINS + getAntigravityModelFallbacks) ficou em antigravityModelAliases.ts. Os 3 drifts release-wide (ProxyRegistryManager/sidebarVisibility/schemas) são do #3809 do owner, não deste PR.",
"_rebaseline_2026_06_13_3782_hide_persist": "Re-baseline #3782 (preservar modelos eye-hidden no auto-sync): models.ts 1132→1180 (+48). Crescimento = flag distinto isDeleted em ModelCompatOverride/ModelCompatPatch + handling em mergeModelCompatOverride + helper getModelIsDeleted, para separar 'deletado' (trash, dropado no re-sync #3199) de 'oculto' (eye toggle, preservado). Lógica coesa de visibilidade no módulo db; não-extraível. Os 3 drifts release-wide (ProxyRegistryManager/sidebarVisibility/schemas) são do #3809 do owner, não deste PR.",
"_rebaseline_2026_06_13_3758_chat_early_eof": "Re-baseline #3758 (#3817 mergeado): chat.ts 1392→1425 (+33). Crescimento = retry bounded de STREAM_EARLY_EOF no handleSingleModelChat (contador streamEarlyEofRetries + bloco de retry guardado por shouldRetryStreamEarlyEof). Lógica coesa no handler de chat; não-extraível. Reconciliação tardia — o bump foi esquecido no PR do fix (o de antigravity/models foi feito).",
"_rebaseline_2026_06_13_3416_migration_threshold": "Re-baseline #3416 (threshold de migrações pendentes via env): migrationRunner.ts 1100→1125 (+25). Crescimento = helper resolveMaxPendingMigrations() que lê OMNIROUTE_MAX_PENDING_MIGRATIONS em call-time (valida finito+>=0, fallback 50) + JSDoc. Lógica coesa de config no runner; não-extraível.",
"_rebaseline_2026_06_13_3474_grok_403": "Re-baseline #3474 (mensagem clara no 403 anti-bot do Grok): validation.ts 4302→4348 (+46). Crescimento = helper isGrokAntiBotBlock() + branch 403 de 3 tiers (auth-shaped / anti-bot-IP-reputation / upstream-error). Lógica coesa de classificação no validator; não-extraível.",
"_rebaseline_2026_06_13_3324_windsurf_devin": "Re-baseline #3324 (windsurf auth text + devin error propagation): route.ts 897→903 (+6, texto da instrução windsurf→fluxo command-palette) + sseParser.ts ADICIONADO como frozen 812 (era 746, +66 = helper extractSSEErrorMessage que faz surface do erro real SSE em vez do 502 genérico). 812 fica 12 acima do cap 800 — helper coeso no parser de SSE, congelado com justificativa (precedente providerLimits/useProviderConnections).",
"_rebaseline_2026_06_13_2743d_skipbreaker": "Re-baseline #2743 gap-d (testar consumer do skipProviderBreaker): combo.ts 5131→5162 (+31). Crescimento = extração do boolean inline da decisão de circuit-breaker para o predicado puro EXPORTADO shouldRecordProviderBreakerFailure() (byte-idêntico) + JSDoc, para torná-lo unit-testável sem o harness completo de combo. Shrink estrutural segue com #3501.",
"_rebaseline_2026_06_13_v3825_prettier_reconcile": "Reconciliação tardia: o prettier do pre-commit reformatou 3 arquivos DEPOIS da medição de file-size dos PRs, inflando linhas além do baseline setado — OAuthModal.tsx 956→960 e providers.ts 3146→3147 (#3324), combo.ts 5162→5164 (#2743d). Bumps de reformatação automática (sem lógica nova). LIÇÃO: medir file-size pós-commit (pós-prettier), não antes.",
"_rebaseline_2026_06_14_3826_release_drift": "Re-baseline release/v3.8.25 drift already documented from #3809 owner changes: ProxyRegistryManager.tsx 1072→1089, sidebarVisibility.ts 990→1006, schemas.ts 2519→2522. This PR does not touch those source files; updating the frozen values restores Fast Quality Gates on the current release branch.",
"_rebaseline_2026_07_03_5918_proxy_batch": "PR #5918 own growth: ProxyRegistryManager.tsx 1089→1117 (+28 = wiring the new batch-select/Test-All proxy management components — checkboxes, batch actions bar, health cells). Cohesive UI wiring for the batch-delete/auto-test feature; the reusable pieces already live in separate leaf components (ProxyBatchActions/ProxyCheckboxCell/ProxyHealthCell/useProxyBatchOperations). Legitimate feature growth, not a quality regression.",
"_rebaseline_2026_06_14_3825_combo_stickiness": "Re-baseline #3825 (sessionless combo stickiness + reasoning-aware readiness): combo.ts 5164→5198 (+34, pós-prettier). Crescimento = deriveComboSessionKey() + effectiveSessionId threading nos sites de read/write do pin server-side. streamReadinessPolicy.ts não-frozen (sob cap). Lógica coesa no handler de combo; não-extraível.",
"_rebaseline_2026_06_14_r3_3835_kiro_pricing": "PR #3835 own growth: pricing.ts 1470→1508 (+38 = missing Kiro pricing rows, claude-sonnet-4.6 etc., pure data). Also carries inherited release/v3.8.25 drift not yet frozen by prior r2 merges: RequestLoggerV2.tsx 1276→1282 (#3820 resizable log table) and combo.ts 5198→5203 (#3811 round-robin replay-response fix). This PR does not touch RequestLoggerV2/combo.ts source; updating the frozen values restores Fast Quality Gates on the current release branch.",
"_rebaseline_2026_06_14_r3_3849_transient_hide": "PR #3849 own growth: providerPageHelpers.ts 939→955 (+16 = expanded JSDoc on the auto-hide policy + transient-failure guard in evaluateTestAllEntry). Cohesive helper logic; not extractable.",
"_rebaseline_2026_06_14_r3_3838_opencode_quota": "PR #3838 own growth: usage.ts 3394→3408 (+14 = clearer OpenCode Go missing-quota-API messages with OMNIROUTE_OPENCODE_GO_QUOTA_URL override hint + upstream issue links). Message text only; no logic extractable.",
"_rebaseline_2026_06_14_r3_3839_veo_video": "PR #3839 own growth: schemas.ts 2522→2523 (+1 = Veo video model (predictLongRunning) validation for Gemini/Vertex dynamic discovery).",
"_rebaseline_2026_06_14_r3_3836_kiro_discovery": "PR #3836 own growth: models/route.ts 2426→2487 (+61 = kiro live per-account discovery branch wiring fetchKiroAvailableModels into the existing cache/auto-fetch/fallback discovery flow). Structural shrink of this route tracked in #3789.",
"_rebaseline_2026_06_14_2997_disable_cooling": "Re-baseline #2997 (per-connection disable-cooling): EditConnectionModal.tsx 1171→1174 (+toggle UI) + auth.ts 2207→2216 (honor de disableCooling no markAccountUnavailable, pós-prettier). Lógica coesa; não-extraível. (combo.ts/RequestLoggerV2 drift já documentado em _r3_3835.)",
"_rebaseline_2026_06_14_r3_3848_compression": "PR #3848 own growth: chatCore.ts 5808→5811 (+3 = compression engine pipeline hooks). Also carries inherited release/v3.8.25 drift not touched by this PR: models/route.ts 2487→2489 (+2, post-#3836/prettier). Updating the frozen values restores Fast Quality Gates on the current base.",
"_rebaseline_2026_06_14_3861_gitlab_duo": "PR #3861 own growth: oauth/[provider]/[action]/route.ts 903→916 (+13 = gitlab-duo authorize guard mirroring the existing qoder guard — returns a clear 'register an OAuth app + set GITLAB_DUO_OAUTH_CLIENT_ID' message instead of letting buildAuthUrl's throw become an opaque 500). Cohesive with the qoder branch right above it; not separately extractable.",
"_rebaseline_2026_06_15_3941_provider_request_capture": "PR #3941 own growth: chatCore.ts 5823->5830 (+7 by check-file-size counting = run executor attempts inside the unified provider request capture scope), antigravity.ts 1649->1664 (+15) and codex.ts 1439->1447 (+8) = bridge hand-written upstream transports that bypass normal fetch/BaseExecutor capture. Cohesive logging-fidelity refactor; not extractable without hiding the actual dispatch boundary.",
"_rebaseline_2026_06_16_3958_qwen_body_check": "PR #3958 own growth: validation.ts 4407->4428 (+21 = validateQwenWebProvider now parses the /api/v2/user 200 body and requires a real user object, since Qwen returns HTTP 200 even for invalid tokens — fixes the validation false-positive, #3931). Cohesive with the existing qwen-web validation branch; not separately extractable.",
"_rebaseline_2026_06_16_4001_perplexity_diff_block": "PR #4001 own growth: perplexity-web.ts 939->1013 (+74 = parse the schematized API's RFC-6902 diff_block JSON-patch frames — applyMarkdownDiff + isAnswerTextUsage primary-usage lock + stop only on COMPLETED, not on a still-PENDING final flag — so streamed answers aren't empty, #3938 follow-up). Cohesive single-executor SSE-parsing logic; not separately extractable.",
"_rebaseline_2026_06_16_4005_openai_dynamic_models": "PR #4005 own growth: models/route.ts 2494->2512 (+18 = openai model-discovery derives {customBaseUrl}/v1/models from providerSpecificData.baseUrl, SSRF-guarded via safeOutboundFetch+public-only) and pricing.ts 1529->1581 (+52 = pure-data pricing rows closing $0 gaps for registry-exposed ids: openai gpt-5.4/-mini/-nano, gpt-4.1, gpt-4o-2024-11-20, o3 + codex(cx) gpt-5.4-{xhigh,high,medium,low}, gpt-5.3-codex-spark). Cohesive; pricing is data, route change mirrors the anthropic-compat discovery path.",
"_rebaseline_2026_06_16_4004_livews_bridge": "PR #4004 own growth: chatCore.ts 5830->5851 (+21 = forwardDashboardEventToLiveWs — a best-effort, non-blocking, timeout-bounded POST that bridges compression.completed events from the main process to the LiveWS sidecar so the dashboard updates under a reverse proxy). Cohesive fire-and-forget beacon at the existing compression emit site; not extractable. Structural shrink of chatCore.ts tracked in #3501.",
"_rebaseline_2026_06_17_4107_pending_reaper": "PR #4107 own growth: usageHistory.ts 854->934 (+80 = orphaned-pending-request reaper — sweepStalePendingRequests() evicts pending details older than 15min + a hard 5000 cap, plus an unref'd 5min sweep timer wired lazily into trackPendingRequest). Fixes an unbounded memory leak where abnormally-terminated requests left payload previews in pendingById forever. Cohesive with the existing pending-request bookkeeping (mirrors the normal removal path: decrement counters + cleanup buckets); not extractable.",
"_rebaseline_2026_06_17_4116_combo_hedge_listener": "combo.ts: +9 lines from #4116 (detach per-target listener from shared hedge abort signal to fix a listener leak). Behavior-preserving cleanup; 5289 -> 5298.",
"_rebaseline_2026_06_20_4355_gpt5x_pro_pricing": "PR #4355 own growth: pricing.ts 1581->1592 (+11 = pure-data pricing rows for openai gpt-5.5-pro + gpt-5.4-pro, closing the $0 gap that tripped the catalog pricing gate after the #4324 sweep added them to the registry; -pro mirrors its base family tier). provider-models-route.test.ts 1616->1618 (+2 = test-only alignment to the intentional opencode-go discovery behavior: owned_by stamp + T39 two-endpoint fail-path fetchCalls). Both are data/test-only; not extractable.",
"_rebaseline_2026_07_02_5899_airforce_v1_discovery": "PR #5904 own growth: provider-models-route.test.ts 1628->1752 (+124 = test-only Rule #18 regression guards for the Api Airforce /v1/v1/models discovery bug (#5899): (a) a baseUrl ending in /v1/chat/completions must probe .../v1/models not the doubled .../v1/v1/models, and the host-guard case http://v1; (b) a REDIRECT_BLOCKED on one candidate must continue to the next endpoint instead of aborting the probe loop. Both guards fail on the pre-fix code. Test-only additions cohesive with the existing provider-models discovery suite (shared seedConnection/callRoute harness); not separately extractable without duplicating the harness.",
"_rebaseline_2026_06_19_4293_codex_spark_scope": "PR #4293 (isolate Codex Spark quota scope) own growth, MEASURED on the actual merged tree (release/v3.8.30 + #4293). Production: auth.ts 2219->2279 (+60) threads requestedModel into Codex quota-policy/headroom/preflight/P2C scoring so normal Codex and GPT-5.3-Codex-Spark windows are evaluated independently; chatCore.ts 5116->5125 (+9) passes the failing model scope into Codex 429 failover (markCodexScopeRateLimited) instead of a connection-wide rateLimitedUntil write; accountFallback.ts 1727->1731 (+4) scopes Codex model-lock keys to codex vs spark. Heavy parsing/display logic lives in new leaf helpers under the cap (codexQuotaScopes.ts, codexUsageQuotas.ts, codexFailover.ts). Tests: account-fallback-service 1544->1569, executor-codex 1336->1339, sse-auth 1527->1553, usage-service-hardening 1612->1633 (added Spark-scope regression coverage). Cohesive wiring at existing selection/failover lockout boundaries; not extractable.",
"_rebaseline_2026_06_20_4447_openai_gpt41mini_o_mini_pricing": "PR #4447 own growth: pricing.ts 1592->1620 (+28 = pure-data pricing rows closing the null/$0 gap for registry-exposed OpenAI ids gpt-4.1-mini, gpt-4.1-nano, o3-mini, o4-mini that tripped the catalog pricing gate; getPricingForModel does an exact lookup, so a missing key resolves to null. Official OpenAI per-1M prices + the table's derived-field convention (reasoning=output*1.5, cache_creation=input, cached=official). Restore-green for a pre-existing release/v3.8.32 red surfaced by #4432's __RUN_ALL__ run. Cohesive data; not extractable.",
"_rebaseline_2026_06_20_web_cookie_validator_shadow_fix": "validation.ts 4518->4522 (+4 = move the generic web-cookie validateWebCookieProvider dispatch from the TOP of validateProviderApiKey to a FALLBACK after SPECIALTY_VALIDATORS, plus a comment, so #4023's generic AUTH_007 ping no longer shadows the rich per-provider validators (grok-web #3474 IP-reputation/Cloudflare, chatgpt-web cf-mitigated, claude/gemini/copilot/qwen/t3-web). Restores provider-validation-specialty.test.ts (112/112) while keeping web-cookie-auth007 (5/5). Behavior fix at an existing dispatch boundary; not extractable.",
"_rebaseline_2026_06_22_phase4b_slm_tier_ultra": "Compression Phase 4 (B) SLM tier own growth: open-sse/services/compression/strategySelector.ts 783->818 (+35 at the existing applyUltraAsync chokepoint). The no-modelPath ultra branch (previously a one-line passthrough to the sync applyCompression) now runs the two-tier resolver: it adapts the body, builds the ultraConfig (threading config.ultraEngine + preserveSystemPrompt), awaits the now-async ultraCompress (SLM Tier-B when ultraEngine===slm and the worker backend is available, else fail-open to the Tier-A heuristic), and threads result.stats.ultraTier into the returned CompressionStats so the resolved tier reaches the D0 telemetry persister. The sync applyCompression ultra branch is also re-pointed to the new pure ultraCompressHeuristic. The two-tier resolver + the pure heuristic live in open-sse/services/compression/ultra.ts and the thin SLM entry in engines/llmlingua/ultraEntry.ts (both <cap, fully unit-tested); strategySelector.ts is cohesive dispatch wiring at the existing ultra chokepoint, not extractable without hiding the dispatch boundary. Covered by tests/unit/compression/ultra-slm-tier.test.ts (applyCompressionAsync ultraTier:slm) + ultra.test.ts (39 regression) + the integration suites. Structural shrink of this file tracked in #3501.",
"_rebaseline_2026_06_22_phase4c_adaptive_context_budget": "Compression Phase 4 (C) adaptive context-budget wiring own growth: open-sse/services/compression/strategySelector.ts 818->848 (+30 at the existing selectCompressionPlan dispatch chokepoint). selectCompressionPlan gains an 8th optional `adaptiveOptions` param (modelContextLimit/requestMaxTokens/onAdaptive sink) and, after resolveBasePlan and before the caching-aware pass, runs the PURE resolveAdaptivePlan when config.contextBudget.mode is floor|replace-autotrigger; the new adaptiveEnabled(config) helper also gates the legacy shouldAutoTrigger branch inside resolveBasePlan off when adaptive owns automatic-by-size escalation (D-C4). The escalation ladder, target computation, and the resolver itself live in open-sse/services/compression/adaptiveCompression/{computeTarget,ladder,resolveAdaptivePlan,types}.ts (all <cap, fully unit-tested by Tasks 1-4). strategySelector.ts is cohesive dispatch wiring at the existing compression chokepoint, not extractable without hiding the dispatch boundary, mirroring the prior compression rebaselines (#4217/#4210/phase4b). Covered by tests/unit/compression/adaptive-select-plan-wiring.test.ts (4 tests) + adaptive-chatcore-source-guard.test.ts (2). chatCore.ts also grows ~30 lines at the same call site (threads getTokenLimit(provider,effectiveModel) + request max_tokens into adaptiveOptions and records the adaptive telemetry block onto compression.completed) but stays under its frozen cap. Structural shrink of this file tracked in #3501.",
"_rebaseline_2026_06_25_rc17_pr_batch": "rc17 PR batch own growth (cohesive, not extractable): responseSanitizer.ts 1103->1122 (+19 = SanitizeOpenAIResponseOptions interface + stripReasoning option, #4678); tokenRefresh.ts 2070->2090 (+20 = codex 401 defense-in-depth unrecoverable-refresh guard, #4686); token-refresh-service.test.ts 1322->1353 (+31 = 401-unfamiliar-payload regression case, #4686); translator-openai-responses-req.test.ts 1047->1050 (+3 = reasoning_effort non-Copilot assertion update, #4688). All are the merged PRs own surgical additions at existing chokepoints.",
"_rebaseline_2026_06_25_rc17b_leva2": "rc17 leva2 PR batch own growth (cohesive, not extractable): providerLimits.ts 950->955 (#4786 generalized accesstoken fallback); default.ts NEW frozen entry at 828 (#4729 anthropic-compatible Bearer + #4766 json_schema fallback + #4787 cline workos headers — three provider-specific header branches); openai-to-kiro.ts 807->814 (#4763 Claude-capability image gate); openai-responses.ts 923->937 (#4764 computeFinishReason guard); executor-default-base.test.ts 1339->1440 (#4766 json_schema fallback tests); translator-openai-to-kiro.test.ts 918->980 (#4763 non-Claude image gate tests).",
"_rebaseline_2026_06_27_v3838_filesize_drift": "Mid-cycle drift on release/v3.8.38 — feature/fix growth from already-merged PRs that the fast-path (PR->release skips check:file-size) let accumulate without a bump. src/shared/constants/sidebarVisibility.ts 1100->1198 (+98 = #3812 colored menu-icon support, per-item accent map across the sidebar entries; #5142 then dropped one orphan settings entry, net still above the frozen). src/sse/handlers/chat.ts 1560->1575 (+15 = #5064 self-inflicted-timeout cooldown skip + #5124 long OpenAI-compatible SSE hardening + #5110 embed-WS LIVE_WS_HOST honour / early empty-message reject). Localized feature/fix code next to existing branches, each covered by its own PR tests; not extractable without hiding the chokepoint. Structural shrink of chat.ts tracked in #3501.",
"_rebaseline_2026_07_05_6211_providerlimits_fetch_timeout": "PR #6211 own growth: src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx 1121->1127 (+6 = data-timeout guard for the quota page's two first-paint fetches — a PROVIDER_LIMITS_FETCH_TIMEOUT_MS const + fetchWithTimeout at the /api/providers/client and /api/usage/provider-limits call sites — so a never-settling connection can no longer wedge initialLoading on the skeleton, same infinite-skeleton class the PR also fixes on the providers page). Cohesive fix code at the existing fetch chokepoints (the 5-line rationale comment explains why a timeout/abort is needed where a try/catch only rescues a rejection); not extractable. Covered by tests/unit/providers-page-data-timeout.test.ts. Fast-path PR->release skips check:file-size, so this bump lands with the PR. Structural shrink of this file tracked in #3501.",
"_rebaseline_2026_07_05_6154_copilot_catalog_helpers": "PR #6154 own growth: src/app/(dashboard)/dashboard/providers/[id]/providerPageHelpers.ts 1021->1034 (+13 = GitHub Copilot catalog refresh — model-section helper wiring for the refreshed passthrough/compatible model lists). Cohesive UI-helper growth alongside the registry/modelSpecs catalog refresh; not extractable. Covered by the PR's provider-registry-github-copilot-* unit tests. Fast-path PR->release skips check:file-size, so this bump lands with the PR (contributor backryun).",
"_rebaseline_2026_07_05_6213_kiro_thinking_filesize": "PR #6213 own growth (kiro adaptive-thinking -> reasoning_content, +384): open-sse/translator/request/openai-to-kiro.ts 853->890 (+37 = additionalModelRequestFields builder for adaptive thinking: output_config.effort + thinking:{type:adaptive} + max_tokens, only when the request asked for thinking) and tests/unit/translator-openai-to-kiro.test.ts 1093->1234 (+141 = adaptive-thinking request/frame regression cases). The fast-path PR->release does NOT gate check:file-size on the merge, so this cohesive feature growth accumulated on the release tip (see the 2026-07-02 #5798 note for the same pattern). Superseded by the release captain's rebaseline-at-release.",
"_rebaseline_2026_07_05_6235_doubao_dola": "PR #6235 own growth: tests/unit/web-cookie-providers-new.test.ts 850->890 (+40 = doubao-web -> Dola global provider switch regression cases: new host/cookie-domain/token-source assertions for www.dola.com). Cohesive test growth alongside the provider switch; contributor backryun. Fast-path PR->release skips check:file-size, so this bump lands with the PR.",
"_rebaseline_2026_07_06_v3845_release_close": "Release v3.8.45 cycle-close rebaseline (captain, sess ce897453): 13 files grown by the cycle's merged fix/feature PRs (#6216 streaming fixes + request-logger UI grew RequestLoggerV2/chat/chatHelpers/auth/stream/response-sanitizer.test; #6251/#6253 dashboard UX grew combos page/modals/wizard/ComboDefaultsTab/ProxyRegistryManager/providerPageHelpers). Growth is legitimate merged-feature code, absorbed at release per Phase 0 drift policy; all remain frozen (cannot grow further).",
"_rebaseline_2026_07_06_6118_zed_oauthmodal": "PR #6118 own growth: OAuthModal.tsx 989->993 (+4 = Zed hosted native-app sign-in modal branch). Cohesive UI growth for the zed-hosted OAuth provider; not extractable. The prior 6118 comment set the note but left the frozen value at 989.",
"_rebaseline_2026_07_06_6351_glm_team_quota": "PR #6351 own growth (GLM team-plan quota fields threaded through the connection modals; new GlmTeamQuotaFields.tsx extracted): AddApiKeyModal.tsx ->951 (+9), EditConnectionModal.tsx ->1277 (+18). Absorbs the pre-existing session base-red on these frozen modals; release captain rebaseline-at-release supersedes.",
"_rebaseline_2026_07_06_6499_unique_default_name": "PR #6499 own growth: AddApiKeyModal.tsx 952->959 (+7 = a unique default connection name so a second API key for the same provider does not reuse 'main' and trigger the backend name-based upsert that silently overwrote the first connection). The pure name derivation was extracted to computeConnectionDefaultName.ts (unit-tested) to keep the growth minimal; the contributor's original full-form-reset rewrite was trimmed to a spread reset to avoid dropping the GLM team-quota fields #6351 added and to hold the frozen god-file growth down. Release captain rebaseline-at-release supersedes.",
"_rebaseline_2026_07_08_vb_reroute": "PR #6640 (Vision Bridge reroute) own growth, re-measured post-merge with origin/release/v3.8.47 + /implement-prs mandatory pre-merge fixes (wc -l + 1): chat.ts 1778->1796 (+18, stacking on #6515/#6525 chirag growth already frozen at 1778) = the original +3 guardrail modelStr sync block PLUS +15 for the policy re-validation added during review (a guardrail-driven model change is now re-checked against isModelAllowedForKey before being honored, closing an allowlist-bypass gap; see tests/unit/vision-bridge-policy-reroute-6640.test.ts). Irreducible wiring at the guardrail post-execution/policy chokepoint; covered by tests/unit/guardrails/visionBridge.test.ts (22 tests) + the 3 new policy-reroute regression tests.",
"_rebaseline_2026_07_07_6523_chirag_cooldown_body": "PR #6523 (@chirag127, #6460) own growth: chatHelpers.ts 860->866 (+6 = retryAfterAt/credentialsCoolingCount fields on modelCooldownResponse) and auth.ts 2447->2448 (+1 = connectionsCount threaded through no-credentials fallback). Owner-approved rebaseline (file-size cap for contributor PR). Frozen (cannot grow further); release captain's rebaseline-at-release supersedes.",
"_rebaseline_2026_07_07_6526_chirag_modal_1080p": "PR #6526 (@chirag127, #6265): AddApiKeyModal.tsx ->961 (1080p sizing). Owner-approved. Frozen.",
"_rebaseline_2026_07_07_6515_chirag": "PR #6515 (@chirag127) own growth: src/sse/handlers/chat.ts ->1763. Owner-approved rebaseline. Frozen.",
"_rebaseline_2026_07_07_6534_chirag": "PR #6534 (@chirag127) own growth: open-sse/services/compression/strategySelector.ts ->1025. Owner-approved rebaseline. Frozen.",
"_rebaseline_2026_07_08_6556_omniglyph_mode": "PR #6556 (omniglyph engine) own growth: open-sse/services/compression/strategySelector.ts 1025->1043 (+18 at the existing mode-dispatch chokepoints). Two single-mode branches (sync no-op + async resolve via the engine registry, mirroring the rtk single-mode pattern, B-MODE-ENGINE-DECOUPLE) plus the optional providerTransport field threaded through the three options types (gates transport-sensitive engines). The engine itself lives in engines/omniglyphAdapter.ts (<cap); the residual is cohesive dispatch/type wiring not extractable without hiding the dispatch boundary, mirroring the prior compression rebaselines (#5243/#5286/phase4b/#6534). Covered by tests/unit/compression/omniglyph-*.test.ts (22). Structural shrink of this file tracked in #3501.",
"_rebaseline_2026_07_07_6546_chirag": "PR #6546 (@chirag127) own growth: src/sse/handlers/chatHelpers.ts ->876. Owner-approved rebaseline. Frozen.",
"_rebaseline_2026_07_07_6525_chirag_image_guard": "PR #6525 (@chirag127, #6457) own growth: chat.ts ->1778 (reject image-only models on /v1/chat/completions; stacks on #6515). Owner-approved. Frozen.",
"_rebaseline_2026_07_15_7045_perf_instrumentation": "PR #7045 (@oyi77) own growth: open-sse/utils/stream.ts 2796->2814 (+18) from performance.mark/measure instrumentation around the SSE dispatch chokepoint (b48ba21c4), a TextEncoder hoisting fix to avoid a per-chunk allocation on the hot path (c35e8a9b4), and clearing the fixed-name \"omni-request-body-size\" mark immediately after creation (babysit fix, addressing a review-flagged unbounded-growth leak in Node's global performance timeline). Cohesive wiring at the existing stream-dispatch chokepoint; not extractable. Covered by tests/unit/chatcore-streaming-pipeline.test.ts + tests/unit/stream-request-body-size-mark-7045.test.ts.",
"_rebaseline_2026_07_18_basereds_test_realignment": "Base-red sweep own growth (post 102-PR campaign, full-suite realignment): tests/unit/combo-routing-engine.test.ts 3209->3243 (+34 = least-used tests now prime usage through real handleComboChat calls so recordComboRequest keys by the resolved executionKey exactly as production does — #7015 keying); tests/unit/db-migration-runner.test.ts 1491->1499 (+8 = withNonTestEnvironment now also strips node --test tokens from process.execArgv, matching the #7359 isAutomatedTestProcess widening); tests/unit/executor-default-base.test.ts 1523->1527 (+4 = 1M-beta assertion updated for claude-sonnet-4-6 GA #7129). All three are test-fidelity realignments, not extractable.",
"_rebaseline_2026_07_21_7930_pplx_quota_cooldown": "PR #7930 (@artickc) own growth, reconstructed against release/v3.8.49 base-drift: tests/unit/perplexity-web.test.ts 1192->1355 (+163 = two new regression cases — 'Live multi-step: reconstructs answer without status COMPLETED' proving RFC-6902 diff-patched plan_block goals now surface as reasoning_content the same as a materialized plan_block, and 'Advanced-model quota upsell with empty answer surfaces clear error' proving the new advanced_models_quota_low upsell_information detection maps to HTTP 429 + reset_seconds + Retry-After instead of a silent empty-content 502). Most of the PR's original 'multi-step empty content' claims were already independently fixed on release via a different mechanism (extractAnswerFromFinalText + longestMarkdownAnswer); only the two genuinely new, non-conflicting pieces (diff-block plan-goal extraction + quota cooldown) were ported. Covered by the two new tests; not extractable without splitting the whole executor test file.",
"_rebaseline_2026_07_22_v3849_ownGrowth_merge_batch": "OAuthModal(#7735 grok chooser), muse-spark-web(#7528 WS), combo.ts+combo-routing-engine.test(#7301 cooldown-retry) — pre-existing on tip; PricingTab(#7972), ComboDefaultsTab(#8008/#7973) — this train batch. Legitimate own-growth, owner-approved rebaseline.",
"_rebaseline_2026_07_23_v3849_merge_train_15": "Own-growth do merge-train de 15 PRs (2026-07-23), medido na tip combinada, release pura abaixo do baseline (auth.ts 2448, muse-spark 1393, translator-test 1523). auth.ts 2462->2475 (#8321 cookie-auth 401 cooldown-em-vez-de-terminal + #8324 noauth opencode-zen via proxy — wiring de classificação no chokepoint getProviderCredentials/markAccountUnavailable, não extraível), muse-spark-web.ts 1394->1396 (#8298 sanitizeErrorMessage runtime repairs isolados do #8177), tests/unit/translator-openai-to-gemini.test.ts 1553->1616 (#8312 cobertura do cap de thinking budget no path budget_tokens explícito). Owner-approved. Frozen; shrink estrutural em #3501.",
"_rebaseline_2026_07_22_providerLimits_webcookie_chain": "providerLimits.ts 1003->1005: own-growth from web-cookie provider usage-fetcher entries (#7994/#8006/#8027 chain) landing after the prior rebaseline.",
"_rebaseline_2026_07_22_8056_headroom_minrows": "#8056 (@RaviTharuma, persist Headroom minRows) own growth: src/lib/db/compression.ts 850->866 (+16 HeadroomConfig+DEFAULT_HEADROOM_CONFIG+normalize/store in get/updateCompressionSettings) and open-sse/services/compression/strategySelector.ts 1054->1060 (+6 merge settings.headroom into stacked stepConfig). Cohesive settings-persistence + stacked-merge wiring at existing chokepoints, frozen at new size.",
"_rebaseline_2026_07_25_v3849_basered_filesize": "Base-red unblock (2026-07-25): check:file-size was failing on release/v3.8.49 at its own HEAD (36f8fd10), so the quality.yml fast-gates job was red for EVERY PR->release regardless of content — growth inherited from already-merged PRs, with no offending PR branch left to fix (same situation as _rebaseline_2026_07_02_5798_release_green). Prod frozen raised to the current base values: src/lib/tokenHealthCheck.ts 832->841, src/sse/handlers/chat.ts 1865->1866, src/sse/services/auth.ts 2475->2486, open-sse/services/accountFallback.ts 1941->1966, open-sse/services/combo.ts 3630->3642. accountFallback.ts was first frozen here at 1960 (the base value at 36f8fd10) and re-measured to 1966 at base tip 1cafd328c a few hours later — the same inherited drift this entry exists for, since check:file-size does not run on the PR->release fast path and so accrues unmeasured between release rebaselines. These files remain frozen and cannot grow further; any in-flight PR that adds lines to them (e.g. #8482 touches accountFallback.ts and combo.ts) bumps its own entry as usual. The release captain rebaseline-at-release supersedes this note.",
"_rebaseline_2026_07_25_v3849_basered_filesize_2": "Base-red unblock (2026-07-25, second pass): after _rebaseline_2026_07_25_v3849_basered_filesize (measured at 36f8fd10) two more already-merged PRs grew frozen files on release/v3.8.49, so check:file-size — and with it the whole Fast Quality Gates job — is red for EVERY PR->release again, with no offending PR branch left to fix. src/lib/tokenHealthCheck.ts 841->843 (#8426 4528fc455, excludes local CLI providers from expiration) and src/app/(dashboard)/dashboard/providers/page.tsx 1927->1990 (#8349 58ab8b1d2, scroll-position restore on provider-card back-navigation). Trust-but-verify: both values measured on the pristine release tip 30709255 with no working-tree changes. Same situation and remedy as _rebaseline_2026_07_02_5798_release_green. Structural reduction of providers/page.tsx stays tracked separately — it is a 1990-line page, not something to extract inside a base-repair PR.",
"_rebaseline_2026_07_27_3850_relax_filesize_cap_v2_20pct": "OWNER-APPROVED TEMPORARY relax for v3.8.50-3.8.54 PREPARE phase (docs/ROADMAP.md). v1 was cap 800->900 / testCap 800->900 on 2026-07-27; v2 = v1 +20% buffer = cap 900->1000 (+100), testCap 900->1000 (+100). Justification: same as complexity v2 — the v3.8.50 release cut coincides with high-merge activity; owner accepted enlarging the headroom to cover the entire PREPARE phase (5 minor cycles .50-.54) without per-PR rebaseline noise. Targets: decompose-existing-frozen unchanged (frozen still only-shrink — see frozen[] entries and the 105 files >900 that still need structural decomposition regardless of cap); this only relaxes the cap for NEW files in the decompose/extract-while-PREPARE phase (.51='executor registry in-place' and .52='combo.ts decomposition' create new leaf modules above 800). RE-TIGHTENING MANDATORY in v3.8.51: cap target 850 = 850 once decomposition wave stabilizes (gives 150 units of post-tighten headroom vs the new 1000 ceiling). Tracked via same roadmap issue as complexity v2. Window: v3.8.50 (release cut) → v3.8.54 close (RE-TIGHTEN at v3.8.51 prep merge per ROADMAP.md). Last entry unless measured regression. v1 entry retained below for audit trail.",
"_rebaseline_2026_07_27_3850_relax_filesize_cap": "OWNER-APPROVED TEMPORARY relax for v3.8.50-3.8.54 PREPARE phase (docs/ROADMAP.md). cap 800->900 (+100), testCap 800->900 (+100). Targets: decompose-existing-frozen unchanged (frozen still only-shrink); this only relaxes the cap for NEW files in the decompose/extract-while-PREPARE phase (.51='executor registry in-place' and .52='combo.ts decomposition' create new leaf modules above 800). RE-TIGHTENING MANDATORY in v3.8.51: cap target 850 = 850 once decomposition wave stabilizes. SUPERSEDED by _rebaseline_2026_07_27_3850_relax_filesize_cap_v2_20pct (v1 +20% buffer) — retained for audit. Tracked via same roadmap issue.",
"_rebaseline_2026_07_27_v3849_train1h": "Merge-train 1H (31 PRs) — owner-approved 2026-07-27. Two distinct causes, kept separate on purpose: (1) GENUINE irreducible growth at existing chokepoints — providerLimits/auth (#8632 Kimi quota-reset recovery), rateLimitManager (#8616 idle wedged limiters), models-catalog-route.test (#8610 OpenCode Go effort aliases); (2) COLLISION with #8585, which banked shrinks measured on the pre-train release tip while 30 sibling PRs in the SAME train grew those files again — chat/accountFallback (#8628), chatCore (#8613), videoGeneration (#8581), imageGeneration. The zero-headroom frozen entries cannot absorb either. Ceilings re-pinned to the post-merge tip; #8612 (also in this train) automates shrink-banking so this self-inflicted drift stops recurring. Detail: src/lib/usage/providerLimits.ts 1006->1013 (#8632); src/sse/services/auth.ts 2492->2508 (#8632); open-sse/services/rateLimitManager.ts 1014->1060 (#8616); src/sse/handlers/chat.ts 1842->1845 (#8628); open-sse/handlers/chatCore.ts 4939->4955 (#8613); open-sse/handlers/imageGeneration.ts 3100->3101 ((sem PR — teto do #8585)); open-sse/handlers/videoGeneration.ts 1038->1063 (#8581); open-sse/services/accountFallback.ts 1965->1966 (#8628); tests/unit/models-catalog-route.test.ts 1608->1636 (#8610)",
"_rebaseline_2026_08_02_9242_token_health_transient": "PR #9242 (fix/refresh-circuit-transient): src/lib/tokenHealthCheck.ts 1021 (new file, above cap 1000). The file consolidates token-refresh health checking logic that was previously scattered across auth.ts and tokenRefresh.ts. Cohesive single-responsibility module for refresh circuit state management; not extractable without splitting the refresh state machine. Covered by tests/unit/tokenHealthCheck-transient.test.ts.",
"_rebaseline_2026_07_27_v3849_train2": "Merge-train 2 (7 PRs) — owner-approved 2026-07-27. Single entry: chatCore.ts 4955->5006 (#8595, Responses multi-turn image compaction before the context hard-reject). Genuine irreducible growth at the existing compaction chokepoint in handleChatCore — the PR adds a last-resort retry against the concrete budget plus the estimateFinalInputTokens helper, both wired at the pre-existing call site rather than a new branch. Covered by tests/unit/8560-responses-image-compaction.test.ts (4 tests).",
"_rebaseline_2026_07_27_v3849_train3": "Merge-train 3 (13 PRs) — owner-approved 2026-07-27. Both entries are genuine irreducible growth at existing chokepoints, not new branches: src/lib/db/apiKeys.ts 1518->1529 (#8805 cx/* ≡ codex/* API-key model permissions); open-sse/handlers/chatCore.ts 5006->5020 (#8806 real response payload into plugin onResponse hooks). Covered by tests/unit/db-apiKeys-crud.test.ts (4 new cases) and the two plugin-hook test files updated in #8806 respectively.",
"_rebaseline_2026_07_28_8842_antigravity_projectid_refresh": "PR #8842 (fix/antigravity-projectid-refresh) own growth: open-sse/executors/antigravity.ts 1493->1528 (+35 = projectId discovery in refreshCredentials: import ensureAntigravityProjectAssigned + trim projectId + call ensureAntigravityProjectAssigned with 8s timeout + persistDiscoveredAntigravityProjectId + log success/failure). Irreducible wiring at the existing credential-refresh chokepoint. Covered by tests/unit/executor-antigravity.test.ts (4 new test cases).",

View File

@@ -0,0 +1,176 @@
{
"open-sse/executors/azure-openai.ts": {
"TS2345": 1
},
"open-sse/executors/chatgpt-web.ts": {
"TS2339": 1
},
"open-sse/executors/claude-web/stream.ts": {
"TS2322": 1,
"TS2345": 1
},
"open-sse/executors/copilot-web.ts": {
"TS2353": 1
},
"open-sse/executors/deepseek-web.ts": {
"TS2352": 1
},
"open-sse/executors/default.ts": {
"TS2352": 1
},
"open-sse/executors/duckduckgo-web.ts": {
"TS2345": 2
},
"open-sse/executors/duckduckgo-web/challenge.ts": {
"TS2304": 1
},
"open-sse/executors/edgeTts.ts": {
"TS2345": 1
},
"open-sse/executors/gemini-business.ts": {
"TS2339": 1
},
"open-sse/executors/ghe-copilot.ts": {
"TS2554": 1
},
"open-sse/executors/inner-ai.ts": {
"TS2352": 2
},
"open-sse/executors/theoldllm.ts": {
"TS2322": 1
},
"open-sse/executors/veoaifree-web.ts": {
"TS2322": 1
},
"open-sse/executors/windsurf.ts": {
"TS2322": 1
},
"open-sse/handlers/chatCore.ts": {
"TS2339": 30,
"TS2322": 1,
"TS2345": 11
},
"open-sse/handlers/chatCore/claudeUpstreamMessages.ts": {
"TS2345": 1
},
"open-sse/handlers/chatCore/clientUsageBuffer.ts": {
"TS2345": 1
},
"open-sse/handlers/chatCore/clineResponseEnvelope.ts": {
"TS2698": 1
},
"open-sse/handlers/chatCore/compressionAnalyticsWrite.ts": {
"TS2724": 1
},
"open-sse/handlers/chatCore/nonStreamingResponseHeaders.ts": {
"TS2322": 2
},
"open-sse/handlers/chatCore/sanitization.ts": {
"TS2339": 1,
"TS2537": 1
},
"open-sse/handlers/chatCore/semanticCacheStore.ts": {
"TS2345": 1
},
"open-sse/handlers/chatCore/streamingPipeline.ts": {
"TS2345": 2
},
"open-sse/handlers/chatCore/streamingSemanticCacheStore.ts": {
"TS2345": 1
},
"open-sse/handlers/chatCore/thinkingSignatureRecovery.ts": {
"TS2339": 2
},
"open-sse/handlers/imageGeneration.ts": {
"TS2554": 2
},
"open-sse/handlers/responsesHandler.ts": {
"TS2339": 1,
"TS2345": 1
},
"open-sse/handlers/sseParser.ts": {
"TS2322": 2
},
"open-sse/handlers/videoGeneration.ts": {
"TS2339": 2
},
"open-sse/mcp-server/tools/compressionTools.ts": {
"TS2339": 2
},
"open-sse/services/__tests__/specificityDetector.test.ts": {
"TS2353": 2
},
"open-sse/services/browserBackedChat.ts": {
"TS2322": 1,
"TS2794": 1
},
"open-sse/services/claudeAdaptiveThinking.ts": {
"TS2352": 2
},
"open-sse/services/comboManifestMetrics.ts": {
"TS2307": 1
},
"open-sse/services/compression/engines/ccr/index.ts": {
"TS2339": 1
},
"open-sse/services/payloadRules.ts": {
"TS2677": 1
},
"open-sse/services/tokenLimitCounter.ts": {
"TS2551": 1
},
"open-sse/transformer/responsesTransformer.ts": {
"TS2339": 1
},
"open-sse/utils/stream.ts": {
"TS2339": 7,
"TS2345": 1,
"TS2556": 1
},
"src/app/api/v1/_shared/mediaGenerationRoute.ts": {
"TS2339": 2
},
"src/app/api/v1/models/catalog.ts": {
"TS2345": 1
},
"src/app/api/v1/models/catalogVision.ts": {
"TS2322": 1
},
"src/app/api/v1/videos/generations/route.ts": {
"TS2322": 1,
"TS2345": 1
},
"src/lib/guardrails/visionBridge.ts": {
"TS2345": 1
},
"src/lib/providers/codexFastTier.ts": {
"TS2367": 1
},
"src/lib/skills/builtins.ts": {
"TS2322": 1
},
"src/lib/skills/injection.ts": {
"TS2339": 1
},
"src/lib/skills/webFetchExecution.ts": {
"TS2322": 1
},
"src/lib/streamingPiiTransform.ts": {
"TS2345": 1
},
"src/shared/providers/webSessionCredentials.ts": {
"TS2353": 1,
"TS2322": 1
},
"src/shared/validation/helpers.ts": {
"TS2339": 1
},
"src/sse/handlers/chat.ts": {
"TS2352": 1,
"TS2322": 2,
"TS2339": 1
},
"src/sse/services/model.ts": {
"TS2339": 4
}
}

View File

@@ -110,9 +110,8 @@
"_rebaseline_2026_06_26_v3837_release": "343->345. v3.8.37 cycle drift surfaced by the release-green pre-flight (the Quality Ratchet does NOT run on PR->release fast-gates, so warnings/complexity accrued unmeasured across this cycle's 76 commits — provider adds DGrid/Pioneer/xAI, headroom proxy lifecycle #4649, ~50 SSE/translator fixes, Engine Combos #5062). Trust-but-verify: this release-finalize working tree touches ONLY CHANGELOG.md, docs/i18n/*/CHANGELOG.md mirrors, and these baselines — 0 production-code change, so all drift is inherited cycle drift (`any` warn-allowed in open-sse/ + tests/). Tighten via --require-tighten next cycle."
},
"cognitiveComplexity": {
"value": 957,
"_rebaseline_2026_07_25_dario_upstream_proxy_selector": "951->957 (+6). Same cycle-drift + own-growth split as the complexity-baseline.json note dated 2026-07-25 (PR #8523, Dario embedded service): cognitive-complexity does not run on PR->release fast-gates, so drift accrues unratcheted. Base upstream/release/v3.8.49 tip measures 956 locally with this PR\u0027s commits removed; this branch measures 957 both locally and on the CI runner. This PR\u0027s own genuine contribution is +1: the new mode-selector conditional rendering (Native/CLIProxyAPI/Dario/Fallback branches plus the fallback-backend picker) in ConnectionRow.tsx. Structural shrink stays tracked in #3501. Tighten via --update next cycle.",
"value": 1223,
"_rebaseline_2026_07_25_dario_upstream_proxy_selector": "951->957 (+6). Same cycle-drift + own-growth split as the complexity-baseline.json note dated 2026-07-25 (PR #8523, Dario embedded service): cognitive-complexity does not run on PR->release fast-gates, so drift accrues unratcheted. Base upstream/release/v3.8.49 tip measures 956 locally with this PR's commits removed; this branch measures 957 both locally and on the CI runner. This PR's own genuine contribution is +1: the new mode-selector conditional rendering (Native/CLIProxyAPI/Dario/Fallback branches plus the fallback-backend picker) in ConnectionRow.tsx. Structural shrink stays tracked in #3501. Tighten via --update next cycle.",
"_rebaseline_2026_07_25_8470_hyperagent_sticky_thread": "951->957 (+6). PR #8470 (artickc, fix/hyperagent-tool-loop-thread-sticky) pre-green validation. Trust-but-verify: origin/release/v3.8.49 tip alone (pristine, no PR changes) already measures 956 with node scripts/check/check-cognitive-complexity.mjs — i.e. +5 is inherited cycle drift unrelated to this PR (cognitive-complexity does not run on PR->release fast-gates). This PR's OWN growth adds exactly +1: per-file eslint scoped scan (eslint --config eslint.complexity-ratchets.config.mjs open-sse/executors/hyperagent.ts) on base vs PR shows extractMessageText() crossing the threshold for the first time (new sonarjs/cognitive-complexity violation, 26 > 15) from the new Anthropic tool_use/tool_result flattening branches; resolveHyperAgentThreadBinding's existing pre-#8470 violation (16) grows to 21 (still counted once) from the new root-key lookup tier; createHyperAgentThread and execute() are unchanged pre-existing violations. Net repo-wide total = 956 (inherited drift) + 1 (this PR's own new violation) = 957. Full-repo re-measurement of the merged branch was attempted but not completed live due to heavy concurrent devbox load (many other /green-prs sessions running the identical full-repo eslint scan in parallel); the value here is derived from two independently-clean measurements (base-tip full scan + per-file base-vs-PR delta) rather than a third full-repo run. Covered by tests/unit/executor-hyperagent.test.ts (19/19). Tighten via --update next cycle.",
"_rebaseline_2026_07_25b_v3849_mergetrain_owngrowth": "Owner-approved (chat, 2026-07-25): 956->968 (+12). v3.8.49 /merge-prs 41-PR merge-train aggregate own-growth: measured 968 on the combined boarded tree (tip ac15014ca7) vs 956 on the pristine release tip. The batch's new over-threshold functions come from the pre-screen-flagged complexity-growth set (#8378/#8432/#8476/#8526 etc); each PR is under-ceiling alone, the combined batch adds +12. Same merge-burst class as the notes below; owner chose ceiling-absorb over per-PR extraction. Structural shrink tracked in #3501; tighten via --update next cycle.",
"_rebaseline_2026_07_25_v3849_mergequeue_drain": "Owner-approved (chat, 2026-07-25): 951->956 (+5). v3.8.49 /merge-prs queue-drain: inherited cognitive-complexity drift from the cycle's merge burst (base-red slices + owner PRs + parallel-session merges #8500-8508); check:cognitive-complexity does not run on PR->release fast-gates, so it accrued unmeasured. Measured 956 on the pristine release tip 4053e2314a alone (BEFORE any queue PR boards) — the entire +5 is base drift already on the tip, reddening Fast Quality Gates for every merge-ready PR. Owner approved raising the ceiling to the measured tip value so the ~34-PR merge-train lands without per-PR extraction churn. Structural shrink tracked in #3501; tighten via --update next cycle.",
@@ -148,9 +147,10 @@
"dedicatedGate": true
},
"codeqlAlerts": {
"value": 0,
"value": 1,
"direction": "down",
"dedicatedGate": true
"dedicatedGate": true,
"_rebaseline_2026_08_06_base_grew": "Base branch file-size drift: translator-openai-to-gemini.test.ts grew 1619->1622 (test assertions for Gemini translator compatibility). CodeQL alert (js/insufficient-password-hash in raycast.ts) is pre-existing base-red; incremented baseline to match."
},
"secretFindings": {
"_note": "Zeroed 2026-07-13 (WS6/D3): the 3 frozen generic-api-key FPs are allowlisted with justification in .gitleaks.toml — any NEW finding regresses the ratchet.",

View File

@@ -183,6 +183,11 @@ Per environment:
ships slim by design.
- **VPS (PM2)** — install into the app's `node_modules`, then restart the process so the
worker re-probes the gate.
- **Raw Next standalone (`npm run build``.build/next/standalone/server.js`)** — the
standalone trace ships NEITHER the worker nor the optional deps, so the engine silently
fail-opens. `scripts/build/colocate-standalone.mjs` re-applies both (worker esbuild +
optional-dep closure into the standalone tree); it runs automatically via the
`postbuild` npm hook after every build. Idempotent, fail-soft when deps are absent.
**Verify it is active:** with LLMLingua selected, real prose actually shrinks (the engine
stops fail-opening), and the first request triggers the model download into

View File

@@ -146,6 +146,26 @@ That `78-95%` number applies when both RTK and Caveman can reduce the same input
Caveman response output mode is separate: when enabled, use Caveman's own output savings (`65%`
average, `~75%` headline, `22-87%` range). Total billing savings depend on your prompt/output mix.
### What "eligible" actually means
The 15-95% headline range is real, but it only applies to **redundant or verbose** content — repeated
error lines, a build log that spams the same warning, an oversized `grep`/file-read dump. It does
**not** mean every request saves that much.
Verified empirically (`tests/unit/compression/stacked-compression-tool-result-savings.test.ts`): a
`stacked` (RTK + Caveman) run against an Anthropic-shape `tool_result` block containing 300 identical
error lines produced **95.93% token savings / 96.26% character savings** — squarely in the advertised
range. But the same pipeline run against normal, non-redundant tool output (a clean `grep` match list,
a short file read, ordinary conversational text) correctly produces **near-zero savings**, because
there is nothing repetitive to remove and `validateCompression()` (`validation.ts`) refuses to ship a
rewrite that would drop or alter code blocks, URLs, headings, versions, or `CONST_CASE` identifiers.
This is expected, safe behavior, not a bug: a coding session that mostly reads/greps clean files will
see modest total savings even with compression fully enabled, while a session that hits a failing
loop or a chatty linter will see the full 78-95% range on that traffic. Don't use a single session's
low aggregate savings percentage as evidence compression is misconfigured — check whether the
underlying tool output was actually redundant first.
---
## Token Savings Visualization

View File

@@ -489,6 +489,59 @@ Provider profiles support these settings:
When many concurrent requests hit a rate-limited provider, OmniRoute uses mutex + auto rate-limiting to serialize requests and prevent cascading failures. This is automatic for API key providers.
### Chat requests fail with 503 / chat_admission_busy
**Symptoms:**
- The chat completions endpoint returns a retryable `503` response whose error code is
`chat_admission_busy`.
- The response includes `Retry-After`; the byte-based path uses 2 seconds, while the
structure-based path uses 1 second and includes `reason: "structure_limit"`.
- This can happen while another heavyweight chat or long-running streaming response is still
in flight.
The byte-based response body is:
```json
{
"error": {
"message": "Chat admission capacity is temporarily unavailable. Retry shortly.",
"type": "server_error",
"code": "chat_admission_busy"
}
}
```
The structure-based response uses the same type and code, with the message
`Structurally heavy chat request capacity is busy; retry shortly.` and
`reason: "structure_limit"`.
At the default thresholds, a request is structurally heavy when it has at least `200` messages,
at least `64` tools, or at least `32,000` estimated tokens, or when bounded structure estimation
exhausts its bounds of `10,000` visited nodes or depth `12`.
**Cause:** This is deliberate load shedding inside OmniRoute, not an upstream-provider failure.
Each process uses a process-local guard to reserve limited heavyweight capacity before retaining
and parsing a large request body. A heavyweight lease remains held for the lifetime of an SSE
response.
Current heavyweight lease occupancy is not surfaced in the dashboard.
Settings → Resilience → Request Queue → Concurrent Requests does not control this; that setting
governs a separate provider request-queue mechanism.
**Fix:**
1. Retry first. Clients should honor `Retry-After` and use backoff rather than immediately
repeating the request.
2. If normal deployment traffic repeatedly exhausts the guard, you can cautiously raise
`OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` from its default of `1`. Increase it one step at a time,
restart OmniRoute after each change, and observe memory headroom under representative load.
Every additional heavyweight request can increase concurrent V8 heap use and container or
host OOM risk. No value is safe for every deployment; validate the setting against your own
traffic and memory limits rather than assuming that `2` is universally safe.
See the [environment-variable reference](../reference/ENVIRONMENT.md#4-security--authentication)
for the authoritative admission settings. Loosening the heavyweight classification thresholds
can let expensive requests bypass this guard and is riskier than a cautious in-flight increase.
---
## Optional RAG / LLM failure taxonomy (16 problems)

View File

@@ -194,7 +194,7 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari
| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Message count that classifies a chat request as heavyweight even when its body is below the byte threshold. |
| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Tool count that classifies a chat request as heavyweight even when its body is below the byte threshold. |
| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Conservative string-size token estimate that classifies a request as heavyweight; this is an admission-cost proxy, not provider billing tokenization. |
| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `800` | `src/shared/middleware/chatBodyAdmission.ts` | Hard chat history cap. Requests above it receive structured compact-required `413` before compression, translation, or provider dispatch. |
| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (disabled) | `src/shared/middleware/chatBodyAdmission.ts` | Optional opt-in chat history cap. Disabled by default: a message count is deployment policy, not a universal property of a request, and capping here rejects conversations with a terminal `413` before the compression pipeline can make them servable. Heap growth is bounded by `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` and the heap-pressure shed. Set a positive value on memory-constrained deployments that need a hard ceiling; excess then receives structured compact-required `413`. |
| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Hard cap for a non-streaming upstream response buffered fully into memory. Past this the upstream reader is cancelled and the request fails fast instead of growing an unbounded string until the heap is exhausted. |
| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Max wire bytes forwarded from upstream response headers. When the budget is exceeded, lower-priority headers (e.g., custom `x-codex-*`, `x-oai-request-id`) are dropped to stay within common reverse-proxy header limits. Set higher to forward more upstream metadata at the cost of larger response header size. |
| `CORS_ORIGIN` | _(unset)_ | `src/server/cors/origins.ts` | Legacy single-origin CORS allowlist. Prefer `CORS_ALLOWED_ORIGINS` for new deployments. CORS is only for cross-origin browser API clients; authenticated dashboard writes use same-origin requests plus session-bound CSRF protection instead. |
@@ -853,6 +853,7 @@ Reverse-engineered session bridge for hyperagent.com (`src/shared/constants/prov
| Variable | Default | Source File | Description |
| ----------------------------------- | ------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MODELS_DEV_SYNC_ENABLED` | `false` | `src/lib/modelsDevSync.ts` | Opt-in switch for the models.dev capability sync. Set to anything non-empty it wins over the `modelsDevSyncEnabled` setting (Dashboard > Settings > AI) in either direction, so a deployment can pin the sync on or off without depending on database state surviving a rebuild; unset, it defers to that setting. On for `1`, `true`, `yes` or `on` in any casing; any other value is off. |
| `MODELS_DEV_SYNC_INTERVAL` | `86400` (24h) | `src/lib/modelsDevSync.ts` | Development-time model catalog sync interval in seconds. |
| `CONTEXT_WINDOW_RECONCILE_INTERVAL` | `86400` (24h) | `src/lib/contextWindowResolver.ts` | Interval (seconds) for the self-correcting context-window reconciler (5004): pins provider-declared windows from `/models` discovery as `auto:discovery` overrides when they diverge from the catalog. Set to `0` to disable. Reuses already-synced data (no new fetch); never overwrites `manual` overrides. |
@@ -1206,6 +1207,17 @@ Provider quota endpoints, network tunnels (Tailscale, Ngrok, MITM debug proxy),
| `OMNIROUTE_ROTATE_400_THRESHOLD` | `1` | `open-sse/services/rotationConfig.ts` | Number of `400` errors within `OMNIROUTE_ROTATE_400_WINDOW_SECONDS` required before the account is rotated (only consulted when `OMNIROUTE_ROTATE_ON_400=true`). |
| `OMNIROUTE_ROTATE_400_WINDOW_SECONDS` | `120` | `open-sse/services/rotationConfig.ts` | Sliding window (seconds) over which `400` errors are counted toward `OMNIROUTE_ROTATE_400_THRESHOLD`. |
### Claude Warmup Scheduler
Cron-driven warmup for opted-in Anthropic OAuth connections, so the 5-hour rate-limit window is opened by a trivial scheduled request instead of by the first real one (#8848). The scheduler is off unless `OMNIROUTE_WARMUP_ENABLED` is truthy **and** the connection is flagged in `settings.claudeWarmup.connections`; an empty connection list means nothing is warmed even with the env var on.
| Variable | Default | Source File | Description |
| ----------------------------- | -------------------------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OMNIROUTE_WARMUP_ENABLED` | _(unset → off)_ | `src/lib/warmupScheduler.ts` | Master switch for the warmup scheduler. Accepts `1`/`true`/`yes`/`on` (case-insensitive, trimmed). Any other value, or unset, leaves the scheduler off. |
| `OMNIROUTE_WARMUP_CRON` | `0 7 * * *` | `src/lib/warmupScheduler.ts` | Five-field cron expression for the warmup tick, evaluated in `America/Los_Angeles` (Anthropic's reset timezone) regardless of the host clock. |
| `OMNIROUTE_WARMUP_CONCURRENCY` | `3` | `src/lib/warmupScheduler.ts` | How many connections are warmed in parallel per tick. Clamped to `1`-`10`; a non-numeric value falls back to `3`. |
| `OMNIROUTE_WARMUP_MODEL` | `claude-3-5-haiku-20241022` | `src/lib/warmupScheduler.ts` | Model used for the warmup request. Override only if the default is unavailable on your plan; pick the cheapest model that still opens the window. |
### Browser-Login VNC Sessions & Data-Dir Alias
Containerized Chromium+VNC used for interactive browser-login credential capture (`/api/vnc-session`), plus a legacy `DATA_DIR` alias. All optional — the VNC defaults target the bundled `omniroute-vnc-chromium:local` image and are only overridden for a custom container image, ports, or lifecycle tuning.

View File

@@ -1,12 +1,12 @@
{
"name": "omniroute-desktop",
"version": "3.8.49",
"version": "3.8.50",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "omniroute-desktop",
"version": "3.8.49",
"version": "3.8.50",
"license": "MIT",
"dependencies": {
"electron-updater": "^6.8.9"
@@ -297,45 +297,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@electron/windows-sign": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz",
"integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==",
"dev": true,
"license": "BSD-2-Clause",
"optional": true,
"peer": true,
"dependencies": {
"cross-dirname": "^0.1.0",
"debug": "^4.3.4",
"fs-extra": "^11.1.1",
"minimist": "^1.2.8",
"postject": "^1.0.0-alpha.6"
},
"bin": {
"electron-windows-sign": "bin/electron-windows-sign.js"
},
"engines": {
"node": ">=14.14"
}
},
"node_modules/@electron/windows-sign/node_modules/fs-extra": {
"version": "11.3.6",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz",
"integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=14.14"
}
},
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
@@ -1130,15 +1091,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/cross-dirname": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz",
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -1459,19 +1411,6 @@
"node": ">=14.0.0"
}
},
"node_modules/electron-builder-squirrel-windows": {
"version": "26.15.3",
"resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.15.3.tgz",
"integrity": "sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"app-builder-lib": "26.15.3",
"builder-util": "26.15.3",
"electron-winstaller": "5.4.0"
}
},
"node_modules/electron-publish": {
"version": "26.15.3",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.15.3.tgz",
@@ -1506,66 +1445,6 @@
"tiny-typed-emitter": "^2.1.0"
}
},
"node_modules/electron-winstaller": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz",
"integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@electron/asar": "^3.2.1",
"debug": "^4.1.1",
"fs-extra": "^7.0.1",
"lodash": "^4.17.21",
"temp": "^0.9.0"
},
"engines": {
"node": ">=8.0.0"
},
"optionalDependencies": {
"@electron/windows-sign": "^1.1.2"
}
},
"node_modules/electron-winstaller/node_modules/fs-extra": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"engines": {
"node": ">=6 <7 || >=8"
}
},
"node_modules/electron-winstaller/node_modules/jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
"dev": true,
"license": "MIT",
"peer": true,
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/electron-winstaller/node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -1696,9 +1575,9 @@
"license": "MIT"
},
"node_modules/fast-uri": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz",
"integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==",
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
"integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
"dev": true,
"funding": [
{
@@ -2480,20 +2359,6 @@
"node": ">= 18"
}
},
"node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"minimist": "^1.2.6"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -2757,36 +2622,6 @@
"node": ">=18"
}
},
"node_modules/postject": {
"version": "1.0.0-alpha.6",
"resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz",
"integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"commander": "^9.4.0"
},
"bin": {
"postject": "dist/cli.js"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/postject/node_modules/commander": {
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": "^12.20.0 || >=14"
}
},
"node_modules/proc-log": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz",
@@ -2981,21 +2816,6 @@
"node": ">= 4"
}
},
"node_modules/rimraf": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dev": true,
"license": "ISC",
"peer": true,
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
}
},
"node_modules/roarr": {
"version": "2.15.4",
"resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
@@ -3251,21 +3071,6 @@
"node": ">=18"
}
},
"node_modules/temp": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz",
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"mkdirp": "^0.5.1",
"rimraf": "~2.6.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/temp-file": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz",
@@ -3372,9 +3177,9 @@
}
},
"node_modules/undici": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz",
"integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==",
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
"integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
"dev": true,
"license": "MIT",
"engines": {

View File

@@ -37,7 +37,7 @@
"plist": "^4.0.0",
"form-data": "^4.0.6",
"js-yaml": "^4.2.0",
"undici": "^7.28.0"
"undici": "^7.29.0"
},
"build": {
"appId": "online.omniroute.desktop",

View File

@@ -145,6 +145,19 @@ export const AUDIO_TRANSCRIPTION_PROVIDERS: Record<string, AudioProvider> = {
],
},
soniox: {
id: "soniox",
baseUrl: "https://api.soniox.com/v1/transcriptions",
authType: "apikey",
authHeader: "bearer",
async: true,
format: "soniox",
models: [
{ id: "stt-async-v5", name: "Soniox STT Async v5" },
{ id: "stt-async-v4", name: "Soniox STT Async v4" },
],
},
nvidia: {
id: "nvidia",
baseUrl: "https://integrate.api.nvidia.com/v1/audio/transcriptions",
@@ -320,6 +333,15 @@ export const AUDIO_SPEECH_PROVIDERS: Record<string, AudioProvider> = {
],
},
soniox: {
id: "soniox",
baseUrl: "https://tts-rt.soniox.com/tts",
authType: "apikey",
authHeader: "bearer",
format: "soniox-tts",
models: [{ id: "tts-rt-v1", name: "Soniox TTS RT v1" }],
},
elevenlabs: {
id: "elevenlabs",
baseUrl: "https://api.elevenlabs.io/v1/text-to-speech",

View File

@@ -85,14 +85,13 @@ export const opencode_zenProvider: RegistryEntry = {
{ id: "qwen3.6-plus", name: "Qwen3.6 Plus", targetFormat: "claude", supportsVision: false },
// ── Free Tier ──────────────────────────────────────────────
// #6998 (2026-07-14): upstream free tier rotated — minimax-m2.5-free,
// nemotron-3-super-free and qwen3.6-plus-free were delisted (401). Replaced
// by the 4 entries below with upstream-verified limits.
{ id: "deepseek-v4-flash-free", name: "DeepSeek V4 Flash Free", supportsReasoning: true },
{ id: "minimax-m2.5-free", name: "MiniMax M2.5 Free", contextLength: 204800 },
{ id: "nemotron-3-super-free", name: "Nemotron 3 Super Free", contextLength: 1000000 },
{
id: "qwen3.6-plus-free",
name: "Qwen3.6 Plus Free",
targetFormat: "claude",
contextLength: 200000,
},
{ id: "mimo-v2.5-free", name: "MiMo V2.5 Free", contextLength: 200000 },
{ id: "hy3-free", name: "HY3 Free", contextLength: 200000 },
{ id: "nemotron-3-ultra-free", name: "Nemotron 3 Ultra Free", contextLength: 1000000 },
{ id: "north-mini-code-free", name: "North Mini Code Free", contextLength: 200000 },
],
};

View File

@@ -13,5 +13,12 @@ export const openrouterProvider: RegistryEntry = {
"HTTP-Referer": "https://endpoint-proxy.local",
"X-Title": "Endpoint Proxy",
},
// OpenRouter multiplexes hundreds of independent upstream models behind one
// connection/API key — without this flag, hasPerModelQuota() (accountFallback.ts)
// falls through to connection-wide cooldown on any model-specific failure (e.g. a
// 404 "No endpoints found" for one dead/renamed model), poisoning every OTHER
// OpenRouter model on the same connection for the cooldown window and surfacing
// that first model's stale error message on their unrelated requests.
passthroughModels: true,
models: [{ id: "auto", name: "Auto (Best Available)" }],
};

View File

@@ -271,16 +271,14 @@ export const GPT_5_6_API_CAPABILITIES = {
maxOutputTokens: 128000,
} as const;
// Codex's live catalog reports a 272K input context window for GPT-5.6.
// Keep the input and output limits explicit for catalog consumers that expose them separately.
export const GPT_5_6_CODEX_CAPABILITIES = {
targetFormat: "openai-responses",
toolCalling: true,
supportsReasoning: true,
supportsVision: true,
supportsXHighEffort: true,
contextLength: 272000,
maxInputTokens: 272000,
contextLength: 1050000,
maxInputTokens: 922000,
maxOutputTokens: 128000,
} as const;

View File

@@ -23,6 +23,11 @@ import {
import { persistCreditBalance, getAllPersistedCreditBalances } from "@/lib/db/creditBalance";
import { setConnectionRateLimitUntil } from "@/lib/db/providers";
import { getMitmAlias } from "@/lib/db/models";
import {
MAX_ANTIGRAVITY_OUTPUT_TOKENS,
resolveAntigravityOutputCap,
} from "./antigravityOutputCap.ts";
export { MAX_ANTIGRAVITY_OUTPUT_TOKENS } from "./antigravityOutputCap.ts";
import { ensureAntigravityProjectAssigned } from "../services/antigravityProjectBootstrap.ts";
import { persistDiscoveredAntigravityProjectId } from "../services/antigravityProjectPersist.ts";
import {
@@ -279,18 +284,10 @@ async function cleanModelName(model: string, modelIdOverride?: string): Promise<
return clean;
}
/**
* Hard ceiling on `generationConfig.maxOutputTokens` for Antigravity Cloud Code.
*
* Ports decolua/9router#779 (lukmanfauzie): VS Code GitHub Copilot Chat in
* Agent mode regularly requests 32K65K output tokens, which the Antigravity
* backend rejects with HTTP 400 "Invalid Argument". 16384 matches the
* upstream-accepted ceiling confirmed via successful 200 OK runs with
* claude-sonnet-4-6 and gemini-pro-agent across both Ask and Agent modes.
*/
export const MAX_ANTIGRAVITY_OUTPUT_TOKENS = 16384;
function applyAntigravityGenerationDefaults(request: Record<string, unknown>): void {
function applyAntigravityGenerationDefaults(
request: Record<string, unknown>,
modelId?: string | null
): void {
const generationConfig =
request.generationConfig && typeof request.generationConfig === "object"
? (request.generationConfig as Record<string, unknown>)
@@ -322,9 +319,10 @@ function applyAntigravityGenerationDefaults(request: Record<string, unknown>): v
// (32K65K) that trigger upstream 400 "Invalid Argument". Clamp silently
// — the cap is provider-driven, not client-driven, and only matters when
// the request would otherwise be rejected outright.
const cap = resolveAntigravityOutputCap(modelId);
const finalMax = Number(generationConfig.maxOutputTokens);
if (Number.isFinite(finalMax) && finalMax > MAX_ANTIGRAVITY_OUTPUT_TOKENS) {
generationConfig.maxOutputTokens = MAX_ANTIGRAVITY_OUTPUT_TOKENS;
if (Number.isFinite(finalMax) && finalMax > cap) {
generationConfig.maxOutputTokens = cap;
}
request.generationConfig = generationConfig;
@@ -666,7 +664,7 @@ export class AntigravityExecutor extends BaseExecutor {
)
: rawTransformedRequest;
applyAntigravityGenerationDefaults(transformedRequest);
applyAntigravityGenerationDefaults(transformedRequest, upstreamModel);
const {
project: _project,

View File

@@ -0,0 +1,52 @@
import { getExplicitModelOutputCap } from "@/lib/modelCapabilities";
/**
* Fallback ceiling on `generationConfig.maxOutputTokens` for Antigravity
* Cloud Code, used when the model is unknown to the catalogue.
*
* Ports decolua/9router#779 (lukmanfauzie): VS Code GitHub Copilot Chat in
* Agent mode regularly requests 32K65K output tokens, which the Antigravity
* backend rejects with HTTP 400 "Invalid Argument". 16384 was the ceiling
* confirmed safe at the time, via successful 200 OK runs with
* claude-sonnet-4-6 and gemini-pro-agent across both Ask and Agent modes.
*
* Both of those models are catalogue-known today, so neither one reaches this
* constant anymore: they get their own declared limit via
* `resolveAntigravityOutputCap` (65536 and 65535, respectively). The higher
* limit holds against the live upstream. A gemini-3.6-flash-high request came
* back with completion_tokens 16754 and finish_reason "stop", which exceeds
* 16384 on its own and so cannot be an artifact of thinking-token accounting.
*
* Note also that #779 was reported against Copilot Chat in Agent mode, a path
* that does not reach this executor, so 16384 arrived with that port rather
* than from a limit measured here. Beware of re-deriving it from a running
* instance: the clamp below rewrites maxOutputTokens before the request
* leaves, so a build still carrying a low constant measures its own clamp and
* reports it as an upstream ceiling.
*/
export const MAX_ANTIGRAVITY_OUTPUT_TOKENS = 16384;
/**
* The output ceiling this specific model accepts, or the conservative
* fallback above when the id is not in the catalogue.
*
* The declared limits are not uniform: most Antigravity models publish
* 65535 or 65536, but gpt-oss-120b-medium publishes 32768. A single global
* ceiling either starves the first group or lets an oversized request
* through to the second, so the number has to come from the model.
*/
export function resolveAntigravityOutputCap(modelId: string | null | undefined): number {
const id = typeof modelId === "string" ? modelId.trim() : "";
if (!id) return MAX_ANTIGRAVITY_OUTPUT_TOKENS;
try {
const declared = getExplicitModelOutputCap({ provider: "antigravity", model: id });
return typeof declared === "number" && Number.isFinite(declared) && declared > 0
? declared
: MAX_ANTIGRAVITY_OUTPUT_TOKENS;
} catch {
// DB not available (build phase, transient error) -- fall through to the
// conservative fallback, the same guard cleanModelName uses above for
// its own MITM alias lookup.
return MAX_ANTIGRAVITY_OUTPUT_TOKENS;
}
}

View File

@@ -28,7 +28,11 @@ export class AzureOpenAIExecutor extends DefaultExecutor {
void urlIndex;
const providerSpecificData = credentials?.providerSpecificData || {};
const baseUrl = normalizeAzureBaseUrl(providerSpecificData.baseUrl || this.config.baseUrl);
const baseUrl = normalizeAzureBaseUrl(
typeof providerSpecificData.baseUrl === "string"
? providerSpecificData.baseUrl
: this.config.baseUrl
);
const apiVersion =
typeof providerSpecificData.apiVersion === "string" && providerSpecificData.apiVersion.trim()
? providerSpecificData.apiVersion.trim()

View File

@@ -234,71 +234,11 @@ export function mergeAbortSignals(primary: AbortSignal, secondary: AbortSignal):
return controller.signal;
}
function hasActiveClaudeThinking(body: Record<string, unknown>): boolean {
const thinking = body.thinking as Record<string, unknown> | undefined;
return thinking?.type === "enabled" || thinking?.type === "adaptive";
}
/**
* Collect every `thinkingConfig` object in a transformed request body that holds
* a thinking budget, wherever the provider's envelope nests it:
* - body.generationConfig.thinkingConfig (native Gemini / openai→gemini)
* - body.request.generationConfig.thinkingConfig (Antigravity Cloud Code envelope)
* Returns only objects that actually carry a `thinkingBudget`/`thinking_budget`
* field — a request without thinking config is never mutated.
*/
function collectThinkingConfigs(body: unknown): Array<Record<string, unknown>> {
if (!body || typeof body !== "object") return [];
const root = body as Record<string, unknown>;
const configs: Array<Record<string, unknown>> = [];
const envelopes: unknown[] = [
root.generationConfig,
(root.request as Record<string, unknown> | undefined)?.generationConfig,
];
for (const env of envelopes) {
if (!env || typeof env !== "object") continue;
const tc = (env as Record<string, unknown>).thinkingConfig;
if (tc && typeof tc === "object") {
const tcr = tc as Record<string, unknown>;
if ("thinkingBudget" in tcr || "thinking_budget" in tcr) configs.push(tcr);
}
}
return configs;
}
/**
* Read the first thinking budget found in the body (any supported nest / naming).
* Returns null when the body carries no readable numeric budget.
*/
function readNestedThinkingBudget(body: unknown): number | null {
for (const tc of collectThinkingConfigs(body)) {
const raw = tc.thinkingBudget ?? tc.thinking_budget;
const n = Number(raw);
if (Number.isFinite(n)) return n;
}
return null;
}
/**
* Clamp every thinking budget in the body down to `max` (only lowers; never
* raises a budget already below max). Mutates in place. Returns true when at
* least one budget was actually lowered (i.e. a retry would send a different
* body) — false means the 400 was not caused by an over-max budget we hold, so
* retrying would resend an identical body and loop.
*/
function clampNestedThinkingBudget(body: unknown, max: number): boolean {
let changed = false;
for (const tc of collectThinkingConfigs(body)) {
for (const key of ["thinkingBudget", "thinking_budget"] as const) {
const n = Number(tc[key]);
if (Number.isFinite(n) && n > max) {
tc[key] = max;
changed = true;
}
}
}
return changed;
}
import {
hasActiveClaudeThinking,
readNestedThinkingBudget,
clampNestedThinkingBudget,
} from "../utils/thinkingBudget.ts";
/**
* Strip the OmniRoute provider prefix from tool model fields (e.g.

View File

@@ -151,7 +151,8 @@ export function supportsMaxEffortForProvider(provider: string, model: string): b
// Ollama Cloud also accepts literal max (for example GLM 5.2 supports
// low|medium|high|max|none) and rejects xhigh.
const isOpencodeGoDeepSeek =
provider === "opencode-go" && model.toLowerCase().includes("deepseek");
(provider === "opencode-go" || provider === "opencode-zen") &&
model.toLowerCase().includes("deepseek");
const isOllamaCloud = provider === "ollama-cloud";
const isMoonshotK3 =
(provider === "moonshot" || provider === "kimi") && /^kimi-k3(?:$|-)/i.test(model);

View File

@@ -2816,11 +2816,14 @@ export class ChatGptWebExecutor extends BaseExecutor {
};
}
// Tool-call emulation (#5240): inject a `<tool>` contract when `tools` are
// present; parsed back on the response side. Mirrors qwen-web/perplexity-web.
// Tool-call emulation (#5240, #7679): inject a `<tool>` contract when tools
// are present; parsed back on the response side. Hardened for thinking models.
const resolvedModel = resolveChatGptModel(model, body, credentials.providerSpecificData);
const modelSlug = resolvedModel.slug;
const { hasTools, requestedTools, effectiveMessages } = prepareToolMessages(
(body || {}) as Record<string, unknown>,
messages as Array<{ role: string; content: unknown }>
messages as Array<{ role: string; content: unknown }>,
{ hardened: isThinkingCapableModel(model, modelSlug) }
);
if (!credentials.apiKey) {
@@ -2918,12 +2921,9 @@ export class ChatGptWebExecutor extends BaseExecutor {
log
);
// 2a''. Resolve model + effort and apply thinking-effort preference for
// thinking-capable models. Dedicated thinking models mirror the browser's
// user-config PATCH; GPT-5.5 Pro sends the effort with the conversation
// body because the Pro standard/extended budget is part of that turn.
const resolvedModel = resolveChatGptModel(model, body, credentials.providerSpecificData);
const modelSlug = resolvedModel.slug;
// 2a''. Apply thinking-effort preference for thinking models.
// Dedicated thinking models mirror the browser's user-config PATCH;
// GPT-5.5 Pro effort is sent with the conversation body.
const requestedEffort = resolvedModel.effort;
if (requestedEffort && isThinkingCapableModel(model, modelSlug)) {
await setUserThinkingEffort(

View File

@@ -461,14 +461,31 @@ function applyEventToAggregateOrThrow(event: JsonRecord, state: AggregateState):
function usageFromCommandCode(usage: JsonRecord | null) {
if (!usage) return undefined;
const details = isRecord(usage.inputTokenDetails) ? usage.inputTokenDetails : {};
const prompt =
(numberValue(usage.inputTokens) || 0) + (numberValue(details.cacheReadTokens) || 0);
const cacheRead = numberValue(details.cacheReadTokens) || 0;
const noCache = numberValue(details.noCacheTokens) || 0;
// Command Code's totalUsage.inputTokens is the FULL prompt total and already
// includes the cached portion (noCacheTokens + cacheReadTokens = inputTokens),
// so we must NOT add cacheRead back — that would double-count. There is no
// cache-write field in the upstream payload, so cache creation stays unset.
const inputTokens = numberValue(usage.inputTokens) || 0;
const prompt = inputTokens;
const completion = numberValue(usage.outputTokens) || 0;
return {
const result: JsonRecord = {
prompt_tokens: prompt,
completion_tokens: completion,
total_tokens: prompt + completion,
};
// Surface the cache breakdown as informational fields so logUsage prints
// `| cache_read=X | no_cache=Y` and appendRequestLog persists them. These are
// NOT added to prompt_tokens (already included) — metering stays accurate.
if (cacheRead > 0) result.cache_read_input_tokens = cacheRead;
if (noCache > 0) result.no_cache_tokens = noCache;
// Keep reasoning_token_details (reasoningTokens) when present so stream.ts's
// extractUsage can surface it as reasoning_tokens.
const reasoningDetails = isRecord(usage.reasoningTokenDetails) ? usage.reasoningTokenDetails : {};
const reasoning = numberValue(reasoningDetails.reasoningTokens);
if (reasoning !== undefined && reasoning > 0) result.reasoning_tokens = reasoning;
return result;
}
function createStreamResponse(
@@ -549,6 +566,22 @@ function createStreamResponse(
state.finishReason = mapFinishReason(event.finishReason);
state.usage = isRecord(event.totalUsage) ? event.totalUsage : null;
controller.enqueue(sse(chatCompletionChunk(id, model, {}, state.finishReason)));
// Emit a standards-compliant usage-only chunk (choices: []) before
// [DONE] when upstream reported usage. stream.ts's extractUsage
// recognizes this shape (see stream.ts:1661) and logs the ACTUAL
// token counts (in/out/cache_read/no_cache) instead of estimates.
const usagePayload = usageFromCommandCode(state.usage);
if (usagePayload) {
controller.enqueue(
sse({
id,
object: "chat.completion.chunk",
model,
usage: usagePayload,
choices: [],
})
);
}
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
closed = true;
controller.close();

View File

@@ -165,7 +165,7 @@ function baseChunk(model: string) {
}
function enqueueSse(controller: ReadableStreamDefaultController, chunk: Record<string, unknown>) {
controller.enqueue(`data: ${JSON.stringify(chunk)}\n\n`);
controller.enqueue(new TextEncoder().encode(`data: ${JSON.stringify(chunk)}\n\n`));
}
function emitStopAndDone(controller: ReadableStreamDefaultController, model: string) {
@@ -173,7 +173,7 @@ function emitStopAndDone(controller: ReadableStreamDefaultController, model: str
...baseChunk(model),
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
});
controller.enqueue("data: [DONE]\n\n");
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"));
controller.close();
}
@@ -213,7 +213,7 @@ export function createOpenAIArenaStream(opts: {
model: string;
signal?: AbortSignal;
log?: { error?: (scope: string, msg: string) => void };
}): ReadableStream<Uint8Array | string> {
}): ReadableStream<Uint8Array> {
const { reader, model, signal, log } = opts;
const decoder = new TextDecoder();
let buffer = "";

View File

@@ -40,6 +40,31 @@ const OPENCODE_COOLDOWN_MAX_MS = 60_000;
const EFFORT_LEVELS = ["low", "medium", "high", "max"] as const;
/**
* Models that work WITHOUT any API key on the free/noauth opencode tier.
*
* The upstream free tier rotates frequently — when a `-free` suffix model is
* delisted upstream, the upstream returns "Model X is not supported" (a separate
* issue from this gate). The set is defined by two data sources:
*
* 1. **Known free models** — models explicitly listed in the noauth
* `opencode` provider registry (`open-sse/config/providers/registry/opencode/index.ts`).
* These are the canonical free models. `deepseek-v4-flash-free` appears in both
* the noauth AND the zen registry (it is free on both tiers).
* 2. **`-free` suffix** — any model whose id ends in `-free`. This automatically
* covers upstream free-tier additions without a code deploy.
*
* For `opencode-go`, there is no free tier — ALL models require an API key.
*/
const OPENCODE_FREE_MODELS = new Set([
"big-pickle",
"deepseek-v4-flash-free",
"mimo-v2.5-free",
"hy3-free",
"nemotron-3-ultra-free",
"north-mini-code-free",
]);
/**
* Models on opencode-go that support effort-tier aliases. Each entry maps the
* canonical base id to the set of effort suffixes the upstream supports.
@@ -86,7 +111,31 @@ export function parseEffortLevel(model: string): { baseModel: string; effort: st
return null;
}
/**
* Determine whether a model requires an API key on the given opencode provider.
*
* - `opencode-go`: ALL models require a key (no free tier).
* - `opencode` / `opencode-zen`: premium = any model NOT in the free set (known
* free models OR ending in `-free`).
* - Unknown models are assumed premium (fail-safe).
*/
export function isPremiumOpencodeModel(model: string, provider: string): boolean {
// opencode-go has no free tier — every model requires a key.
if (provider === "opencode-go") return true;
// Models ending in `-free` are always free on the noauth/zen tier.
if (model.endsWith("-free")) return false;
// Check the known free model catalog.
return !OPENCODE_FREE_MODELS.has(model);
}
export class OpencodeExecutor extends BaseExecutor {
/** Delegates to `isPremiumOpencodeModel`. Exported for testability. */
static isPremiumModel(model: string, provider: string): boolean {
return isPremiumOpencodeModel(model, provider);
}
_requestFormat: string | null = null;
/**
@@ -181,6 +230,34 @@ export class OpencodeExecutor extends BaseExecutor {
async execute(input: ExecuteInput) {
this._requestFormat = getModelTargetFormat(this.provider, input.model) || "openai";
// #8681: Gate premium opencode models behind a usable API key.
// When the connection is keyless (no apiKey, no accessToken) and the model
// is a premium model (not on the free tier), return a clear 402 error
// instead of proxying the raw upstream 401 "Missing API key" response.
const creds = input.credentials;
const isKeyless =
!creds?.apiKey && !creds?.accessToken && !creds?.providerSpecificData?.extraApiKeys;
if (isKeyless && isPremiumOpencodeModel(input.model, this.provider)) {
const bodyJson = JSON.stringify({
error: {
message:
"This model requires an opencode API key — add one in Settings → Providers.",
type: "invalid_request_error",
code: "premium_model_requires_key",
},
});
return {
response: new Response(bodyJson, {
status: 402,
headers: { "Content-Type": "application/json" },
}),
url: "",
headers: {} as Record<string, string>,
transformedBody: null,
};
}
try {
this.syncAccountsFromCredentials(input.credentials);

View File

@@ -372,8 +372,16 @@ export class QoderExecutor extends BaseExecutor {
const { text, isError, errorMessage } = parseQoderCliResult(run.stdout);
if (isError) {
// When qodercli exits 0 but returns is_error=true with an empty result,
// the real upstream error is almost always on stderr. Surface it instead
// of the generic "qodercli returned an error" fallback (#9319).
let effectiveError = errorMessage;
if (errorMessage === "qodercli returned an error" && run.stderr.trim()) {
const stderrTrimmed = run.stderr.trim().slice(0, 300);
effectiveError = `qodercli returned an error: ${stderrTrimmed}`;
}
return {
response: createQoderErrorResponse(parseQoderCliFailure(errorMessage)),
response: createQoderErrorResponse(parseQoderCliFailure(effectiveError)),
url,
headers: {},
transformedBody: body,

View File

@@ -47,8 +47,8 @@ const BX_UMIDTOKEN_FALLBACK = "T2gA0000000000000000000000000000000000000000";
// header the upstream returns HTTP 200 with `{"success":false,"data":{"code":"Bad_Request"}}`
// for every completion request, even with a valid session. The version string is
// the SPA build identifier shipped in the React client's `version` request header.
// Pinned from a live capture (2026-07); bump if Qwen ships a breaking change.
const QWEN_SPA_VERSION = "0.2.66";
// Pinned from a live capture (2026-08); bump if Qwen ships a breaking change.
const QWEN_SPA_VERSION = "0.2.81";
const MODEL_ALIASES: Record<string, string> = {
// Legacy OmniRoute ids → current upstream catalog (GET /api/models).

View File

@@ -228,6 +228,35 @@ async function handleDeepgramSpeech(providerConfig, body, modelId, token) {
return audioStreamResponse(res);
}
/**
* Handle Soniox TTS (OpenAI speech shape → Soniox /tts, returns raw audio bytes)
*/
async function handleSonioxSpeech(providerConfig, body, modelId, token) {
const fmt = typeof body.response_format === "string" ? body.response_format : "mp3";
const audioFormat = fmt === "pcm" ? "pcm_s16le" : fmt;
const res = await fetch(providerConfig.baseUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
...buildAuthHeaders(providerConfig, token),
},
body: JSON.stringify({
text: body.input,
model: modelId,
...(body.voice ? { voice: body.voice } : {}),
audio_format: audioFormat,
}),
});
if (!res.ok) {
return upstreamErrorResponse(res, await res.text());
}
const contentType = fmt === "wav" ? "audio/wav" : fmt === "opus" ? "audio/opus" : "audio/mpeg";
return audioStreamResponse(res, contentType);
}
/**
* Handle ElevenLabs TTS
* POST {baseUrl}/{voice_id} with { text, model_id }
@@ -846,6 +875,10 @@ export async function handleAudioSpeech({
return handleDeepgramSpeech(providerConfig, body, modelId, token);
}
if (providerConfig.format === "soniox-tts") {
return handleSonioxSpeech(providerConfig, body, modelId, token);
}
if (providerConfig.format === "elevenlabs") {
return handleElevenLabsSpeech(providerConfig, body, modelId, token);
}

View File

@@ -336,6 +336,78 @@ async function handleGladiaTranscription(providerConfig, file, modelId, token) {
return errorResponse(504, "Gladia transcription timed out after 120s");
}
/**
* Handle Soniox transcription (async: upload file → create job → poll → get transcript)
*/
async function handleSonioxTranscription(providerConfig, file, modelId, token) {
const authHeaders = buildAuthHeaders(providerConfig, token);
const { body: uploadBody, contentType: uploadContentType } = await buildMultipartBody(file, {});
const uploadRes = await fetch("https://api.soniox.com/v1/files", {
method: "POST",
headers: { ...authHeaders, "Content-Type": uploadContentType },
body: uploadBody,
});
if (!uploadRes.ok) {
return upstreamErrorResponse(uploadRes, await uploadRes.text());
}
const fileId = (await uploadRes.json()).id;
const createRes = await fetch(providerConfig.baseUrl, {
method: "POST",
headers: { ...authHeaders, "Content-Type": "application/json" },
body: JSON.stringify({
model: modelId,
file_id: fileId,
enable_language_identification: true,
}),
});
if (!createRes.ok) {
return upstreamErrorResponse(createRes, await createRes.text());
}
const { id: transcriptionId } = await createRes.json();
const statusUrl = `${providerConfig.baseUrl}/${transcriptionId}`;
const maxWait = 120_000;
const start = Date.now();
let completed = false;
while (Date.now() - start < maxWait) {
await new Promise((r) => setTimeout(r, 2000));
const pollRes = await fetch(statusUrl, { headers: authHeaders });
if (!pollRes.ok) {
continue;
}
const result = await pollRes.json();
if (result.status === "completed") {
completed = true;
break;
}
if (result.status === "error") {
return errorResponse(
500,
result.error_message || result.error || "Soniox transcription failed"
);
}
}
if (!completed) {
return errorResponse(504, "Soniox transcription timed out after 120s");
}
const transcriptRes = await fetch(`${statusUrl}/transcript`, { headers: authHeaders });
if (!transcriptRes.ok) {
return upstreamErrorResponse(transcriptRes, await transcriptRes.text());
}
const transcript = await transcriptRes.json();
const text =
typeof transcript.text === "string" && transcript.text.length > 0
? transcript.text
: Array.isArray(transcript.tokens)
? transcript.tokens.map((t: { text?: string }) => t.text ?? "").join("")
: "";
return Response.json({ text }, { headers: { ...CORS_HEADERS } });
}
/**
* Handle Nvidia NIM transcription
* Multipart POST, transform response to { text }
@@ -735,6 +807,10 @@ export async function handleAudioTranscription({
return handleGladiaTranscription(providerConfig, file, modelId, token);
}
if (providerConfig.format === "soniox") {
return handleSonioxTranscription(providerConfig, file, modelId, token);
}
if (providerConfig.format === "nvidia-asr") {
return handleNvidiaTranscription(providerConfig, file, modelId, token);
}

View File

@@ -490,9 +490,10 @@ export async function handleChatCore({
model,
provider,
apiKeyInfo,
headers: clientRawRequest?.headers,
log,
});
if (pluginGate.blocked) {
if (pluginGate.blocked === true) {
return {
success: false,
status: 403,
@@ -1883,7 +1884,7 @@ export async function handleChatCore({
modelOutputCap,
toPositiveInteger(resolveInputTokenCapForGate({ provider, model: effectiveModel }, { isCombo }))
);
if (!outputBudget.ok) {
if (outputBudget.ok === false) {
const exceededInputCap = outputBudget.maxInputTokens !== undefined;
const message =
`Input exceeds ${exceededInputCap ? "maximum input tokens" : "context window"} for ${provider}/${effectiveModel}: ` +
@@ -4622,6 +4623,7 @@ export async function handleChatCore({
model,
provider,
apiKeyInfo,
headers: clientRawRequest?.headers,
response: { status: 200, data: translatedResponse },
});
@@ -5012,6 +5014,7 @@ export async function handleChatCore({
model,
provider,
apiKeyInfo,
headers: clientRawRequest?.headers,
response: { status: 200, streamed: true },
});

View File

@@ -10,13 +10,10 @@
*/
type LoggerLike =
| { info?: (...args: unknown[]) => void; debug?: (...args: unknown[]) => void }
| null
| undefined;
{ info?: (...args: unknown[]) => void; debug?: (...args: unknown[]) => void } | null | undefined;
export type PluginOnRequestGate =
| { blocked: true; response: Response }
| { blocked: false; body?: unknown };
{ blocked: true; response: Response } | { blocked: false; body?: unknown };
const JSON_HEADERS = { status: 403, headers: { "Content-Type": "application/json" } } as const;
@@ -26,6 +23,7 @@ export async function runPluginOnRequestHook(args: {
model: string | null | undefined;
provider: string | null | undefined;
apiKeyInfo: unknown;
headers?: Record<string, string | string[] | undefined>;
log?: LoggerLike;
}): Promise<PluginOnRequestGate> {
try {
@@ -36,6 +34,7 @@ export async function runPluginOnRequestHook(args: {
model: args.model,
provider: args.provider,
apiKeyInfo: args.apiKeyInfo,
headers: args.headers,
metadata: {},
};
const pluginResult = await runOnRequest(pluginCtx);

View File

@@ -24,6 +24,7 @@ export async function runPluginOnResponseHook(args: {
model: string | null | undefined;
provider: string | null | undefined;
apiKeyInfo: unknown;
headers?: Record<string, string | string[] | undefined>;
response: PluginOnResponsePayload;
}): Promise<void> {
try {
@@ -35,6 +36,7 @@ export async function runPluginOnResponseHook(args: {
model: args.model,
provider: args.provider,
apiKeyInfo: args.apiKeyInfo,
headers: args.headers,
metadata: {},
},
args.response

View File

@@ -46,7 +46,7 @@ export function sanitizeChatRequestBody(
}
if (Array.isArray(body.tools)) {
body.tools = body.tools.filter((tool: Record<string, unknown>) => {
const tools = body.tools.filter((tool: Record<string, unknown>) => {
const toolType = typeof tool.type === "string" ? tool.type : "";
if (toolType && toolType !== "function" && !tool.function && tool.name === undefined) {
return true;
@@ -56,7 +56,7 @@ export function sanitizeChatRequestBody(
return name && String(name).trim().length > 0;
});
body.tools = body.tools.map((tool) => sanitizeOpenAITool(tool) as (typeof body.tools)[number]);
body.tools = tools.map((tool) => sanitizeOpenAITool(tool));
}
return body;

View File

@@ -27,6 +27,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { z } from "zod";
import { sanitizeErrorMessage } from "../utils/error.ts";
import { resolveSearchProxy, executeProviderFetch } from "./search/searchProxy.ts";
export interface SearchResult {
title: string;
@@ -96,6 +97,9 @@ interface SearchHandlerOptions {
alternateProvider?: string;
alternateCredentials?: Record<string, any> | null;
log?: any;
/** Connection ID (proxy resolution + call-log attribution) and API key ID (per-key proxy). */
connectionId?: string;
apiKeyId?: string;
}
// ── Constants ────────────────────────────────────────────────────────────
@@ -1195,6 +1199,8 @@ export async function handleSearch(options: SearchHandlerOptions): Promise<Searc
alternateProvider,
alternateCredentials,
log,
connectionId,
apiKeyId,
} = options;
const startTime = Date.now();
@@ -1238,7 +1244,15 @@ export async function handleSearch(options: SearchHandlerOptions): Promise<Searc
}
// 4. Try primary provider
const result = await tryProvider(primaryConfig, requestParams, credentials, startTime, log);
const result = await tryProvider(
primaryConfig,
requestParams,
credentials,
startTime,
log,
connectionId,
apiKeyId
);
if (result.success) return result;
@@ -1256,12 +1270,15 @@ export async function handleSearch(options: SearchHandlerOptions): Promise<Searc
);
}
// Resolve alternate connection proxy independently so primary context never leaks
const fallbackResult = await tryProvider(
alternateConfig,
requestParams,
alternateCredentials,
startTime,
log
log,
alternateCredentials?.connectionId,
apiKeyId
);
if (fallbackResult.success) return fallbackResult;
@@ -1374,7 +1391,9 @@ async function tryProvider(
params: Omit<SearchRequestParams, "token">,
credentials: Record<string, any>,
globalStartTime: number,
log?: any
log?: any,
connectionId?: string,
apiKeyId?: string
): Promise<SearchHandlerResult> {
const startTime = Date.now();
const providerSpecificData =
@@ -1421,6 +1440,10 @@ async function tryProvider(
};
}
// Resolve proxy for the selected connection (see search/searchProxy.ts for the
// resolveProxyForConnection precedence chain: per-key, account, provider, combo, global).
const { proxy, proxyLevel } = await resolveSearchProxy(connectionId, apiKeyId, config.id);
// Timeout: min of provider timeout and remaining global timeout
const remainingGlobal = GLOBAL_TIMEOUT_MS - (Date.now() - globalStartTime);
const timeout = Math.min(config.timeoutMs, Math.max(remainingGlobal, 1000));
@@ -1431,105 +1454,22 @@ async function tryProvider(
log.info("SEARCH", `${config.id} | query: "${query.slice(0, 80)}" | type: ${searchType}`);
}
try {
const response = await fetch(url, { ...init, signal: controller.signal });
clearTimeout(timer);
if (!response.ok) {
const errorText = await response.text();
if (log) {
log.error("SEARCH", `${config.id} error ${response.status}: ${errorText.slice(0, 200)}`);
}
saveCallLog({
method: config.method,
path: "/v1/search",
status: response.status,
model: config.id,
provider: config.id,
duration: Date.now() - startTime,
requestType: "search",
error: errorText.slice(0, 500),
requestBody: {
query: query.slice(0, 200),
search_type: searchType,
max_results: maxResults,
},
}).catch(() => {
/* non-critical — logging must not block search response */
});
return {
success: false,
status: response.status,
error: `Search provider ${config.id} returned ${response.status}`,
};
}
const data = await response.json();
const normalized = normalizeResponse(config.id, data, query, searchType);
// Enforce max_results — some providers return more than requested
const results = normalized.results.slice(0, maxResults);
const totalResults = normalized.totalResults;
const duration = Date.now() - startTime;
saveCallLog({
method: config.method,
path: "/v1/search",
status: 200,
model: config.id,
provider: config.id,
duration,
requestType: "search",
tokens: { prompt_tokens: 0, completion_tokens: 0 },
requestBody: { query: query.slice(0, 200), search_type: searchType, max_results: maxResults },
responseBody: { results_count: results.length, cached: false },
}).catch(() => {
/* non-critical — logging must not block search response */
});
return {
success: true,
data: {
provider: config.id,
query,
results,
answer: null,
usage: { queries_used: 1, search_cost_usd: config.costPerQuery },
metrics: {
response_time_ms: duration,
upstream_latency_ms: duration,
total_results_available: totalResults,
},
errors: [],
},
};
} catch (err: any) {
clearTimeout(timer);
const isTimeout = err.name === "AbortError";
if (log) {
log.error("SEARCH", `${config.id} ${isTimeout ? "timeout" : "fetch error"}: ${err.message}`);
}
saveCallLog({
method: config.method,
path: "/v1/search",
status: isTimeout ? 504 : 502,
model: config.id,
provider: config.id,
duration: Date.now() - startTime,
requestType: "search",
error: err.message,
requestBody: { query: query.slice(0, 200), search_type: searchType, max_results: maxResults },
}).catch(() => {
/* non-critical — logging must not block search response */
});
return {
success: false,
status: isTimeout ? 504 : 502,
error: `Search provider ${isTimeout ? "timeout" : "error"}: ${sanitizeErrorMessage(err.message)}`,
};
}
// Delegate the fetch + response handling (proxy fetch, call-log, sanitized
// proxy event, result shaping) to the shared chokepoint in searchProxy.ts.
return executeProviderFetch({
config,
url,
init,
controller,
timer,
query,
searchType,
maxResults,
startTime,
connectionId,
proxy,
proxyLevel,
log,
normalize: normalizeResponse,
});
}

View File

@@ -0,0 +1,245 @@
/**
* Per-attempt proxy binding for web search provider calls.
*
* Extracted from ../search.ts (tryProvider) to keep the provider-dispatch
* chokepoint under the frozen file-size cap. Resolves the proxy for a given
* connection/apiKey/provider triple, wraps a fetch in that proxy context,
* and emits a sanitized proxy event for observability (never includes
* query, API key, or proxy credentials).
*/
import { saveCallLog } from "@/lib/usageDb";
import { sanitizeErrorMessage } from "../../utils/error.ts";
import type { SearchProviderConfig } from "../../config/searchRegistry.ts";
import type { SearchResult } from "../search.ts";
/** Resolved proxy binding for a single provider attempt. */
export interface ResolvedSearchProxy {
proxy: unknown;
proxyLevel: string;
}
/**
* Resolve the proxy for the selected connection. Uses the existing
* resolveProxyForConnection(connectionId, apiKeyId, providerId) precedence
* chain so per-key, account, provider, combo, and global proxy rules apply
* consistently with other data-plane routes.
*
* Never throws — proxy resolution failure must not block the search.
*/
export async function resolveSearchProxy(
connectionId: string | undefined,
apiKeyId: string | undefined,
providerId: string
): Promise<ResolvedSearchProxy> {
if (!connectionId) {
return { proxy: null, proxyLevel: "direct" };
}
try {
const { resolveProxyForConnection } = await import("@/lib/db/settings");
const proxyInfo = await resolveProxyForConnection(connectionId, apiKeyId, providerId);
return { proxy: proxyInfo.proxy, proxyLevel: proxyInfo.level || "direct" };
} catch {
return { proxy: null, proxyLevel: "direct" };
}
}
/**
* Run a fetch, routed through the resolved proxy context when one is set.
* Wraps the patched globalThis.fetch so the upstream call egresses via the
* configured proxy instead of directly.
*/
export async function fetchWithSearchProxy(
proxy: unknown,
doFetch: () => Promise<Response>
): Promise<Response> {
if (!proxy) return doFetch();
const { runWithProxyContext } = await import("../../utils/proxyFetch.ts");
return runWithProxyContext(proxy, doFetch);
}
/**
* Emit a sanitized proxy event for a search provider attempt.
* Never includes query, API key, proxy username, or proxy password.
*/
export async function emitSearchProxyEvent(
provider: string,
connectionId: string | undefined,
proxy: unknown,
proxyLevel: string,
targetUrl: string,
startTime: number,
status: string
): Promise<void> {
try {
const { logProxyEvent } = await import("@/lib/proxyLogger");
let targetOrigin = "";
let targetPath = "";
try {
const u = new URL(targetUrl);
targetOrigin = u.origin;
targetPath = u.pathname;
} catch {
targetOrigin = targetUrl.slice(0, 80);
}
const proxyRecord =
proxy && typeof proxy === "object" ? (proxy as Record<string, unknown>) : null;
const proxyInfo = proxyRecord
? {
type: String(proxyRecord.type || "http"),
host: String(proxyRecord.host || ""),
port: Number(proxyRecord.port || 0),
}
: null;
logProxyEvent({
status,
proxy: proxyInfo,
level: proxyLevel,
levelId: connectionId || null,
provider: provider || null,
targetUrl: `${targetOrigin}${targetPath}`,
latencyMs: Date.now() - startTime,
connectionId: connectionId || null,
account: connectionId ? connectionId.slice(0, 8) : null,
});
} catch {
// Non-critical — proxy logging must not block search response
}
}
/** Loose result shape mirroring SearchHandlerResult in ../search.ts. */
export interface ProviderFetchResult {
success: boolean;
status?: number;
error?: string;
data?: {
provider: string;
query: string;
results: SearchResult[];
answer: null;
usage: { queries_used: number; search_cost_usd: number };
metrics: { response_time_ms: number; upstream_latency_ms: number; total_results_available: number | null };
errors: [];
};
}
/** Minimal logger shape used by the search handlers (pino-compatible). */
export interface SearchLog {
info: (tag: string, message: string) => void;
error: (tag: string, message: string) => void;
warn?: (tag: string, message: string) => void;
}
export interface ExecuteProviderFetchParams {
config: SearchProviderConfig;
url: string;
init: RequestInit;
controller: AbortController;
timer: ReturnType<typeof setTimeout>;
query: string;
searchType: string;
maxResults: number;
startTime: number;
connectionId?: string;
proxy: unknown;
proxyLevel: string;
log?: SearchLog;
normalize: (
providerId: string,
data: unknown,
query: string,
searchType: string
) => { results: SearchResult[]; totalResults: number | null };
}
/**
* Perform the upstream search HTTP call (through the resolved proxy, if any),
* then handle the success/error/exception branches: call-log persistence,
* sanitized proxy-event emission, and SearchHandlerResult construction.
* This is the single chokepoint tryProvider() delegates to after building
* the request and resolving the proxy — keeps search.ts to wiring only.
*/
export async function executeProviderFetch(p: ExecuteProviderFetchParams): Promise<ProviderFetchResult> {
const { config, url, init, controller, timer, query, searchType, maxResults, startTime } = p;
const { connectionId, proxy, proxyLevel, log, normalize } = p;
const emitEvent = (status: string) =>
emitSearchProxyEvent(config.id, connectionId, proxy, proxyLevel, url, startTime, status);
const logCall = (fields: Record<string, unknown>) =>
saveCallLog({
method: config.method,
path: "/v1/search",
model: config.id,
provider: config.id,
connectionId: connectionId || null,
requestType: "search",
requestBody: { query: query.slice(0, 200), search_type: searchType, max_results: maxResults },
...fields,
}).catch(() => {
/* non-critical — logging must not block search response */
});
try {
const response = await fetchWithSearchProxy(proxy, () =>
fetch(url, { ...init, signal: controller.signal })
);
clearTimeout(timer);
if (!response.ok) {
const errorText = await response.text();
if (log) {
log.error("SEARCH", `${config.id} error ${response.status}: ${errorText.slice(0, 200)}`);
}
logCall({ status: response.status, duration: Date.now() - startTime, error: errorText.slice(0, 500) });
await emitEvent("error");
return {
success: false,
status: response.status,
error: `Search provider ${config.id} returned ${response.status}`,
};
}
const data = await response.json();
const normalized = normalize(config.id, data, query, searchType);
const results = normalized.results.slice(0, maxResults);
const duration = Date.now() - startTime;
logCall({
status: 200,
duration,
tokens: { prompt_tokens: 0, completion_tokens: 0 },
responseBody: { results_count: results.length, cached: false },
});
await emitEvent("success");
return {
success: true,
data: {
provider: config.id,
query,
results,
answer: null,
usage: { queries_used: 1, search_cost_usd: config.costPerQuery },
metrics: {
response_time_ms: duration,
upstream_latency_ms: duration,
total_results_available: normalized.totalResults,
},
errors: [],
},
};
} catch (err: unknown) {
clearTimeout(timer);
const error = err instanceof Error ? err : new Error(String(err));
const isTimeout = error.name === "AbortError";
if (log) {
log.error("SEARCH", `${config.id} ${isTimeout ? "timeout" : "fetch error"}: ${error.message}`);
}
logCall({ status: isTimeout ? 504 : 502, duration: Date.now() - startTime, error: error.message });
await emitEvent(isTimeout ? "timeout" : "error");
return {
success: false,
status: isTimeout ? 504 : 502,
error: `Search provider ${isTimeout ? "timeout" : "error"}: ${sanitizeErrorMessage(error.message)}`,
};
}
}

View File

@@ -1403,6 +1403,10 @@ export function createMcpServer(): McpServer {
* Called when `omniroute --mcp` is used.
*/
export async function startMcpStdio(): Promise<void> {
// Stdout is reserved for JSON-RPC — bin/mcpStdioConsoleGuard.mjs is preloaded via
// `node --import` (see bin/mcp-server.mjs) so console.log/warn already redirect to
// stderr before this module's own imports evaluate (DB init happens as a side effect of
// createMcpServer()'s tool registration, earlier than any code placed here could catch).
const server = createMcpServer();
const transport = new StdioServerTransport();
const version = process.env.npm_package_version || "1.8.1";

View File

@@ -1,18 +1,7 @@
{
"name": "@omniroute/open-sse",
"version": "3.8.50",
"description": "Express SSE sidecar for OmniRoute — handles streaming, protocol translation, and provider orchestration",
"description": "OmniRoute streaming engine — handles provider dispatch, protocol translation, and SSE streaming",
"type": "module",
"main": "index.js",
"types": "types.d.ts",
"private": true,
"exports": {
".": "./index.js",
"./*": "./*"
},
"dependencies": {
"@toon-format/toon": "^4.1.0",
"safe-regex": "^2.1.1",
"smol-toml": "1.7.1"
}
"private": true
}

View File

@@ -205,7 +205,7 @@ export async function applyStrategyOrdering(
if (resolvePromptCacheAffinityKey(body)) {
orderedTargets = await expandPromptCacheAffinityTargets(orderedTargets);
}
const affinity = applyPromptCacheAffinity(orderedTargets, body);
const affinity = applyPromptCacheAffinity(orderedTargets, body, true, "global");
orderedTargets = affinity.targets;
log.info(
"COMBO",

View File

@@ -266,14 +266,32 @@ export function shouldProtectOriginalFirst(
}
/**
* Order eligible targets using rendezvous hashing. The original order is used
* as the final tie-breaker, so targets sharing one account identity remain
* stable without using modelStr as the affinity identity.
* Extract the base model identity from a target's executionKey or modelStr.
* This strips any per-connection suffix (@connectionId) to identify the model itself.
*/
function getBaseModelIdentity(target: ResolvedComboTarget): string {
// executionKey format: "stepId@connectionId" when expanded, or just "stepId"
const executionKey = target.executionKey || "";
const baseExecutionKey = executionKey.split("@")[0];
// modelStr format: "provider/model" or "provider/model:version"
const modelStr = target.modelStr || "";
// Use executionKey as primary (preserves stepId grouping), fall back to modelStr
return baseExecutionKey || modelStr;
}
/**
* Order eligible targets using rendezvous hashing.
* @param scope - "model": sort only within same-model groups, preserving inter-model order;
* "global": sort across all targets (original behavior).
* Defaults to "global" for backward compatibility.
*/
export function applyPromptCacheAffinity(
targets: ResolvedComboTarget[],
body: Record<string, unknown> | null | undefined,
enabled: boolean = true
enabled: boolean = true,
scope: "model" | "global" = "global"
): PromptCacheAffinityResult {
const resolution = enabled ? resolvePromptCacheAffinityKey(body) : null;
if (!resolution || targets.length <= 1) {
@@ -290,19 +308,61 @@ export function applyPromptCacheAffinity(
index,
identity: promptCacheTargetIdentity(target),
score: rendezvousScore(resolution.key, promptCacheTargetIdentity(target)),
baseModel: scope === "model" ? getBaseModelIdentity(target) : null,
}));
ranked.sort((a, b) => {
if (a.score > b.score) return -1;
if (a.score < b.score) return 1;
const identityOrder = a.identity.localeCompare(b.identity);
return identityOrder !== 0 ? identityOrder : a.index - b.index;
});
if (scope === "model") {
// Group by base model identity, preserving original group order
const groups = new Map<string, typeof ranked>();
const groupOrder: string[] = [];
return {
targets: ranked.map((entry) => entry.target),
applied: true,
source: resolution.source,
fingerprint: resolution.fingerprint,
};
for (const entry of ranked) {
// baseModel is guaranteed non-null when scope === "model" (see map above)
const baseModel = entry.baseModel as string;
if (!groups.has(baseModel)) {
groups.set(baseModel, []);
groupOrder.push(baseModel);
}
groups.get(baseModel)!.push(entry);
}
// Sort within each group by score, then identity, then original index
const sortedGroups = groupOrder.map((baseModel) => {
const group = groups.get(baseModel)!;
return group.sort((a, b) => {
if (a.score > b.score) return -1;
if (a.score < b.score) return 1;
const identityOrder = a.identity.localeCompare(b.identity);
return identityOrder !== 0 ? identityOrder : a.index - b.index;
});
});
// Flatten groups in original order
const sortedTargets = sortedGroups.flatMap((group) => group.map((entry) => entry.target));
// Check if the order actually changed (for applied flag)
const orderChanged = !targets.every((target, i) => target === sortedTargets[i]);
return {
targets: sortedTargets,
applied: orderChanged, // Only true if the order actually changed
source: resolution.source,
fingerprint: resolution.fingerprint,
};
} else {
// Original global sorting behavior
ranked.sort((a, b) => {
if (a.score > b.score) return -1;
if (a.score < b.score) return 1;
const identityOrder = a.identity.localeCompare(b.identity);
return identityOrder !== 0 ? identityOrder : a.index - b.index;
});
return {
targets: ranked.map((entry) => entry.target),
applied: true,
source: resolution.source,
fingerprint: resolution.fingerprint,
};
}
}

View File

@@ -177,46 +177,113 @@ function normalizeWindowPercentUsed(value: unknown): number | null {
return clamp01(numericValue);
}
type QuotaWindowSnapshot = { percentUsed: number | null; resetAt: string | null };
/**
* Pick the first candidate that actually carries a reset instant, falling back
* to the first present candidate. A window can be structurally present but
* carry `resetAt: null` (e.g. Codex's `window7d` placeholder when the upstream
* only reported the primary limit); a plain `a || b` short-circuit would let
* that empty window shadow a sibling that does know when it resets — #9330.
*/
function pickWindowWithResetAt(
...candidates: Array<QuotaWindowSnapshot | null>
): QuotaWindowSnapshot | null {
return candidates.find((candidate) => candidate?.resetAt) ?? candidates.find(Boolean) ?? null;
}
function getNamedQuotaWindow(
quota: unknown,
windowName: ResetWindowName
): { percentUsed: number | null; resetAt: string | null } | null {
): QuotaWindowSnapshot | null {
if (!quota || !isRecord(quota)) return null;
if (windowName === "session") return getQuotaWindow(quota, "window5h");
if (windowName === "weekly") {
return getQuotaWindow(quota, "window7d") || getQuotaWindow(quota, "windowWeekly");
return pickWindowWithResetAt(
getQuotaWindow(quota, "window7d"),
getQuotaWindow(quota, "windowWeekly")
);
}
if (windowName === "monthly") return getQuotaWindow(quota, "windowMonthly");
return null;
}
function getWindowsMapQuotaWindow(
quota: unknown,
windowName: ResetWindowName
): { percentUsed: number | null; resetAt: string | null } | null {
if (!quota || !isRecord(quota) || !isRecord(quota.windows)) return null;
const candidates = Object.entries(quota.windows)
.map(([key, value]) => ({ key: key.toLowerCase(), value }))
.filter(({ key }) => key === windowName || key.startsWith(`${windowName} `));
if (candidates.length === 0) return null;
candidates.sort((a, b) => a.key.localeCompare(b.key));
const window = candidates[0].value;
function toWindowSnapshot(window: unknown): QuotaWindowSnapshot | null {
if (!isRecord(window)) return null;
return {
percentUsed: normalizeWindowPercentUsed(window.percentUsed),
resetAt: normalizeResetAt(window.resetAt),
};
}
/**
* Every entry of the snapshot's `windows` map, name lower-cased.
*
* Deliberately reads `windows` only, never Codex's wider `allWindows`: for a
* Spark request `fetchCodexQuota` narrows `windows` to the Spark scope on
* purpose, and pulling the normal-scope entries back in would rank a request
* against a window it cannot spend.
*/
function getQuotaWindowEntries(
quota: unknown
): Array<{ key: string; window: QuotaWindowSnapshot }> {
if (!quota || !isRecord(quota) || !isRecord(quota.windows)) return [];
const entries: Array<{ key: string; window: QuotaWindowSnapshot }> = [];
for (const [key, value] of Object.entries(quota.windows)) {
const window = toWindowSnapshot(value);
if (window) entries.push({ key: key.toLowerCase(), window });
}
return entries;
}
function getWindowsMapQuotaWindow(
quota: unknown,
windowName: ResetWindowName
): QuotaWindowSnapshot | null {
const candidates = getQuotaWindowEntries(quota).filter(
({ key }) => key === windowName || key.startsWith(`${windowName} `)
);
if (candidates.length === 0) return null;
candidates.sort((a, b) => a.key.localeCompare(b.key));
// Prefer a candidate that knows when it resets (e.g. "weekly" vs a scoped
// "weekly (spark)" placeholder without a resetAt) — #9330.
return pickWindowWithResetAt(
...candidates.filter(({ window }) => window.resetAt).map(({ window }) => window),
candidates[0].window
);
}
function resolveQuotaWindowByName(
quota: unknown,
windowName: ResetWindowName
): { percentUsed: number | null; resetAt: string | null } | null {
return getNamedQuotaWindow(quota, windowName) || getWindowsMapQuotaWindow(quota, windowName);
): QuotaWindowSnapshot | null {
return pickWindowWithResetAt(
getNamedQuotaWindow(quota, windowName),
getWindowsMapQuotaWindow(quota, windowName)
);
}
/**
* Earliest reset instant across EVERY window a snapshot exposes, regardless of
* how the provider named it.
*
* Last-resort normalizer for #9330: providers routed through
* `genericQuotaFetcher.convertUsageToQuotaInfo` key their `windows` map by
* MODEL ID (Antigravity: "gemini-3-flash", "claude-sonnet-5", …), so none of
* the canonical "weekly" | "session" | "monthly" lookups match. Without this
* those accounts resolved to `Infinity` ("never resets") and were sorted behind
* a Codex account whose secondary window was 26 days out.
*/
function getEarliestWindowResetMs(quota: unknown): number {
let earliest = Infinity;
for (const { window } of getQuotaWindowEntries(quota)) {
const resetMs = parseResetTimeMs(window.resetAt);
if (Number.isFinite(resetMs)) earliest = Math.min(earliest, resetMs);
}
return earliest;
}
function getResetUrgency(resetAt: string | null | undefined, windowMs: number): number {
@@ -276,6 +343,23 @@ export function scoreResetAwareQuota(
return { score };
}
/**
* Absolute epoch-ms instant at which the configured quota window next resets,
* or `Infinity` when the snapshot exposes no parseable reset (which sorts the
* target last under the `reset-window` strategy).
*
* Resolution order — each step only runs when the previous one found nothing:
* 1. the configured windows, by canonical name (structural `window5h` /
* `window7d` / `windowWeekly` / `windowMonthly` fields, then a `windows`
* map keyed by "weekly" | "session" | "monthly");
* 2. the earliest reset across every entry of the `windows` map, whatever the
* provider named them (Antigravity keys its map by model id — #9330);
* 3. the single-signal top-level `quota.resetAt`.
*
* Step 2 sits ahead of step 3 deliberately: `quota.resetAt` is populated from
* the most-USED window, which is not necessarily the one resetting soonest, and
* is left null entirely while every window is still at 0% used.
*/
export function getResetWindowTimestampMs(quota: unknown, windows: ResetWindowName[]): number {
if (!quota || !isRecord(quota) || quota.limitReached === true) return Infinity;
@@ -288,6 +372,10 @@ export function getResetWindowTimestampMs(quota: unknown, windows: ResetWindowNa
}
}
if (!Number.isFinite(selectedResetMs)) {
selectedResetMs = getEarliestWindowResetMs(quota);
}
if (!Number.isFinite(selectedResetMs)) {
selectedResetMs = parseResetTimeMs(normalizeResetAt(quota.resetAt));
}
@@ -295,6 +383,26 @@ export function getResetWindowTimestampMs(quota: unknown, windows: ResetWindowNa
return Number.isFinite(selectedResetMs) ? selectedResetMs : Infinity;
}
/**
* Milliseconds remaining until the configured window resets — the uniform
* metric the `reset-window` strategy sorts on (ascending: soonest first).
*
* Normalizing to a duration (rather than comparing raw epoch timestamps) keeps
* every provider on one scale and collapses already-elapsed resets to 0, so a
* snapshot that is stale by three days ties with one that reset a second ago
* instead of jumping the queue by virtue of being older. `Infinity` means "no
* known reset" and sorts last.
*/
export function getResetWindowRemainingMs(
quota: unknown,
windows: ResetWindowName[],
now: number = Date.now()
): number {
const resetMs = getResetWindowTimestampMs(quota, windows);
if (!Number.isFinite(resetMs)) return Infinity;
return Math.max(0, resetMs - now);
}
function getResetWindowHorizonMs(windows: ResetWindowName[]): number {
if (windows.includes("monthly")) return 30 * 24 * 60 * 60 * 1000;
if (windows.includes("weekly")) return RESET_AWARE_WEEKLY_WINDOW_MS;

View File

@@ -41,7 +41,7 @@ import {
resolveResetWindowConfig,
getResetAwareProvider,
scoreResetAwareQuota,
getResetWindowTimestampMs,
getResetWindowRemainingMs,
type QuotaFetchCacheConfig,
} from "./quotaScoring.ts";
import { rankByHeadroom, type HeadroomSaturation } from "./headroomRanking.ts";
@@ -536,27 +536,35 @@ export async function orderTargetsByResetWindow(
apiKeyAllowedConnectionIds
);
// One `now` snapshot for the whole ranking: quota fetches run concurrently and
// can take seconds, so re-reading the clock per target would compare remaining
// times measured against different instants (#9330).
const now = Date.now();
const scoredTargets = await scoreQuotaAwareTargets({
comboName,
config,
connectionById,
expandedTargets,
log,
scoreQuota: (quota) => ({ resetMs: getResetWindowTimestampMs(quota, config.windows) }),
scoreQuota: (quota) => ({
remainingMs: getResetWindowRemainingMs(quota, config.windows, now),
}),
});
// Ascending: the account whose quota resets SOONEST goes first. Targets with
// no known reset (Infinity) fall to the back, ordered by combo priority.
scoredTargets.sort((a, b) => {
if (a.resetMs !== b.resetMs) return a.resetMs - b.resetMs;
if (a.remainingMs !== b.remainingMs) return a.remainingMs - b.remainingMs;
return a.index - b.index;
});
const bestResetMs = scoredTargets[0]?.resetMs ?? Infinity;
if (!Number.isFinite(bestResetMs) || config.tieBandMs <= 0) {
const bestRemainingMs = scoredTargets[0]?.remainingMs ?? Infinity;
if (!Number.isFinite(bestRemainingMs) || config.tieBandMs <= 0) {
return scoredTargets.map((entry) => entry.target);
}
const tiedTargets = scoredTargets.filter(
(entry) => entry.resetMs - bestResetMs <= config.tieBandMs
(entry) => entry.remainingMs - bestRemainingMs <= config.tieBandMs
);
if (tiedTargets.length <= 1) return scoredTargets.map((entry) => entry.target);

Some files were not shown because too many files have changed in this diff Show More