Commit Graph

1556 Commits

Author SHA1 Message Date
Davide Baraldo
8a95a2bced fix(settings): cache-config alwaysPreserveClientCache was a runtime no-op (#12304)
Validado em worktree combinada sobre o tip de release/v3.8.51: os dois boardaram sem conflito, typecheck:core limpo, check-file-size sem violação nova (as duas restantes — codex.ts e stream.ts — são drift anterior) e 51/51 nos 5 arquivos de teste que os PRs trazem.
2026-09-03 20:48:40 -03:00
Diego Rodrigues de Sa e Souza
f51cd295c8 chore(providers): bump Claude Code wire identity + Devin bridge pin to 2.1.258 (#12604)
17 de 18 checks verdes, **zero falhas** — incluindo os quatro shards de unit, Vitest, CodeQL, semgrep, Docs Gates, Merge integrity e o **Fast Quality Gates**, que era exatamente o que o rebaseline do `RoutingTab.tsx` (1606→1607, a linha do seletor que acompanha a nova versão de identidade) veio consertar.

O único check restante, `No new ESLint warnings`, ficou enfileirado sem iniciar (duração 0) atrás da saturação de runners de hoje. Cobri os dois comandos que ele executa, localmente e sobre este HEAD:

- `npm run check:codeql-ratchet` → **0 alertas abertos** contra baseline 6 (sem regressão).
- `npx eslint --max-warnings 0` nos 9 arquivos de código que esta branch altera → **exit 0**, nenhum warning.

Conteúdo: os dois commits do #12402 que não estavam subsumidos, com autoria do @ggiak preservada pelo cherry-pick `-x`, mais o rebaseline e o fragmento de changelog que faltava. `typecheck:core` limpo e **138/138** nos testes focados. As três versões (`claudeCodeClient.ts`, `Dockerfile`, `compose.yml`) conferem em 2.1.258, e `npm view @anthropic-ai/claude-code@2.1.258` resolve — o Dockerfile instala esse pin exato.
2026-09-03 13:49:46 -03:00
Diego Rodrigues de Sa e Souza
ad500de9e3 chore(quality): rebaseline file-size caps the hartmark batch grew past (#12623)
Rebaseline medido no tip com os 9 PRs da leva hartmark mergeados. Não toca codex.ts nem stream.ts, drift anterior à leva.
2026-09-03 13:04:34 -03:00
Diego Rodrigues de Sa e Souza
d9526cefea chore(quality): rebaseline file-size caps the HouMinXi batch grew past (#12619)
Rebaseline medido no tip com os 9 PRs da leva mergeados. Desfaz o vermelho de file-size que os PRs empilhados deixaram; não toca codex.ts nem stream.ts, que já violavam antes da leva.
2026-09-03 12:49:53 -03:00
Bob.Hou
a47d2e521e feat(providers): add SeekAi OpenAI-compatible New-API gateway (#12557)
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51`: `typecheck:core` limpo, `check:provider-consistency` OK (273 entradas REGISTRY, **356** providers canônicos), `check-docs-counts-sync` exit 0 e **300/300** nos testes que a leva toca.

O crescimento de arquivo que os PRs empilham uns sobre os outros foi rebaselinado num único registro datado (`_rebaseline_2026_09_03_houminxi_batch`), com a decomposição por arquivo: `providers/page.tsx` +18 (import CSV do #12504 + busca do #12495 no mesmo painel), `accountFallback.ts` +6 (o #12566 sobre o rebaseline que o #12590 já registrou — os dois tocam `checkFallbackError`) e `chatCore.ts` +3 (invalidez de cache de quota no 429 do #12325). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
2026-09-03 12:46:06 -03:00
Bob.Hou
35caeb31f2 feat(settings): persist headroomUrl for the Headroom proxy (#12487)
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51`: `typecheck:core` limpo, `check:provider-consistency` OK (273 entradas REGISTRY, **356** providers canônicos), `check-docs-counts-sync` exit 0 e **300/300** nos testes que a leva toca.

O crescimento de arquivo que os PRs empilham uns sobre os outros foi rebaselinado num único registro datado (`_rebaseline_2026_09_03_houminxi_batch`), com a decomposição por arquivo: `providers/page.tsx` +18 (import CSV do #12504 + busca do #12495 no mesmo painel), `accountFallback.ts` +6 (o #12566 sobre o rebaseline que o #12590 já registrou — os dois tocam `checkFallbackError`) e `chatCore.ts` +3 (invalidez de cache de quota no 429 do #12325). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
2026-09-03 12:39:27 -03:00
Bob.Hou
c2d2b0ac14 feat(providers): surface CSV import row errors and ship a template (#12504)
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51`: `typecheck:core` limpo, `check:provider-consistency` OK (273 entradas REGISTRY, **356** providers canônicos), `check-docs-counts-sync` exit 0 e **300/300** nos testes que a leva toca.

O crescimento de arquivo que os PRs empilham uns sobre os outros foi rebaselinado num único registro datado (`_rebaseline_2026_09_03_houminxi_batch`), com a decomposição por arquivo: `providers/page.tsx` +18 (import CSV do #12504 + busca do #12495 no mesmo painel), `accountFallback.ts` +6 (o #12566 sobre o rebaseline que o #12590 já registrou — os dois tocam `checkFallbackError`) e `chatCore.ts` +3 (invalidez de cache de quota no 429 do #12325). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
2026-09-03 12:39:03 -03:00
Bob.Hou
52456a1cea fix(quota): drop generic quota cache on upstream 429 (#12325)
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51`: `typecheck:core` limpo, `check:provider-consistency` OK (273 entradas REGISTRY, **356** providers canônicos), `check-docs-counts-sync` exit 0 e **300/300** nos testes que a leva toca.

O crescimento de arquivo que os PRs empilham uns sobre os outros foi rebaselinado num único registro datado (`_rebaseline_2026_09_03_houminxi_batch`), com a decomposição por arquivo: `providers/page.tsx` +18 (import CSV do #12504 + busca do #12495 no mesmo painel), `accountFallback.ts` +6 (o #12566 sobre o rebaseline que o #12590 já registrou — os dois tocam `checkFallbackError`) e `chatCore.ts` +3 (invalidez de cache de quota no 429 do #12325). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
2026-09-03 12:38:39 -03:00
Bob.Hou
0f5fc78d8a feat(providers): search connections by name and baseUrl (#12495)
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51`: `typecheck:core` limpo, `check:provider-consistency` OK (273 entradas REGISTRY, **356** providers canônicos), `check-docs-counts-sync` exit 0 e **300/300** nos testes que a leva toca.

O crescimento de arquivo que os PRs empilham uns sobre os outros foi rebaselinado num único registro datado (`_rebaseline_2026_09_03_houminxi_batch`), com a decomposição por arquivo: `providers/page.tsx` +18 (import CSV do #12504 + busca do #12495 no mesmo painel), `accountFallback.ts` +6 (o #12566 sobre o rebaseline que o #12590 já registrou — os dois tocam `checkFallbackError`) e `chatCore.ts` +3 (invalidez de cache de quota no 429 do #12325). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
2026-09-03 12:38:16 -03:00
Bob.Hou
c9fb06e26c fix(grok-cli): treat omitted SuperGrokPro creditUsagePercent as 0% (#12312)
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51`: `typecheck:core` limpo, `check:provider-consistency` OK (273 entradas REGISTRY, **356** providers canônicos), `check-docs-counts-sync` exit 0 e **300/300** nos testes que a leva toca.

O crescimento de arquivo que os PRs empilham uns sobre os outros foi rebaselinado num único registro datado (`_rebaseline_2026_09_03_houminxi_batch`), com a decomposição por arquivo: `providers/page.tsx` +18 (import CSV do #12504 + busca do #12495 no mesmo painel), `accountFallback.ts` +6 (o #12566 sobre o rebaseline que o #12590 já registrou — os dois tocam `checkFallbackError`) e `chatCore.ts` +3 (invalidez de cache de quota no 429 do #12325). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
2026-09-03 12:37:54 -03:00
Diego Rodrigues de Sa e Souza
c41d8755db fix(ci): document eloqnt MIT exceptions and isolate A2A vitest (#12595)
Validado numa worktree sobre o tip de `release/v3.8.51`. Verifiquei as premissas em vez de aceitar a justificativa:

**Exceções de licença** — todas as afirmações batem. Os três pacotes estão instalados exatamente nas versões travadas citadas (`@eloqnt/config@0.0.2`, `@eloqnt/format-json@0.0.3`, `@eloqnt/format-po@0.0.3`), os três **omitem `package.json#license` e não têm arquivo LICENSE**, e `npm ls` confirma que são transitivos de `next-intl@4.14.1` (MIT). No registry, o SPDX das três é MIT e o `@eloqnt/config@0.1.0` existe, como o texto diz. Uso de `exceptions` (com `risk`/`reviewAt`) em vez de `allowed: UNKNOWN` é o mecanismo certo.

`check:licenses` verde: **0 violações de política**, 948 permitidos, as três novas entradas aparecendo como exceções sinalizadas não bloqueantes ao lado das duas já existentes. Teste-guarda `tests/unit/build/check-licenses.test.ts`: **36/36**.

**Vitest do A2A lifecycle** — as duas seams usadas já existiam antes deste PR: `constructor(ttlMinutes = 5, persistence: A2APersistence = defaultPersistence)` e o 4º parâmetro opcional `deps?: MemoryHitsDeps` de `executeA2ATaskWithState`. O padrão é idêntico ao que `tests/unit/a2a-task-persistence.test.ts` já fazia. Nenhuma asserção foi removida ou enfraquecida — e como o comportamento de persistência tem suíte dedicada, injetar no-ops aqui tira acoplamento incidental, não cobertura.

Ganho medido nos dois lados: corpo dos testes **784ms no tip → 233ms com o PR**, sem nenhuma linha `[DB] SQLite database ready`. Registro honesto: **no tip o arquivo passa localmente** (4/4) — o red era do CI, sob o thread pool com as 167 migrações; localmente dá para comprovar o mecanismo e a aceleração, não a falha em si.

Suíte vitest completa: **51/51 arquivos, 465/465 testes**.
2026-09-03 08:39:46 -03:00
Diego Rodrigues de Sa e Souza
7d0b264eda chore(electron): drop openAsHidden/wasOpenedAsHidden, removed in Electron 44 (#12554)
CI verde: 18 checks passando (4 shards de unit, Vitest, CodeQL, Fast Quality Gates, No new ESLint warnings, semgrep). Local: 8/8 no teste atualizado e 165/165 nos 16 arquivos tests/unit/electron-*.test.ts.
2026-09-03 08:32:22 -03:00
Diego Rodrigues de Sa e Souza
a628d28898 feat(dashboard): orchestration canvas fase 2 — repeat action + A2A memory hits (2.6/2.7) (#12508)
* feat(api): conductor task creation route (repeat support)

* feat(a2a): record memoryHits consulted per task (observability, 2.7)

* feat(dashboard): repeat action in orchestration drawer (2.6 — repeat only)

* fix(dashboard): require every field each repeat contract needs before enabling the action

* feat(dashboard): memory-used drawer section + fase2 i18n/changelog (2.7)

* fix(dashboard): validate memoryHits shape before rendering + locale wording fixes

* fix(dashboard,a2a): stop memoryHits leaking into repeats, harden drawer guard + status clamp

Final whole-branch review fix wave for the Orchestration Canvas Fase 2 PR-C.

- a2a: `createTask` stores a COPY of `input.metadata` instead of aliasing it, so the
  observability `memoryHits` written by `executeA2ATaskWithState` no longer leak into
  `task.input.metadata`, into the persisted `a2a_tasks.input_json`, or into the drawer's
  "Repeat" body (a repeated task was born carrying the previous run's memory snippets,
  even with the `OMNIROUTE_A2A_MEMORY_HITS=0` kill-switch on).
- dashboard: `repeatReqFor` strips `memoryHits` from the a2a repeat metadata, so tasks
  persisted before the copy-fix do not propagate them either.
- dashboard: the "Memory used" section now requires all four rendered fields (id, key,
  type, snippet) to be strings — `{ id: "x", key: { a: 1 } }` used to throw "Objects are
  not valid as a React child" and take the whole drawer down.
- dashboard: an `/a2a` action answered with a JSON-RPC error under HTTP 200 is reported as
  a failure (`RPC <code>`, code only — never the upstream message) instead of a success
  toast; the secured-deployment rejection keeps surfacing the sanitized `HTTP 400`.
- api: the conductor task-creation route clamps a hub status outside 400-599 to 502, so an
  out-of-range status can no longer turn a hub refusal into a `RangeError`.
- dashboard: the History tab's `onActionDone` keeps the drawer mounted (and refreshes the
  range) instead of closing it, so the repeat/cancel confirmation is actually visible.
- a2a: documented the recall owner-id limitation — `task.owner` is a SHA-256 key prefix
  while memory rows are keyed by the DB api-key id, and no hash-to-id lookup exists today,
  so recall only resolves under the keyless posture.

* refactor(dashboard): split drawer repeat helpers and test file under the size/complexity gates

---------

Co-authored-by: Markus Hartung <diegosouzapw@users.noreply.github.com>
2026-09-02 20:37:02 -03:00
Diego Rodrigues de Sa e Souza
143c087d47 feat(i18n): locale-expansion tooling — add-locale orchestrator, aliases, translation-ratio gate; retire duplicate 'in' locale (#12496)
Locale aliases (uk, fil/tl, zh-hant, legacy in→id) with fixed browser/CLI/cookie detection; config/i18n.json shipped in the npm package; duplicate 'in' locale retired (42 honest locales); real-translation ratio gate with ratchet baseline (advisory); add-locale orchestrator + shared libs (batched translation backend, config-generated language bars, run-translation --adopt/--targets-only, scaffold helpers); tests derive the locale count from config with surface parity guards; docs and every public locale-count claim aligned.
2026-09-02 17:45:50 -03:00
Diego Rodrigues de Sa e Souza
84b345d9c0 feat(dashboard): orchestration canvas fase 2 — History tab over persisted A2A runs (2.2) (#12479)
* feat(db): a2a task history module over migration-002 tables

* feat(a2a): persist task lifecycle to a2a_tasks with 30d retention purge

* feat(api): a2a task history listing + historical detail fallback

* feat(dashboard): orchestration History tab (Airflow-grid) over persisted runs (2.2)

* fix(dashboard): assert history preset window + loading and time axis in the grid

* chore(dashboard): history i18n + changelog

* fix(dashboard): explicit history purge cascade + keep live drawer off the History tab

* docs: document OMNIROUTE_A2A_HISTORY_RETENTION_DAYS

* refactor(dashboard): split HistoryTab helpers under the complexity ratchet

---------

Co-authored-by: Markus Hartung <diegosouzapw@users.noreply.github.com>
2026-09-02 15:07:58 -03:00
Diego Rodrigues de Sa e Souza
500568a1cd fix(providers): migrate web cookie TLS transport to wreq-js (#12429)
Migrates the Claude, Grok, LMArena, Notion and Perplexity web-cookie transports from the tls-client-node/Koffi sidecar to the exactly pinned wreq-js 3.2.0 runtime, keeping the per-provider browser/OS profiles, making request cookies ephemeral, bounding and generation-protecting the shared native transport pool, removing the legacy downloader and repair path, and carrying the native binding and license evidence through the npm, standalone, Electron, Docker and Bun packaging surfaces.

This is the consolidation of the two competing migrations, and the consolidation was decided by evidence rather than by preference. #11753's six suites were installed over this implementation and run as an independent specification: 31 of 36 passed. All five failures are artefacts of #11753 being the older design, not coverage gaps —

- two hardcode the 3.0.0 pin in their assertions (this branch pins 3.2.0, which is what the release tip already resolves; #11753's 3.0.0 would have conflicted);
- one reads open-sse/services/chatgptTlsClient.ts, deleted when #11754 retired ChatGPT Web, so the test is stale against the current tip;
- two import WREQ_JS_NATIVE_BINARY_NAMES / resolveWreqJsNativeBinaryName, which this branch redesigned into WREQ_JS_NATIVE_BINDINGS / resolveWreqJsNativeBinding plus WREQ_JS_VERSION — a rename from modelling natives as file names to modelling them as package bindings, verified as an API difference rather than a lost capability (the linux-x64-gnu .node is present and serviceable).

This branch is also the strict superset by scope: 7 files exclusive to it, including the wreq-js Rust license inventory and notices, .trivyignore, open-sse/utils/tlsClient.ts and assembleStandalone.mjs. #11753 had one exclusive file, its changelog fragment. Nothing needed porting, so #11753 is superseded rather than merged, and the changelog entry credits both.

Reconciled on merge: clean against the tip. The new migration suite (tests/unit/tls-client-wreq-migration.test.ts, 1374 lines, 31 cases) is frozen at its exact LOC with the rationale — it shares one native-transport harness, so splitting it mid-merge would duplicate that harness for no coverage gain. Verified that no existing cap moves.

Verified: 182/182 across the eight TLS, native-manifest, postinstall, standalone-bundle, pack-artifact and provider-validation suites, typecheck:core clean, check:cycles OK, check-changelog-integrity OK, check-file-size OK, and every changed TypeScript file parses.
2026-09-02 10:41:00 -03:00
Diego Rodrigues de Sa e Souza
c420a51df6 fix(providers): finalize Nimble and Opper asset provenance (#12415)
Closes the two remaining unresolved provider-asset records without weakening the provenance boundary: public/providers/nimble-search.svg is removed because no sufficient redistribution evidence is recorded, and Nimble Search renders with the internal generic icon before any CDN tier while staying fully functional; Opper is registered in the local SVG resolver with its existing bytes proven against opper-ai/provider-omniroute at immutable commit 9aacef7d6a, recording the MIT repository license from that same commit while keeping trademarkClearance null.

Validated in a combined worktree with the batch's ready set boarded onto the current tip: parse sweep clean on every changed TypeScript file, typecheck:core clean, check:dashboard-typecheck OK (207 pre-existing errors, all within baseline), check:cycles OK, check-file-size OK, 203/205 focused node tests and 94/94 vitest — the two failures belong to #12427, which is held back.
2026-09-02 10:02:03 -03:00
Paco Cartones
2c6e6cd13e fix(providers): list gemini-business models in the registry (#12389)
GET /v1/providers/gemini-business/models returned nothing because gemini-business had no RegistryEntry: the listing route resolves the provider through getRegistryEntry and filters the unified catalog by owned_by, and open-sse/config/providers/index.ts only registered gemini and gemini-web.

Adds a registry entry mirroring gemini_webProvider — id gemini-business, alias gembiz, cookie auth — with the twelve ids from the executor's MODEL_CATEGORY_MAP. Each model is declared toolCalling: false, supportsReasoning: false, the same live-behaviour contract applied to gemini-web in #9356: the executor returns plain text, hard-wires the thinking mode and parses no tool calls.

Reconciled on merge: the only conflict was the reserved-prefix count assertion, which the tip had moved. Took the tip's text and measured the real value with this PR applied — 406 to 408, the gemini-business id plus its gembiz alias — rather than carrying the branch's number.

Validated in a combined worktree with all 25 PRs of this batch boarded together (typecheck:core clean, 443/443 node-runner plus 14/14 vitest, all static gates green), and re-verified standalone on the current tip after the other 24 landed: 33/33 across provider-node-reserved-prefix, gemini-business-model-registry-12107 and web-cookie-validation-fallback, with check:provider-consistency OK at 272 REGISTRY entries and 355 canonical providers.

Thanks @pacocartones.
2026-09-02 03:19:08 -03:00
Paco Cartones
f41a9bd835 feat(admin): localize the anomalies page and add it to the sidebar (#12401)
The gamification anomalies page had hard-coded English for its loading state, Status column header and Suspicious badge, and was the only standalone non-redirect dashboard page without a sidebar entry. Both are fixed: the strings come from the common catalog, and the page joins the Gamification sidebar group as a hideable section item shown only by the "all" preset, like its siblings. The loading and empty states also become role="status" aria-live="polite" live regions with aria-busy, matching profile/page.tsx and health/page.tsx. Three new keys in en.json, propagated to the other 42 locales with the __MISSING__ sentinel.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:15:42 -03:00
Paco Cartones
26d20a0009 fix(api-manager): preserve allowedCombos entries the Combo picker cannot render (#12397)
The API key permissions modal silently dropped allowedCombos entries its Combo picker cannot render — routing-rule names such as rt-*, which matchesComboAccessRule() already honours. Stored entries rendered as zero selected, and clicking All then Restrict then Save persisted allowedCombos: [], which is deny-all for combo requests.

Those entries now survive the All toggle, are listed read-only under the combo list so the header count and the list agree, and are saved back verbatim. The UI does not learn routing-rule semantics (option 1 from the issue). The Allowed Combos section moves out of the frozen ApiManagerPageClient.tsx into its own component following the UsageLimitSettings pattern.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:15:31 -03:00
Paco Cartones
990aeca1db fix(api): list self-aliased providers in canonical models catalog mode (#12381)
GET /v1/models with MODELS_CATALOG_PREFIX_MODE=canonical dropped every chat row of a provider whose registry alias is undefined (antigravity) or equal to its own id (agy, most built-ins). Each emission loop pushes alias/model only when includeAlias, and canonicalProviderId/model only when the ids differ — for a self-aliased provider both are the same string, so neither fired. #11918 fixed the class for custom nodes but not built-ins, and not the static loop. The alias row is now treated as the canonical row whenever the ids coincide, across the static, synced, custom and alias-backed loops; the canonical branch's !== alias guard is untouched, so dual and alias output cannot double up. Docs that described the omission as intended are corrected.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:15:24 -03:00
Paco Cartones
0ec7504024 fix(sse): preserve ZWNJ and ZWJ in sanitized responses (#12359)
The response de-obfuscation stripped the whole U+200B..U+200D range, so Persian/Kurdish half-spaces (U+200C), Arabic/Indic shaping and emoji ZWJ sequences (U+200D) were deleted from every assistant response — text, reasoning and tool-call arguments, streaming and non-streaming, every provider: ارائه‌دهنده came back as ارائهدهنده.

The request side only ever inserts a U+200D between two ASCII word characters, so the new stripObfuscationZeroWidth() removes a joiner only there, or at a string edge next to one so a word split across streaming deltas is still cleaned; U+200B and U+FEFF keep their unconditional removal. All seven copies of the old regex now go through the helper.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:15:19 -03:00
Paco Cartones
5a34111125 fix(resilience): count resolved 5xx results against the provider breaker (#12360)
CircuitBreaker.execute() treated every resolved promise as a success, but handleChatCore() reports most upstream failures by resolving with { success: false, status: 5xx }. On the chat path that spurious _onSuccess() decayed failureCount right before the call site's _onFailure() for the same attempt, so a provider answering 503s indefinitely stayed CLOSED at failureCount: 1 and kept receiving traffic — the breaker was structurally unable to open. Combo dispatches hit the same cancellation through the shared per-provider breaker.

execute() now takes an optional per-call classifyResult; without it the resolved-means-success contract every throw-based caller relies on is unchanged. executeChatWithBreaker() passes ignore and the chat path accounts for the outcome exactly once where the request context lives, so a combo success is no longer counted twice.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:14:54 -03:00
Paco Cartones
62e2481eef fix(resilience): derive the chat_admission_busy Retry-After from observed lease occupancy (#12395)
The retryable chat_admission_busy 503 advertised a fixed Retry-After of 1s or 2s while the heavyweight lease it waits on is held for the entire SSE lifetime. Clients that honour the header — Codex CLI, agent fan-out — re-sent the same ~1 MiB /v1/responses body every second into a gate that could not have cleared, producing the queue_timeout retry storm that persisted even after OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT was raised.

ChatAdmissionController now tracks each live heavy lease's acquisition time and derives the hint from observed occupancy: the larger of the queue window the waiter already exhausted and the age of the youngest live lease, rounded up and capped at 60s. Both builders floor it at the historical 1s / 2s, so an idle gate answers exactly as before. Using the youngest rather than the oldest lease avoids a pessimistic hint when several slots are in flight.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:14:50 -03:00
Paco Cartones
5a490b19e2 feat(gamification): show API key names on the leaderboard (#12385)
The Leaderboard rendered apiKeyId.slice(0, 8)… under a column translated as "name". The route now enriches each entry with the key's display name — route-local, so the shared getTopN helper and the federation leaderboard stay id-only — and the page renders name ?? shortId with the full id in a title attribute. The lookup selects only id and name from api_keys, chunked at 200 ids, with unknown ids and blank names omitted; no key material leaves the DB layer.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:14:46 -03:00
Paco Cartones
4f4aa74199 feat(gamification): show the real daily streak on the profile page (#12377)
The Profile page already rendered a streak card but fed it a hard-coded useState(0) with a "streak data comes from future API" note — while streaks.ts tracked per-key streaks all along and the MCP gamification_profile tool already returned them. GET /api/gamification/level now returns streak: { current, longest } next to level: the key's own streak with apiKeyId, the operator-wide maximum otherwise, matching the aggregate mode getAggregateXp uses (#3484). No new route, no OpenAPI change, no new i18n keys; a missing or zero streak keeps the card hidden exactly as before.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:14:41 -03:00
Paco Cartones
01d97beb8b fix(translator): drop unsigned thinking blocks instead of fabricating a Claude signature (#12386)
A thinking content part arriving with no signature — typical after a cross-provider hop where reasoning_content was converted into a thinking block — was stamped with DEFAULT_THINKING_CLAUDE_SIGNATURE. prepareClaudeRequest treats any non-empty signature on the latest assistant turn as genuine and preserves it verbatim, so the fabricated one reached Anthropic and the replay failed with "Invalid signature". A missing signature is now treated the same as an empty one, aligned with the stricter check claudeHelper.ts already used: the block is dropped rather than fabricated. Real signatures are still preserved verbatim and redacted_thinking is unchanged.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:14:18 -03:00
Paco Cartones
70f33e323c fix(executors): let the ambient proxy stand when an OpenCode account has none (#12380)
A proxy assigned to an opencode / opencode-go connection is pinned by the chat handler as the ambient proxy context before the executor runs. OpencodeExecutor only reads per-account proxies from providerSpecificData.accountProxies, so an API-key connection with none took the single-account fast path — which wrapped the dispatch in runWithDirectFetchContext(), and that direct sentinel makes patchedFetch bypass the ambient context and hit native fetch. The assigned proxy was discarded and the request egressed from the host IP, giving `403 This model is not available in your country` on geoblocked hosts. The fast path now applies the direct pin only when no ambient context exists.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:14:13 -03:00
Paco Cartones
8d16a50df5 fix(api): keep the images wrapper on combo routes and default Codex to b64_json (#12362)
POST /v1/images/generations through a combo returned a bare array instead of the OpenAI {created, data} payload: executeImageCombo() unwrapped one level too many, and the n used for cost calculation read the same double-nested shape, so it was always 0. The combo path now returns the handler payload unchanged, matching the direct-model path.

Second half: Codex image results emitted a data: URI in url whenever response_format was not b64_json, but OpenAI returns b64_json for the gpt-image-* family — clients that omit the field, Codex CLI's built-in image_gen among them, could decode neither shape. Codex now defaults to b64_json; an explicit response_format: "url" keeps its previous behaviour. Both land together because fixing one leaves Codex CLI failing at the other.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:14:09 -03:00
Paco Cartones
3a5641839e fix(sse): name the shadowed custom provider node in the no-credentials error (#12365)
When a built-in provider's id or alias reserves the prefix of an existing OpenAI/Anthropic-compatible node — v3.8.50 added openference with alias of, shadowing nodes created earlier with prefix of — the runtime error `No active credentials for provider: openference` gave the operator nothing to act on. It now explains that the prefix routed to the built-in, names the shadowed node, and logs an AUTH warning. Precedence is unchanged and the lookup runs only on the credential-failure path when no connection was tried, so the hot routing path is byte-identical.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:14:05 -03:00
Paco Cartones
c8e2cb3ffc fix(db): install busy_timeout before the connection's first statement (#12394)
getDbInstance() ran PRAGMA journal_mode = WAL as the connection's first statement, before PRAGMA busy_timeout, and openSqliteDatabase() passes no driver-level timeout. A process opening the database while another closed its WAL connection — checkpoint plus WAL delete hold an EXCLUSIVE lock for a few hundred microseconds — therefore died with `database is locked` instead of waiting. That is the flake behind exclusive-connection-leases.test.ts on release/v3.8.51 runs 33525300898 and 33493797519 and on unrelated PR runs.

The second half is worse than the flake: isTransientProbeError matched /SQLITE_BUSY/ against error.message, but both drivers report the plain text `database is locked` and put the code in .code / .errcode. A transient lock during the corruption probe therefore took the corrupt-database path and renamed the file to storage.sqlite.probe-failed-… with "Manual recovery required". The probe now recognises the drivers' real BUSY/PROTOCOL/IOERR signals.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:13:41 -03:00
Paco Cartones
bb5c6d148e feat(gamification): enforce the per-key XP rate limit on the award path (#12390)
validateScoreChange() — the documented 1000 XP/min per-API-key limit plus the velocity anomaly check — was exported but never called, so the award path applied every XP delta unconditionally. It now runs before addXp; a rejected award is logged at warn level and skipped, and the fire-and-forget path never throws.

The second finding is the one that made the first invisible: getRecentXp's window query was inert. created_at is stored by the table default as YYYY-MM-DD HH:MM:SS and was compared lexically against a JS ISO string, so same-day rows never matched and the limit could not have tripped even if it had been wired. The window start is now computed in SQLite, matching the style computeZScore already used.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:13:37 -03:00
Paco Cartones
eb09e894cb docs: align env and troubleshooting docs with the code (#12404)
Four documentation claims contradicted the code: .env.example called OMNIROUTE_USE_TURBOPACK dev-only and said the production build still uses webpack (it reads the same flag and defaults to Turbopack); the README's Bun section said `bun run build` auto-detects Bun and switches to Webpack (only `bun run dev` does — the production bundler is decided by the flag alone); TROUBLESHOOTING.md gave OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT a default of 1 when unset means no request-count cap; and it quoted the pre-#12223 wording of the structural 503 chat_admission_busy message. The Retry-After bullet in the same section is deliberately untouched because #12395 rewrites it.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:13:31 -03:00
Paco Cartones
1146c9b5b5 fix(catalog): write NUL key separators as escape sequences instead of raw bytes (#12403)
Four files embedded the U+0000 separator of a memo/group key as a raw NUL byte rather than the \\0 escape the codebase uses for the same idiom elsewhere. The runtime value is identical, but the raw byte trips the binary heuristics of git, GitHub and ripgrep: git diff --numstat reported `-  -`, the introducing PRs rendered three of the files as "Binary file not shown", and rg silently skipped them in recursive mode. Rewritten as escapes, with a guard test keeping raw NUL bytes out of src/, open-sse/ and tests/.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:13:27 -03:00
Paco Cartones
674d39137d fix(cli): resolve the Bun preload path against the package root (#12387)
Under Bun the server child is spawned with --preload <path>/open-sse/utils/setupPolyfill.ts, and all three spawn sites built that path next to the server bundle — but the polyfill only ships at the package root and nothing copies it into dist/. Every `bun install -g omniroute` start died with `error: preload not found`. The preload now resolves from the supervisor module's own location and is shared by the two serve.mjs spawns, with the child argv moved into a pure buildServerSpawnArgs() so both branches are directly assertable (same seam as #8131). Node users are unaffected.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:13:00 -03:00
Paco Cartones
290f723ec0 fix(guardrails): keep auto combos exempt from the vision bridge credential guard (#12373)
getBestVisionModel() validates a configured fixedModel with hasUsableCredentialsForModel() before short-circuiting (#8430). auto / auto/* ids are virtual combos with no provider row, so that check always reported a confirmed false and the combo was silently discarded in favour of global auto-selection — it never got the chance to rotate its members. This mirrors the exemption the reroute guard in visionBridge.ts already carries; concrete fixedModel ids keep the #8430 fall-through unchanged.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:12:55 -03:00
Paco Cartones
393c305a71 fix(providers): resolve the Codex auto-ping model from the live catalog instead of a retired id (#12361)
The opt-in Codex quota auto-ping pinned gpt-5.1-codex-mini. OpenAI shut that model down on 2026-07-23 and the repo's own lifecycle registry already rejects it on the request path, but the scheduler never consulted that gate — every window slide sent a dead id, hit the 15-minute failure cooldown, and retried the same id forever. The ping model now resolves per tick from the provider catalog through isModelSelectable(), the same gate chatCore uses, with the registry import kept lazy because this module sits on the instrumentation boot path (#12074). When nothing is selectable the provider is paused before any throttle slot, usage read or executor call, with one warning per state change.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:12:51 -03:00
Paco Cartones
0389b07257 fix(auth): prefer accounts without backoff in least-used rotation (#12375)
The least-used strategy ranked candidates by lastUsedAt alone, so after a 429 excluded the active account the replacement could be one that was merely oldest while still carrying its own backoff — it served a single request before the next one settled on a healthy account, the one-request detour with two cache misses reported on Codex. least-used now applies the backoffLevel tie-break the round-robin fallback branch already had, ahead of the existing never-used / oldest / priority order.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:12:25 -03:00
Paco Cartones
e7b1448281 fix(combo): name output_tokens as the exclusion reason instead of structured output (#12374)
When every combo target is excluded because the request's max_tokens exceeds each target's known output limit, the terminal 400 now says so — requested max_tokens against the pool's highest known ceiling — instead of the unrelated "supports structured output for this request". Diagnostics (unmet, excluded[].reason, terminalReason) are unchanged; only the message for the output_tokens primary reason moves.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:12:22 -03:00
Paco Cartones
8e474914ea fix(providers): mark groq compound and allam-2-7b as non-reasoning models (#12379)
groq/compound and allam-2-7b were absent from the curated Groq registry, so the capability heuristic defaulted them to reasoning-capable and forwarded reasoning_effort verbatim — Groq answers HTTP 400. Declaring supportsReasoning: false makes applyThinkingBudget() strip reasoning_effort, output_config.effort and thinking, same class as #3258. The gpt-oss reasoning models keep the field.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:12:18 -03:00
Paco Cartones
090ae83e12 fix(docker): find the Chrome binary in chrome-linux64 for the codex browser image (#12376)
The playwright:v1.62.0-noble base ships Chromium as a Chrome for Testing build, which extracts to chrome-linux64/chrome. The CMD's find -path '*/chrome-linux/chrome' matched nothing, $chrome_path came out empty, and the container crash-looped on `exec: --headless=new: not found`. Widening the glob to '*/chrome-linux*/chrome' resolves both the legacy and the Chrome for Testing layout.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
2026-09-02 03:12:13 -03:00
Armin Anton” ∴
530096a3be feat(providers): add UC (uncensored.com) — persona (un-metered) + direct (metered) (#11513)
Adds uncensored.com as two OpenAI-compatible providers mirroring UC's own surfaces: uc, the persona/subscription side over WebSocket with a durable Clerk credential minting a short-lived per-connect token (no API key, un-metered), as a full multimodal port — chat, tools, vision, doc-RAG, image, video, TTS; and uc-direct, the metered Developer API over REST with X-api-key. Same underlying models, two billing surfaces.

Reconciled on merge. 57 files conflicted; only seven carried UC content, the rest was drift from the older release line and took the tip's side.

- executors/index.ts: the tip has since refactored the executor map to lazy dynamic imports, so uc is registered in that shape. uc-direct needs no entry — it routes through the default OpenAI-compatible executor.
- imageGeneration.ts: the branch still carried the retired designerWeb import alongside ucImage; kept only the UC one.
- config/providers/index.ts, webSessionCredentials.ts and web-cookie.ts resolved additively against the MaxAI entries #11461 put on the tip an hour earlier.
- web-cookie.ts: the uc entry declared no serviceKinds, required since #11392, so provider validation would have thrown at load. Declared ["llm"]. uc-direct already declared it at the end of its own entry — an earlier pass of mine added a second one after id and TypeScript caught the duplicate (TS1117); the author's placement is what shipped.

Every count was measured against the merged tree rather than taken from the branch, and all three would have been wrong: reserved prefixes are 406, not 399; APIKEY_PROVIDERS is 237, not 234; providers are 355. PROVIDER_REFERENCE.md regenerated, the count updated across README/AGENTS.md/llm.txt and its 42 mirrors, package.json and 6 SVGs — every changed line in the protected surfaces is a digit substitution and nothing else, verified by masking digits and comparing the removed and added sets (90 lines each, identical). The executor-map golden snapshot went 134 -> 135.

The branch's file-size-baseline.json predates #12411's ratchet re-tightening and was discarded rather than merged; imageGeneration.ts (+12 for the uc-image format branch) was entered against the current baseline under a _rebaseline annotation, and no other cap moves.

Verified: typecheck:core clean, check:provider-consistency OK (271 REGISTRY entries, 355 canonical providers), check:docs-counts exit 0, check-file-size OK, check:cycles OK, 119/119 across the PR's test files, and 2/2 executor-map-golden.

Thanks @arminanton — two providers for two real billing surfaces, rather than one entry pretending to be both, is the right modelling.
2026-09-02 02:23:33 -03:00
Armin Anton” ∴
cabbbe410a feat(providers): add MaxAI — signed OpenAI-compatible provider (chat, tools, vision, image-gen, doc-RAG) (#11461)
MaxAI joins as a first-class signed provider: 13 chat models discovered live from /models/get_config plus 6 image models, routed through the standard /v1 endpoints with per-request X-Authorization signing, browserless onboarding, prompted tool-calling, vision input, image generation and document RAG.

Reconciled on merge — worth reading, because the branch forked 227 commits back and 77 files conflicted. Only five carried MaxAI content; the rest was drift from the older release line and took the tip's side, taking the diff from 113 files to 37 (then 93 as counted against the current base).

- executors/index.ts: the tip has since refactored the executor map to lazy dynamic imports, so MaxAI is registered in that shape rather than the branch's static import.
- imageRegistry.ts: kept only the maxai block. The branch still carried microsoft-designer-web, which #11754 retired.
- models/route.ts: the conflicting hunk was an unrelated Vertex/Anthropic URL change, not MaxAI — tip's side.
- volcengine agent-plan/coding-plan registries: git auto-merged both sides and produced a duplicated supportsVision key, which TypeScript rejects (TS1117). Removed.

One real integration break that only the combined state shows: the MaxAI entry declared no serviceKinds, which #11392 made required a few hours ago. Provider validation threw at load time and check:provider-consistency crashed outright. Declared ["llm"] — the image kinds derive from imageRegistry, per the convention in that PR's backfill.

Every count was measured rather than taken from the branch, and each would have been wrong: reserved prefixes are 402, not the 397 the branch computed from its stale 395 base; providers are 353, not 354. PROVIDER_REFERENCE.md regenerated, the count updated across README/AGENTS.md/llm.txt and its 42 mirrors, package.json and 6 SVGs — every changed line in those files is a digit substitution and nothing else, verified by masking digits and comparing the removed and added sets (90 lines, identical). The executor-map golden snapshot was regenerated: keyCount 133 -> 134.

The branch's file-size-baseline.json predates #12411's ratchet re-tightening, so it was discarded rather than merged — taking it would have silently undone that. The three files this PR grows (proxyFetch.ts +20 for the Windows/firefox_150 TLS profile, imageGeneration.ts +12, models/route.ts +48) were entered against the current baseline under one _rebaseline annotation; no other cap moves.

Verified: typecheck:core clean, check:provider-consistency OK (269 REGISTRY entries, 353 canonical providers), check:docs-counts exit 0, check-file-size OK, check:cycles OK, and 79/79 across the MaxAI suites plus 21/21 reserved-prefix and 2/2 executor-map-golden.

Thanks @arminanton — the provider work itself is thorough; it was the 227 commits of base that needed the attention.
2026-09-02 01:55:42 -03:00
Diego Rodrigues de Sa e Souza
6b4519c317 feat(dashboard): orchestration canvas fase 2 — agents WS channel (2.1) (#12409)
* feat(dashboard): agents WS channel + agent.task.updated event

* feat(a2a): publish agent.task.updated on cloud-agent and a2a task writes

* feat(dashboard): mirror conductor fleet transitions into agents channel

* feat(dashboard): orchestration snapshot rides the agents WS channel (2.1)

---------

Co-authored-by: Markus Hartung <diegosouzapw@users.noreply.github.com>
2026-09-02 01:19:49 -03:00
小妍儿 ✨
afb91a83bd fix(analytics): expose flat-rate estimates on cost dashboards (#11460)
Claude Code (claude / cc) is correctly classified as a flat-rate subscription, so the analytics API reports $0 — accurate as billed cost, and useless as a view of what the subscription actually consumed. Neither the Costs nor the Analytics dashboard had a token-price-equivalent view.

The fix keeps both meanings rather than picking one: ordinary analytics callers keep billed-cost semantics ($0 for flat-rate), /dashboard/costs and /dashboard/analytics opt in explicitly via includeFlatRateEstimates=true, the response reports whether estimates were included so a caller cannot mistake them for vendor billing records, and the figures on /dashboard/costs are labelled as flat-rate estimates rather than presented as spend. Omitted, false and unknown values all retain the existing behaviour.

Scope note carried from the description: this is a checkpoint on #11459, not its full closure — the issue stays open.

Verified in a combined worktree with three sibling PRs of this batch: typecheck:core clean, 134/134 focused tests (4 skipped), and i18n UI coverage PASS across all 42 locales for the 43-file locale pass.

One cross-PR interaction worth recording, since it is invisible from either side: this grows CostOverviewTab.tsx from 1282 to 1318 lines, which is fine against the tip's current 2002 cap but exceeds the 1283 that #12411 (file-size ratchet re-tightening) would freeze. Neither PR fails alone. Merged first on purpose so #12411's mechanical --update recomputes against the real post-merge LOC — the cap still only goes down.

Thanks @xiaoyaner0201 — the opt-in contract plus the "were estimates included" flag is the right shape for this.
2026-09-02 01:18:47 -03:00
Rouzbeh†
451dd73870 fix(memory): list and serve embedding/rerank models from every configured provider (#11390)
On dashboard/memory?tab=engine the Embedding Model quick-select (and the rerank selector) built their lists from a keyword heuristic over the CHAT catalog (AI_MODELS) plus OpenRouter live discovery. Providers whose embedding models are not in that catalog never appeared — mistral, gemini, nvidia nim, groq, vercel-ai-gateway and others that serve embeddings on a standard OpenAI-compatible /embeddings endpoint — and typing such a model by hand failed at runtime with "Unknown embedding provider".

The fix is one generic mechanism rather than a list of per-provider patches: deriveEmbeddingProviderForChatProvider() turns any chat-registry entry with a /chat/completions base into an OpenAI-compatible /embeddings config, with curated EMBEDDING_PROVIDERS entries always winning; the embeddings service resolves a derived config for unknown-but-configured providers instead of rejecting them; deriveRerankProviderForChatProvider() does the same for Cohere-compatible /rerank; and both memory selectors fall back to a free-text provider/model input when no static catalog exists. No provider is special-cased by name, so adding one to the chat registry now makes it embedding- and rerank-capable here automatically.

Verified on the current release tip: merged clean, typecheck:core clean, check:cycles OK across 417 files, and 35/35 across the PR's five new suites (qdrant-quick-select-catalog, memory-provider-listings, rerank-provider-listings, embedding-generic-provider-fallback, rerank-generic-provider-fallback) plus the updated hard-session-lease-bypass-inventory and embeddings-handler.

Note: the base-red disclaimer in the description referenced #9985 against release/v3.8.50 — that window is closed and the current tip carries no open base-red, so nothing was inherited here.

Thanks @rqzbeh — deriving the capability instead of enumerating providers is the version of this that stays correct as the registry grows.
2026-09-02 00:27:29 -03:00
Nguyen Thanh Dat
9327990be6 fix(memory): measure the embedding width instead of waiting for a probe (#12180)
* fix(memory): measure the embedding width instead of waiting for a probe

resolveEmbeddingSource() reports dimensions: null for any source the
hard-coded registry does not describe, and a self-hosted endpoint is by
definition absent from it. Both write paths then deadlocked on that null:

- scheduleVectorUpsert called ensureReady() with the null resolution, which
  declines to create vec_memories, and then ignored the {ready:false} answer
  and upserted anyway -- straight into the catch, so every memory was stored,
  marked needs_reindex, and never vectorized;
- reindexPending refused to embed until the width was known, and the width
  could only ever come from an embedding.

Nothing surfaced it: POST /api/memory returned 200 and the health check
stayed green while rowCount stayed at 0.

The comment on EmbeddingResolution.dimensions already calls this a lazy
probe; nobody performed the probe. The upsert path holds a finished vector
when it calls ensureReady, so measure it there, and let reindex spend one
embedding up front to measure -- reusing that vector rather than paying for
it twice. withMeasuredDimensions rebuilds the signature the same way the
resolution did, identity first, so two endpoints serving the same model id
still reindex independently.

scheduleVectorUpsert now also honours a {ready:false} answer instead of
upserting into a table that is not there.

Fixes #12154

* chore(changelog): point the fragment at the real PR number

* fix(memory): extract reindex helpers so the complexity ratchet stays green

runReindexBatch grew past max-lines-per-function and cognitive-complexity
when the lazy-probe path landed. Split measure/ready/item helpers without
changing the #12154 behavior.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
2026-09-02 00:12:22 -03:00
Chewji
af0a9609f4 feat(sse): support native max reasoning effort and per-model clamping (#11875)
* fix(sse): map normalized xhigh to max for GLM-5.x+, DeepSeek-V4+, and provider aliases

* feat(sse): support native max reasoning effort and per-model clamping

* test(sse): add unit tests for Qwen 3.8, Claude 4.7+, GPT-5.6, and 2026 reasoning models

* fix(sse): align tests and file-size split for native max effort

Keep `max` as a first-class canonical tier. Split the new sanitizer
coverage out of base-executor-sanitize-effort.test.ts so the file stays
under testCap, and update discovery/catalog/vscode assertions to expect
native max instead of the old xhigh alias.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* fix(sse): keep combo effort lists and drop unused collectSSE helper

Combo vscode routes still advertise the 5-tier list. Canonical `max` is
preserved in discovery (#9160) and github model metadata. Remove the
unused collectSSE helper that failed the absolute ESLint gate.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: Chewji <Chewji9875@users.noreply.github.com>
2026-09-02 00:12:13 -03:00
WebPerson
24b784e9bb [Performance] Enable React Compiler for automatic memoization (#11783)
* feat(ui): enable React Compiler (#67)

Enable reactCompiler: true in next.config.mjs (Next 16 + React 19.2.8).
This automates memoization at build time, removing manual useCallback/useMemo
debt (591 + 283 instances respectively) and preventing stale-closure bugs.

Test results (pre-existing failures unchanged):
  vitest UI: 282/295 files pass (13 fail = missing router/ReactFlow mocks)
  vitest:    1805/1857 tests pass (52 fail = same pre-existing mock issues)
  node:test: api/services/db all pass (except platform-specific
             serviceSupervisorSpawnError — Windows spawn("ls") issue)

No new failures introduced by the compiler transform.
Optional cleanup: remove now-redundant useCallback/useMemo in hot components.

* fix(build): add babel-plugin-react-compiler peer dependency (#67)

React Compiler (reactCompiler: true in next.config.mjs) requires
babel-plugin-react-compiler as an explicit peer dependency — Next.js
declares it as optional ("*") and does not auto-install it.

Installed babel-plugin-react-compiler@1.0.0 as a devDependency.
Resolves correctly from both the project root and the next package
context (Turbopack resolution path).

* fix(ci): allowlist babel-plugin-react-compiler for React Compiler

The React Compiler peer is a real npm package (facebook/react, MIT) required
by Next 16 `reactCompiler: true`. Adding it to the anti-slopsquat allowlist
unblocks check:deps and the 6A.8 unit-test gate.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* fix(ci): drop unused collectSSE helper that trips ESLint

The helper was leftover from #12151 and fails the absolute
lint:json --max-warnings 0 gate on every PR that includes it.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: WebPerson <jonlwheat2-gif@users.noreply.github.com>
2026-09-02 00:10:59 -03:00
Mr White
5a0a131bc7 feat(usage): devin-cli agentic quota + openrouter credits in Provider Limits (#12256)
* feat(usage): devin-cli agentic quota + openrouter credits in Provider Limits

Two provider families with live quota APIs were missing from the Provider
Limits dashboard because their list entries were absent:

- devin-cli: new usage leaf querying the Codeium seat-management Connect API
  (exa.seat_management_pb.SeatManagementService/GetUserStatus, protobuf over
  POST with the raw `Basic <token>-<token>` auth header the CLI itself uses).
  Surfaces the plan name plus daily/weekly agentic quota percentages with
  reset timestamps from the GetUserStatus plan_status payload, via a minimal
  hand-rolled protobuf encoder/reader (no proto dependency warranted for two
  fixed messages).
- openrouter: the /key + /credits quota fetcher (#6842) was already wired
  into the dispatcher but gated out of the bulk sync — add it to
  USAGE_SUPPORTED_PROVIDERS and PROVIDER_LIMITS_APIKEY_PROVIDERS so key
  limits and account credits actually surface.

* fix(build): externalize tiktoken so tiktoken_bg.wasm resolves at runtime

The vendored ChatGPT Web connector v4.0.7 (#12181) imports tiktoken
(get_encoding) at module level. tiktoken's node build reads
tiktoken_bg.wasm via a __dirname-relative fs.readFileSync during import;
when Next bundles the package the wasm asset is not traced into the server
chunk, and page-data collection for every route reaching the tokenizer
(e.g. /api/providers/[id]/chatgpt-web-codex-doctor) aborts with
"Missing tiktoken_bg.wasm" — breaking the whole standalone build.

Externalize it like the other runtime-resolved native/wasm packages
(sql.js, sqlite-vec, better-sqlite3): the require stays at runtime, where
node_modules/tiktoken/tiktoken_bg.wasm resolves normally.

* fix(openrouter): /credits balance survives a /key failure

OpenRouter is credit-based, not subscription-based: the authoritative
remaining-credits signal is GET /api/v1/credits (total_credits -
total_usage, the documented "get remaining credits" endpoint), while the
/key limit fields are optional per-key caps that most accounts never set.

fetchOpenrouterQuota previously treated /key as mandatory — any /key
failure (429 rate limit, transient error, unexpected shape) discarded the
whole payload and the Usage dashboard showed "OpenRouter (usage endpoint
unreachable)" even though /credits was reachable. Now:

- /key unavailable + /credits OK → credits-only quota (creditBalance =
  total_credits - total_usage) instead of null
- /key 401/403 alone no longer means an invalid token; only a double
  auth-rejection (both endpoints) does
- null is returned only when both endpoints fail, and the dashboard label
  reflects that ("credits endpoint unreachable")

* fix(openrouter): render AI Credits as a USD credit count in Provider Limits

The Provider Limits card's dollar renderer only activates on
isCredits/creditCount rows (QuotaCardExpanded), but openrouter went through
parseGeneric — which drops `currency` and never sets those flags — so the
credits balance rendered as a meaningless "100% left" (the unlimited-credits
row is always 100%) instead of the actual credit count.

Route openrouter's `credits` quota through buildCreditsQuota() like the
DeepSeek/AgentRouter credits rows: label "AI Credits", dollar-formatted
balance. Free-tier request windows keep the generic percentage treatment.

* fix(usage): document DEVIN_SEAT_API_URL and split quota parsers

Keep fetchOpenrouterQuota and decodeProtoFields under the complexity
ratchets, and add the seat-management URL to the env/docs contract.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* test(usage): drop duplicated GLM quota-ordering test in provider-limits-ui

* test(usage): drop stale openrouter ACCEPTED_DIVERGENCE

OpenRouter is now in both USAGE_FETCHER_PROVIDERS and
USAGE_SUPPORTED_PROVIDERS, so the recorded aggregator divergence
is no longer real. Add the changelog fragment.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
2026-09-02 00:02:15 -03:00