Compare commits

..

399 Commits

Author SHA1 Message Date
Markus Hartung
b574f0d14e Merge remote-tracking branch 'origin/release/v3.8.51' into fix/v3850-replace-tls-client-node
# Conflicts:
#	.env.example
#	Dockerfile
#	Dockerfile.bun
#	THIRD_PARTY_NOTICES.md
#	config/quality/dependency-allowlist.json
#	docs/reference/ENVIRONMENT.md
#	docs/security/STEALTH_GUIDE.md
#	open-sse/executors/chatgpt-web.ts
#	open-sse/services/__tests__/chatgptTlsClient.test.ts
#	open-sse/services/__tests__/grokTlsClient.test.ts
#	open-sse/services/chatgptTlsClient.ts
#	package-lock.json
#	package.json
#	scripts/build/fixTlsClientNodeBinary.mjs
#	scripts/build/pack-artifact-policy.ts
#	scripts/build/postinstall.mjs
#	tests/unit/chatgpt-web.test.ts
#	tests/unit/fix-tls-client-node-binary-7802.test.ts
#	tests/unit/tls-client-download-dir-8579.test.ts
#	tests/unit/tls-client-node-docker-binary-7802.test.ts
2026-08-30 16:28:55 -03:00
Abhishek Divekar
cda832c3a7 feat(settings): raise sticky round-robin limit caps to 1000 (#12015)
Aumenta o teto do sticky round-robin limit para 1000, com teste próprio (3/3 verdes). Fiz cherry-pick só dos 2 commits reais direto na tip atual: a branch original carregava 4 commits antigos de drift do ciclo (release/electron/CI, já resolvidos de outras formas) que geravam conflito redundante contra `.github/workflows/electron-release.yml`. Nenhum conteúdo seu foi perdido — força-pushed a branch limpa (autoria preservada). Obrigado!
2026-08-30 11:51:07 -03:00
Abhishek Divekar
1dd046814f fix(combo): honor an operator-set context_length at request time (#12090)
Honra um `context_length` definido pelo operador em tempo de requisição no roteamento do combo (supersede #12014, que estava incluída nos mesmos commits). Boa cobertura de testes, incluindo o refactor de `resolveComboContextLimit` para módulo próprio. Validado no worktree combinado (13/13). Obrigado!
2026-08-30 11:44:18 -03:00
Abhishek Divekar
26bfda3cb9 feat(resilience): operator-configurable global credential health check interval (#12043)
Intervalo de checagem de saúde de credencial configurável pelo operador, com boa cobertura de testes. Validado no worktree combinado (20/20).

Corrigi o import de `getCachedSettings` em `src/app/api/resilience/route.ts` e `src/lib/credentialHealth/scheduler.ts`, que apontava para `@/lib/db/settings` (path antigo antes do split para `@/lib/db/readCache`, já na tip). Resolvido também um conflito de tradução vi.json entre chaves duplicadas de outra feature (exclusive lease), sem relação com esta PR — mantida a versão já mergeada. Obrigado!
2026-08-30 11:41:17 -03:00
Mr White
6f914b7a32 feat(zai): add GLM-5.3-Flash Coding Plan support (#11801)
Adiciona suporte ao GLM-5.3-Flash Coding Plan (endpoint OpenAI-compatible, tiers de esforço low/high/max via reasoning_effort). Boa cobertura de testes. Validado no worktree combinado.

Dois problemas resolvidos antes de mergear:
1. **Duplicata silenciosa de "glm-5.3-flash"** em `src/shared/constants/modelSpecs.ts` e `open-sse/config/glmProvider.ts` (#11830, já mergeado nesta sessão, e sua PR inserem a mesma entrada em pontos diferentes do arquivo — git não detecta como conflito textual). Removida a duplicata, preservando a ordem que o teste pré-existente `open-sse/mcp-server/__tests__/glmCodingProviderConfig.test.ts` espera (glm-5.3-flash primeiro no array `GLM_SHARED_MODELS`).
2. Conflito real em `zai/index.ts`, `default.ts`, `pricing/shared-tiers.ts` e no teste de catálogo — todos aditivos, resolvidos mantendo ambos os lados.

27/27 + 10/10 (vitest) testes focados verdes. Obrigado!
2026-08-30 11:37:32 -03:00
Nguyen Thanh Dat
e93c5e765d fix(diagnostics): keep the call-log error when the size limit strips the bodies (#12026) (#12095)
Mantém o erro do call-log quando o limite de tamanho corta os bodies, com `preserveErrorForSizeLimit` (UTF-8-safe, preserva o valor original quando cabe, trata erro circular/não-serializável) — implementação mais robusta que a alternativa que já estava na tip (via #12027, que resolvi combinando: mantive a camada extra "errorOnly" do #12027 usando o helper mais seguro deste). Testes próprios + os de #12027 todos verdes (30/30) no worktree combinado. Obrigado!
2026-08-30 11:29:21 -03:00
watchingdogs
d13c6cb19a fix(sse): emit native web_search_call for Responses web_search fallback (#12031)
Emite `web_search_call` nativo para o fallback de web_search da Responses API, com boa cobertura (integração + unitário). Validado no worktree combinado.

Corrigi 3 problemas no próprio `tests/integration/skills-pipeline.test.ts` desta PR antes de mergear: faltava `encodeSkillToolName` no import (usado em 3 lugares, causava `ReferenceError` que se propagava como 502 no teste "matching tool calls execute the registered skill") e 2 asserções comparavam nomes decodificados (`decodeSkillToolName`) contra valores re-codificados (`encodeSkillToolName`) — copy-paste do helper usado para montar o mock. 30/30 testes focados verdes após a correção.
2026-08-30 11:24:19 -03:00
Paijo
9b9ea88d47 fix(migrations): add renamed migration compatibility for 056/073/077/101 (#12036)
Adiciona compatibilidade de renomeação de migração para 056/073/077/101. Teste próprio atualizado (7/7 verde no worktree combinado + isolado).

Fiz cherry-pick só dos 2 commits reais da PR (o fix + o ajuste do teste) direto na tip atual: a branch original carregava 3 commits antigos de drift do ciclo (release-workflow/electron, já mergeados de outras formas) mais um commit de auto-resolução de merge seu, que juntos geravam conflito redundante contra `.github/workflows/electron-release.yml`. Nenhum conteúdo seu foi perdido — força-pushed a branch limpa (autoria preservada). Obrigado!
2026-08-30 11:19:51 -03:00
Alvin T. Veroy
838fc00f25 fix(resilience): decouple rate-limit execution expiration from queue-wait budget; preserve errors in oversized call-log artifacts (#12027)
Desacopla a expiração de execução do rate-limit do orçamento de espera na fila, e preserva erros em artefatos de call-log oversized. Testes próprios (`call-log-cap.test.ts` + atualizações em `rate-limit-execution-timeout-message-4165.test.ts`/`ratelimit-admission-control-6593.test.ts`). Validado no worktree combinado. Obrigado!
2026-08-30 11:16:18 -03:00
Dohyun Jung
a2c5d8a2f5 feat(quota): use official OpenCode Go usage API (#12124)
Migra o quota fetcher do OpenCode Go para a API oficial de uso, com refactor substancial que remove ~1850 linhas de código legado e atualiza a suíte de testes existente inteira para o novo contrato. Validado no worktree combinado (typecheck limpo, testes focados verdes). Obrigado!
2026-08-30 11:11:46 -03:00
SHANMUGAPRIYAN
908c1b823d fix(codex): preserve existing provider state when bulk-import upserts a matching connection (#12122)
Corrige o bulk-import do Codex apagando `providerSpecificData`/`tokenExpiresAt`/duplicando `priority` de conexões existentes ao fazer upsert num match — mescla o payload importado sobre o estado existente em vez de substituir tudo, igual ao caminho de import single-file já fazia. Findings 4, 6 e 7 do #12113. Teste próprio (224 linhas). Validado no worktree combinado. Obrigado!
2026-08-30 11:11:33 -03:00
SHANMUGAPRIYAN
d812585b5a fix(plugins): refresh stored manifest from disk on activate so new hook fields reach existing installs (#12120)
Refresca o manifest do plugin a partir do disco ao ativar, para que instalações pré-existentes ganhem hooks novos adicionados por schema updates (ex.: `onStreamComplete` do #11825/#11934 nunca chegava a plugins já instalados antes do upgrade, pois o manifest persistido no DB era stripado pelo schema antigo). Finding 3 do #12113. Teste próprio (259 linhas). Validado no worktree combinado. Obrigado!
2026-08-30 11:11:29 -03:00
SHANMUGAPRIYAN
1b2c6f4c36 fix(guardrails): restore injection-guard logging on middleware-only routes (#12117)
Restaura o log do injection-guard nas 13 rotas não-chat (embeddings, images, audio, moderations, etc.) — a correção de log duplicado anterior (#11936) silenciou completamente o único emissor de log dessas rotas, deixando tentativas de injeção sem rastro nenhum em modo warn, e sem log mesmo quando bloqueadas em modo block. Achado de segurança real (Finding 2 do #12113). Teste próprio (141 linhas). Validado no worktree combinado. Obrigado!
2026-08-30 11:11:12 -03:00
SHANMUGAPRIYAN
00bc397cda fix(plugins): do not kill the plugin process when a fire-and-forget hook times out (#12116)
Corrige o kill do processo inteiro do plugin quando um handler fire-and-forget de `onStreamComplete` demora >10s — hook documentado como fire-and-forget não deveria derrubar o processo a cada stream completo. Finding 5 do #12113. Teste próprio (214 linhas). Validado no worktree combinado. Obrigado!
2026-08-30 11:11:08 -03:00
SHANMUGAPRIYAN
3d15294967 fix(leases): project status lease row to lease columns so joined connection PII never escapes (#12115)
Corrige vazamento de colunas de conexão (email/nome/etc.) através do cast em `getExclusiveConnectionLeaseStatus` — a projeção agora fica restrita às colunas de lease, evitando que um futuro consumidor sirva PII sem querer via o tipo `ExclusiveConnectionLease`. Documentado como Finding 8 do seu próprio bug-audit (#12113). Teste próprio (103 linhas). Validado no worktree combinado. Obrigado!
2026-08-30 11:11:03 -03:00
Paijo
55f6b9808b fix(executors): DuckDuckGo ERR_BN_LIMIT without blind retry + proxy pool support (#12110)
Corrige ERR_BN_LIMIT do DuckDuckGo sem retry cego, com suporte a pool de proxy e teste próprio (199 linhas). Validado no worktree combinado. Obrigado!
2026-08-30 11:10:47 -03:00
Bob.Hou
039a425401 fix(oauth): bind Google refresh to the client that issued the token (#12106)
Vincula o refresh OAuth do Google ao client que emitiu o token, com teste próprio (`google-oauth-client-binding.test.ts`). Validado no worktree combinado. Obrigado!
2026-08-30 11:10:44 -03:00
Tux-Garply
14dc6e8513 feat(providers): add Perplexity Agent API provider (#12103)
Adiciona o provider Perplexity Agent API, com dois arquivos de teste próprios (provider + sanitização de chatCore). Validado no worktree combinado. Obrigado!
2026-08-30 11:10:40 -03:00
Ravi Tharuma
2da9ade59b fix(sse): honor CLIProxyAPI environment API key (#12099)
Honra a chave de API dedicada de ambiente do CLIProxyAPI, com teste próprio. Validado no worktree combinado. Obrigado!
2026-08-30 11:10:25 -03:00
Ravi Tharuma
6096ea51f8 test(ui): correct inactive auto-fetch expectation (#12098)
Corrige a expectativa de auto-fetch inativo num teste de UI existente. Validado no worktree combinado (vitest 114/114). Obrigado!
2026-08-30 11:10:22 -03:00
KeelTrace
54a1114382 fix(sse): keep unavailable forced connections scoped (#12080)
Mantém conexões forçadas indisponíveis com escopo correto, com teste próprio ampliado (`forced-connection-fallback.test.ts`). Validado no worktree combinado. Obrigado!
2026-08-30 11:10:19 -03:00
Marcelo Karval
82f09f4c86 fix(api): align combo body and legacy key access (#12070)
Alinha o body do combo e o acesso legado por chave, com testes atualizados (CLI api-generator + row parsers). Validado no worktree combinado. Obrigado!
2026-08-30 11:10:06 -03:00
Oonishi
09428da3d9 fix(dashboard): prevent provider icons collapsing to zero size (#12054)
Corrige ícones de provider colapsando para tamanho zero, com teste próprio. Validado no worktree combinado. Obrigado!
2026-08-30 11:10:02 -03:00
Prajeeth H
6a41a78132 fix(catalog): derive vision/modalities for built-in auto combos from effective target pool (#12046)
Deriva as modalidades do auto-combo a partir do pool de targets efetivo, com teste próprio robusto (174 linhas). Validado no worktree combinado. Obrigado!
2026-08-30 11:09:59 -03:00
Karan
ff4ac6c4d5 fix(provider/nous): inject required user tag into inference requests (#11861) (#12044)
Injeta a tag de usuário obrigatória nas requisições de inferência do provider Nous, com teste próprio ampliado. Validado no worktree combinado. Obrigado!
2026-08-30 11:09:44 -03:00
Chewji
51e4930d05 fix(build): prune non-production trees in NFT trace excludes and tsconfig (#12028)
Poda árvores não-produção nos excludes do NFT trace e no tsconfig, com teste próprio atualizado. Validado no worktree combinado. Obrigado!
2026-08-30 11:09:40 -03:00
FeiWei
476b20bd69 fix(providers): cloudflare-ai flattens message content unconditionally, but the #2539 constraint is model-scoped — this blocks image input to Cloudflare vision models (#12002)
Corrige o achatamento incondicional de conteúdo de mensagem no cloudflare-ai — a restrição #2539 é model-scoped, não global, e estava bloqueando entrada de imagem em modelos de visão da Cloudflare. Teste próprio atualizado. Validado no worktree combinado. Obrigado!
2026-08-30 11:09:37 -03:00
Fábio Silva
fe8ef4fa90 fix(providers): use v1beta1 Model Garden publisher list for Vertex Anthropic discovery (#11998)
Usa a lista de publishers v1beta1 do Model Garden para descoberta de modelos Vertex Anthropic, com teste próprio. Validado no worktree combinado. Obrigado!
2026-08-30 11:09:22 -03:00
b3nw
5d07bf32fe feat(catalog): add feature flag to disable thinking level variants in catalog (#11971)
Feature flag para desabilitar variantes de nível de thinking no catálogo, com testes de gate e de settings. Validado no worktree combinado. Obrigado!
2026-08-30 11:09:19 -03:00
Nguyễn Viết Tuấn
4d20d37974 fix(sse): keep cache-write tokens in OpenAI-shaped usage (#11814)
Mantém tokens de cache-write no formato de usage do OpenAI, com teste próprio (`cache-write-openai-shape.test.ts`) e atualização do teste existente de tokens detalhados. Validado no worktree combinado (typecheck limpo, 351/351 testes focados). Obrigado!
2026-08-30 11:09:15 -03:00
Diego Rodrigues de Sa e Souza
1f4dc830f3 chore(quality): velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly (#12125)
Owner decision (2026-08-30): shipping speed matters more than holding the debt line
until the v4.0 LTS modularization; the base was going red on every merge batch and
each red baseline cost a sweep.

Relaxation (one auditable pass, scripts/quality/relax-baselines.mjs):
- quality-baseline.json metrics: lower-is-better ×1.2, higher-is-better ÷1.2
  (coverage floor 60 kept; eslintErrors stays 0; eslintWarnings 0 → 1050 = 20% of
  the 5,247 frozen suppressions). Adds `_policy {phase: velocity, until: 4.0.0,
  relaxPct: 20, requireTighten: false}` + a `_relax_velocity_2026_08_30` note
  listing every before → after.
- complexity count 2681 → 3218; duplication 5.72 → 6.86; file-size cap/testCap
  1000 → 1200 and all 127 frozen caps ×1.2; api/dashboard/open-sse typecheck
  per-file counts ×1.2; openapi-coverage THRESHOLD 36 → 30.
- check-quality-ratchet: --require-tighten is advisory while _policy.requireTighten
  is false (2 new tests); nightly bank-ratchet-shrinks pauses during the phase (it
  would bank the measured shrink and undo the headroom every night).

Monitoring (scripts/quality/baseline-headroom.mjs, npm run quality:headroom):
measures each numeric gate the way CI does, prints live / baseline / headroom per
gate (ok ≥10%, warn <10%, critical <0); the new nightly `baseline-headroom` job
posts the table to the living issue "📈 Baseline headroom (velocity phase)" and
toggles the `headroom-alert` label. 6 unit tests on the pure helpers.

Also aligns the remaining red tests on the tip to contracts already merged:
#11775 (FREE lease-capable connections are ordinary capacity: gate inventory 48/97/99,
sse-auth selection, warmup scheduler), #11794 (dual-loopback readiness probe), and the
8 vi strings #11775 left as __MISSING__.

Docs: QUALITY_GATES.md → "Velocity phase" (what changed, tooling, how to close the
phase at 4.0), AGENTS.md quick reference.
2026-08-30 10:39:01 -03:00
Diego Rodrigues de Sa e Souza
77f6f73706 fix(ci): clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) (#12123)
- api-route-typecheck: 56dddfce34 (antigravity loadCodeAssist metadata) made
  getAntigravityLoadCodeAssistMetadata() return Record<string, number> while
  onboardAntigravityUser() still typed the parameter Record<string, string> —
  TS2345 in src/lib/oauth/providers/antigravity.ts, gate red on every PR. The
  parameter now derives from the getter's return type.
- env-doc contract: 0b19c5a09b (#11852, 5dive configure target) reads
  CLI_5DIVE_BIN and CLI_5DIVE_STATE_DIR without documenting them —
  Docs Gates red on every PR. Added to .env.example and ENVIRONMENT.md.

Gates: check:api-typecheck OK (289 frozen), check:env-doc-sync OK,
antigravity oauth tests 14/14.

Refs #11852
2026-08-30 09:54:59 -03:00
Diego Rodrigues de Sa e Souza
32702d313b test(providers): regenerate the translate-path golden for OrcaRouter (#11923) (#12118)
#11923 (22011437f8) deliberately routes OrcaRouter chat requests to
https://api.orcarouter.ai/v1/chat/completions; the golden snapshot in
tests/unit/provider-translate-path-golden.test.ts still pinned /v1, so
Unit Tests fast-path (2/4) is red on the release tip for every PR.
Regenerated with UPDATE_GOLDEN=1 — the only delta is the orcarouter block.

Refs #11923
2026-08-30 09:47:04 -03:00
Diego Rodrigues de Sa e Souza
485c2dcdb6 fix(dashboard): make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) (#12114)
* fix(dashboard): make RequestLoggerDetail loadable outside Next — CSS via globals.css, CJS/ESM interop for react18-json-view

Origin: #11703 (5684589ce7) imported `react18-json-view/src/{style,dark}.css` at
module level in RequestLoggerDetail(.sections).tsx and relied on the bundler's
default-import interop. Next is fine with both, but every test that renders the
component died on the release tip:

- node:test / tsx: ERR_UNKNOWN_FILE_EXTENSION ".css" — request-log-detail-layout,
  request-log-detail-stream, request-logger-detail-copy-all,
  request-timeline-lane-allocation (4 unit shards red on every PR).
- esbuild bundle-safety check (media-page-client-browser-bundle): cannot resolve the
  .css specifiers.
- node ESM resolves the package's CJS `main` (no `exports` map), so the default
  import is the module namespace: "Element type is invalid … got: object".

Fix at the source: the two stylesheets are @imported from src/app/globals.css
(same as material-symbols / fumadocs), and the component unwraps `mod.default ??
mod` like redisQuotaStore/keytar already do. Also adds the 5 vi strings #11703
introduced (requestLogger.detail.{collapseAllLevels,collapseOneLevel,
currentExpandLevel,expandOneLevel,expandAllLevels}) — vi has strict parity.

Refs #11703

* refactor(dashboard): move the react18-json-view interop into shared/components/jsonView.ts

RequestLoggerDetail.tsx is frozen by check:file-size (1111 lines, cannot grow); the
inline interop pushed it to 1118. One tiny module serves both components and keeps
the CSS-import warning in a single place.
2026-08-30 09:46:55 -03:00
Markus Hartung
f5742c3a8b chore(quality): tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline (#11771)
- complexity-baseline.json: 2774 -> 2681 (npm run quality:ratchet-style --update, measured on release/v3.8.51 tip after this session's merge batch)
- quality-baseline.json cognitiveComplexity: 1223 -> 1197 (same measurement)
- file-size-baseline.json: gateways.ts 1347 -> 1348, the #11771 rebaseline that only ever landed in a scratch worktree, never in the merged commit

Prompted by PR #11847's stale ratchet-shrink numbers (measured on release/v3.8.50, both below what the current tip actually measures — 2351 vs 2681 real, 1060 vs 1197 real) — remeasured directly instead of merging the stale values.
2026-08-30 09:29:22 -03:00
Nguyễn Viết Tuấn
52521984de fix(lease): remove global static reservation and gate routing on live active lease occupancy (#11775)
Remove a reserva estática global e passa a gatear o roteamento pela ocupação real do lease exclusivo ativo, com boa cobertura de testes (5 arquivos, 54 casos, todos verdes no worktree combinado). Typecheck limpo.

Dois ajustes feitos por cima antes do merge:
1. **26 arquivos `.pyc` órfãos removidos** (`scripts/ops/__pycache__/…`, `tests/unit/ops/__pycache__/…`) — cache compilado do Python sem relação com o fix de lease, provavelmente commitado sem querer do ambiente local.
2. **Conflito em `src/app/api/keys/[id]/route.ts`**: mantida a checagem mais ampla desta PR (`instanceof ApiKeyPolicyInvariantError || código LEASE_KEY_POLICY_INVALID`), que é um superset da versão anterior — cobre o caso original e o novo caminho de erro do lease.

Obrigado pela contribuição!
2026-08-30 09:21:53 -03:00
Damian Pozimski
385e90f444 feat(dashboard): continuous call-log export to pluggable destinations (BigQuery first) (#11945)
Feature grande e bem construída: exportação contínua de call logs para destinos plugáveis (BigQuery primeiro). Revisei especificamente o tratamento de segredos (`src/lib/logExport/secrets.ts`) e a migração — encryption gate real (`requiresEncryptionKey` recusa gravação em texto plano quando `STORAGE_ENCRYPTION_KEY` não está setada), redação antes de qualquer resposta de API, e a migração cria a tabela com `enabled=0`/`include_bodies=0` por padrão (opt-in, sem exportar nada até o operador configurar). 62/62 testes focados verdes, typecheck limpo.

Resolvido o conflito com o barrel `src/lib/localDb.ts` (removido nesta mesma sessão, #11795 fase 5 — todo consumidor já migrado para `src/lib/db/*`); a PR só adicionava um re-export nele, que não é mais necessário. Obrigado pela contribuição!
2026-08-30 09:16:52 -03:00
b3nw
d213ef0304 feat(dashboard): show cache percentage in request logs (#11970)
Mostra a % de cache nos logs de requisição, com teste próprio (`request-logger-cache-percentage.test.ts`). Validado no worktree combinado do lote.

Pequeno ajuste feito por cima: `formatCachePercentage` movida de `RequestLoggerV2.tsx` para `src/shared/utils/formatting.ts`. `RequestLoggerV2.tsx` importa `RequestLoggerDetail`, que desde o #11703 (mergeado nesta mesma sessão) importa CSS bruto de `react18-json-view` — algo que o Node native test runner não consegue carregar. O teste original importava a função direto do componente e quebrava por causa dessa cadeia de import, não por bug na PR. Movida a função (pura, sem dependências) para o utils compartilhado; ajustado o import do componente e do teste. Typecheck limpo, teste passando (6/6).
2026-08-30 09:13:39 -03:00
5dive
0b19c5a09b feat(nodejs): add 5dive as a configure target (#11852)
Adiciona 5dive como configure target, com teste de regressão próprio (`tests/unit/cli/setup-5dive.test.ts`) e strings i18n em 12 locales. Validado no worktree combinado (typecheck limpo, 26 testes focados). 

Nota: um dos subtestes desse arquivo ("falls back to the local server when no context") depende de não haver contexto CLI ativo em `~/.omniroute/` — nesta máquina de desenvolvimento compartilhada existe um contexto real configurado, então o teste lê a config real em vez do fallback via `PORT`. Confirmado que é vazamento de ambiente do devbox (não do CI): reproduzido isoladamente, rastreado até `resolveActiveContext()` lendo `~/.omniroute/*.json` antes de cair no fallback de `PORT`. Não bloqueia o merge, mas fica registrado — o teste merece ficar hermético (mockar/isolar o data dir) numa limpeza futura.
2026-08-30 09:10:31 -03:00
Jacob Stoner
9903a6d2eb refactor(auto-combo): fix divergent scoring in combo health reporting (#11854)
Corrige divergência de scoring no relatório de saúde do auto-combo, com testes atualizados em `combo-resolve-auto-strategy-split.test.ts` e `combo-scoring-inspector.test.ts`. Validado no worktree combinado. Obrigado!
2026-08-30 09:09:57 -03:00
Abhishek Sharma
4c8074ba7a fix(sse): give extended-thinking targets the reasoning readiness budget (#11959)
Dá aos alvos de extended-thinking o orçamento de prontidão de reasoning, com cobertura de teste ampliada em `stream-readiness-policy.test.ts`. Validado no worktree combinado. Obrigado!
2026-08-30 09:09:54 -03:00
rifqiawl
56dddfce34 fix(antigravity): send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) (#11969)
Envia metadata completo do loadCodeAssist (ideType/platform/pluginType como enums numéricos) para o Antigravity, com teste de regressão atualizado. Validado no worktree combinado. Obrigado!
2026-08-30 09:09:51 -03:00
ZaimMarzuki
8f38dcd32b fix(dashboard): use opaque background and readable text color on cost chart tooltips (#11960)
Fix de contraste no tooltip do gráfico de custos (fundo opaco + cor de texto legível). Mudança isolada de CSS/classe, validada no worktree combinado. Obrigado!
2026-08-30 09:09:39 -03:00
Andrew B.
4c187de99b fix(docs): resolve relative markdown and wiki links across Fumadocs and GitHub wiki (#11834)
Resolução de links relativos entre Fumadocs e a wiki do GitHub, com dois arquivos de teste novos e bem focados (`docs-link-resolver.test.ts`, `sync-wiki.test.ts`). Validado no worktree combinado. Obrigado!
2026-08-30 09:09:36 -03:00
Andrew B.
e0029eb5a6 feat(pricing): add GLM-5.3-Flash pricing, model specs, and catalog registration (#11830)
Adiciona GLM-5.3-Flash ao catálogo com pricing/specs e teste próprio. Validado no worktree combinado (typecheck limpo, teste focado verde). Obrigado!
2026-08-30 09:09:33 -03:00
Nads
dfc84ba030 fix: regenerate package-lock.json for packages/browser-pool workspace (#11784)
Regeneração legítima do `package-lock.json` do workspace `packages/browser-pool`. Sem alteração de código, validado no worktree combinado. Obrigado!
2026-08-30 09:09:20 -03:00
vermasomesh835
b07eaafcc4 fix(cli): probe both IPv4 and IPv6 loopback for server readiness (#11766) (#11794)
Fix correto — CLI agora sonda IPv4 e IPv6 no probe de prontidão do servidor, com teste de regressão próprio (`tests/unit/cli-waitForServer.test.mjs`). Validado no worktree combinado (typecheck limpo, teste focado verde). Obrigado!
2026-08-30 09:09:16 -03:00
Rahul sharma
131e413cbd fix: mark Vercel AI Gateway as passthroughModels (#11771)
Correção pequena e correta — `passthroughModels: true` para o Vercel AI Gateway. Validado no worktree combinado do lote (typecheck limpo, gates estáticos verdes). Obrigado!
2026-08-30 09:09:13 -03:00
Ravi Tharuma
212fba734f docs: document native dependency check escape hatch (#12101)
Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
2026-08-30 08:51:33 -03:00
dependabot[bot]
25aa95f0d0 chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 (#11925)
Routine patch bump of a GitHub-owned action (codeql-action 4.37.7 → 4.37.8).
2026-08-30 05:30:01 -03:00
dependabot[bot]
23144ad644 chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 (#11926)
Routine patch bump of a GitHub-owned action (codeql-action 4.37.7 → 4.37.8).
2026-08-30 05:29:54 -03:00
dependabot[bot]
4254b1fce1 chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 (#11927)
Routine patch bump of a GitHub-owned action (codeql-action 4.37.7 → 4.37.8).
2026-08-30 05:29:46 -03:00
Paco Cartones
66e02ec737 fix(plugins): deliver onStreamComplete to disk-installed plugins (#11825) (#11934)
Resynced onto release/v3.8.51 (originally targeted main; retargeted since the default branch is release/v3.8.51). One real conflict in open-sse/handlers/chatCore.ts, but it was entirely unrelated to this PR's actual purpose: the antigravity-aware lockExactModel branching and deferAntigravityQuotaStateToCaller state exist on main but haven't been synced to release/v3.8.51 yet (confirmed by diffing your branch against its own main merge-base — the only change there was a Prettier reformat, not new logic). Discarded that unrelated drift and kept the release tip's current quota-lock shape; the onStreamComplete plugin wiring itself is untouched and intact. typecheck:core clean, 13/13 plugin delivery tests pass. Thanks for the thorough three-layer root-cause writeup.
2026-08-30 05:29:26 -03:00
Alvin T. Veroy
823dae0e9d fix(skills): expand shorthand property types in injected tool schemas (#11857)
Boarded with 8 other PRs in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-native-deps all green; 75/75 focused tests pass. Live-reproduced root cause (byte-for-byte reproduction/removal of the malformed schema) is solid evidence. Thanks for tracing this to the builtin skill schemas rather than stopping at "provider outage".
2026-08-30 05:28:56 -03:00
Ujjawal kaushik
3852e0534f fix(build): fail fast when an externalised optional native dep was silently dropped (#11863)
Boarded with 8 other PRs in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-native-deps all green; 75/75 focused tests pass. Fixes a genuinely confusing failure mode — matches the documented TROUBLESHOOTING.md symptom exactly. Thanks for the preflight check and clear diagnostics.
2026-08-30 05:28:46 -03:00
Paco Cartones
15b164866c feat(providers): expose a usage-fetch capability in the provider plugin manifest (#11903)
Boarded with 8 other PRs in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-native-deps all green; 75/75 focused tests pass. Discovery-only as claimed — nothing reads the new tag yet, dashboard quota widget stays gated by USAGE_SUPPORTED_PROVIDERS. Thanks.
2026-08-30 05:28:38 -03:00
Paco Cartones
2471a0d95e feat(plugins): add OMNIROUTE_PLUGINS_DIR to override the plugin scan directory (#11827) (#11906)
Boarded with 8 other PRs in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-native-deps all green; 75/75 focused tests pass. Clean, well-scoped env-override with correct blank-value handling and a startup log naming the resolution source. Thanks.
2026-08-30 05:28:29 -03:00
KaspaPulse
81bf1ef98a feat(leases): expose owner-authenticated connection display name (#11910)
Boarded with 8 other PRs in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-native-deps all green; 75/75 focused tests pass. Reviewed the security fencing closely — the status query fences on lease_owner_hash + api_key_id + generation + state=ACTIVE + not-expired, gated behind the existing lease:exclusive scope check. configuredConnectionName() correctly excludes email-derived fallback labels from the response. Test coverage explicitly verifies foreign key / different owner / stale generation all fail closed with 409, and no metadata leaks for released/expired/invalidated/missing leases. Thanks for the careful privacy-safe design.
2026-08-30 05:28:18 -03:00
Paco Cartones
41c6135257 fix(ollama): preserve multi-byte UTF-8 content split across stream chunks (#11921)
Boarded with 8 other PRs in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-native-deps all green. Retargeted from main to release/v3.8.51. Confirmed ollamaTransform.ts was the only streaming transform not using a persistent { stream: true } decoder — matches the pattern already established in responsesTransformer.ts. TDD repro included. Thanks for finding an unreported bug.
2026-08-30 05:28:07 -03:00
echel0n
22011437f8 fix(providers): route OrcaRouter chat requests to /v1/chat/completions (#11923)
Boarded with 8 other PRs in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-native-deps all green. Retargeted from main to release/v3.8.51. One-line baseUrl fix with a live endpoint probe documenting the exact 404→401 transition — solid verification. Thanks.
2026-08-30 05:27:57 -03:00
Patryk Kopyciński
8bed101303 fix(guardrails): prevent duplicate prompt-injection-guard log output (#11936)
Boarded with 8 other PRs in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-native-deps all green; 75/75 focused tests pass. Verified the console fallback → null fix removes the duplicate plain-text line while the structured pino log is unaffected. Thanks.
2026-08-30 05:27:47 -03:00
Patryk Kopyciński
ff0743071e fix(auth): downgrade expected transient states from warn to debug (#11937)
Boarded with 8 other PRs in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-native-deps all green; 75/75 focused tests pass. Trivial, correct log-level fix — both conditions are already handled gracefully by callers. Thanks.
2026-08-30 05:27:39 -03:00
Diego Rodrigues de Sa e Souza
41f4f83772 fix(release): never let the tag-push Create Release append auto notes to the curated body (#12096)
Phase 3 creates the GitHub Release with the curated notes right after the tag push,
so softprops always finds an existing body; generate_release_notes must be false
on every event, not only on workflow_dispatch (v3.8.48 shipped with the auto block
appended; the body sits ~3 KB under the 125,000-char cap). Same hunk as main (#12086);
the #12085 squash did not carry it.

Refs #12084
2026-08-30 05:25:02 -03:00
Bl0ck
79b2e92c4e fix(codex): fail over image generation for imported free plans (#11948)
Boarded with #11954/#11953/#11951/#11952 in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles all green; 85/85 focused tests pass. Verified both halves of the gap directly: isCodexFreePlan() (open-sse/executors/codex/tools.ts) only checks workspacePlanType, while codexImport.ts normalizes the JWT plan into providerSpecificData.chatgptPlanType — confirmed imported free-plan accounts would bypass the existing guard. Thanks for tracing the full import-to-guard path.
2026-08-30 05:10:39 -03:00
Bl0ck
e96e40c035 fix(images): forward Antigravity image size (#11952)
Boarded with #11954/#11953/#11951/#11948 in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles all green; 85/85 focused tests pass. Confirmed the Antigravity Gemini path only forwarded aspectRatio into generationConfig, dropping the requested size tier entirely. Thanks for the fix and the 3:4/2K regression coverage.
2026-08-30 05:10:11 -03:00
Bl0ck
097226b617 fix(codex): normalize non-stream responses (#11951)
Boarded with #11954/#11953/#11952/#11948 in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles all green; 85/85 focused tests pass. Confirmed the codex registry entry was missing forceStream: true while every other JSON-only-client provider (cline, clinepass, ghe-copilot, kimi, zed-hosted, chatgpt-web-codex) already has it. Clean reuse of the existing bridge, no Codex-specific response handling needed. Thanks!
2026-08-30 05:09:48 -03:00
Bl0ck
70af41b9f6 fix(db): invalidate connection cache after upsert (#11953)
Boarded with #11954/#11951/#11952/#11948 in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles all green; 85/85 focused tests pass. Verified the exact gap: invalidateDbCache("connections") after _updateConnectionRow() (src/lib/db/providers.ts:599) is only reached inside the retired-provider special-case branch (line 610-617) — the common return path (line 619) skips it entirely, confirmed. Thanks for the precise fix.
2026-08-30 05:09:24 -03:00
Bl0ck
8180b3213a fix(codex): restore imported account state (#11954)
Boarded with #11953/#11951/#11952/#11948 in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles all green; 85/85 focused tests pass. Verified the root cause directly: createProviderConnection() matches existing rows via provider_specific_data.workspaceId (src/lib/db/providers.ts:462-470), but codexImport.ts only emitted chatgptAccountId — confirmed re-import would miss the intended stable-identity match. Thanks for the careful diagnosis.
2026-08-30 05:08:56 -03:00
Diego Rodrigues de Sa e Souza
a3c19dd27c fix(ci): accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch (#12085)
Validated: actionlint clean on all three touched workflows, check-api-typecheck.mjs OK (289 pre-existing, all frozen) after boarding on top of #12094. Confirmed the .trivyignore justification against the documented CVE Variance process (docs/security/SUPPLY_CHAIN.md) — has tracking issue #12084, expiry before the v3.8.51 tag, and a real technical reason the .so can't be rebuilt in this repo. Scorecard branch guard correctly targets the actual default branch (release/vX.Y.Z), not a hardcoded main.
2026-08-30 04:51:48 -03:00
Diego Rodrigues de Sa e Souza
e620c50f3c fix(api): clear the six API-route TypeScript regressions the new gate landed red on (#12094)
Validated: check-api-typecheck.mjs OK (289 pre-existing, all frozen), typecheck:core clean, 8/8 check-api-typecheck.test.ts pass. Spot-checked two of the six fixes directly — the webhooks/[id]/test/route.ts duplicate import is confirmed removed (real ESM defect), and the volcengine-plan strict-boolean-narrowing fix (`validation.success === false` vs `!validation.success`) is behaviorally identical since `.success` is a strict boolean. This unblocks every other open PR into release/v3.8.51 that was landing red on the new API Route Typecheck gate — including #12085.
2026-08-30 04:48:54 -03:00
brick30llc-ctrl
2e3cd599b6 feat(routing): add LiquidAI LFM2.5-2.6B free tier via OpenRouter (#11752)
Resynced onto the release tip — the FREE_CATALOG_CURATED_AT bump conflicted with a later bump already on the tip; resolved to today's date since real content is landing. typecheck:core clean, 23/23 focused tests pass (free-model-catalog, free-models). Verified live against OpenRouter's own /api/v1/models pricing as claimed. Thanks for the new free-tier entry.
2026-08-30 04:31:31 -03:00
Markus Hartung
5684589ce7 feat(dashboard): collapsible JSON tree viewer for request/response payloads (#11703)
Resynced onto the release tip. Two fixes applied during boarding: (1) the branch forked before the recent optionalDependencies placement of @huggingface/transformers and onnxruntime-node — its own diff re-added both into "dependencies" as duplicates alongside the real new dependency (react18-json-view); removed the duplicates, ran npm install to sync the lockfile. (2) config/quality/dependency-allowlist.json referenced the wrong package name (react-json-view-lite, an earlier iteration per the PR body) — the code actually imports react18-json-view; fixed the allowlist entry to match. RequestLoggerDetail.tsx crossed its frozen file-size cap (1018->1111); rebaselined with a note — the PR does split out the new logic (RequestLoggerDetail.sections.tsx, JsonTreeExpandControls.tsx, useTimestampTitles.ts, jsonTreeExpandStore.ts, all well under cap), the growth here is irreducible wiring. typecheck:core, check:dashboard-typecheck, check:file-size, check-deps all green after resync; 8/8 vitest + 11/11 native tests pass. Nice, well-structured 6-commit feature with full i18n and good test coverage. Thanks!
2026-08-30 04:26:15 -03:00
Diego Rodrigues de Sa e Souza
ccee48d34a fix(db): drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) (#12087)
* test(cli): align the nodes --base-url contract test with #12033

#11860 asserted that `nodes add/update/validate` must NOT register `--base-url`
(reserved for the global server target); #12033 (issue #11999) then registered
it on purpose so `omniroute nodes add --provider p --base-url <url>` stops being
rejected by Commander's global option. Both PRs landed and the older test turned
the base red on unit shard 2/4 (`Unit Tests fast-path (2/4)`, run 33293442568).

The test now asserts the current contract: both flags are registered and each
parses into its own option; the server-target/payload separation keeps its own
test right below.

* test(mutation): register lkgp-stale-pin-exhaustion-11911 in tap.testFiles

38e2baa879 (#11911) added a unit test covering src/shared/utils/circuitBreaker.ts
without listing it in stryker.conf.json tap.testFiles, so check:mutation-test-coverage
--strict (Fast Quality Gates) is red on the release tip.

* fix(db): drop three consumer-less 1proxy exports the deleted localDb barrel was masking

50bc8ab8aa (#12055) removed the @/lib/localDb barrel; its re-exports were the only
thing keeping getOneproxyStats / deleteOneproxyProxy / clearAllOneproxyProxies (and
the private mapStatsRow + OneproxyStats type) 'used' for knip. The 1proxy routes
are 308 compat redirects to /api/settings/free-proxies since v3.8.4, so nothing
calls them: check:dead-code went 413 -> 419 on the release tip (baseline 416).
Back to 416 with typecheck:core, eslint and check:db-rules green.

* chore(mutation): drop the duplicate tap.testFiles entry — #12082 already registered it
2026-08-30 04:23:09 -03:00
Sabee Ur Rehman Khan
1c37fff056 fix(memory): honor category filter in GET /api/memory (#11699)
Boarded with #11756 (a duplicate fix for the same underlying issue #11650). Compared both implementations directly: this one is technically superior — guards the json_extract() call with json_valid(metadata) so malformed/legacy metadata returns no match instead of throwing a 500, and covers genericBackend.ts/obsidianBackend.ts in addition to sqliteBackend.ts. #11756 only touched SQLite and had no malformed-JSON guard. Closing #11756 with credit. Resynced onto the updated release tip: the test file's `await import("../../src/lib/localDb.ts")` broke after #12055 deleted the barrel earlier this session (your branch forked before that migration) — fixed to import updateSettings directly from @/lib/db/settings, matching the pattern already used by other integration tests. typecheck:core, check:dashboard-typecheck, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-deps all green; 4/4 integration + 35/35 vitest pass after resync. Thanks for the thorough, well-tested fix.
2026-08-30 04:12:08 -03:00
Sabee Ur Rehman Khan
c2c97aff82 ci: add API route TypeScript regression gate (#11705)
Boarded in a combined worktree: typecheck:core, check:dashboard-typecheck, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-deps all green. Clean, self-contained addition (5 new files, 0 modifications to existing code) that mirrors the existing dashboard-typecheck baseline-ratchet pattern. Thanks for closing a real coverage gap — API routes had no dedicated typecheck gate.
2026-08-30 04:07:28 -03:00
santosraju99-hub
faebf6de5f fix(shared): block cloud-metadata hosts under default remote-image guard (#11755)
Boarded in a combined worktree: typecheck:core, check:dashboard-typecheck, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-deps all green; 10/10 focused tests pass. Real SSRF gap confirmed — the default "block-metadata" guard mode fell through to the unchecked parseOutboundUrl() while 3 other call sites of the same guard mode already routed through parseAndValidateNonMetadataUrl(). Good catch that the existing test suite only ever exercised "public-only" explicitly. Retargeted from the stale release/v3.8.50 base to release/v3.8.51. Thanks for closing a real cloud-metadata SSRF exposure.
2026-08-30 04:07:18 -03:00
Nguyễn Viết Tuấn
55691e0416 fix(usage): allow quota refresh for FREE lease-reserved connections (#11758)
Boarded in a combined worktree with 6 other PRs: typecheck:core, check:dashboard-typecheck, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-deps all green. Verified the root-cause diagnosis directly against the code: isConnectionUnavailableToAuxiliaryActivity() does return true for any connection reachable by an active exclusive lease regardless of whether the lease is actively serving a request, confirming the fix's scoping is correct. The change is surgically limited to providerLimits.ts's live-usage-fetch path — the shared isolation function and its other call sites (warmupScheduler, quotaAutoPing, modelTestRunner, etc.) are untouched. Well tested (214 lines across 3 test files). Thanks for tracking this down.
2026-08-30 04:07:06 -03:00
ANIRUDDHA ADAK
a1d6ff5fbf fix(api): preserve caller-provided X-Correlation-Id on chat completions (#11760)
Boarded with #11741 (a duplicate fix for the same underlying issue #11739). Compared both implementations directly: this one is technically superior — a dedicated resolveIncomingCorrelationId() helper that strips CRLF (header-injection prevention) and bounds length to 1-256 chars, with 4 unit tests covering those edge cases. #11741's simpler `header || generateRequestId()` has no sanitization. Closing #11741 with credit. Validated in a combined worktree: typecheck:core, check:dashboard-typecheck, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-deps all green; 84/84 + 43/43 focused tests pass across this batch. Thanks for the careful sanitization work.
2026-08-30 04:06:52 -03:00
backryun
49827c1db1 fix(dev): bound webpack and Tailwind scans (#12075)
Boarded with #12082 in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles all green; 77/77 focused tests pass. Genuinely conservative as described — dev-only Tailwind/webpack scanning bounds, production chunking untouched. The later phases of #12074 (2/3/4/4b) are being held for a dedicated review given their combined architectural weight (DB init graph, credential refresh, process lifecycle, network dispatch boundary) — flagged separately on those PRs. Thanks for the clean Phase 1 baseline.
2026-08-30 03:31:14 -03:00
backryun
47ea113b99 fix(ci): reconcile release test contract drift (#12082)
Boarded with #12075 in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles all green; 77/77 focused tests pass. CI-contract-only reconciliation as described — no production behavior change, and the referenced files (lkgp-stale-pin-exhaustion-11911.test.ts, cli-nodes-commands.test.ts) confirmed already present and correctly aligned. Thanks for keeping this separate from the dev-bundler phase PRs.
2026-08-30 03:30:54 -03:00
Syed Raheemuddin
d26fe03801 feat(routing): add relayMode for schema-locked context handoffs (#11839)
Boarded with #12003/#11841/#11840 in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles all green; 24/24 focused tests pass. relayMode is opt-in and defaults to standard, so this is backward-compatible as claimed — verified the plumbing through resolveUniversalHandoffConfig/resolveContextRelayConfig/selectMessagesForSummary. Thanks for the clean, well-tested addition.
2026-08-30 02:52:10 -03:00
Syed Raheemuddin
92574de164 fix(chat): preserve unstripped model string for passthrough provider routing (#11840)
Boarded with #12003/#11841/#11839 in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles all green; 24/24 focused tests pass. Contained fix — preserves the unstripped model string for passthrough providers (cline/kilocode) only when the combo actually redirected to a passthrough provider. Thanks for the regression coverage.
2026-08-30 02:51:53 -03:00
Syed Raheemuddin
da678bd3ff feat(config): add support for runtime system prompt configuration and hot-reloading (#11841)
Boarded with #12003/#11840/#11839 in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles all green; 24/24 focused tests pass. Clean, well-contained addition mirroring the existing systemTransforms hot-reload pattern, tested for both set and cleared states. Thanks for the tidy runtime-config feature.
2026-08-30 02:51:34 -03:00
Syed Raheemuddin
2ec24e7c0b fix(core): resolve DB init race condition and reasoning translation (#12003)
Boarded with #11841/#11840/#11839 in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles all green; 24/24 focused tests pass. Both fixes are surgical and well-reasoned: explicit ensureDbInitialized() call for MCP stdio (verified the function exists at src/lib/db/core.ts:1496) avoids a startup race, and the reasoningContent fallback prevents empty message.content when only reasoning was returned. Thanks for tracking down both root causes.
2026-08-30 02:51:18 -03:00
Webman
50bc8ab8aa fix(barrel): delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) (#12055)
Resynced onto the release tip after #12051/#12052/#12053 landed. Same LKGP-clear conflict as #12053 (kept the current clearStaleLKGP() helper at both call sites). One additional issue this final phase's combined-worktree validation surfaced: clearStaleLKGP() itself (added by #12013, which none of the 4 phase PRs could have seen since it landed after they were authored) still had a dynamic `await import("@/lib/localDb")` — a real break once this PR deletes the barrel. Fixed to `await import("@/lib/db/settings")`, matching the direct-import pattern used at every other call site. typecheck:core, check-db-rules, check:cycles, and the eslint-import-boundaries regression test (3/3, including "G14 rejects localDb barrel imports") all green after resync — zero barrel-importing production files remain. Nice clean 5-phase migration, and thanks for taking on the full #11795 cleanup.
2026-08-30 02:36:26 -03:00
Webman
4e11887085 fix(barrel): migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) (#12053)
Resynced onto the release tip after #12051/#12052 landed. One real conflict in open-sse/services/combo.ts at both LKGP-clear call sites (handleComboChat + round-robin path): the release tip already has #12013's clearStaleLKGP() helper, which this PR's branch predates — kept the current helper call at both sites, discarding the pre-refactor inline pattern. typecheck:core and the open-sse test suite (vitest, 9/9 on volumeDetector) both green after resync. Thanks for the well-scoped Phase 4 migration.
2026-08-30 02:31:09 -03:00
Webman
38a29661d3 URGENT fix(build): route ChatGPT Web MCP bundle through runBuildTool (Windows/Node 24 build crash) v.50/.51 (#11706)
Confirmed the bug is real and unfixed on the current tip before merging: `scripts/build/prepublish.ts` line 332 was still calling `execFileSync(NPX_BIN, ...)` directly (raw win32 npx.cmd spawn), the exact CVE-2024-27980 shim pattern the file's own header warns about. Root cause, fix, and evidence match — routing through the existing `runBuildTool()` helper. Thanks for catching the one call site the earlier refactor missed.
2026-08-30 02:27:54 -03:00
Webman
2463781e00 fix(barrel): migrate src/lib/ off the localDb barrel to direct db imports (#59) (#12052)
Boarded together with Phases 2, 4, 5 (#12051, #12053, #12055) and validated in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-db-rules all green. Mechanical import-path migration only, no behavior change. Thanks for the phased, well-tested cleanup.
2026-08-30 02:27:25 -03:00
Webman
aa861a80d2 fix(barrel): migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) (#12051)
Boarded together with Phases 3-5 (#12052, #12053, #12055) and validated in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-db-rules all green. Mechanical import-path migration only, no behavior change. Thanks for the phased, well-tested cleanup.
2026-08-30 02:26:55 -03:00
Bob.Hou
38e2baa879 fix(resilience): clear persisted LKGP pin on target exhaustion and skip (#11911) (#12013)
When an auto/*/lkgp combo target failed into exhaustion (e.g. an unauthenticated free-tier 401) or was skipped pre-dispatch (cooldown, model lockout, unavailability), the Last Known Good Provider pin was never cleared — so subsequent requests kept re-selecting the same dead provider, causing repeated failures and mass-skipping instead of falling through to a healthy target. Centralizes invalidation into clearStaleLKGP(), invoked from both handleComboChat and handleRoundRobinCombo on exhaustion, pre-dispatch skip, and body-specific 400 termination.
2026-08-29 19:52:06 -03:00
Bob.Hou
d3420d29f1 fix(admission): exclude reclaimable page cache from the cgroup pressure ratio (#12017)
Real production incident (2026-08-29): the resource-pressure guard ratioed raw cgroup v2 memory.current (which counts reclaimable page cache) against memory.max, so a busy host with ~3GiB of page cache latched a global 503 across every model for 26 minutes even though PSI/OOM/memory.events all showed zero real pressure — the kernel would have reclaimed those pages instantly. Fix: ratio the working set (current - file) for the trip/recovery check, falling back to the raw ratio when memory.stat is missing/stale/zero (never clamping to a false zero-pressure reading).

12 new tests including direct incident reproduction (raw 95%/workingset 32% stays normal) + bug-injection round trips. Full resource-pressure + admission suites green (48/48, re-verified in this batch together with the other 3 PRs: 57/57).
2026-08-29 19:52:02 -03:00
Bob.Hou
5698769aba fix(cli): support --base-url alongside --endpoint in nodes subcommands (#11999) (#12033)
Commander's top-level global --base-url option was shadowing the flag on `omniroute nodes add/update/validate`, rejecting the command with "required option '--endpoint <url>' not specified" even when --base-url was correctly supplied. Now both flags are accepted on all three subcommands, falling back to whichever the user passes.
2026-08-29 19:51:59 -03:00
Bob.Hou
55e33f3dc8 fix(sse): default crash-guard logger to console.warn, not console (#12042)
Real production incident (2026-08-29): the crash guard #11556 introduced defaulted its logger to `log ?? console` — console is an object, not a function, so a burst of client aborts (ECONNRESET) reaching the process-level guard threw TypeError inside the uncaughtException handler itself and killed the server, twice in three minutes. Fix: default to console.warn.bind(console).

Bug-injection round trip confirms the new test fails on the old default and passes on the fix. Existing guard suite stays green: 9/9 (verified together with the new test).
2026-08-29 19:51:56 -03:00
Diego Rodrigues de Sa e Souza
36b7920db1 refactor(video): extract a Video Bridge pipeline with explicit ports (#12016)
Extracts videoBridge.ts's per-part loop body, whole-result cache identity/key helpers, and describeWithVisionModel into a new videoBridgePipeline.ts with explicit port boundaries (VideoMediaBrokerPort, VideoAudioTranscriptionPort, VideoDrilldownPort). videoBridge.ts shrinks 820→255 lines, now only handling request traversal, policy resolution, aggregation, and response payload. Moved as whole blocks, parameterized rather than rewritten — byte-for-byte traceable to the pre-extraction code.

Rebased onto the tip after sibling #12009 (FU-05 core) landed first and bumped the result-cache version v4→v5 in videoBridge.ts — that same bump (plus its explanatory comment) is now carried into the extracted videoBridgePipeline.ts instead. Re-validated: 20/20 focused tests, typecheck clean.
2026-08-29 19:39:37 -03:00
Diego Rodrigues de Sa e Souza
5fcd39bd6f feat(video): orchestrate Audio Bridge STT with one-download budgets (FU-06, #11654) (#12012)
FU-06 (Audio Bridge STT orchestration): one download, two extractions — takes already-downloaded video bytes and extracts bounded mono 16kHz PCM WAV via the loopback broker's new mode=audio operation, sharing the exact same queue/deadline/byte budgets as the frame path. Dual opt-in (operator setting default false + per-request), only reaches the STT call when both are on.

Rebased onto the tip after sibling #12011 (subtitle mode) landed first, both touching the same broker route/client — combined additively so frames/audio/subtitles all share the one extractionQueue singleton. Re-validated: 59/59 focused tests pass.
2026-08-29 19:34:57 -03:00
Diego Rodrigues de Sa e Souza
e8b2cd208d docs(video): clarify Video Bridge transcript provenance is caller-declared (#11661) (#12001)
Reconciles the Video Bridge FU-01..09 backlog docs against verified code and GitHub state (ground truth established first, per this repo's Documentation accuracy rule), correcting a real gap in GUARDRAILS.md: the transcript source field was documented as validated without noting OmniRoute didn't yet verify server-side extraction — exactly the gap #11652 (now merged as #12009) closes. Refs #11661, not Closes — truthfully closing it needs the sibling PRs' actual landed state folded back in, left as an explicit follow-up.
2026-08-29 19:33:39 -03:00
Diego Rodrigues de Sa e Souza
f30e5b2675 feat(video): connect tenant-bound drill-down lifecycle and multiresolution variants (FU-08) (#12006)
FU-08 (Refs #11655): drill-down producer/consumer lifecycle on top of the existing cache substrate, without modifying it — new VideoDrilldownLifecycle (opaque sha256 handles, principal-bound resolve/delete with no existence oracle, preview/standard/detail multiresolution variants, 8-frame/32MiB page budget) plus a new authenticated remote-consumer route, both opt-in (default false).
2026-08-29 19:33:37 -03:00
Diego Rodrigues de Sa e Souza
ef668967f6 test(video): freeze FU-07/FU-09 promotion-evidence manifest, aggregator, evaluator and allowlist scaffold (#11656) (#12008)
FU-07/FU-09 promotion-evidence harness (Refs #11656): delivers the manifest schema, deterministic fixture recipes, metrics aggregator, and promotion-verdict evaluator #11656 asks for — deliberately does NOT deliver the promotion verdicts themselves (they require real models against real fixtures on a live host, HOLD with explicit reason instead of any fabricated result). New files only, no collision with sibling PRs.
2026-08-29 19:33:33 -03:00
Diego Rodrigues de Sa e Souza
60dc242178 feat(video): derive embedded subtitle provenance in the protected broker (#11659) (#12011)
FU-05 subtitle adapter (Refs #11659 — deliberately not Closes: the adapter is not yet wired into the live describeVideoPart path, that composition point is sibling #12009 which just landed): server-owned, loopback-only ffprobe/ffmpeg subtitle extraction that legitimately earns the "embedded" provenance label, mirroring the existing frame-extraction lifecycle. Broker route now also serves ?subtitles=1, stamped with the shared broker fingerprint so the client-side adapter can verify the payload actually came from the trusted process. Bounded, ReDoS-safe WebVTT parser.
2026-08-29 19:33:27 -03:00
Diego Rodrigues de Sa e Souza
3b00535d04 feat(guardrails): enforce video transcript provenance, budgets and reconciliation (#11652) (#12009)
FU-05 core (closes #11652): caller-supplied Video Bridge transcripts had no bounded, deterministic contract — a client could self-assert source: "embedded"/"audio-bridge" and it was accepted verbatim. normalizeVideoTranscript gained a code-only trustedSource seam unreachable from request-body JSON; without it, any cue declaring embedded/audio-bridge is reclassified to client. Added budgets (256 cues, 4096 code units/cue, 4KiB/cue, 64KiB total), malformed-surrogate rejection, focus-window scoping, deterministic cross-source reconciliation, and bumped the result-cache version v4→v5 so old-contract cache entries can never serve new-contract requests.

All 187 videoBridge* tests pass (185 pass, 2 unrelated pre-existing skips).
2026-08-29 19:32:35 -03:00
Diego Rodrigues de Sa e Souza
2b8d3a8291 fix(radar): restore D12 public boundary (#12057) 2026-08-29 17:37:26 -03:00
Diego Rodrigues de Sa e Souza
34e2f84c04 feat(api): explicit model exposure allow/deny list for /v1/models (#11481) (#11997)
Adds opt-in modelVisibilityAllowlist/modelVisibilityDenylist settings so an operator can curate exactly which models GET /v1/models advertises, mirrored into auto/* combo candidate pools (the same trap #6512 fixed for hidePaidModels). Default off, no behavior change for anyone who doesn't opt in.

TDD: 4 new test files, 22/22 passing (16 node:test + 6 vitest) + regression sweep across virtual-auto-combo/hide-paid/hide-auto-no-think suites (21/21).

Rebased onto the updated tip (a sibling #9133 landed first, same file) — kept both rebaseline annotations in file-size-baseline.json and set the value to the real measured line count after both merged.
2026-08-29 15:40:32 -03:00
Diego Rodrigues de Sa e Souza
065d998407 fix(cli): update flow now says whether the running process needs a restart (#11885) (#12005)
Fixes three defects in the "update doesn't restart the running process" bug class: CLI update guidance now detects a live server and tells the operator to restart instead of implying the update is already live; the dashboard's Update button tries OmniRoute's own PID-file supervisor before falling back to pm2 instead of hardcoding pm2 and silently skipping; getLatestVersionFromNpmCli now uses --prefer-online (same fix pattern as #4376). TDD throughout, 63/63 targeted regression tests pass.
2026-08-29 15:27:53 -03:00
Diego Rodrigues de Sa e Souza
d32c76f85a fix(config): correct Hermes-4-405B display label from 7B to 405B (#11861) (#11993)
Fixes a copy-paste label typo (Hermes-4-405B mislabeled "7B") in both the registry and the free-model catalog data, spotted in the #11861 comment thread. TDD: 3/3 tests, generic parameter-size consistency check + exact regression guard.
2026-08-29 15:27:49 -03:00
Diego Rodrigues de Sa e Souza
c9b1c12cfd fix(db): include local no-API-key providers in Qdrant embedding-model list (#11949) (#11995)
Local no-API-key providers (ollama-local, lm-studio, vllm, etc.) were invisible in the Qdrant embedding-model dropdown because configuredProviders required a real apiKey or OAuth. Extended the filter to also include providerAllowsOptionalApiKey(connection.provider) — the same canonical helper already used for the identical check elsewhere. TDD: 21/21 integration tests pass (was 20/21 before the fix).
2026-08-29 15:27:44 -03:00
Diego Rodrigues de Sa e Souza
c8dc982eaa fix(ci): drop the stale ESLint cache restore-keys fallback from ci.yml (#11600) (#11996)
Fixes the blocking Lint job's own ci.yml cache: PR #11963 removed the stale restore-keys fallback from quality.yml but left ci.yml's two "Restore ESLint file cache" steps carrying the same prefix-match fallback that lets a cache from a different lint config report stale per-file verdicts. Byte-level parity with #11963's already-merged fix.

Deliberately half of #11600 — the other half (run-eslint-json.mjs) is covered by PR #11983 from a parallel session, so the two don't collide on the same file.
2026-08-29 15:27:39 -03:00
Diego Rodrigues de Sa e Souza
9ec4d39a74 fix(ci): webpack for docker-publish even on omni-build (#12050)
Turbopack had 31 GB on omniroute-113-6 and still panicked
(TurbopackInternalError: there must be a path to a root, run
33253576569). The same tree's arm64 webpack build on hosted ARM
succeeded. Dockerfile already documents webpack as the Docker
escape hatch. Keep amd64 on the one omni-build slot (#12048).
2026-08-29 15:18:33 -03:00
Diego Rodrigues de Sa e Souza
a9aee94a00 docs(ops): the .113 heavy-build ceiling is one runner, not two (#12048)
* docs(ops): the .113 heavy-build ceiling is one runner, not two

Two concurrent next-builds (15.4 GB + 17.2 GB RSS) OOM-killed one on 2026-08-29 17:26 UTC;
systemd booked the kill on the other runner's unit and its job died with the same
"shutdown signal" text a hosted-runner OOM shows. omni-build now lives on
omniroute-113-5 only; 113-6 keeps omni-release. The janitor ceiling counts every
listener on the box (4 OmniRoute + OmniHeuris + OmniMind = 6). The second heavy slot
returns when the Proxmox VM gets more RAM; the exact command is in the doc.

* docs(ops): apply the single-heavy-slot text (previous commit only carried formatting)
2026-08-29 14:41:20 -03:00
Diego Rodrigues de Sa e Souza
47f7e5a306 fix(release): the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) (#12047)
* fix(release): the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (release/v3.8.51 twin of #12032)

Same change as #12032 on main: the packaged app opens SQLite during the smoke but
its primary open path prints no "[DB] Driver: …" line (only the recovery path and
the sql.js fallback do), so the #7592 assertion failed every Linux release leg. The
guard rejects the sql.js fallback line, accepts a native driver line, and otherwise
accepts demonstrable database activity; after readiness the smoke requests
/api/monitoring/health and waits for that activity outside the readiness loop.
electron-smoke-script suite 10/10.

* fix(release): reapply the smoke rework on top of release/v3.8.51's own copy of the script

The previous commit copied main's file wholesale and dropped this branch's
ensureSmokeEnvDirs(currentPlatform) fix and its tests; this reapplies only the
DB-open evidence change as a patch. electron-smoke-script suite green.
2026-08-29 14:07:58 -03:00
Diego Rodrigues de Sa e Souza
38e2616464 fix(ci): stop hosted docker-publish OOM and unpaint Build (advisory) (#12021)
* fix(ci): stop hosted docker-publish OOM and unpaint Build (advisory)

docker-publish was firing 8 concurrent hosted builds on every merge
storm; each died ResourceExhausted in npm run build (#11976). One
publish per ref, webpack instead of Turbopack so native RSS stays
inside the V8 heap we can cap. Build (advisory) is skipped: continue-on-error
still reports FAILURE and was painting every fork PR red.

Closes #11976

* fix(ci): run docker-publish amd64 on omni-build and share the heavy lane

The .113 box is 31 GB / 32 cores — enough for one next-build. Hosted
ubuntu-24.04 is ~7 GB and ResourceExhausted every publish (#11976).
amd64 now targets [self-hosted, omni-build] (Turbopack) when
USE_VPS_RUNNER is on, joins the existing heavy-build-main group so it
queues beside ci.yml Build instead of becoming a third heavy, and
falls back to hosted + webpack if the VPS is off. arm64 stays on
ubuntu-24.04-arm with webpack (no ARM box).

* test(ci): align the advisory-build contract with the hosted-OOM skip

if: ${{ false }} tripped zizmor obfuscation (194→195). Bare if: false
skips the job without a new finding. The #7307 test now pins the skip
and keeps the job body as the restore recipe.
2026-08-29 09:52:00 -03:00
Diego Rodrigues de Sa e Souza
c4bd8b8ec4 fix(release): electron lockfile resync, build_ref, curated notes and SBOM on dispatch (twin of #11982 + #12020) (#12022)
* fix(release): resync the electron lockfile, build a dispatch from a repaired ref, keep curated notes, attach the SBOM on dispatch (release/v3.8.51 twin of #11982 + #12020)

Same four changes as #11982 and #12020 on main, applied to this branch's own copies:

- electron/package-lock.json regenerated (271 -> 284 entries): the optional
  electron-builder-squirrel-windows subtree was missing and `npm ci` refused the lock
  (EUSAGE) on the Linux and macOS legs; a clean `npm ci --ignore-scripts` on the
  result exits 0.
- electron-release.yml: `build_ref` dispatch input (default: the version tag) and
  `generate_release_notes` only on the tag push (a re-attach dispatch appended
  GitHub's auto notes to the curated body on v3.8.50).
- npm-publish.yml: the SBOM attaches to the GitHub Release on workflow_dispatch
  publishes too, whenever a release for the tag exists.

actionlint and prettier clean; electron-release-desktop-channel-8949,
electron-release-efficiency, electron-release-latest-yml.repro, check-workflows
and npm-publish-artifact-provenance suites pass.

* fix(release): validate build_ref in the validate job before any checkout uses it

CodeQL (actions/cache-poisoning/poisonable-step, high) on release/v3.8.51 — the
default branch: a raw dispatch input checked out next to setup-node's npm cache is a
cache-poisoning vector. The input now goes through the validate job's regex
allowlist (main or release/vX.Y.Z, empty = the version tag) and every build job
checks out needs.validate.outputs.build_ref, never the input itself.

* fix(release): drop the build_ref input — a dispatch builds the ref it is dispatched on

CodeQL (actions/cache-poisoning/poisonable-step) tracks the input through the
validate job's output regardless of the regex allowlist: an input-controlled
checkout next to setup-node's npm cache on the default branch is a cache-poisoning
vector. The ref is not an input any more; the checkouts use github.ref, so
`gh workflow run electron-release.yml --ref v3.8.50 -f version=v3.8.50` rebuilds
the tag and `--ref main` builds the repaired line. The tag-push path is unchanged.
2026-08-29 09:28:03 -03:00
Diego Rodrigues de Sa e Souza
e6de61f0c2 fix(sse): stop the auto-combo candidates inspector from dropping blocked rows (#9133) (#11994)
* fix(sse): stop the auto-combo candidates inspector from dropping blocked rows (#9133)

prepareVirtualAutoComboInputs applied filterResilienceBlockedCandidates
before the #7819 read-only candidate inspector ever saw the pool, so a
model-locked or cooled-down candidate silently disappeared from
/auto-combo/*/candidates instead of showing up as reachable:false with a
reason (modelLocked/connectionCooldown/breakerState were dead fields by
construction). Add an opt-in `skip` parameter so the inspector builds its
own unfiltered pool; routing (createVirtualAutoCombo/createBuiltinAutoCombo
called without a prepared override) is unchanged. Also aligns
isModelLocked's model argument to the bare model id, matching every lock
writer and the routing-side filter, instead of the "provider/model" string.

Regression test: tests/unit/auto-combo-candidates-locked-model-visible.test.ts
(red before the fix — locked account's row silently missing; green after).

* chore(quality): register the #9133 regression test in stryker tap.testFiles

tests/unit/auto-combo-candidates-locked-model-visible.test.ts covers
open-sse/services/accountFallback.ts (via isModelLocked) but wasn't listed,
so its mutant kills wouldn't count toward mutation coverage.

---------

Co-authored-by: Markus Hartung <mail@hartmark.se>
2026-08-29 08:10:40 -03:00
Diego Rodrigues de Sa e Souza
02ba573730 fix(providers): scope Antigravity mitmAlias tier ids to the safe static alias (#11824) (#11988)
Co-authored-by: Markus Hartung <mail@hartmark.se>
2026-08-29 08:10:37 -03:00
Diego Rodrigues de Sa e Souza
bd04bb9cc6 fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches (#11810) (#11986)
Co-authored-by: Markus Hartung <mail@hartmark.se>
2026-08-29 08:10:33 -03:00
Diego Rodrigues de Sa e Souza
322b218f06 fix(cli): drop the never-produced dist/index.cjs requirement from prepublish's opencode-plugin skip check (#11787) (#11990)
* fix(cli): drop the never-produced dist/index.cjs requirement from prepublish's opencode-plugin skip check (#11787)

* test(build): resolve tsup/npm portably in the #11787 regression test instead of a hardcoded .bin path

The old test assumed @omniroute/opencode-plugin/node_modules/.bin/tsup
already existed. A fresh checkout (CI's npm ci never installs this
standalone package's own deps) has no such node_modules at all, so the
test failed with MODULE_NOT_FOUND in CI while passing locally on a devbox
that had installed it before. Mirror scripts/build/prepublish.ts's own
install-then-resolveLocalBinEntry approach.

---------

Co-authored-by: Markus Hartung <mail@hartmark.se>
2026-08-29 08:10:29 -03:00
Diego Rodrigues de Sa e Souza
71093eda77 fix(codex): keep parallel_tool_calls:false on translated Responses Lite path (#11707) (#11984)
enforceCodexResponsesLiteParallelToolCalls() forces parallel_tool_calls:false
at the top of CodexExecutor.execute(), but transformRequest() early-returns
the body before its RESPONSES_API_ALLOWLIST field filter only when
_nativeCodexPassthrough is set. Any request that reaches the codex
executor via the translated (non-native-passthrough) path never gets that
flag, so the allowlist filter silently deleted parallel_tool_calls right
before the fetch body was sent, reproducing the reported upstream
rejection ('X-OpenAI-Internal-Codex-Responses-Lite requires
parallel_tool_calls to be false') for every model.

Add parallel_tool_calls to RESPONSES_API_ALLOWLIST so the value survives
the translated path too. Update the sibling #2608 allowlist test that
previously asserted parallel_tool_calls gets stripped like other Chat
Completions-only fields -- it is a legitimate Responses API field that
must now survive.

Co-authored-by: Markus Hartung <mail@hartmark.se>
2026-08-29 08:10:26 -03:00
Diego Rodrigues de Sa e Souza
fb9cbe9566 fix(ci): pass --pass-on-unpruned-suppressions in run-eslint-json.mjs (#11600) (#11983)
Co-authored-by: Markus Hartung <mail@hartmark.se>
2026-08-29 08:10:21 -03:00
Diego Rodrigues de Sa e Souza
674cc5feb1 fix(db): rate-limit Arena ELO fetch-failure warnings on repeated timeouts (#11500) (#11989)
* fix(db): rate-limit Arena ELO fetch-failure warnings on repeated timeouts (#11500)

* test(quality): split the #11500 fetch-failure-dedup tests into their own file

tests/unit/arena-elo-sync.test.ts crossed the 1000-line new-test-file cap
(file-size gate, PR mode). The two new tests don't need the file's DB
fixture (fetchArenaLeaderboards() never touches the DB), so they move to a
self-contained sibling file instead of growing the frozen suite.

---------

Co-authored-by: Markus Hartung <mail@hartmark.se>
2026-08-29 08:10:17 -03:00
Diego Rodrigues de Sa e Souza
d2ad71cf56 fix(kie): route flux/kontext to its dedicated endpoint, not the Market createTask flow (#11296) (#11985)
flux/kontext is catalogued with isMarket: true, so handleKieImageGeneration
routed it through KIE's unified Market createTask endpoint with
model: "flux/kontext". KIE does not expose Flux Kontext through the Market
catalog at all -- it lives under a dedicated API tree
(POST /api/v1/flux/kontext/generate, poll GET /api/v1/flux/kontext/record-info,
models flux-kontext-pro/flux-kontext-max) -- so the Market endpoint rejected it
with "model name not supported", matching the reporter's exact error text.

Special-case flux/kontext ahead of the isMarket branch so it hits the
dedicated endpoint/payload shape instead of being treated as a Market entry.
z-image/4.0-*/4.5-* remains intentionally untouched (still blocked on
reporter/live confirmation per the existing in-code comment).

Co-authored-by: Markus Hartung <mail@hartmark.se>
2026-08-29 08:10:13 -03:00
Tobias Andersen
c705147de2 docs(i18n): finish Freepik → Magnific rebrand in locale strings and README (#11772)
Finishes the Freepik → Magnific rebrand from #10594 across 40 locale files and 3 README feature-list bullets (README.md, docs/i18n/it, docs/i18n/tr) — legacy `freepik` alias intentionally left in code/tests/redirects for backward compatibility, and historical CHANGELOG entries left untouched as documented history.

The README bullet had base-drifted since the PR branched (release tip's "What's New" changelog snippet had already dropped two providers mentioned nowhere else in the codebase, unrelated to this PR's scope) — resolved by keeping the tip's current bullet shape and applying only the Freepik→Magnific rename on top, in both the combined-worktree validation and the pushed branch.

Validated: all 40 edited locale JSON files parse; re-verified after resync onto the updated tip (post #11762/#11774/#11781).
2026-08-29 05:30:13 -03:00
Tobias Andersen
d8879371ea fix(combo): lock GitHub models rejected as "not supported" for future requests (#11781)
Follow-up to #11762/#11774, same bug class in combo's own model-lockout wiring: GitHub rejects several models (gpt-5.4, gpt-5.3-codex, etc.) with a 400 that's permanently unavailable for this account's Copilot integration, but nothing recorded a cross-request lockout — combo's #5249 in-request advance guard is correct but doesn't persist, so the same doomed model gets retried from scratch on every new request, indefinitely.

Fix: on a model-scoped 400 (`isModelScoped400`), call `lockModelIfPerModelQuota(provider, connectionId, rawModel, "model_capacity", 1h)`. GitHub already has per-model-quota enabled, so only the rejected model locks — siblings keep working. `isModelLocked()` is already checked pre-dispatch, so no other wiring needed.

Validated: 3/3 new tests + fixed a pre-existing test-isolation gap in combo-model-scoped-400-advance.test.ts (shared model name across sub-tests without clearing lockout state). Thanks!
2026-08-29 05:21:50 -03:00
Tobias Andersen
91f9a01fda fix(resilience): stop hammering permanently-moved endpoints and billing-suspended accounts (#11774)
Follow-up to #11762, same bug class hitting freeaiapikey (410 permanently-moved endpoint) and fireworks (412 billing-suspension) — both fell through checkFallbackError's generic transient-cooldown branch and got retried every ~1 minute for a full day.

Fix: `ENDPOINT_PERMANENTLY_MOVED_PATTERNS`/`isEndpointPermanentlyMoved()` → 24h lockout; `ACCOUNT_SUSPENDED_BILLING_PATTERNS`/`isAccountSuspendedForBilling()` → treated as credits-exhausted (1h cooldown), independent of status code so it also catches Fireworks' 412.

#11762 landed first and touched the same file — rebased/re-merged onto the updated tip (additive, no logic changes) and re-validated: 13/13 tests pass. Thanks for tracing this with real production logs again!
2026-08-29 05:19:15 -03:00
Tobias Andersen
87b3bdf85e fix(resilience): lock permanently retired models instead of short backoff (Gemini ban prevention) (#11762)
Root-caused via a real Gemini-ban incident log: deprecated-model 404/410s (e.g. gemini-2.5-flash "no longer available to new users") fell through checkFallbackError's generic transient-cooldown branch, so combo/auto-routing kept re-selecting a permanently dead model every cooldown window forever — the hammering that got the account flagged as abusive.

Fix: `MODEL_PERMANENTLY_UNAVAILABLE_PATTERNS` + `isModelPermanentlyUnavailable()` classify these as a 24h lockout instead, surfaced via `quotaResetHintMs` so combo's per-request model-lockout honors it in full.

Validated: 6/6 new tests + 133/133 existing accountFallback/error-classification tests, no regressions. Thanks for tracing this end-to-end with real production logs!
2026-08-29 05:09:37 -03:00
Diego Rodrigues de Sa e Souza
3b752f9d4c chore(quality): type the 55 no-explicit-any sites frozen under #11924 (#11975)
Production (open-sse/utils/socksConnectorWithFamily.ts, 4 sites): every cast was
redundant — undici's buildConnector.BuildOptions already has `timeout?: number | null`,
socks' SocksClientOptions has `timeout?: number`, and Agent.Options' `connect` /
`connectTimeout` narrow to the connector's parameter types on their own. Behaviour
unchanged; check:open-sse-typecheck stays at the frozen 5.

Tests (51 sites): the socks-timeout mocks now carry the real types — the patched
SocksClient.createConnection is typed as the static it replaces, the fake
buildConnector returns buildConnector.connector, the proxy is a SocksProxy, the
dynamic import is typed as the module it loads; the e2e suite passes a SocksProxy and
Agent.Options and no longer casts undici's fetch init (its RequestInit already has
`dispatcher`); the isFree suites narrow getCustomModels()' JSON to a declared row
shape, feed deliberately-wrong values through `unknown`, and stop casting for
zod's safeParse, which takes unknown.

The six files' suppression entries are removed: 1238 → 1232 files, 5487 → 5432
suppressed. ESLint without the suppressions file reports 0 problems on all six;
with it, no stale entry is left. The five suites pass (4, 2, 5, 4, 4).
2026-08-29 03:06:50 -03:00
Diego Rodrigues de Sa e Souza
757cc3bb9d fix(release): let the Electron workflow start again — grant actions:read to the npm leg (release/v3.8.51 twin of #11973) (#11974)
Same three changes as #11973 on main, applied to this branch's newer copy of the
workflow so the v3.8.51 tag does not repeat v3.8.50's zero-asset release:
publish-npm grants actions:read (the called publish job requests it — a caller that
grants less is refused at startup and the release job dies with it), a publish_npm
dispatch input gates the npm leg, and web-build/build/release check out the tag
named by the dispatch. actionlint clean; the five workflow-pinning suites pass.
2026-08-29 02:42:13 -03:00
Diego Rodrigues de Sa e Souza
60bbf0f8f0 fix(ci): stop a stalled Codecov upload from cancelling the Coverage job and the main run (#11972)
The job has timeout-minutes: 20; the c8 merge across 8 shards takes ~10 min and the
Codecov upload (declared informational) then hung for the rest of the budget on two
consecutive main runs (33207760653, 33215115341) — GitHub cancels the step, the job
ends cancelled, and the run's conclusion turns cancelled although every blocking job
was green. The upload step now has its own 5-minute ceiling and continue-on-error;
the job budget is 30 min. check-workflows suite 32/32; zizmor ratchet unchanged.
2026-08-29 02:10:25 -03:00
Diego Rodrigues de Sa e Souza
3d4f3e4960 test(infra): retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) (#11968)
* test(infra): retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966)

Two shards on release/v3.8.51 went red in one day with the same signature —
"ENOTEMPTY, Directory not empty: /tmp/omniroute-<test>-XXXXXX" — from
combo-same-provider-cascade (Unit Tests fast-path 4/4, on a PR that touches only
.github/) and auth-policy-embeddings-webfetch-7785 (the 20k-test TIA step). Both pass
alone and on re-run: the cleanup races something still writing into the directory
(SQLite WAL/-shm checkpoint, a worker, the backup) and under a loaded hosted runner
the window opens. 1154 test files do their own cleanup with
fs.rmSync(dir, { recursive: true, force: true }); 57 already asked for retries.

One-shot codemod (scripts/ad-hoc/codemod-rm-maxretries.mjs, kept for the record):
every rm / rmSync / rmdirSync option object with `recursive: true` and no
`maxRetries` gains `maxRetries: 5, retryDelay: 100` — Node itself then retries
ENOTEMPTY/EBUSY/EPERM for up to ~0.5 s before giving up. 2243 call sites in 1292
files under tests/, the shared tests/_setup/isolateDataDir.ts exit hook included.
Only the option object changes: no call site, assertion or import is touched.

Validation: prettier and ESLint (with the frozen suppressions) clean on all 1292
files; a random 20-file sample runs green (quota-redis-store hangs identically on
the untouched tree — it needs a Redis on localhost, an environment matter). The
four unit shards on this PR are the full run.

* fix(quality): let check-forgotten-sibling-tests read a 1,000-file diff

The gate shells out to `git diff` through execFileSync with Node's default 1 MB
maxBuffer; the 1,292-file codemod in this PR is the first diff large enough to
overflow it, and the gate died with `spawnSync git ENOBUFS` before comparing
anything. 64 MB is far above any real PR and costs nothing when unused.
2026-08-29 01:17:40 -03:00
Diego Rodrigues de Sa e Souza
751710616a fix(ci): run the build-bearing nightly jobs on the box's light pool (#11965) (#11967)
Four nightly jobs run a backend-only `next build` on ubuntu-latest (7 GB):
Schemathesis, promptfoo injection guard, garak probes and the axe a11y suite
(self-building webServer). On release/v3.8.51 three of them died with the hosted
VM shutdown signature and nobody saw it — nightlies have no audience — and the
fourth passes by a margin of minutes. They now target [self-hosted, omni-light]
(hosted fallback when USE_VPS_RUNNER is off), a new two-listener label on the .113
box for jobs that need ~6 GB, not the 14-16 GB of a full build; they run once a day
in the 04:00-06:00 UTC window, when the box is idle.

Fleet reshaped the same day and documented in docs/ops/RUNNER_BOX.md: 4 active
OmniRoute listeners (omniroute-113-5/-6 omni-build, omniroute-113/-2 omni-light),
omniroute-113-3/-4/-7/-8 disabled (systemctl enable --now brings one back), janitor
ceiling MAX_ACTIVE_RUNNERS=4. The remaining headroom limit is the VM's 31 GB of RAM
(2 heavy + 2 light ≈ 42 GB peak, inside the 16 GB swap); more RAM on the Proxmox VM
is the lever that turns the label ceilings into 3 heavy + 2 light.

check:workflows --ratchet unchanged (194/194); check-workflows and
backend-only-smoke-workflows suites pass; docs-sync PASS.
2026-08-28 23:26:52 -03:00
Diego Rodrigues de Sa e Souza
d7cdfcad43 fix(ci): take the two hosted-runner builds off the PR rail (#11946, option 3) (#11962)
The hosted 7 GB runner cannot build release/v3.8.51 in any profile: `Build App`
(build.yml, push on every branch, full `build:release`) died in 19 of the last 30
runs — the branch tip included — with "The runner has received a shutdown signal"
~8 min into `next build`, swapfile and all; the advisory quality.yml build failed on
8/8 recent fork PRs with the same recipe; and `DAST smoke (PR)`'s backend-only build
died ~7 min in before the server even started, hidden as a permanently red
continue-on-error check. Together they painted every PR into release/** red with
zero signal and, on build.yml, produced an artefact nothing downloads.

- build.yml: workflow_dispatch only. The bundle is validated where a build fits —
  ci.yml `Build` on the self-hosted omni-build pool after every merge to main, and
  nightly-release-green.yml on the same pool for release/**.
- dast-smoke.yml: pull_request into main only (plus workflow_dispatch to smoke a
  release branch by hand); main's tree still builds on the hosted runner in ~5.5 min.
- quality.yml: the fork-only rationale of `Build (advisory)` updated to say why
  own-origin PRs no longer get a hosted build either. Behaviour unchanged.

check:workflows --ratchet: 194 zizmor findings, baseline 194. check-workflows and
backend-only-smoke-workflows suites pass. Trade-off stated in the PR: own-origin PRs
into release/** lose a pre-merge build that was not succeeding anyway; the nightly
rail files a base-red issue within a day if a merge breaks the build.
2026-08-28 22:52:15 -03:00
Diego Rodrigues de Sa e Souza
034314262e docs(agents): sync-back landings are fast-forward, never squash (#11964)
Records the v3.8.50 → v3.8.51 precedent in the single source of truth: a
main → release/vX+1 sync PR lands by fast-forward push so main stays an ancestor
of the release branch (squash re-conflicts the next sync-back on every file main
touched — 551 conflicts this cycle before the two-step merge), plus the two
post-landing checks (ancestry assert; ratchet files carried main's freezes).
The full procedure lives in the generate-release Phase 5 skill.
2026-08-28 22:44:06 -03:00
Diego Rodrigues de Sa e Souza
3d125647c8 chore(ci): cap the unit shards at 30 min and stop restoring stale ESLint caches (#11963)
- quality.yml fast-unit: timeout-minutes: 30. A shard finishes in ~10 min; without
  a ceiling a hung test process holds the PR for GitHub's 6 h default. On
  2026-08-28 shard 1/4 sat 64 min without a line of output — twice at the same spot,
  a timing race that vanished on the third run — while the other three shards were
  long green. A fast red plus a re-run beats a silent multi-hour hold.
- quality.yml lint-guard + the earlier ESLint cache block: drop the
  `restore-keys: eslint-<os>-` fallback (#11600, P-II.1 of the v3.8.50 postmortem).
  The key already hashes the lint config, the suppressions file and the lockfile;
  the fallback restored a cache built under a DIFFERENT configuration and its stale
  per-file verdicts are how 215 pre-existing errors stayed invisible for a cycle.
  Exact key or a cold full lint — never a partial cache from another configuration.

check:workflows --ratchet unchanged (194/194); check-workflows suite 32/32.
2026-08-28 22:43:11 -03:00
Diego Rodrigues de Sa e Souza
d0f69e4c70 chore(quality): re-freeze the ESLint suppressions on release/v3.8.51 from a clean-room run (#11955)
* chore(quality): re-freeze the ESLint suppressions on release/v3.8.51 from a clean-room run

`No new ESLint warnings` failed on every PR against release/v3.8.51 with exit 2:
"There are suppressions left that do not occur anymore". Measured in a depth-1 clone
with `npm ci` from the branch's own lockfile and the job's exact command
(`npm run lint:json -- --max-warnings 0`): 56 errors — 55 `no-explicit-any` in six
files that landed while the base was red (#11843 isFree tests: 22; b7102140d5 socks
connect timeout: 33) plus one `no-unused-vars` — and stale entries for files that no
longer violate. The devbox figure previously quoted in #11924 (280, with 224
react-hooks/*) does not reproduce on the lockfile install and is withdrawn.

- config/quality/eslint-suppressions.json: `--prune-suppressions` (two stale file
  entries removed) and the 55 pre-existing `any` frozen at their exact counts — the
  file is a ratchet, counts only go down; the debt stays tracked in #11924.
- open-sse/services/adobeFireflyCatalog.ts: remove `GPT_SIZE_MAP`, a constant the
  f3d9279b44 split left behind with no reader (the real violation, fixed not frozen).

Verification in the clean room after both changes, same command as CI: exit 0,
0 errors, 0 warnings (1238 files / 5487 suppressions).

* chore(quality): tighten openapiCoverage.pct to the measured 39 (require-tighten)

With ESLint back to 0/0 on this PR, the job's next step (check-quality-ratchet
--require-tighten) started failing: openapiCoverage.pct improved from 38.4 to 39
(delta 0.6 > slack 0.5) and the baseline must be tightened in the same PR. 39 is
the value CI collect-metrics measured on run 33213844112 and a clean-room checkout
of 777d9d1629 reproduces it; the cycle's new routes landed documented in
docs/openapi.yaml. Only this metric moves; annotation follows the file's convention.
2026-08-28 22:07:40 -03:00
diegosouzapw
777d9d1629 test(translator): fix the relative imports of the relocated deferred-finish test
#11940 moved tests/unit/translator/openai-to-claude-trailing-usage.test.ts one level
up so a collector would run it, but kept the ../../../ import path from the old
directory, so the file failed to load and painted Unit Tests fast-path (3/4) red on
every PR since a94fe23e89. The path now matches its new location (5/5 pass).
2026-08-28 19:23:37 -03:00
diegosouzapw
9661611e31 Merge remote-tracking branch 'origin/main' into chore/sync-main-into-3851-20260828c 2026-08-28 19:03:24 -03:00
Diego Rodrigues de Sa e Souza
24c0643a94 test(check): escape the runs-on fixture with JSON.stringify, not a quote-only replace (#11942)
CodeQL js/incomplete-sanitization (alert #888 on #11929): the hand-rolled replace
only escaped double quotes, so a backslash in the fixture would have produced a
malformed YAML scalar. JSON.stringify covers every escape the double-quoted YAML
scalar needs. Test-only change (7/7 pass).
2026-08-28 19:01:54 -03:00
Diego Rodrigues de Sa e Souza
a94fe23e89 fix(release): drain the twelve reds every PR against release/v3.8.51 was born with (#11940)
* fix(release): drain the twelve reds every PR against release/v3.8.51 was born with

Measured on the cycle tip: fifteen unit files were red on every PR. Two came
from the v3.8.50 sync-back (fixed in #11929); the other thirteen predate it and
are the branch's own drift. This sweep clears all of them but the ESLint debt
(#11924), each with the smallest change that keeps the guard honest:

- .env.example + ENVIRONMENT.md: NEXT_PUBLIC_SW_BUILD_ID / OMNIROUTE_SW_BUILD_ID /
  SOURCE_VERSION (#11779 service-worker cache busting) documented — the env/docs
  contract gate was failing on every PR.
- stryker.conf.json: the six tests the mutation gate found covering mutated modules
  (four retirement runtime-block suites, combo connection-aware expansion, tunnel
  error sanitization) registered in tap.testFiles.
- dependency-allowlist: eslint-plugin-react-hooks 7.0.1 approved; its findings are
  tracked in #11924.
- i18n: the six combo.sort.* strings (d5dfcfff58) translated for vi (strict parity)
  and pt-BR.
- docs/providers/CHATGPT_WEB.md: the retirement test is migration-168, not 163.
- g4f gateways: authHint now says member key, which the discontinued-providers
  guard asserts.
- tests realigned to the catalog the branch actually ships: qwen-web (#11713) and
  chatgpt-web (#11720) are retired, so web-session-contract and
  token-health-check-webcookie use perplexity-web, grok-web and chatgpt-web-codex.
- db-core-init: the two minimal legacy fixtures gained the columns migrations 164-168
  UPDATE (error_code, last_error*, test_status) — they exist on every real legacy DB
  (base CREATE TABLE); the fixtures simply never declared them.
- no-js-extension guard: a .js specifier whose target is a genuine JavaScript file
  (open-sse/lib/deepseek-pow-hash.js, shared with a worker) is not the #10674
  defect; the test now skips targets that exist as .js.

All twelve files pass locally; docs-sync, docs-counts, env-doc-sync, the tap
drift gate and the fabricated-docs gates are green on the tree.

* test(release): move the deferred-finish translator test into a collected path

tests/unit/translator/ is not one of the unit collectors (package.json test:unit,
merge-train.sh, build-test-impact-map, check-test-discovery), so the suite that
dd35750e5f added there never ran — check:test-discovery flagged it as a new orphan
on every PR. Relocated next to its sibling openai-to-claude-trailing-usage-11817
under tests/unit/, where the root glob collects it (5/5 pass).

* fix(dashboard): type the four sort-method sites #11812 left red on the dashboard typecheck ratchet

d5dfcfff58 added the combo model sort and raised combos/page.tsx from 23 to 27
scoped TypeScript errors (TS2339 +1, TS2345 +2, TS2322 +1), which fails
check:dashboard-typecheck on every PR against release/v3.8.51:

- initialSortMethod: sanitizeComboRuntimeConfig() is untyped, so config.modelSort is
  unknown; narrow it before reading .method (normalizeSortMethod takes unknown anyway).
- handleAddModels: the batch path passes ComboBuilderDraftModelStep[] to the ComboStep[]
  sort helpers without the cast handleSortChange already uses; mirror it.
- ComboSortSelect expects a translate-with-fallback (k, f) => string, but received
  next-intl's Translator whose second argument is a values object. Pass the page's
  getI18nOrFallback adapter instead of the raw translator — that is also what makes
  the `has()` check and the fallback text actually work at runtime.

Baseline untouched (no widening). Scoped tsc: 0 new/regressed errors.
2026-08-28 18:18:33 -03:00
Diego Rodrigues de Sa e Souza
8dfdd95187 test(release): align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 (#11944)
Eleven PRs landed on release/v3.8.51 while the branch carried fifteen base reds, and
nine more red tests hid among them. None is a defect in the shipped code; each test
still encoded the contract that the merged PR deliberately replaced:

- openai-to-claude finish deferral (dd35750e5f, #11933): a finish chunk that carries no
  usage is now held until the end-of-stream flush that production performs
  (open-sse/utils/stream.ts flush -> translateResponse(..., null, state)). The drivers in
  stream-markdown-token-boundary, translator-tool-call-shim and
  gemini-malformed-function-call-finish-reason-2462 fed the finish chunk and asserted
  the terminal events immediately; they now mirror the flush. Assertions unchanged.
- authoritative live catalog (3d2832b836, #11919 fixes #11829): a synced catalog replaces
  the static registry, so model-lifecycle-integration no longer expects the static-only
  gpt-5.6-sol row to survive a sync. The #8627 contract the file guards (stale chat rows
  suppressed, typed media retained) is untouched.
- provider asset provenance (#11876): the unit shards check out with depth 1. The fixture
  pinned a historical commit as auditedCommit (absent on a shallow clone), the
  "binds auditedCommit" case relied on the repository root commit (the grafted HEAD on
  a shallow clone, which matches the physical snapshot), and the real-manifest case
  needs the audited commit fetched. The fixture now audits HEAD, the mismatch case
  builds a dangling empty-tree commit (no ref written), and the real-manifest case
  skips only on a shallow checkout that lacks the commit - the gate itself keeps
  running on both fetch-depth-0 rails, which the next test asserts.

All five files pass locally (30, 11, 38, 3 and 18 tests); lint with the frozen
suppressions is clean.
2026-08-28 18:10:56 -03:00
Diego Rodrigues de Sa e Souza
226538fa27 feat(ci): publish to npm through Trusted Publishing (OIDC) by default (#11931)
* feat(ci): publish to npm through Trusted Publishing (OIDC) by default

npm rejects provenance from self-hosted runners and is retiring tokens that
bypass 2FA; v3.8.49 answered with staged publishing (WS1.3) so a leaked token
could never publish alone — at the price of a manual `npm stage approve` per
release. Trusted Publishing gives the same guarantee with no token at all: the
github-hosted stage-npm job exchanges GitHub's id-token for a credential scoped
to that run, provenance included, and the flow is automatic again as it was up
to v3.8.48.

publish_mode gains `auto` (the default, also the path for the release event);
`staged` now runs only when asked for; `direct` stays as the emergency token
fallback. Until the owner registers the Trusted Publisher on npmjs.com
(diegosouzapw/OmniRoute, workflow npm-publish.yml) the automatic step fails
with ENEEDAUTH and either other mode can be dispatched — documented in
docs/ops/RELEASE_CHECKLIST.md.

* docs(release): date the checklist for the Trusted Publishing change and drop the env-var claim

check-deprecated-versions flags a touched doc whose header still says
2026-06-28 / v3.8.40; the fabricated-docs gate read the backticked NPM_TOKEN as
an environment variable the code never reads (it is a repository secret).
2026-08-28 18:02:51 -03:00
diegosouzapw
fb7445eaa3 test(check): escape the runs-on fixture with JSON.stringify, not a quote-only replace
CodeQL js/incomplete-sanitization (#888): the hand-rolled replace only escaped
double quotes, so a backslash in the fixture would have produced a malformed YAML
scalar. JSON.stringify covers every escape the double-quoted YAML scalar needs.
2026-08-28 17:29:04 -03:00
diegosouzapw
9968e1ce6e Merge remote-tracking branch 'origin/release/v3.8.51' into chore/sync-main-into-3851-20260828b 2026-08-28 17:27:31 -03:00
Diego Rodrigues de Sa e Souza
f907b5ea8e fix(ci): cap heavy builds at two runners with the omni-build label (#11932)
The .113 box (31 GB) holds one next-build (14–16 GB RSS) comfortably and two
at the edge; on 2026-08-28 the kernel killed main's build twice while PR
builds ran beside it. Labels are the runner-side cap: only omniroute-113-5
and omniroute-113-6 carry omni-build (added through the runners API, no
re-registration), and every job that runs a next build — ci.yml build,
npm-publish.yml publish, both nightly-release-green validations — now asks
for that label. A third heavy job queues on GitHub instead of racing for
memory. The six other runners keep omni-release and no longer take builds.
Pairs with the heavy-build-* concurrency lanes (#11901); documented in
docs/ops/RUNNER_BOX.md.
2026-08-28 17:19:45 -03:00
Diego Rodrigues de Sa e Souza
5b38ec717d fix(ci): keep the next-build artefact on disk, not on the runner's tmpfs (#11896)
* fix(ci): keep the next-build artefact on disk, not on the runner's tmpfs

On the .113 pool /tmp is a 12 GB tmpfs — it is RAM. The 1.3 GB next-build
artefact was parked there four times over: the Build job tar'd it to
/tmp/e2e-build.tar.gz (6 min), three E2E jobs downloaded it to /tmp/ and
extracted from there, and npm-publish.yml pulled it with gh run download into
/tmp/next-build. Measured on the v3.8.50 publish runs: that download step took
27 min (9th attempt) and 32 min (10th) — 42% of a 76-minute job — while the
very same bytes upload from disk in 2 min and the box pulls from GitHub at
7.3 MB/s (1.3 GB ≈ 3 min). Network was never the bottleneck; a tmpfs at 75%
under memory pressure was.

Every site now uses $RUNNER_TEMP / ${{ runner.temp }}: per-runner, on disk
(_work/_temp under the runner dir on the pool, /home/runner/work/_temp on
hosted images), and cleaned by the runner between jobs.

It also removes a latent race: e2e-build.tar.gz is a FIXED name under a /tmp
shared by every runner on the box, so two E2E shards on different runners could
overwrite each other's download mid-extraction. RUNNER_TEMP is per runner.

The supply-chain guard in tests/unit/npm-publish-artifact-provenance.test.ts
pins the candidate-run selection and the --name, not the directory; it stays
green. check:workflows --ratchet: zizmor unchanged at the baseline.

* fix(ci): download the next-build artefact to a workspace-relative dir (pwsh has no $RUNNER_TEMP)

The Electron Package Smoke matrix runs on windows-latest, whose default shell
is pwsh: $RUNNER_TEMP is empty there (pwsh spells it $env:RUNNER_TEMP), so the
first cut's tar -xzf "$RUNNER_TEMP/e2e-build.tar.gz" tried to open
'/e2e-build.tar.gz' and failed. A path relative to the workspace works in bash
and pwsh alike, and hosted workspaces are ephemeral. The producer (Build, Linux,
bash) and npm-publish keep $RUNNER_TEMP.
2026-08-28 17:08:58 -03:00
diegosouzapw
5ade9e0851 fix(sync): repair the two regressions the v3.8.50 sync-back left on release/v3.8.51
Fifteen unit files were red on this branch's PRs; running them on the pre-sync
tip (d5dfcfff58) and on the synced one showed thirteen already failed before
the sync — the cycle's own drift — and exactly two regressed:

- open-sse/services/tokenExtractionConfig.ts: git kept BOTH sides' identical
  volcengine-console config (23 entries instead of 22). The duplicate is gone.
- src/lib/usage/providerLimits.ts: the sync took release/v3.8.50's cooldown
  release helper, which is looser than this branch's #11277 contract (it frees
  an extra_usage block when the policy is off and a window with no reset
  evidence). tests/unit/provider-limits-recovery.test.ts pins the contract;
  the pre-sync call site is restored and the unused helper and its imports
  dropped. 20/20 again, siblings unchanged.
2026-08-28 16:41:42 -03:00
Diego Rodrigues de Sa e Souza
33763f06cc chore(changelog): add missing fragments for #11919/#11918/#11916 (#11938)
Adds the 3 missing changelog fragments.
2026-08-28 16:26:32 -03:00
Bob.Hou
6b259812a7 fix(sse): preserve store parameter semantics for openai-compatible responses (#11826) (#11916)
stripStore() now forces store=false for stateless OpenAI-compatible Responses-API targets unless the connection explicitly opts in via providerSpecificData.openaiStoreEnabled, instead of only handling the openai/agentrouter cases — a client-supplied store value previously passed through untouched to backends that don't actually persist responses server-side. Closes #11826. Thanks!
2026-08-28 16:25:19 -03:00
Bob.Hou
dc75a02ca7 fix(models): expose custom node models in canonical prefix mode (#11832) (#11918)
Custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled. Closes #11832. Thanks!
2026-08-28 16:25:05 -03:00
Bob.Hou
3d2832b836 fix(models): suppress static registry models when live catalog is synced (#11829) (#11919)
Suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not just providers using exclusive-synced-listing — closing a gap where a connection with providerUsesAuthoritativeLiveCatalog kept serving both the live-synced models and the stale static rows side by side. Closes #11829. 4/4 focused tests passing. Thanks!
2026-08-28 16:24:53 -03:00
Diego Rodrigues de Sa e Souza
cea1baa797 fix(ui): guard remaining ProviderIcon lookups against prototype collisions (#11920 port) (#11935)
Ports the 3 still-needed guards from #11920 that #11880 didn't cover. 90/90 + 4/4 focused tests passing.
2026-08-28 16:19:07 -03:00
Diego Rodrigues de Sa e Souza
dd35750e5f fix(sse): defer OpenAI-to-Claude finish emission until real usage arrives (#11915 follow-up on #11883) (#11933)
Merges #11883's already-merged usage-harvesting extraction with #11915's finish-deferral mechanism, verified to fix a real remaining bug: the client-visible message_delta carried stale/zero usage when finish_reason arrived before the trailing usage chunk. 86/86 tests passing across 16 translator regression files.
2026-08-28 16:11:38 -03:00
Diego Rodrigues de Sa e Souza
c661e1c811 port(playground): specific step warnings from #11882, keep #11862's string-step handling (#11930)
Ports the specific-warning improvement from #11882 (combo-ref/provider-wildcard steps get their own message instead of a generic count) onto #11862's already-merged crash fix. 4/4 focused tests passing.
2026-08-28 15:51:37 -03:00
diegosouzapw
529e4415c5 chore(release): sync main into release/v3.8.51 — the five post-release pipeline fixes
Brings e4683cd22d (#11867 Alibaba allowlist time bomb), 09de69edc7 (#11891
config expiry detector), e71be03398 (#11893 runner janitor), 9dc8eab70e
(#11895 provenance × self-hosted lint) and f564b64f7d (#11901 heavy-build
lanes). main is already an ancestor of this branch (v3.8.50 sync-back), so the
merge is exactly these five commits.

# Conflicts:
#	tests/unit/alibaba-free-tier-allowlist.test.ts
2026-08-28 15:49:53 -03:00
NoxzRCW
f08f35d6f0 fix(providers): pass xAI reasoning_effort xhigh through to grok-4.6+ (#11879)
normalizeXaiReasoningEffort() folded xhigh onto high before the request reached xAI, so anyone picking xhigh on grok-4.6 silently got high instead. xhigh is a real xAI tier (grok-4.6+); xAI already degrades it itself on unsupported models, so forwarding verbatim is safe everywhere. Closes #11816. Measured against live grok-4.6: reasoning_tokens 830 (high) vs 1052 (xhigh) — previously indistinguishable. Thanks!
2026-08-28 15:49:42 -03:00
NoxzRCW
d846692c30 fix(dashboard): guard provider icon lookups against prototype collisions (#11880)
getLobeProviderIcon() indexed two plain-object maps with no own-property check — a provider id that lowercases to an Object.prototype member (e.g. constructor) resolved through the prototype chain and threw on the follow-up .color/.mono lookup, surfacing as the misleading 'Failed to load providers, check your connection' error boundary card with a healthy server and clean logs. Thanks for the precise root-cause trace!
2026-08-28 15:49:32 -03:00
NoxzRCW
c5ebbb733c fix(skills): expand shorthand property types in injected tool schemas (#11881)
Every request through a strictly-validating provider (reproduced on opencode-go/glm-5.3-flash) failed with a 400: normalizeInputSchema() wrapped a skill's shorthand property map without expanding string values, so every injected omr_skill_* tool carried an invalid JSON Schema. Closes #11856. Thanks for the root-cause!
2026-08-28 15:49:22 -03:00
NoxzRCW
b8c7ee599d fix(translator): keep upstream usage from trailing empty-choices chunks (#11883)
openaiToClaudeResponse() returned early on !chunk.choices?.[0], dropping the trailing usage-only chunk many OpenAI-compatible upstreams send when stream_options.include_usage is set (confirmed on Fireworks kimi-k3) — state.usage stayed undefined and billing fell back to an uncached token estimate. 154/154 focused assertions across the fix + regression suite. Thanks for tracking down the billing impact!
2026-08-28 15:49:14 -03:00
Diego Rodrigues de Sa e Souza
f564b64f7d fix(ci): give main's build its own lane on the self-hosted pool (#11901)
The .113 box has 31 GB and a single next-build peaks at 14–16 GB RSS: one
build fits with room, two sit at the edge, three take the box down. On
2026-08-28 13:50Z the kernel OOM-killed main's next-build (15.7 GB) while a PR
build ran beside it — five Build jobs had been queued by a burst of PRs — and
the publish lost its artefact, which sends it into the 40-minute rebuild that
OOMs on its own (attempt 5 of this release).

Job-level concurrency on `build`, two lanes:

  heavy-build-main   pushes to main — never contended, never behind PR traffic
  heavy-build-pr     pull requests — serialize among themselves

cancel-in-progress stays false: a running build is never killed by a newer
one. GitHub's own rule for a group is one running + one pending, older pendings
cancelled — so under a burst the third PR build shows "cancelled" and needs a
re-run. That is the trade-off, stated: a cancelled PR check is re-runnable; a
dead main build costs a release.

The proper fix remains a label split (omni-build on two runners, omni-light on
the rest) so the queue lives on the runner side without cancellations — an
operator decision recorded in docs/ops/RUNNER_BOX.md.
2026-08-28 15:44:50 -03:00
Diego Rodrigues de Sa e Souza
9dc8eab70e feat(quality): fail check:workflows on --provenance from a self-hosted runner (#11895)
npm rejects provenance-signed uploads from self-hosted runners:

  422 Unprocessable Entity - Error verifying sigstore provenance bundle:
  Unsupported GitHub Actions runner environment: "self-hosted".
  Only "github-hosted" runners are supported when publishing with provenance.

v3.8.50 learned that at minute 76 of its 10th publish attempt, after the tag,
the GitHub Release and the Docker images were already out. USE_VPS_RUNNER had
routed the job to the .113 pool on 2026-08-02; no release ran between 07-30 and
08-28, so the pairing sat latent for four weeks.

It is pure text — a job whose runs-on resolves to self-hosted and a step whose
run contains --provenance — so the workflow lint now checks it as a hard rule:
reported in plain mode, blocking under --strict and --ratchet (the CI mode),
emitted as provenanceRunnerFindings=<n> next to the other counters.

Against origin/main the rule finds the two real offenders (the staged upload
AND the DIRECT emergency fallback in npm-publish.yml); against the #11877 split
it finds none. --provenance-file is deliberately not matched (different flag,
pre-built bundle) and an opaque runs-on expression with no literal self-hosted
is classified unknown and skipped — the check never guesses.

The unit suite's last case walks the real .github/workflows and asserts zero
findings, so it is red on main until #11877 lands and green after; that is the
regression guard working, not a flake.
2026-08-28 15:44:40 -03:00
Diego Rodrigues de Sa e Souza
e71be03398 chore(ops): make the runner janitor act on what it can prove, not advise (#11893)
* chore(ops): make the runner janitor act on what it can prove, not advise

The .113 janitor already knew the rules and had been shouting them into a log
nobody reads: on 2026-08-28 12:00Z it reported "10 listeners > ceiling 8" and
"disk 85%" — for hours — while 6.7 GB of dead-run leftovers sat on the 12 GB
tmpfs (RAM) because its patterns matched neither e2e-build.tar.gz nor
next-build/, its 24 h fuse is a day too long for memory, and its _work/_temp
base (/home/*/actions-runner*) does not exist on this box (runners live under
/opt). Measured while draining the v3.8.50 npm publish (postmortem, Parte III).

What changes:

- idle is PROVEN before removal, with ONE lsof snapshot filtered to the swept
  bases (lsof +D per path walked whole trees and took minutes; 460 candidates
  grepping a re-printed 83k-line string was the other half). 20 s on the box.
  Without lsof the janitor removes nothing and says why (exit 1).
- tmpfs leftovers go after 3 h, disk _work/_temp after 24 h; both overridable.
  Patterns gain next-build* and e2e-build.tar.gz; /opt/actions-runner* is swept.
- zombie builds: a next-build older than 75 min has no job (a real Build step is
  ~26 min). On 2026-08-27 one ran 70 min after GitHub had declared its job lost,
  holding 3.6 GB. KillMode=mixed on the units covers systemctl stop/restart;
  this covers the lost-connection path.
- prunes 48 h-old checkouts under _work of runners whose unit is STOPPED — an
  active runner is never touched.
- alerts on memory PSI (full/avg60) and reports the listener ceiling with an
  omniroute/other breakdown (the box also hosts OmniHeuris and OmniMind).
  Enforcing the ceiling stays an operator decision (label split), not cron's.
- --dry-run prints exactly what it would do and touches nothing; unknown
  arguments are rejected.

Dry-run on the real box: 460 stale omniroute-* test fixtures (930 MB of RAM) it
would reclaim, 0 busy, 0 false "removed" lines, 20 s. The unit suite drives the
script against a fixture tree with every base redirected; the sweep branch runs
where lsof exists (hosted CI images) and the without-lsof contract everywhere.

docs/ops/RUNNER_BOX.md reconciled to the measured box: 31 GB (it said 16), ten
listeners, the 14 GB next-build ceiling, the KillMode drop-in, and the rule that
nothing is cleaned by hand while a runner is busy.

* docs(ops): restore the frontmatter fumadocs requires on RUNNER_BOX.md

Rewriting the page whole dropped its `title:` frontmatter, and docs/ is
compiled into the Next build by fumadocs-mdx — so Build, Fast Production Build
and dast-smoke all died with "[MDX] invalid frontmatter in
docs/ops/RUNNER_BOX.md". Same block as before, verbatim.
2026-08-28 15:44:29 -03:00
Diego Rodrigues de Sa e Souza
09de69edc7 test(config): fail seven days before a dated config pack lapses (#11891)
config/alibaba-free-tier-allowlist.json carried "validUntil": "2026-08-27".
On the 28th the loader started rejecting it — correctly, that is the design —
and a test that asserted "the shipped pack loads" turned every PR and main red
with no commit involved (#11866). A time bomb: the one class of defect a diff
review can never catch, because there is no diff.

scripts/check/lib/configExpiry.mjs walks config/**/*.json for validUntil /
validTo / expiresAt / expiry / expires (and snake_case forms), parses the dates,
and classifies each as expired / expiring (< 7 days) / ok / unparseable.

The repo-wide test fails on expired or expiring packs unless the file is in a
small allowlist keyed to the issue that owns the renewal — and fails the OTHER
way when an allowlisted pack is no longer expiring, so entries cannot go stale.
A positive anchor requires at least one dated pack to be found, so a renamed
key cannot silently turn the suite into a no-op.

The Alibaba pack is allowlisted against #11866: whether the curated free-tier
list still matches reality is an operator data decision, not a test fix.
Removing that entry makes the suite fail as intended (verified).
2026-08-28 15:44:18 -03:00
Diego Rodrigues de Sa e Souza
e4683cd22d fix(test): stop the Alibaba allowlist test from expiring with the catalog (#11867)
`Unit Tests (1/8)` went red on 2026-08-28 across every PR and on main, with
nothing changed — the clock had moved past the shipped catalog's expiry:

  config/alibaba-free-tier-allowlist.json → "validUntil": "2026-08-27"

isAlibabaFreeTierAllowlistPackValid() compares that against Date.now(), so from
28/08 loadAlibabaFreeTierAllowlistPack() returns null and the old
assert.ok(pack) could never pass again. Refreshing the date would only reschedule
the same break.

Production was never affected: resolveActiveAllowlistPack() falls back to the
embedded list when a pack expires, which is the intended design. The defect was
the test asserting the shipped catalog is currently fresh — a data property, not
a behavioral contract.

The test now writes its own packs to a temp dir with dates it controls, and
pins both halves of the contract:

  - inside the validity window, the pack REPLACES the embedded list (anchored on
    a model that exists nowhere else, so loading alone cannot satisfy it);
  - once expired, the pack is ignored and the embedded list serves.

That second path is what production has been running since 27/08 and had no
coverage at all, which is why the expiry surfaced as a red test rather than as
understood behavior. A third case pins the comparison against an injected
instant, including the no-expiry pack that never goes stale.

Whether the curated free-tier catalog still matches reality — and so deserves a
freshly dated pack — is a data question left to the operator in #11866.

Closes #11866
2026-08-28 15:43:53 -03:00
diegosouzapw
e70bea30e9 docs(counts): bring the documented provider and migration counts to the measured values
release/v3.8.51 retired Raycast, Hailuo, Qwen Web, Designer Web and Felo and
added migrations 163–168 without touching the numbers README, AGENTS.md,
llm.txt (and its 42 mirrors), package.json and the README diagrams quote:
351 providers (was 357/353/350), 166 migrations (was 160). The strict
docs-counts gate was already red on a pristine release/v3.8.51; the v3.8.50
sync-back's release-green pass surfaced it.
2026-08-28 15:20:24 -03:00
diegosouzapw
a32ba0e362 test(security): drop the guard-target set left unused by the sync
The sync-back kept release/v3.8.50's per-handler positive-anchor version of
this suite, which no longer needs the guardDelegatingTargets set the cycle
branch had added — the only ESLint error the merge introduced (281 vs 280 on a
pristine release/v3.8.51).
2026-08-28 15:19:29 -03:00
diegosouzapw
9ec7b72248 chore(release): sync main (v3.8.50 close) into release/v3.8.51 — parallel-cycle sync-back
Merge commit on purpose: origin/main becomes an ancestor of the cycle branch,
so the next sync-back (v3.8.51 close → release/v3.8.52) merges against this
point instead of the July base that turned this one into 551 conflicts.

Tree = release/v3.8.51 + release/v3.8.50 tip (step 1, b68af3f090) + main's
post-tag fixes (step 2, 21c488f210) + main's CHANGELOG verbatim with the
## [3.8.51] — TBD section re-inserted on top + the 42 i18n CHANGELOG mirrors
regenerated by scripts/release/sync-changelog-i18n.mjs.
2026-08-28 14:41:19 -03:00
diegosouzapw
282de841ae revert(models): drop the memo reset hook ported from #10055
The port destabilized tests/unit/modelsDevSync-extended.test.ts (6/14 with it,
18/2 without; 20/0 on a pristine release/v3.8.50). The cycle's own memo, keyed
on the catalog cache version, stays as it was; the remaining failures predate
this sync and are tracked separately.
2026-08-28 14:39:18 -03:00
diegosouzapw
f2825bad75 fix(typecheck): guard the optional connectionId before the reactive model sync
release/v3.8.51 fails typecheck:core on its own (verified on a pristine
checkout): maybeTriggerReactiveModelSync(provider, connectionId: string) is
called with credentials.connectionId, which base.ts declares optional. No
connection row means there is no synced catalog to refresh, so skip instead of
passing undefined. Surfaced by the release-green gate of the v3.8.50 sync-back,
which refuses to push a tree with a hard typecheck failure.
2026-08-28 14:36:34 -03:00
diegosouzapw
70f5d4cbf9 chore(db): renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at)
#11845 landed on main as 163 while release/v3.8.51 had already used 163–168.
Per the cross-PR collision precedent (#3365/#3371) the later arrival takes the
next free number; the SQL is idempotent, so installs that already ran it as
163 on main are unaffected.
2026-08-28 14:21:46 -03:00
diegosouzapw
21c488f210 chore(release): bring main's post-tag fixes into release/v3.8.51 — sync-back step 2/2
The eighteen commits main carries beyond the cycle branch, and what each one
became here:

  already in release/v3.8.51 by its own PR (no-op, verified by content):
    b090b601a5 / 026e1cadaa  deps: nanoid 3.3.18 equal, dompurify 3.4.14 newer
    918fba5e39  .gitignore: /_tasks already anchored
    5f0a394091  #10026 hide health-check-excluded models — same helper, 5 call sites
    c68cda7dfb  #11075 shared passthrough providers — superseded by #11071/#11078
  superseded, one piece kept:
    ca23eed77c  #10055 memoize models.dev pricing — the cycle memoizes on the
                catalog cache version already; only the resetDbInstance() hook is
                ported, wired to that memo
  applied as-is:
    8778ea7d18  stamp dist/BUILD_SHA before the npm provenance gate (#11721)
    aa52351113  decouple the Bun image from the release manifest (#11724)
    925feb27b8  let the bun digest artifact be absent (#11740)
    b65ef333da  size the install-upgrade gate to a measured run
    0ce21232db  #11845 converge install/upgrade schemas (migration renumbered in
                the next commit: 163 collides with 163_radar_feed_cache_generated_at)
    b7c07edad8  #11855 install-upgrade gate on disk, not tmpfs
    8e2fb04329  #11864 drop *.nft.json from the npm tarball (413)
    dea6bb8b6b  #11877 publish npm from a hosted runner (provenance 422)
  handled by the sync script that follows (CHANGELOG protocol):
    b4ec7807ab  Release v3.8.50 — squash of content this branch already carries
    5458026c21 / c44c0a29e8  CHANGELOG aggregation, stats and top-25
  applied separately (its own commit, ten files):
    65e81158ab  #11088 Ollama capability routing — a 5,094-file squash from a
                stale base; only the Ollama files are the change

Every cherry-pick that touched a file this branch had also changed was
resolved by hand and re-run through the tests both sides own for it.
2026-08-28 14:21:44 -03:00
diegosouzapw
b68af3f090 chore(release): merge release/v3.8.50 tip into release/v3.8.51 — sync-back step 1/2
The v3.8.50 close left 134 post-freeze commits on release/v3.8.50 that never
reached the cycle branch (the freeze cut release/v3.8.51 at 3192eb88d5). A
plain merge of main reproduces all of them through the `Release v3.8.50`
squash against a July merge-base and conflicted on 551 files; merging the
release tip first, against the recent common ancestor, narrows the real
conflicts to 102 (51 generated, 51 judged file by file with a proof each —
see _tasks/postmortems/2026-08-25-release-v3.8.50-pipeline-eficiencia.md,
Parte IV). Step 2 brings main's own post-tag fixes and the finalized
CHANGELOG through scripts/release/sync-next-cycle.mjs.

Resolution rules applied, in order of evidence:
- generated files regenerated with the repo's own generators
  (sync-llm-mirrors, gen-budget-card-svg, gen-provider-reference);
- where release/v3.8.51 already carried the same fix in a newer shape
  (#11524 search sweep, #11551 catalog scheduler, Google BYOP retry, KIE
  Market id map, Docker worker budget measured in #7518) its version stays;
- where release/v3.8.50 carried the newer shape (Volcengine cookie-domain
  CodeQL fix + shared Zod schemas, #11355/#10534 cooldown release helper,
  positive-anchor tests for security-hardening and cli-oneproxy) it wins;
- GPL-retired Raycast/Hailuo (#11691) stay retired: nothing of theirs comes
  back and the public-route test keeps the retired route out;
- the ten changelog.d fragments of v3.8.50 are dropped — they are already
  aggregated in main's CHANGELOG and would double-aggregate at v3.8.51.

Three things git's auto-merge silently produced were caught by a per-line
detector and fixed: providerLimits.ts lost T's imports and the
windowStillExhaustedAfterRealReset helper; catalogCache.ts and
providerLimits.ts kept both sides' identical copies of three declarations;
contextHandoff.ts's new provider-allowlist skip returned undefined against
the #11552 outcome type. Every decision was re-run through the tests both
sides own for it.
2026-08-28 14:07:39 -03:00
Dizzle
d5dfcfff58 feat(combo): choose sort method for combo models (manual/provider/score/name) (#11812)
Lets the combo dashboard builder order models manually/by-provider/by-score/by-name — the choice is stored in config.modelSort and re-applied on load and after adding models. Score-based ordering fetches provider rankings from the existing /api/free-provider-rankings endpoint; the field is inert on execution (client-side hint only). 9/9 focused tests passing (schema, sort logic, and rendered component). Thanks!
2026-08-28 12:37:58 -03:00
Dizzle
51ca7edd97 feat(free): per-model isFree for custom models (catalog + UI) (#11843)
Adds an opt-in customModels[].isFree flag so a self-hosted local model can be marked free-tier without touching the curated free-model catalog (providerHasFreeModels stays curated). 9/9 focused tests passing across the DB round-trip, schema tri-state validation, and free-model detection. Thanks!
2026-08-28 12:37:44 -03:00
Dizzle
b7102140d5 fix(socks): forward Agent.connectTimeout to SocksClient and TLS, unify family null (#11842)
Fixes a SOCKS proxy timeout bypass: Agent.connectTimeout now reaches both the SocksClient.createConnection handshake and the TLS buildConnector phases (previously a stalled/blackholed SOCKS connection could hang past the configured budget), and the fetch-socks family===null path is unified onto createSocksDispatcherWithFamily. Verified against a faux RFC 1928 SOCKS server exercising both pre-grant and post-grant stalls. 6/6 focused tests passing. Thanks!
2026-08-28 12:37:32 -03:00
Bob.Hou
cab9cdc765 fix(pwa): stop serving the stale shell after deploys (#11779)
Fixes the stale-shell PWA lockout after a deploy: navigationFallback now returns
Response.error() instead of replaying a cached shell whose /_next/static chunk
references are dead, and the worker is registered as /sw.js?v=<build-id> so each
deploy is actually observed instead of never updating until a navigation to the
new build first succeeds.

Recreated onto release/v3.8.51 (original base was main, which had diverged too far
for a clean retarget) — both commits cherry-picked and force-pushed to the
contributor's branch (author preserved), then the PR's base edited in place.
4/4 focused tests passing (2 via vitest for the jsdom-environment PwaRegister
suite, 2 via node:test for the service-worker fallback suite). Thanks for the fix!
2026-08-28 12:23:35 -03:00
Bob.Hou
495d10de18 fix(mcp): avoid async initialization inside sync esm wrappers (#11569) (#11849)
Fixes the startup crash SyntaxError: Unexpected reserved word 'await' on Node 24/26 by pinning esbuild to 0.28.2 and preventing async initialization inside synchronous __esm wrappers in the MCP server bundle. Closes #11569. Verified: 2/2 focused tests pass with esbuild 0.28.2 correctly installed (root-caused a stale-node_modules false negative in my own validation pass — resolved with a fresh npm ci, not a PR issue). Thanks!
2026-08-28 12:22:27 -03:00
Bob.Hou
00b7b71bd3 feat(routing): expand connection-aware quota prefilter across combo strategies (#11682) (#11850)
Expands per-connection quota-aware pre-filtering across all 20 combo strategies so exhausted accounts are filtered before strategy resolution instead of causing avoidable upstream errors. Closes #11682. 15/15 + 131/131 + 457/457 (vitest) focused tests passing. Thanks!
2026-08-28 12:22:18 -03:00
Bob.Hou
1b39873ea9 fix(sse): preserve coherent markdown token boundaries across stream translation chunks (#11606) (#11851)
Preserves markdown token boundaries (bold/italic markers, code fences/spans, escape-backslash parity) split across stream translation chunks by tracking boundary state and flushing held buffers correctly instead of emitting broken markdown mid-stream. Closes #11606. 38/38 + 74/74 focused tests passing. Thanks!
2026-08-28 12:22:09 -03:00
Paco Cartones
13afbfafb2 fix(vision-bridge): honor authoritative live catalogs (#11813)
Reconciles Vision Bridge auto-selection with each provider's authoritative live model
catalog, revalidating cached selections and preserving routable aliases / live-catalog
IDs / registered effort variants, with fail-open behavior kept when the catalog is
unavailable or non-authoritative. Closes #11767.

One test-side fix applied before merge: "accepts a registry model whose liveCatalogIds
match upstream" used `cgpt-web` (ChatGPT Web) as its fixture provider — retired by #11754
after this PR was authored, which removed every live registry entry populating
`liveCatalogIds` and made the test's expected model unreachable (null, not the retired
id). Swapped the fixture to a synthetic PROVIDER_MODELS entry (the registry Proxy is
writable and reverted in `finally`) so the same production predicate is exercised without
depending on since-deleted registry data. 16/16 focused tests passing on the current tip.
Thanks for the fix!
2026-08-28 11:19:18 -03:00
Paco Cartones
e3caa205fd fix(ci): detect stale workspace lockfile entries (#11714)
Removes stale nested browser-pool lock entries pinning Playwright 1.61.1/@types/node 22 despite the workspace declaring 1.62.1/26, and extends the lockfile gate with npm ls --workspaces --depth=0 so future manifest/lock drift fails visibly. 21/21 focused tests passing. Thanks!
2026-08-28 11:14:55 -03:00
Paco Cartones
e3563d2512 fix(search): allow explicit AnySearch web fetch (#11716)
Allows /v1/web/fetch callers to explicitly select the already-advertised anysearch-search provider — the REST schema previously rejected it with a Zod error while MCP's web-fetch tool already accepted it. TDD RED demonstrated, 11/11 + 14/14 focused tests passing. Thanks!
2026-08-28 11:14:43 -03:00
Paco Cartones
db87fb6207 fix(providers): honor start day for overnight peak windows (#11718)
Fixes overnight peak-hour windows so 'days' is interpreted as the UTC day the window STARTS, keeping the post-midnight segment protected until its exclusive end boundary — a Monday-only 22:00-02:00 window incorrectly returned inactive on Tuesday at 01:00Z. TDD RED demonstrated, 5/5 focused tests passing. Follow-up to #11622. Thanks!
2026-08-28 11:14:33 -03:00
Paco Cartones
808992a717 fix(antigravity): cap unverified body retry hints (#11823)
Preserves whether a 429 retry hint came from transport headers, structured google.rpc.RetryInfo, or unverified response-body text, and caps body-derived cooldowns at the operator's configured maxCooldownMs so an unverified upstream hint can no longer force an arbitrarily long model/semaphore lockout — authoritative header/structured resets stay intact across combo, chat, and Responses paths. Closes #11695. 29/29 focused tests passing. Thanks!
2026-08-28 11:14:24 -03:00
Paco Cartones
bd4fd893e6 fix(profile): expose accessible status and progress (#11838)
Exposes Profile loading/terminal-error states and exact clamped XP progressbar semantics to assistive technologies, plus a responsive page heading that doesn't duplicate the desktop Dashboard heading. 6/6 focused a11y tests passing. Thanks!
2026-08-28 11:14:12 -03:00
Paco Cartones
07541c4d1f fix(cli): separate node endpoint from server base URL (#11860)
Renames the provider-node URL flag to --endpoint for nodes add/update/validate so it no longer collides in meaning with the global --base-url (the OmniRoute server target), while keeping the API payload field as baseUrl. Closes #11818. Thanks!
2026-08-28 11:14:03 -03:00
Paco Cartones
49a6336b68 fix(playground): simulate persisted combo models (#11862)
Fixes /api/playground/simulate-route to map persisted combo model steps to ordered playground simulation targets and resolve configured providers through their canonical identity, warning explicitly when structural combo steps are omitted instead of silently reporting a complete simulation. Closes #11822. Thanks!
2026-08-28 11:13:54 -03:00
Paco Cartones
5c0b68eeee fix(ui): expose DataTable loading semantics and hide the decorative spinner (#11869)
DataTable's loading state now mirrors PageLoading's a11y convention (role=status, aria-live=polite, aria-busy=true on the container, aria-hidden on the decorative glyph) instead of announcing a bare emoji as content to assistive tech. Honest scope note in the PR body about when aria-live actually fires today. Thanks!
2026-08-28 11:13:47 -03:00
Paco Cartones
0c3181ce16 fix(api): bound provider login timeout to the shared browser-login window (#11871)
Bounds an unclamped provider-login timeout on the generic web-cookie path — a body of {"timeout": 9007199254740991} produced a 9-trillion-iteration poll budget on the shared headful-browser login slot. Extracts the same clamp contract the two provider-specific login services already enforce (300000 default, 15000 min, 600000 max) into a single reusable src/lib/api/loginTimeout.ts. 6/6 focused tests passing. Thanks!
2026-08-28 11:13:33 -03:00
Paco Cartones
dff3f8b424 fix(api): return public-safe errors from the tunnel and MITM routes (#11872)
Closes a real Hard Rule #12 violation — 14 catch blocks across the tunnel/MITM routes echoed a raw err.message, which for Tailscale could leak a live tskey-* credential and always disclosed host layout / OS account name. Routes all 14 sites through a new toPublicSafeTunnelError() classifier, verified by a dedicated regression suite (14/14 passing) asserting no route echoes a raw error.message. Thanks for the security fix!
2026-08-28 11:13:19 -03:00
Paco Cartones
3f35f3afad fix(open-sse): sample stream TTFT/ITL from a monotonic clock (#11873)
Switches stream TTFT/ITL sampling from Date.now() (wall clock) to performance.now() (monotonic) — an NTP correction or manual clock adjustment mid-stream was poisoning routing metrics (inflated TTFT on forward steps, silently-dropped negative TTFT on backward steps). Matches the existing earlyStreamKeepalive.ts precedent on the same streaming path. Thanks!
2026-08-28 11:13:08 -03:00
Diego Rodrigues de Sa e Souza
dea6bb8b6b fix(ci): publish npm from a hosted runner so provenance is accepted (#11877)
* fix(ci): publish npm from a hosted runner so provenance is accepted

The v3.8.50 staged publish failed at the upload:

  npm error code E422
  npm error 422 Unprocessable Entity - POST https://registry.npmjs.org/-/stage/package/omniroute
  Error verifying sigstore provenance bundle: Unsupported GitHub Actions runner
  environment: "self-hosted". Only "github-hosted" runners are supported when
  publishing with provenance.

3.8.49 published fine on 2026-07-30 because it predates USE_VPS_RUNNER being
turned on (2026-08-02). 3.8.50 is the first release since, so the incompatibility
had been latent for four weeks with nothing to surface it.

Neither obvious fix works on its own:

  - dropping --provenance would regress supply-chain posture; 3.8.49 carries a
    SLSA attestation and 3.8.50 must not ship without one;
  - moving the whole job to a hosted runner reintroduces the failure that made it
    self-hosted in the first place — 16 GB is not enough for build:cli's
    next-build fallback (documented on the job's runs-on).

So the work is split by what each runner is actually needed for. The self-hosted
job keeps every heavy gate — build, artifact validation, boot-smoke, the
clean-install/upgrade proof — and then packs the tarball it just proved and hands
it over. A new `stage-npm` job on ubuntu-latest downloads those exact bytes and
performs the upload, which needs no memory at all.

`npm pack --ignore-scripts` on the producing side and `--ignore-scripts` on the
publishing side both matter: prepublishOnly is `build:cli-api && build:cli &&
check:pack-artifact`, and the job already runs all three as explicit steps (the
dist/ prune is logged twice today — once at Build CLI bundle, once redundantly
inside npm stage publish). Re-running them on the small hosted runner would
rebuild bytes that were already built, validated and boot-smoked.

The DIRECT emergency fallback moved too — it published with --provenance and
would have hit the identical 422.

* chore(quality): re-baseline zizmor for the new hosted publish job

The `stage-npm` job adds 2 zizmor findings (192 -> 194), both of the same
deliberate @vN convention every workflow in this repo already follows:
unpinned-uses on actions/download-artifact@v8 and actions/setup-node@v7, plus
the cache-poisoning that setup-node@v7 already raises on the two other jobs in
this very file. SHA-pinning only the new job would break the convention.

No new class: zero template-injection, artipacked, dangerous-triggers or
excessive-permissions. The job declares contents:read + id-token:write, which is
the minimum npm provenance needs.
2026-08-28 10:26:22 -03:00
Diego Rodrigues de Sa e Souza
44cb226b40 fix(quality): ratchet-bank verifier rejects object-valued notes (#11892)
Recreated from #11702 (MumuTW) onto the current release/v3.8.51 — fixes the object-note
comparator bug that has silently blocked the automated ratchet-bank lane since 2026-08-11;
24/24 focused tests + file-size gate green on this tip. Thanks for tracking this down!
2026-08-28 09:40:13 -03:00
Diego Rodrigues de Sa e Souza
9d8f723d65 fix(ci): surface and clear release-green ESLint failure (#11890)
Recreated from #11734 (MumuTW) onto the current release/v3.8.51 — full ESLint inventory 0
errors, release-green ESLint hard gate fixed, the useApiKeySave hook-render fix preserved
alongside all 3 existing test cases. Thanks for the fix!
2026-08-28 09:34:32 -03:00
MumuTW
394ae23720 fix(sse): harden session affinity key extraction (#11746)
Hardens session-affinity key extraction: no more JSON.stringify on arbitrary request objects,
recognizes bounded text from Responses/chat/Anthropic/Gemini/common string-root shapes, enforces
a shared 4096-char processing budget, and rejects oversized explicit session IDs before
trim/regex/hash work. 128/128 focused affinity/failover tests passing. Closes #11744. Thanks!
2026-08-28 09:31:08 -03:00
MumuTW
f3d9279b44 refactor(quality): split adobeFireflyClient.ts (2,952 lines) into nine leaf modules (#11728)
Mechanical split of the 2952-line adobeFireflyClient.ts into nine leaf modules, each under the
1000-line cap — follows the existing Adobe Firefly family decomposition pattern
(adobeFireflySecurity.ts, ModelSnapshot, References, Upscale, Models, Session, BrowserLogin).
No behavior change. Thanks for the cleanup!
2026-08-28 09:30:57 -03:00
Diego Rodrigues de Sa e Souza
2c17b77de3 feat(models): auto-aliases and token limits from synced Antigravity discovery (#11889)
Recreated from #11689 (MumuTW) onto the active release/v3.8.51 — commit cherry-picked cleanly
with author preserved (auto-merged onto the just-boarded #11888); 10/10 focused tests +
file-size all green on this tip. Thanks for the fix!
2026-08-28 09:30:03 -03:00
Diego Rodrigues de Sa e Souza
675bca8c9c feat(models): reactive discovery sync after Antigravity model-not-found 404 (#11888)
Recreated from #11688 (MumuTW) onto the active release/v3.8.51 — commit cherry-picked cleanly
with author preserved; 9/9 focused tests + file-size all green on this tip. Thanks for the fix!
2026-08-28 09:28:01 -03:00
Diego Rodrigues de Sa e Souza
0f88b74dbe feat(oauth): default Antigravity-family connections to model auto-sync (#11887)
Recreated from #11685 (MumuTW) onto the active release/v3.8.51 — both commits cherry-picked
cleanly with author preserved; 5/5 focused tests + file-size + changelog-integrity all green
on this tip. Thanks for the fix!
2026-08-28 09:26:26 -03:00
Diego Rodrigues de Sa e Souza
0188685a1e fix(lint): pin react hooks plugin to 7.0.1 (#11886)
Recreated from #11749 (MumuTW) onto the active release/v3.8.51 — commit cherry-picked with
author preserved; regression test + check:lockfile + check-file-size all green on this tip.
Thanks for the fix!
2026-08-28 09:24:33 -03:00
Diego Rodrigues de Sa e Souza
b45c97df08 fix(quality): reconcile provider-assets-provenance manifest to the real 142-file snapshot (#11876)
Self-authored follow-up fix, part of the merge-batch session that drained the provider-retirement/provenance sweep PRs this manifest went stale from. Validated (see PR body): gate passes 142/142, regression test 18/18, typecheck/file-size/changelog-integrity/tracked-artifacts all clean.
2026-08-28 06:56:55 -03:00
Diego Rodrigues de Sa e Souza
7d57d9f4a1 fix(providers): retire common ChatGPT Web provider (#11754)
Rebased onto the current release/v3.8.51 tip as part of a combined provider-retirement/provenance merge batch (Designer Web, Felo Web, Runtime, GPL-derived removal, Qwen Web already landed). Large conflict set (this is the biggest PR in the batch — the common ChatGPT Web provider touches chat, images, count-tokens, session leases, and combos). Conflicts resolved:

- `open-sse/config/providers/registry/chatgpt-web/*`, `open-sse/executors/chatgpt-web*`, `open-sse/handlers/imageGeneration/providers/chatgptWeb.ts`, and their tests: kept deleted, matching the PR's stated scope.
- `open-sse/config/providers/registry/minimax/web/index.ts`, `open-sse/handlers/imageGeneration/providers/geminiWeb.ts`, `open-sse/executors/gemini-web.ts`'s stale image-mode branch: base-drift collisions against already-merged sibling retirements (#11691, #11708) — kept deleted / dropped the dead code, since this PR's own branch forked before those merged.
- `src/shared/constants/reservedProviderPrefixes.ts`, `open-sse/executors/index.ts`, `executorProxy.ts`, `virtualFactory.ts`, `autoStrategy.ts`, `src/lib/db/providers.ts`, `src/sse/handlers/chat.ts`: combined the Designer + Runtime (Felo/Qwen) + common-ChatGPT-Web retirement guard calls at each shared chokepoint — compute-once-then-OR pattern, consistent with prior combinations in this batch.
- `src/sse/services/model.ts` / `src/sse/handlers/chatHelpers.ts`: adopted this PR's new `getModelInfoOrRetirementResponse()` central wrapper (a real improvement over ad-hoc try/catch), and extended it to also catch the Designer + Runtime retirement errors it didn't originally cover, so the consolidation doesn't regress the other two mechanisms.
- `src/app/api/v1/images/edits/route.ts`: this PR moved the retirement check earlier (before `enforceApiKeyPolicy`) but left the old later call+catch block in place from base drift — removed the now-redundant duplicate `resolveImageRouteModel()` call and merged the Designer catch into the earlier one.
- `open-sse/config/imageRegistry.ts`, `tests/snapshots/executors/executor-map.json` (`keyCount` recomputed to 133), `tests/snapshots/provider/translate-path.json`: same "both sides inserted a different retired provider at the same slot" pattern — resolved by dropping both.
- `tests/unit/chatcore-executor-proxy.test.ts`, `provider-node-reserved-prefix.test.ts`, `combo-auto-candidate-expansion.test.ts`, `messages-count-tokens-route.test.ts`, `virtual-auto-combo.test.ts`: split into independent per-mechanism test blocks (established pattern); `virtual-auto-combo.test.ts`'s old "includes cookie web-session providers" positive-inclusion test (which used chatgpt-web as its example) was retired along with the provider and replaced by this PR's negative-exclusion test for the same slot.
- `docs/architecture/ARCHITECTURE.md`, `CODEBASE_DOCUMENTATION.md` (+ 4 i18n mirrors), `README.md`, `FREE-TIERS-GUIDE.md`, `docs/diagrams/free-tier-budget.svg`, `docs/screenshots/free-tier-budget-card.svg`, `docs/reference/PROVIDER_REFERENCE.md`: recomputed every stale count from the real merged state — 104 executors (`countFiles` gate logic), 351 providers (regenerated via `gen:provider-reference`), 152/351 `hasFree` entries, 445/438/7 free-tier catalog rows, 13 ToS-avoid providers, budget-card regenerated via its real generator script. One doc conflict (`oauth/` module list) needed picking HEAD's side specifically — theirs still listed the already-removed `raycast` module instead of the real `openference`.
- `config/quality/test-masking-allowlist.json`: additive merge of the PR's 17 `_deletedWithReplacement` entries alongside the batch's existing ones (one real duplicate-key mistake in my first pass, caught and fixed via a `object_pairs_hook` duplicate-key check before finalizing).

Also fixed two real, unrelated-to-my-merge issues surfaced by the focused suite:
- `tests/unit/resolve-web-provider-host.test.ts`: the PR's own test had a typo — it asserted `perplexity-web`'s resolved host as `"perplexity.ai"`, but the provider's registered `website` is `"https://www.perplexity.ai"` and the resolver returns the URL's `host` verbatim (no www-stripping), so the correct value is `"www.perplexity.ai"` (consistent with the same test's own `url` assertion).
- `tests/unit/hard-session-lease-bypass-inventory.test.ts`: this golden call-site inventory was already stale on the pristine post-#11713 tip (confirmed via a throwaway probe worktree) — `src/lib/db/providers.ts`'s 3 connection-fallback sites and a third `src/app/api/providers/route.ts` site were never added to the golden list by the earlier-merged #11698/#11720 PRs. Updated it to the real current inventory (dated inline comments explain each delta and which PR introduced it), plus this PR's own legitimate deltas (image-edits duplicate-call removal, `ChatGptWebExecutor.execute()` site removed).

Focused suite green (433/433 across executor-proxy, reserved-prefix, hard-session-lease-bypass-inventory, resolve-web-provider-host, retirement/runtime-block/source-retirement/management-retirement/image-handler-retirement, migration-168, combo-auto-candidate-expansion, virtual-auto-combo, executor-map-golden and siblings), plus `typecheck:core`, `check-file-size`, and `check-changelog-integrity` clean. Thanks for the thorough provenance-hold retirement work — appreciated.
2026-08-28 06:52:46 -03:00
Diego Rodrigues de Sa e Souza
f48e25194f fix(providers): retire Qwen Web pending provenance review (#11713)
Rebased onto the current release/v3.8.51 tip as part of a combined provider-retirement/provenance merge batch (Designer Web, Felo Web, Runtime, GPL-derived removal all landed together already). Conflicts resolved:

- `src/shared/constants/providerRetirement.ts`: add/add conflict — combined `felo-web`/`felo` (already-merged) with `qwen-web`/`qw` into one `RUNTIME_RETIRED_PROVIDER_IDS` set, kept both `assertRuntimeProviderAvailable`/`assertRuntimeModelProviderAvailable` helpers.
- `open-sse/config/providers/registry/minimax/web/index.ts`: modify/delete — kept deleted (file is hailuo-web's registry entry, already retired by #11691; this PR's own change to it was just a comment reword on a since-removed target).
- `open-sse/executors/index.ts`, `executorProxy.ts`, `virtualFactory.ts`, `autoStrategy.ts`, `model.ts`, `chat.ts`, `chatHelpers.ts`, `auth.ts`, `src/lib/db/providers.ts`, `reservedProviderPrefixes.ts`: combined the Designer + Runtime (Felo + Qwen) retirement guard calls at each shared chokepoint — compute-once-then-OR pattern, consistent with the prior Designer+Felo combination.
- `src/shared/constants/providers/web-cookie.ts`, `tests/snapshots/provider/translate-path.json`, `tests/snapshots/executors/executor-map.json`: both sides had inserted a different retired provider (qwen-web vs. already-retired raycast/hailuo-web) at the same dict position — resolved by dropping both. `executor-map.json`'s `keyCount` recomputed to 135 (matches actual merged `entries`).
- `tests/unit/chatcore-executor-proxy.test.ts`, `tests/unit/provider-node-reserved-prefix.test.ts`: split into independent Felo/Qwen test blocks (established pattern for coexisting retirement-mechanism tests); recomputed `RESERVED_PREFIX_COUNT` to 398 (Designer+Felo+Qwen tombstones on top of the post-#11691 REGISTRY, verified via direct module evaluation, not hand-derived).
- `config/quality/test-masking-allowlist.json`: additive merge of Qwen's `_deletedWithReplacement` entries alongside Designer's.
- `README.md` + all `docs/i18n/*/README.md` mirrors, `docs/getting-started/FREE-TIERS-GUIDE.md`, `docs/reference/FREE_TIERS.md`, `docs/diagrams/free-tier-budget.svg`, `docs/screenshots/free-tier-budget-card.svg`: recomputed the free-tier catalog counts (447 entries / 440 active / 7 discontinued) from the actual merged `freeModelCatalog.data.ts`, regenerated the budget-card SVG via its real generator (`scripts/research/gen-budget-card-svg.mjs`), and dropped the retired Qwen quick-start row / QWEN MODELS section from every i18n README (identical unlocalized block across all 34 locales).
- Also fixed a duplicate-import merge artifact in `src/lib/db/providers.ts` (`isRuntimeRetiredProviderId` imported twice) caught by `typecheck:core`, and rebaselined `file-size-baseline.json` for the combined retirement-guard growth (`virtualFactory.ts` +3, with justification).

Focused suite green (345/345 across executor-proxy, reserved-prefix, migration-167, qwen-web-retirement, virtual-auto-combo, web-cookie/session, executor-map-golden and siblings), plus `typecheck:core` and `check-file-size`/`check-changelog-integrity` clean. Thanks for the provenance-hold retirement work — appreciated.
2026-08-28 06:01:21 -03:00
Diego Rodrigues de Sa e Souza
8e2fb04329 fix(build): drop Next trace manifests from the npm tarball (413 on publish) (#11864)
The v3.8.50 staged publish was refused by the registry:

  npm error code E413
  npm error 413 Payload Too Large - POST https://registry.npmjs.org/-/stage/package/omniroute

The tarball had reached 288.7 MB packed / 1.1 GB unpacked, against 174.5 MB /
792.3 MB for the 3.8.49 that published fine. 842 *.nft.json files accounted for
668.7 MB of that — 61% of the whole package — having doubled from the 325.0 MB
across 748 files shipped in 3.8.49.

Those are Next.js Node File Trace manifests: build-time metadata used to compute
the standalone bundle, never read while serving. Nothing under src/, open-sse/
or bin/ references them, which the new test pins.

Excluding them follows the negation pattern files[] already uses for
node_modules and test sources. Verified against an isolated package that the
glob drops page.js.nft.json while keeping page.js and other.json, so it does
not over-match.

Separately worth tracking: the compiled JS under dist/.build/next also grew 69%
between 3.8.49 and 3.8.50 (231.9 MB to 391.9 MB, +3695 files). That is not what
broke the publish and is left for its own investigation.
2026-08-28 05:29:52 -03:00
Diego Rodrigues de Sa e Souza
0ad03316e8 chore(providers): retire GPL-derived Raycast and Hailuo integrations (#11691)
Rebased onto the current release/v3.8.51 tip as part of a combined provider-retirement/provenance merge batch (Designer Web, Felo Web, Runtime, and this GPL-derived Raycast/Hailuo Web removal all landed together). Conflicts resolved:

- `config/quality/test-masking-allowlist.json`: additive merge of the Hailuo-Web/Raycast-auth/Raycast-local-extract entries alongside prior sibling retirement entries.
- `docs/reference/PROVIDER_REFERENCE.md`: kept the branch's generated content (deferred to a future `npm run gen:provider-reference` regeneration pass).
- `src/app/api/providers/[id]/test/webSessionTestDispatch.ts`: comment-only, dropped stale retired-provider examples.
- `tests/snapshots/executors/executor-map.json`: recomputed `keyCount` to 137 (matches the actual merged `entries` object).
- `tests/unit/provider-test-token-web-session-dispatch.test.ts`: kept both sibling assertions (hailuo-web + t3-chat-web), avoided duplicating the dedicated microsoft-designer-web test already present.

Also recomputed the golden `RESERVED_PREFIX_COUNT` (397, down from 400) to reflect the 3 GPL-derived ids/aliases this PR removes from `REGISTRY`, and rebaselined `file-size-baseline.json` for the combined retirement-guard growth accumulated across the sibling PRs in this batch.

Focused suite green (86 tests across authz/oauth-autoimport, public-route-exact-match, gpl-derived-provider-removals, migration-166, muse-spark-ws-auth-token, oauth-providers-config, provider-alias-uniqueness, provider-test-token-web-session-dispatch, providers-constants-split, ts7-executor-override-signatures, executor-map-golden, provider-node-reserved-prefix), plus `typecheck:core` and `check-file-size` clean. Thanks for the GPL-license cleanup — appreciated.
2026-08-28 05:16:59 -03:00
Diego Rodrigues de Sa e Souza
3026183120 refactor(mitm): replace inspector utilities clean-room (#11748)
Merged via /merge-batch (v3.8.51 provenance sweep). Boarded and validated together with the batch's other provenance PRs in a combined worktree — full gate suite green. Thank you.
2026-08-28 05:04:49 -03:00
Diego Rodrigues de Sa e Souza
84504e3f1f chore(providers): retire Felo Web on provenance hold (#11698)
Merged via /merge-batch (v3.8.51 provenance sweep). Boarded and validated together with the batch's other retirement PRs in a combined worktree — full gate suite green. This PR's conflicts (against #11720's Designer retirement, both introducing a retirement-guard mechanism across executors/index.ts, executorProxy.ts, providers.ts, reservedProviderPrefixes.ts, auth.ts, chat.ts, chatHelpers.ts, model.ts) were reconciled by combining both guards at every chokepoint, with the shared reserved-prefix count recomputed (not guessed) at 400. Re-validated with this PR's own 72 node:test + 20 vitest focused tests, all passing, and pushed before merge. Thank you.
2026-08-28 05:03:51 -03:00
Diego Rodrigues de Sa e Souza
bb1b01124b fix(assets): replace unresolved provider logos with generic icons (#11750)
Merged via /merge-batch (v3.8.51 provenance sweep). Boarded and validated together with the batch's other provenance PRs in a combined worktree — full gate suite green. Reconciled against 3 sibling PRs (#11735, #11736, #11711) that landed first and independently retired 6 further unproven assets this PR never targeted, in both the README media-badge row and the "148 non-target assets" golden count (now the real 142, computed not guessed). This PR's own 23 node:test + 91 vitest focused tests all pass. Thank you for the careful provenance/generic-fallback work.
2026-08-28 04:54:00 -03:00
Diego Rodrigues de Sa e Souza
890cbfbfde fix(sse): retire Microsoft Designer Web runtime (#11720)
Merged via /merge-batch (v3.8.51 provenance sweep). Boarded and validated together with the batch's other retirement PRs in a combined worktree — full gate suite green. This PR's own conflicts (against #11711's EdgeTTS retirement, both touching test-masking-allowlist.json and the "Image / video / audio generation" README bullet) were reconciled additively/subtractively (both retirements now correctly reflected), re-validated with this PR's own 62 focused tests, and pushed before merge. Thank you.
2026-08-28 04:49:02 -03:00
Diego Rodrigues de Sa e Souza
dd8575eb2c chore(audio): retire EdgeTTS pending provenance review (#11711)
Merged via /merge-batch (v3.8.51 provenance sweep). Boarded and validated together with the batch's other provenance/retirement PRs in a combined worktree — full gate suite green, including the audio/speech-combo regression suites. Thank you.
2026-08-28 04:45:17 -03:00
Diego Rodrigues de Sa e Souza
657d3a484a fix(deepseek-web): replace unlicensed PoW artifacts (#11732)
Merged via /merge-batch (v3.8.51 provenance sweep). Boarded and validated together with the batch's other provenance/dependency PRs in a combined worktree — full gate suite green. Thank you.
2026-08-28 04:44:34 -03:00
Diego Rodrigues de Sa e Souza
700a735949 fix(tests): await async getExecutor() in gemini-web-image-retirement test (#11868)
Direct follow-up fixing a bug my own /merge-batch process introduced. Test-only change, focused test passing.
2026-08-28 04:42:14 -03:00
Diego Rodrigues de Sa e Souza
a81895847d fix(sse): retire Gemini Web image generation (#11708)
Merged via /merge-batch (v3.8.51 provenance sweep). Boarded and validated together with the batch's other provenance/retirement PRs in a combined worktree — full gate suite green, including the video/image regression suites and the new gemini-web-image-retirement test file (fixed a getExecutor async-signature drift found during the combined validation pass; Gemini Web chat and legitimate Gemini image providers unaffected). Thank you.
2026-08-28 04:40:13 -03:00
Diego Rodrigues de Sa e Souza
acb9ba6978 chore(quality): gate provider asset provenance (#11743)
Merged via /merge-batch (v3.8.51 provenance sweep). Boarded and validated together with the batch's other provenance PRs in a combined worktree — full gate suite green, including the new provider-asset-provenance gate/manifest introduced here. Thank you.
2026-08-28 04:39:35 -03:00
Diego Rodrigues de Sa e Souza
3f5b08f2a6 fix(providers): correct g4f.space gateway claims (#11738)
Merged via /merge-batch (v3.8.51 provenance sweep). Boarded and validated together with the batch's other provider/provenance PRs in a combined worktree — full gate suite green. Thank you.
2026-08-28 04:38:48 -03:00
Diego Rodrigues de Sa e Souza
451917f648 docs(legal): add pinned provider asset notices (#11737)
Merged via /merge-batch (v3.8.51 provenance sweep). Boarded and validated together with the batch's other provenance PRs in a combined worktree — full gate suite green. This PR's THIRD_PARTY_NOTICES.md addition (LobeHub + theSVG provenance sections) conflicted with #11726's own addition (blackwell-systems/gcf-typescript + lipis/flag-icons) landing first; reconciled additively (both sections kept), re-validated with this PR's own 3 focused tests, and pushed before merge. Thank you.
2026-08-28 04:37:55 -03:00
Diego Rodrigues de Sa e Souza
46b8e7a9b7 fix(video-bridge): preserve fused timeline ordering (#11681)
Merged via /merge-batch (v3.8.51). Boarded and validated in a combined worktree alongside the batch's other in-flight PRs — full gate suite green (typecheck:core, lint, complexity/cognitive-complexity, file-size, changelog-integrity, focused tests including the video-bridge fusion/transcript suites). Thank you.
2026-08-28 04:35:30 -03:00
Diego Rodrigues de Sa e Souza
26a28b0843 fix(dashboard): replace unprovenanced notification MP3 (#11731)
Merged via /merge-batch (v3.8.51 provenance sweep). Boarded and validated together with the batch's other provenance/asset-cleanup PRs in a combined worktree — full gate suite green. Static-asset-only cleanup, no runtime code changes. Thank you.
2026-08-28 04:34:51 -03:00
Diego Rodrigues de Sa e Souza
e525f1d9b7 docs(assets): retire unproven PicoClaw and Zoo Code rasters (#11736)
Merged via /merge-batch (v3.8.51 provenance sweep). Boarded and validated together with the batch's other provenance/asset-cleanup PRs in a combined worktree — full gate suite green. Static-asset-only cleanup, no runtime code changes. Thank you for the provenance audit.
2026-08-28 04:34:15 -03:00
Diego Rodrigues de Sa e Souza
2efe22bd0c docs(legal): add pinned MIT third-party notices (#11726)
Merged via /merge-batch (v3.8.51 provenance sweep). Boarded and validated together with the batch's other provenance PRs in a combined worktree — full gate suite green (typecheck:core, lint red-discriminator, complexity/cognitive-complexity ratchets, file-size, changelog-integrity, focused tests). THIRD_PARTY_NOTICES.md additive merge, no runtime code changes. Thank you.
2026-08-28 04:33:39 -03:00
Diego Rodrigues de Sa e Souza
103d0dedc4 chore(assets): prune unused assets with unresolved provenance (#11735)
Merged via /merge-batch (v3.8.51 provenance sweep). Boarded and validated together with the batch's other provenance/asset-cleanup PRs in a combined worktree (typecheck:core, lint red-discriminator vs the pure release tip, complexity/cognitive-complexity ratchets, file-size, changelog-integrity, and the full focused test suite for every touched area all green). Static-asset-only cleanup, no runtime code changes. Thank you for the careful provenance audit.
2026-08-28 04:32:57 -03:00
Diego Rodrigues de Sa e Souza
7c04e75e54 docs(dedup): correct the CodeQL claim on the tenant-namespace comment (#11712)
Obrigado! Correção honesta e bem documentada — só comentário, nenhuma mudança de lógica.

- Corrige a alegação de que omitir `tenantId` do digest evita o falso-positivo do CodeQL `js/insufficient-password-hash`; documenta que o alerta #874 já foi levantado no `createHash` de qualquer forma e foi dispensado por HR#14 (documentação de segurança, não código).
- Deixa explícito por que não "consertar" com um KDF: quebraria o determinismo de que o dedup depende.
2026-08-28 01:57:27 -03:00
Diego Rodrigues de Sa e Souza
b7c07edad8 fix(ci): run the install-upgrade gate on disk, not on the /tmp tmpfs (#11855)
* fix(ci): run the install-upgrade gate on disk, not on the /tmp tmpfs

The v3.8.50 publish failed this gate again, and this time it said why:

  free space in /tmp: 2.9 GB
  ⚠️  only 2.9 GB free — this gate needs roughly 12 GB
  crashed: upgrade install ran out of disk space (58269 ENOSPC errors)

On the self-hosted runner `/tmp` is a **12 GB tmpfs backed by RAM**, while the
root filesystem had 66 GB free. The gate builds two ~3 GB install trees, installs
the second one over twice, and packs a 275 MB tarball — roughly 12 GB, all of it
demanded from the wrong filesystem.

This is why freeing disk never fixed it: 84 GB were freed on `/`, and none of it
ever reached the volume the gate was using. The check even measured the right
number and reported it against the wrong path, so the warning read as "the disk
is full" when the disk was fine.

- work in `<repo>/.install-upgrade/` (gitignored) instead of `os.tmpdir()`,
  overridable with `OMNIROUTE_INSTALL_UPGRADE_WORKDIR`
- the free-space log and the ENOSPC crash message now name the directory the run
  actually uses, so the next reader is sent to the filesystem that ran out

Phase A already passes on the current main: clean install healthy, version
reported correctly, 130 tables — the authentication fix and migration 163 from
#11845 both hold. Only Phase B was starved.

* docs(env): document OMNIROUTE_INSTALL_UPGRADE_WORKDIR

The workdir override introduced in this branch is a new `process.env.*` read, and
two gates caught it immediately: `issue #7793: real .env.example is in sync with
process.env.* reads in code` and `check:env-doc-sync` (Docs Sync STRICT).

Both were right — an env var that exists only in code is an env var nobody can
find. Documented in `.env.example` and `docs/reference/ENVIRONMENT.md` with the
reason it exists: the gate needs ~12 GB and must not land on a small tmpfs.
2026-08-28 00:18:02 -03:00
Ravi Tharuma
825f8fe425 fix(providers): honor model auto-fetch opt-in on create (#11798)
Obrigado! Validado em lote combinado (8 PRs, release/v3.8.51):

- Honra o contrato `autoFetchModels` default-off nos dois caminhos de criação de provider; zero chamadas de auto-discovery quando a flag está ausente/false.
- Roda exatamente um sync quando habilitado explicitamente (clientes API usam o sync em background server-owned; o dashboard assume quando precisa de UI de progresso).
- Testes focados verdes: `tests/unit/providers-route-model-autofetch-optin.test.ts` (node) + `useApiKeySaveSkipsFullSync.test.tsx` (vitest).
- Gates estáticos do lote OK.
2026-08-27 22:15:23 -03:00
Ravi Tharuma
cfeed516e8 fix(dashboard): make model auto-fetch order independent (#11805)
Obrigado! Validado em lote combinado (8 PRs, release/v3.8.51):

- TDD claro: implementação antiga falhava 1/5 no caso "enabled-first mixed" (postava `/sync-models?mode=sync` incorretamente); implementação corrigida passa 5/5.
- Avalia todas as conexões ativas antes de tratar o auto-fetch como habilitado, tornando o resultado independente da ordem de conexões API/DB.
- `tests/unit/ui/use-provider-models-auto-fetch.test.tsx` — verde via vitest.
- Gates estáticos do lote OK (incluindo confirmação de que o único erro de lint pré-existente no arquivo tocado apenas mudou de linha 130→137 por causa das linhas adicionadas por esta PR — sem regressão real).
2026-08-27 22:15:18 -03:00
Ravi Tharuma
d937b5229e fix(sse): preserve CLIProxyAPI wrappers per connection (#11797)
Obrigado! Validado em lote combinado (8 PRs, release/v3.8.51):

- Roteia corretamente o override `cliproxyapiMode: "claude-native"` por conexão através dos wrappers de credencial/mapeamento já existentes, evitando vazamento de credencial nativa e modelo não mapeado no caminho de passthrough.
- Regressão de wire-level cobrindo headers e body: `tests/unit/cliproxyapi-dedicated-credential-7645.test.ts` — verde, incluindo os testes irmãos `cliproxyapi-model-mapping-dispatch` e `cliproxyapi-fallback-wiring`.
- Gates estáticos do lote OK.
2026-08-27 22:15:10 -03:00
Ravi Tharuma
321935d261 fix(services): separate CLIProxyAPI health and model auth (#11811)
Obrigado! Validado em lote combinado (8 PRs, release/v3.8.51):

- Separação correta de responsabilidades: probe de saúde via `/healthz` público vs. autenticação de `/v1/models` com `settings.cliproxyapi_api_key` dedicada; `MANAGEMENT_PASSWORD` mantida estritamente no plano de gestão do CLIProxyAPI.
- Evidência RED→GREEN documentada e reproduzida: `tests/unit/services/cliproxy-health-model-auth.test.ts` — verde no lote.
- `⚠️ base-red inherited: #11449` reconhecido — não é causado por esta PR.
- Gates estáticos do lote OK.
2026-08-27 22:15:06 -03:00
Ravi Tharuma
a14a91dcfe perf(compression): avoid cloning memo misses twice (#11727)
Obrigado! Validado em lote combinado (8 PRs, release/v3.8.51):

- TDD claro: sem o fix, o teste focado falha nas asserções de contagem exata de lookup sync/async; com o fix, `tests/unit/compression/result-memo.test.ts` passa (34/34).
- Remove supressão eslint agora obsoleta (`no-unused-vars` no arquivo de teste).
- `⚠️ base-red inherited: #11449` reconhecido e verificado — não é responsabilidade desta PR (confirmado via probe-worktree do tip puro).
- Gates estáticos do lote OK.
2026-08-27 22:14:57 -03:00
Ravi Tharuma
09fa818365 fix(docker): make GHA cache exports best effort (#11806)
Obrigado! Validado em lote combinado (8 PRs, release/v3.8.51):

- Incidente real documentado com link da run do GHA (`404 BlobNotFound` no cache exporter do Azure Actions), publicação Docker Hub/GHCR já bem-sucedida.
- Regressão estática nova: inventário dos 4 escopos de cache GHA garantindo `ignore-error=true`.
- Não altera falhas de build/push — só isola falha opcional de cache.
- Gates estáticos do lote OK.
2026-08-27 22:14:53 -03:00
Ravi Tharuma
9be4fd0a0e fix(models): hide unsupported Gemini Live discovery (#11710)
Obrigado! Validado em lote combinado (8 PRs, release/v3.8.51):

- Follow-up correto de #10590/#10591 — omite modelos Gemini Live-only da descoberta dinâmica, preserva chat para modelos híbridos.
- Teste focado atualizado: `tests/unit/gemini-models-parser.test.ts` — 6/6 verde.
- Gates estáticos do lote OK (typecheck, lint sem regressão, complexity, cognitive, file-size, changelog-integrity).
2026-08-27 22:14:45 -03:00
Ravi Tharuma
a45e80af43 fix(db): skip absent compression telemetry cleanup table (#11802)
Obrigado pela correção! Validado em lote combinado (8 PRs, release/v3.8.51):

- Reprodução com imagem pública confirmada no corpo da PR (`compression_run_telemetry` ausente após fresh install).
- Reutiliza o helper `tableExists()` já existente — sem SQL cru novo.
- Testes focados: 7/7 em `tests/unit/telemetry-auto-cleanup-6848.test.ts` (verde).
- Gates estáticos do lote: typecheck, lint (228 erros pré-existentes na tip pura, confirmado via probe-worktree — zero regressão), complexity, cognitive-complexity, file-size e changelog-integrity — todos OK.
2026-08-27 22:14:42 -03:00
Diego Rodrigues de Sa e Souza
0ce21232db fix(db): converge the install and upgrade schemas; stop ENOSPC from faking a divergence (#11845)
* fix(db): converge the install and upgrade schemas; stop ENOSPC from faking a divergence

The v3.8.50 publish run failed `check:install-upgrade` with "15 tables a CLEAN install
creates but an UPGRADE does not" (agentic_conversations, ccr_blocks, the whole Radar set,
jobs/job_runs, exclusive_connection_leases, …). None of them was missing.

Root cause, from the CI log (run 33104507735): the Phase B upgrade `npm install` hit
`npm warn tar TAR_ENTRY_ERROR ENOSPC: no space left on device` 5611 times, npm still exited
0, and the resulting truncated package made `omniroute serve` "exit with code 0 before
serving". No migration ever ran, so the database still held the 3.8.49 schema (115 tables)
and every post-133 migration table read as a divergence.

Verified against the real thing: booting the published omniroute@3.8.49 and replaying that
database through the current runner applies exactly 29 migrations and lands on the same
table set a clean install produces — the migration set was never at fault.

What changes:

- `163_model_capabilities.sql` — the one genuine convergence defect. The table was only
  ever created by `ensureCapabilitiesTable()` on the first models.dev sync, so whether a
  database has it depends on timing, not on the schema version. It is the residual the
  gate reported. A migration makes both install paths deterministic.
- `check:install-upgrade` now fails on an ENOSPC-truncated install instead of measuring a
  broken tree; authenticates its health probe with a minted internal-service token, so the
  version assertion works against the health payload hardened by GHSA-mvf8-qc78-5mxm
  (an anonymous caller gets no version — the same run also failed with "health reports
  version undefined"); frees the ~3 GB clean-install tree before the upgrade phase; warns
  when the temp filesystem cannot hold the run; prints the failing server's output; and
  skips the convergence verdict when a phase never served, so a broken boot can no longer
  manufacture a schema divergence on top of the real failure.

Tests: `tests/unit/db-install-upgrade-schema-parity.test.ts` pins the deterministic half of
the gate in milliseconds (every migration reachable on a clean install; model_capabilities
comes from the migration set; its DDL does not drift from the runtime helper), and the
ENOSPC guard is covered in the existing gate test.

* docs(db): record the real cause of the cache_metrics residual in the allowlist

The allowlist described every residual as "a CREATE that left the migration set in some
past cycle". cache_metrics never was in the migration set: it is created lazily by
ensureCacheMetricsTable() (src/lib/semanticCache.ts:34) the first time the semantic cache
runs, which is the same class as the model_capabilities divergence that blocked the v3.8.50
publish. Document both causes so the next residual is fixed with a migration where that is
the right answer, instead of reflexively allowlisted.

* docs: bump the migration count to 160 after 163_model_capabilities

check:docs-counts-sync enforces the shipped migration count as a STRICT claim in README.md,
AGENTS.md and llm.txt.

* docs(i18n): re-sync the 42 llm.txt mirrors after the migration-count bump
2026-08-27 19:38:01 -03:00
Diego Rodrigues de Sa e Souza
b65ef333da fix(ci): size the install-upgrade gate to a measured run, and log the pack cost (#11776)
The v3.8.50 publish died at `Prove clean-install AND upgrade-over-previous both
boot` — timed out after 30 minutes. Not a defect found: the gate never got to
finish.

The log says why, once you read past the first line:

  03:42:49  packing v3.8.50…
  04:07:28  PHASE A — clean install of the packed tarball
  04:13:08  timeout

`npm pack` alone took **24m37s**, leaving 5 minutes for two installs and two
boots. The budget was never going to hold.

Worth naming: this gate landed in #8953 and the 2026-08-27 run was the FIRST to
ever reach it. Every earlier publish died upstream — disk exhaustion, a missing
dist/BUILD_SHA — so `timeout-minutes: 30` had never been measured against a real
execution. It was a guess, and it blew on its debut. Same shape as the rest of
this cycle: a gate that had never been allowed to finish speaking.

Two changes, and the second is the one that matters next time:

- `timeout-minutes: 30` -> `60`, sized to the single measurement available.
- the script now times the pack and prints duration + tarball size. Without it
  the log showed `packing…` and then nothing for 30 minutes, which reads like a
  hang and is not — raising a limit blind would have been a guess on top of a
  guess.

If 60 also proves short, the next log will say exactly which phase ate it.
2026-08-27 13:39:29 -03:00
diegosouzapw
31d3702a37 chore(changelog): link wreq transport migration to PR 11753 2026-08-27 02:43:56 -03:00
diegosouzapw
cb623b3cef refactor(stealth): migrate web-cookie TLS transport to wreq-js 2026-08-27 02:39:44 -03:00
Diego Rodrigues de Sa e Souza
2acbfc6fa6 Merge pull request #11751 from diegosouzapw/test/v3851-tiered-rotation-timeout
test(auto-combo): tolerate full-suite rotation load
2026-08-27 02:02:24 -03:00
diegosouzapw
ad3e293f9f test(auto-combo): tolerate full-suite rotation load 2026-08-27 01:41:30 -03:00
Diego Rodrigues de Sa e Souza
c9f11d86b5 Merge pull request #11733 from diegosouzapw/chore/v3851-prune-stale-eslint-suppressions-2
chore(lint): drain remaining release base-reds
2026-08-27 01:15:48 -03:00
diegosouzapw
21ed68d8ac test: drain inherited quality snapshot reds 2026-08-27 00:43:57 -03:00
Diego Rodrigues de Sa e Souza
925feb27b8 fix(docker): let the bun digest artifact be absent, not fatal (#11740)
Follow-up to #11724. That PR made the bun image non-blocking and taught the
manifest step to skip its tags when no digest exists — but stopped one step
short: the upload still carried `if-no-files-found: error`, so an absent digest
(now the *expected* outcome of a skipped bun build) failed the job anyway.

Run 33030348950 shows it precisely: both arches died at `Upload bun-base
digests`, after the decoupling had already done its part. The blocker had simply
moved from the manifest to the upload.

- bun digest uploads: `if-no-files-found: ignore`
- bun digest downloads: `continue-on-error`, since the artifact may not exist

base/web keep `error` on both sides — a supported image producing no digest is
still a real failure that must stop the publish.
2026-08-27 00:41:09 -03:00
diegosouzapw
d35b3f9779 chore(quality): rebaseline measured CLI bundle growth 2026-08-27 00:11:28 -03:00
diegosouzapw
9587e07b69 fix(ui): fall back from invalid AnySearch icon alias 2026-08-27 00:05:47 -03:00
diegosouzapw
12cc6ca834 test(ui): reproduce invalid AnySearch icon alias 2026-08-27 00:01:12 -03:00
diegosouzapw
4f11b2ae3d docs: sync provider catalog count 2026-08-26 23:20:31 -03:00
diegosouzapw
b4e76a7ed9 docs(skills): sync omni-inference search fallback 2026-08-26 23:08:56 -03:00
diegosouzapw
b85d0abb0b fix(test): capture hook result after render 2026-08-26 22:45:24 -03:00
diegosouzapw
1325047d56 chore(lint): prune two stale base suppressions 2026-08-26 22:42:38 -03:00
Diego Rodrigues de Sa e Souza
aa52351113 fix(docker): decouple the best-effort Bun image from the release manifest (#11724)
The v3.8.50 Docker publish failed on both arches with:

  process "/bin/sh -c bun run --quiet build" ... cannot allocate memory

Only Dockerfile.bun failed. The SUPPORTED images built fine — runner-base in
16m03 (amd64) / 14m13 (arm64), runner-web in 3m15 / 1m31 — yet none of them
reached the registry, because one best-effort target sank the whole workflow.

AGENTS.md is explicit that Bun is a compatibility path and NOT a supported
runtime. Giving it the power to block the release inverts that: the runtime
users actually run stayed unpublished so an experimental one could fail loudly.

The Bun image is still built and still pushed on every run — it only stops
being a release blocker:

- both Bun build steps are `continue-on-error`
- the digest files are only created when a digest actually exists
- `create_manifest` takes an `optional` flag: an empty digest dir now warns and
  skips that tag instead of exiting 1. base/web stay hard-fail, so a real
  regression in a supported image still stops the publish.

Applied to both the Docker Hub and GHCR manifest steps.

One trap worth naming: the digest guard uses `if` blocks rather than
`[ -n "$X" ] && touch ...`. Under `set -euo pipefail` a failing AND-list aborts
the step — which is exactly the empty-digest case this is meant to handle, so
the terse form would have swapped one blocker for another.
2026-08-26 22:30:19 -03:00
Diego Rodrigues de Sa e Souza
8778ea7d18 fix(ci): stamp dist/BUILD_SHA before the npm publish provenance gate (#11721)
The publish job builds with `build:cli`, which assembles dist/ but does not
write dist/BUILD_SHA — only `build:release` does, via write-build-sha.mjs. The
#10427 provenance guard inside check:pack-artifact then rejects the artifact for
having no SHA, so the build+validate pair in this job could never pass:

  [provenance] dist/BUILD_SHA is missing — the artifact cannot be traced to a commit.

This is the same structural gap that was fixed in ci.yml's Package Artifact job
earlier in the v3.8.50 cycle; npm-publish.yml carried it too and it only became
visible now that the job finally got past the runner's disk exhaustion.

Stamp from github.sha (on a release event that is the tag commit, which is on
main) and fetch origin/main so the ancestry probe can resolve the ref that the
guard checks against by default.
2026-08-26 22:30:11 -03:00
Diego Rodrigues de Sa e Souza
c44c0a29e8 docs(changelog): add consolidated v3.8.50 stats, top-25 ranking and a .mailmap (#11715)
Two things, and the second is the reason the first is trustworthy.

.mailmap: between 2026-08-13 and 2026-08-26 this checkout carried a
`git config --local` pairing one contributor's name (Xiangzhe / @xz-dev) with
ANOTHER contributor's email (@backryun). 237 commits made here were therefore
signed with @backryun's address. The timezone split is unambiguous: @backryun's
own work commits from +0900 throughout the window and never stopped, while all
237 came from -0300, this machine. No repository file sets that address, so it
was a local config mix-up, not anything in the codebase. The local override is
now removed; the global identity was correct all along.

History is NOT rewritten: those commits live on release/v3.8.50 and
release/v3.8.51, which open PRs and other sessions build on, and the v3.8.50 tag
was cut from that line. .mailmap repairs the record for log/shortlog/blame — the
git-native answer for exactly this — without a force-push. main is unaffected:
releases squash-merge, so it carries none of the 237.

Stats: counts measured, not estimated — 1,714 commits and 248 people over
ed2db6cb19..v3.8.50, 1,666 distinct PR refs, and the 1,182 changelog entries
broken down by type. The top-25 ranking uses the consolidated identities, so
@backryun keeps their 88 real cycle commits and the misattributed 68 return to
the maintainer.
2026-08-26 22:30:02 -03:00
Diego Rodrigues de Sa e Souza
2e8326d531 fix(db): dedupe lazy-decrypt-view failure logging across sync cycles (#11500) (#11678) 2026-08-26 20:50:15 -03:00
Diego Rodrigues de Sa e Souza
7133585d1d fix(combos): use the routing-alias prefix, not raw providerId, when serializing combo model steps (#11433) (#11670) 2026-08-26 20:50:09 -03:00
Diego Rodrigues de Sa e Souza
bcda889f84 fix(sse): keep each functionCall's own thoughtSignature on parallel Gemini tool-call turns (#11510) (#11669) 2026-08-26 20:50:02 -03:00
Diego Rodrigues de Sa e Souza
91ecb6be9c fix(sse): cap streaming headers-wait timeout to a client-realistic ceiling (#11526) (#11668) 2026-08-26 20:49:55 -03:00
Diego Rodrigues de Sa e Souza
e02f11984f fix(kie): correct 12 more mismatched KIE Market model ids (#11296) (#11667) 2026-08-26 20:49:48 -03:00
Diego Rodrigues de Sa e Souza
ca4df9bef8 fix(combo): attach combo diagnostics to round-robin and runtime-unit retry-limit 503s (#11462) (#11665) 2026-08-26 20:49:42 -03:00
Diego Rodrigues de Sa e Souza
0ce2b83005 fix(dashboard): skip full /sync-models catalog fetch when caller opts out (#11324) (#11664) 2026-08-26 20:49:34 -03:00
Diego Rodrigues de Sa e Souza
124f4cf761 fix(docker): re-derive build memory budget from measured per-process RSS (#7518) (#11663) 2026-08-26 20:49:27 -03:00
Abhishek Sharma
4977f1adcf fix(opencode-plugin): stop a rounded free budget rendering as 1000K instead of 1M (#11684)
Merged via /merge-batch (2026-08-26, v3.8.51). Conflito trivial no package.json (lista de testes, mesclada com #11660). Também atualizei um teste pré-existente que documentava explicitamente o bug ("characterises the 999_999 rounding wart... Not this PR's bug to fix") — este PR é exatamente esse futuro fix, então virei a asserção para o comportamento correto (1M em vez de 1000K). Validado: 16/16 testes passando. Obrigado pela contribuição.
2026-08-26 17:58:35 -03:00
Xxx91n
99044ed044 feat(search): add fallback-only AnySearch provider (webSearch + webFetch) (#11690)
Merged via /merge-batch (2026-08-26, v3.8.51). Encontrei e corrigi algumas lacunas ao validar: (1) o tipo do provider em handleWebFetch (MCP) não incluía anysearch-search, (2) a lista FETCH_PROVIDERS do dashboard (/api/search/providers) não tinha a entrada anysearch-search apesar dela ser webSearch+webFetch, (3) contagens desatualizadas em search-registry/search-route/context7-provider/search-providers-catalog (19→20 search, +1 fetch). Enviei tudo para seu branch antes do merge. Validado: 113/113 testes relacionados passando, typecheck limpo. Obrigado pela contribuição — provider bem completo.
2026-08-26 17:54:57 -03:00
Rhian Batista
68aa69bc21 fix(ddgw): normaliza system no upstream da release v3.8.51 (#11692)
Merged via /merge-batch (2026-08-26, v3.8.51). Draft avaliada normalmente (diretiva padrão do dono) — passou na validação combinada. Obrigado pela contribuição.
2026-08-26 17:41:28 -03:00
小妍儿 ✨
14278e301c fix(models): gate the Codex-native catalog loop on prefix mode (#11632) (#11662)
Merged via /merge-batch (2026-08-26, v3.8.51). Draft avaliada normalmente (diretiva padrão do dono) — passou na validação combinada: typecheck/complexity/file-size/changelog verdes, testes focados passando. Obrigado pela contribuição.
2026-08-26 17:41:24 -03:00
Webman
38bc925187 test(chatcore): move fixtures off models the lifecycle guard now rejects (#11675)
Merged via /merge-batch (2026-08-26, v3.8.51). Boarded no worktree combinado; validação única passou. Obrigado pela contribuição.
2026-08-26 17:41:15 -03:00
Webman
8946ce71eb docs: sync canonical provider count 354 -> 356 (#11673)
Merged via /merge-batch (2026-08-26, v3.8.51). Boarded no worktree combinado; validação única: typecheck/complexity/cognitive-complexity/file-size/changelog verdes, lint nos mesmos 228 achados pré-existentes confirmados contra o tip puro, testes focados passando. Obrigado pela contribuição.
2026-08-26 17:40:54 -03:00
Webman
c8f3bc888e test(providers): update count-derived assertions for v3.8.51 additions (#11674)
Merged via /merge-batch (2026-08-26, v3.8.51). Boarded no worktree combinado; validação única: typecheck/complexity/cognitive-complexity/file-size/changelog verdes, lint nos mesmos 228 achados pré-existentes confirmados contra o tip puro, testes focados passando. Obrigado pela contribuição.
2026-08-26 17:40:50 -03:00
Diego Rodrigues de Sa e Souza
955c7bbf42 fix(security): compare the OIDC state cookie in constant time (#11646)
Merged via /merge-batch (2026-08-26, v3.8.51). Boarded no worktree combinado; validação única: typecheck/complexity/cognitive-complexity/file-size/changelog verdes, lint nos mesmos 228 achados pré-existentes confirmados contra o tip puro, testes focados passando. Obrigado pela contribuição.
2026-08-26 17:40:45 -03:00
Diego Rodrigues de Sa e Souza
ff25849530 fix(security): namespace the dedup hash by the calling API key (#11649)
Merged via /merge-batch (2026-08-26, v3.8.51). Boarded no worktree combinado; validação única: typecheck/complexity/cognitive-complexity/file-size/changelog verdes, lint nos mesmos 228 achados pré-existentes confirmados contra o tip puro, testes focados passando. Obrigado pela contribuição.
2026-08-26 17:40:41 -03:00
KaspaPulse
756b47fdd7 fix(dashboard): project Codex routing quota in account pools (#11666)
Merged via /merge-batch (2026-08-26, v3.8.51). Boarded no worktree combinado; validação única: typecheck/complexity/cognitive-complexity/file-size/changelog verdes, lint nos mesmos 228 achados pré-existentes confirmados contra o tip puro, testes focados passando. Obrigado pela contribuição.
2026-08-26 17:40:37 -03:00
Bob.Hou
e69f2109aa chore(search): drop unreachable credential sweep after #11642 (#11687)
Merged via /merge-batch (2026-08-26, v3.8.51). Boarded no worktree combinado; validação única: typecheck/complexity/cognitive-complexity/file-size/changelog verdes, lint nos mesmos 228 achados pré-existentes confirmados contra o tip puro, testes focados passando. Obrigado pela contribuição.
2026-08-26 17:40:32 -03:00
f9td56dbgh-hub
bda83c1d39 test: add unit tests for formatFreeBudget in opencode-plugin (#11660)
Merged via /merge-batch (2026-08-26, v3.8.51). Boarded no worktree combinado; validação única: typecheck/complexity/cognitive-complexity/file-size/changelog verdes, lint nos mesmos 228 achados pré-existentes confirmados contra o tip puro, testes focados passando. Obrigado pela contribuição.
2026-08-26 17:40:28 -03:00
Webman
67f4e5201d fix(sse): import httpClientAbortGuard with explicit .mjs extension (#11671)
Merged via /merge-batch (2026-08-26, v3.8.51). Boarded no worktree combinado; validação única: typecheck/complexity/cognitive-complexity/file-size/changelog verdes, lint nos mesmos 228 achados pré-existentes confirmados contra o tip puro, testes focados passando. Obrigado pela contribuição.
2026-08-26 17:40:22 -03:00
Webman
e95a25512d fix(resilience): restore expired-connection retry-budget probe in health sweep (#11672)
Merged via /merge-batch (2026-08-26, v3.8.51). Boarded no worktree combinado; validação única: typecheck/complexity/cognitive-complexity/file-size/changelog verdes, lint nos mesmos 228 achados pré-existentes confirmados contra o tip puro, testes focados passando. Obrigado pela contribuição.
2026-08-26 17:40:14 -03:00
Webman
146897d9bd test(gamification): pin aggregate level to #11604 XP-derived semantics (#11676)
Merged via /merge-batch (2026-08-26, v3.8.51). Boarded no worktree combinado; validação única: typecheck/complexity/cognitive-complexity/file-size/changelog verdes, lint nos mesmos 228 achados pré-existentes confirmados contra o tip puro, testes focados passando. Obrigado pela contribuição.
2026-08-26 17:40:02 -03:00
Webman
f75bd75389 test(sse): bump hard-lease inventory count for #11495 sweep query (#11677)
Merged via /merge-batch (2026-08-26, v3.8.51). Boarded no worktree combinado; validação única: typecheck/complexity/cognitive-complexity/file-size/changelog verdes, lint nos mesmos 228 achados pré-existentes confirmados contra o tip puro, testes focados passando. Obrigado pela contribuição.
2026-08-26 17:39:57 -03:00
Diego Rodrigues de Sa e Souza
5458026c21 docs(changelog): aggregate the ten v3.8.50 fragments into CHANGELOG.md (#11683)
The fragments were written during the pre-flight but the aggregation step was
left uncommitted, so the release PR merged with the ten files still loose in
changelog.d/ and CHANGELOG.md missing their entries. Tagging from that state
would have published v3.8.50 without documenting the discarded upstream call
burning quota (#11552), the configured search connection being silently ignored
(#11524), the /v1/models SWR refresh blocking its own stale response (#11551),
the combo builder's manual model entry (dark since #8285) and the local CLI's
health view (dark since #11040) — the entries an operator actually reads.

Runs the aggregator on top of main and deletes the fragments in the same commit,
per changelog.d/README.md.

Co-authored-by: Xiangzhe <bakryun0718@proton.me>
2026-08-26 16:29:05 -03:00
Diego Rodrigues de Sa e Souza
b4ec7807ab Release v3.8.50
Release v3.8.50 — see CHANGELOG.md for the full entry.
2026-08-26 14:25:01 -03:00
Bob.Hou
42a13fedef fix(search): properly resolve configured search connection in /v1/responses pipeline (#11524) (#11642)
Merged via /merge-batch (2026-08-26, v3.8.51). O fix de produção já estava coberto por #11565 (mesma issue #11524, mergeada mais cedo hoje) — o conflito era só de comentários, mantido o texto já validado. Seu valor real: o novo teste de regressão do pipeline /v1/responses, que não existia ainda. Também corrigi 2 issues de lint introduzidas pelo novo teste (params `init` não usados renomeados para `_init`, contagem de supressão `no-explicit-any` ajustada) e o cap de tamanho do arquivo de teste (rebaseline). Validado: 55/55 testes de busca passando. Obrigado pela contribuição.
2026-08-26 13:43:58 -03:00
Bob.Hou
242c451298 fix(authz): trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) (#11643)
Merged via /merge-batch (2026-08-26, v3.8.51). Validado: typecheck/complexity/file-size/changelog verdes, 18/18 testes de authz passando (com o isolamento correto de DATA_DIR — meu primeiro run isolado falhou por eu não ter carregado tests/_setup/isolateDataDir.ts, não é bug seu). Obrigado pela contribuição.
2026-08-26 13:39:29 -03:00
Webman
6e62aad32e chore(lint): prune stale ESLint suppressions (base-red #11449) (#11644)
Merged via /merge-batch (2026-08-26, v3.8.51). Validado.
2026-08-26 13:39:26 -03:00
Diego Rodrigues de Sa e Souza
91aeca0440 fix(security): redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script (#11639)
Merged via /merge-batch (2026-08-26, v3.8.51). Validado no worktree combinado.
2026-08-26 10:59:26 -03:00
Webman
49749eb0d0 fix(types): clear the two type regressions red on release/v3.8.51 (#11640)
Merged via /merge-batch (2026-08-26, v3.8.51). Confirma o fix do typecheck:core que eu vinha classificando como drift pré-existente nas últimas levas (zai-web.ts mimeType) — obrigado por resolver de vez. Validado: typecheck limpo, 7/7 testes passando.
2026-08-26 10:59:19 -03:00
Bob.Hou
8d17110082 fix(build): resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) (#11641)
Merged via /merge-batch (2026-08-26, v3.8.51). Validado: typecheck/complexity/file-size/changelog verdes, 3/3 testes do prepublish workspace-protocol passando. Obrigado pela contribuição.
2026-08-26 10:59:14 -03:00
Webman
1ee4818224 deps: realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep (#11633)
Merged via /merge-batch (2026-08-26, v3.8.51). Validado com `npm install` completo: 0 vulnerabilidades, lockfile consistente após o realinhamento do pin onnxruntime-node. Obrigado pela contribuição.
2026-08-26 09:54:52 -03:00
Prajeeth H
6afc7ddab0 fix(install): guard prepare script against missing husky (#11634)
Merged via /merge-batch (2026-08-26, v3.8.51). Validado: 5/5 testes passando, `npm install` limpo confirmando que o script prepare não quebra sem husky. Obrigado pela contribuição.
2026-08-26 09:54:48 -03:00
Webman
71fd806bb0 test(autoCombo): port models_dev_tier #11508 guard from node:test to vitest (#11635)
Merged via /merge-batch (2026-08-26, v3.8.51). Validado com o config correto (`vitest run --config vitest.mcp.config.ts`, que já inclui `tests/unit/autoCombo/**/*.test.ts`): 5/5 testes passando. Obrigado por destravar esse base-red.
2026-08-26 09:54:44 -03:00
Diego Rodrigues de Sa e Souza
adedc351d7 chore(quality): rebaseline file-size for #11083/#11631/#11628 growth (#11636)
Merged via /merge-batch (correção de lacuna, 2026-08-26). check:file-size confirmado OK após o rebaseline.
2026-08-26 09:52:44 -03:00
Webman
1d476e4ced fix(ci): resolve inherited release/v3.8.51 base-red CI failures (#11608)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). 7 conflitos, todos triviais/duplicados (mesmos base-reds já corrigidos por PRs paralelas mergeadas neste lote — #11580/#11582/#11583/#11585/#11588/#11589/#11590/#11591/#11609): mantida a versão já validada nesses casos. Validado: 68/68 testes passando. Obrigado por resolver os base-reds.
2026-08-26 09:28:08 -03:00
Meet shah
18d5b75335 chore(ci): unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) (#11609)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Dois conflitos, ambos triviais: (1) stryker.conf.json — mesma linha com indentação diferente, mantida a indentação correta; (2) ts7-executor-shared-shapes.test.ts — mesma correção já coberta por #11591/#11608 (mergeados neste lote), mantido o describe/comentário melhor deste PR sobre a asserção já validada. Validado: 4/4 testes passando. Obrigado pela contribuição.
2026-08-26 09:26:37 -03:00
MumuTW
00d385b498 fix(autoCombo,sse): drop vendor-retired ids from the auto-combo pool and skip leftover arena_elo (#11625) (#11626)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Conflito real em taskFitness.ts: seu código pula corretamente as camadas 1-3 para ids vendor-retired (a versão anterior fazia lookup redundante de arena_elo/inherited/tier mesmo quando já sabia que o id era retired). Resolvido mantendo o skip antecipado. Validado: 23/23 testes passando. Obrigado pela contribuição.
2026-08-26 09:25:25 -03:00
Benson K B
7b9b36836c fix(db): fallback load STORAGE_ENCRYPTION_KEY from env files (#11614)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Encontrei um bug real na validação: o guard `NODE_ENV==="test"||VITEST` não cobre o test runner nativo do projeto (`node --import tsx/esm --test`, que não seta NODE_ENV), então o fallback estava lendo `~/.omniroute/.env` — o .env de PRODUÇÃO real do operador — durante qualquer execução de teste sem esse env var. Corrigi usando o `isTestContext()` já existente e mais robusto em `dataPaths.ts` (cobre `NODE_TEST_CONTEXT`/`--test` argv), e troquei o path hardcoded `os.homedir()/.omniroute` por `resolveDataDir()` para respeitar `DATA_DIR` quando configurado. Enviei a correção para o seu branch antes do merge. Validado: 7/7 testes de `db-encryption.test.ts` passando. Obrigado pela contribuição — a ideia central (fallback do STORAGE_ENCRYPTION_KEY) é boa e necessária.
2026-08-26 09:23:57 -03:00
Paco Cartones
ae8ab27294 fix(dashboard): restore focus after closing modals (#11607)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Boarded no worktree combinado junto com outras ~20 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e 292 testes focados (unit) + 18 (vitest) passando. Obrigado pela contribuição.
2026-08-26 09:22:26 -03:00
Paco Cartones
9237a0562f fix(dashboard): conceal locked hidden badge details (#11605)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Boarded no worktree combinado junto com outras ~20 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e 292 testes focados (unit) + 18 (vitest) passando. Obrigado pela contribuição.
2026-08-26 09:22:22 -03:00
Paco Cartones
c4d149c2eb fix(dashboard): keep Profile XP and level consistent (#11604)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Boarded no worktree combinado junto com outras ~20 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e 292 testes focados (unit) + 18 (vitest) passando. Obrigado pela contribuição.
2026-08-26 09:22:18 -03:00
Paco Cartones
a0422b9af8 fix(dashboard): harden search analytics responses (#11603)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Boarded no worktree combinado junto com outras ~20 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e 292 testes focados (unit) + 18 (vitest) passando. Obrigado pela contribuição.
2026-08-26 09:22:15 -03:00
Paco Cartones
5f5d1101ba fix(dashboard): enable keyboard activation for data-table rows (#11610)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Boarded no worktree combinado junto com outras ~20 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e 292 testes focados (unit) + 18 (vitest) passando. Obrigado pela contribuição.
2026-08-26 09:22:11 -03:00
vsd2807
5b8fe4d8c7 fix(handoff): enforce provider allowlist for universal handoff (#11602) (#11619)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Boarded no worktree combinado junto com outras ~20 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e 292 testes focados (unit) + 18 (vitest) passando. Obrigado pela contribuição.
2026-08-26 09:22:06 -03:00
Kobi Kadosh
d1768de716 feat(providers): add Nimble web search and fetch (#11620)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Boarded no worktree combinado junto com outras ~20 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e 292 testes focados (unit) + 18 (vitest) passando. Obrigado pela contribuição.
2026-08-26 09:22:03 -03:00
Mr White
c11f661a8a feat(resilience): add provider peak-hour protection (#11622)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Boarded no worktree combinado junto com outras ~20 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e 292 testes focados (unit) + 18 (vitest) passando. Obrigado pela contribuição.
2026-08-26 09:21:59 -03:00
Hsia97
0481f61750 fix: pass tool result content through as string in OpenAI/Claude -> Gemini translation (#11624)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Boarded no worktree combinado junto com outras ~20 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e 292 testes focados (unit) + 18 (vitest) passando. Obrigado pela contribuição.
2026-08-26 09:21:56 -03:00
KaspaPulse
86c03f1fed fix(sse): scope credential health to exact key (#11628)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Boarded no worktree combinado junto com outras ~20 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e 292 testes focados (unit) + 18 (vitest) passando. Obrigado pela contribuição.
2026-08-26 09:21:52 -03:00
zero-executioner
8abcd639d1 feat(providers): add 1min.ai provider (#11631)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Boarded no worktree combinado junto com outras ~20 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e 292 testes focados (unit) + 18 (vitest) passando. Obrigado pela contribuição.
2026-08-26 09:21:48 -03:00
rifqiawl
9f1f5ecf1f fix(kiro): add runtime.us-east-1.kiro.dev as first-attempt endpoint (#11517)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Boarded no worktree combinado junto com outras ~20 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e 292 testes focados (unit) + 18 (vitest) passando. Obrigado pela contribuição.
2026-08-26 09:21:44 -03:00
rifqiawl
e616130496 fix(providers): poll onboardUser LRO before classifying BYOP (#11519)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Boarded no worktree combinado junto com outras ~20 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e 292 testes focados (unit) + 18 (vitest) passando. Obrigado pela contribuição.
2026-08-26 09:21:41 -03:00
Paulo Roberto Fernandes Holanda
7a10d3856a fix: restore live websocket URL helpers (#11509)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Boarded no worktree combinado junto com outras ~20 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e 292 testes focados (unit) + 18 (vitest) passando. Obrigado pela contribuição.
2026-08-26 09:21:37 -03:00
Andrew B.
5984c43f5b feat(logs): add copy-all button to request detail modal (#11083)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Boarded no worktree combinado junto com outras ~20 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e 292 testes focados (unit) + 18 (vitest) passando. Obrigado pela contribuição.
2026-08-26 09:21:32 -03:00
Tobias Andersen
331580b499 fix(providers): mark Brave Search as no longer having a free tier (#11621)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Boarded no worktree combinado junto com outras ~20 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e 292 testes focados (unit) + 18 (vitest) passando. Obrigado pela contribuição.
2026-08-26 09:21:27 -03:00
Diego Rodrigues de Sa e Souza
3a7ed20bcd chore(quality): rebaseline file-size for #11448/#11495/#11561 growth (#11630)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). check:file-size confirmado OK após o rebaseline.
2026-08-26 08:17:54 -03:00
Diego Rodrigues de Sa e Souza
9c34e88f6b feat(providers): add Opper as an API-key gateway provider (#11629)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Porta de #11538 com autoria original preservada — ver detalhes na descrição desta PR.
2026-08-26 08:17:03 -03:00
Rafa Martins
131f8cb15a fix(i18n): complete Brazilian Portuguese translations (#11558)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Branch original tinha divergido de um ponto bem antigo de `main` (phantom-diff, dezenas de commits já mergeados no meio); extraí o commit de valor real (completar traduções pt-BR) e reapliquei sobre o tip atual. Houve conflito genuíno em `pt-BR.json`: algumas chaves já tinham sido traduzidas por outra PR mais recente (mantive essas) e outras (errorOccurred/comboDeleted/hide/creating/comboCreated/swapFormats) só existiam em inglês até esta PR (apliquei a tradução de vocês). Validado: 134/134 testes de integridade i18n passando, incluindo paridade de chaves com en.json. Obrigado pela contribuição.
2026-08-26 08:15:50 -03:00
Rafa Martins
2617841449 fix: accept Gemini Web cookie exports (#11554)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). O branch original tinha divergido de um ponto bem antigo de `main` (phantom-diff — dezenas de commits já mergeados no meio); extraí os 2 commits de valor real (aceitar cookies completos/PSID/JSON exportado do Gemini Web) e reapliquei sobre o tip atual de `release/v3.8.51` (o commit adicional de 'restore endpoint page JSX' já estava coberto pelo tip, então não era necessário). Validado: 125/125 testes de `provider-validation-specialty.test.ts` passando. Obrigado pela contribuição.
2026-08-26 08:13:22 -03:00
Ronaldo Davi
65aeaeece8 Only advertise verified-working connections by default (#11448)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:11:35 -03:00
Paijo
d351960be9 feat(providers): verify-only health sweep for web-cookie connections (#11488) (#11495)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:11:30 -03:00
Burak Keskin
4914954866 fix(mitm): keep Windows DNS elevation off the POSIX sudo path (#11430) (#11542)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:11:26 -03:00
Abhishek Sharma
b7d91a6b6e fix(quota-share): give each in-flight request its own lease (#11547)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:11:22 -03:00
Nguyễn Viết Tuấn
ebbfcf4ffe fix(sse): scale chat admission by ingest byte budget instead of a fixed request count (#11548)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:11:18 -03:00
Az1muth
2d185de9d9 fix(sse): prevent client-abort (aborted/ECONNRESET) from crashing the server (#11556)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:11:14 -03:00
Giorgos Giakoumettis
c63e519b09 feat(tunnels): support named cloudflared tunnels via CLOUDFLARED_CONFIG (#11561)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:11:09 -03:00
vsd2807
a02b4b404d fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) (#11565)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:11:05 -03:00
Webman
6e96057f1d fix(sse): explicit types for openai-responses pureHelpers — clears last failing core typecheck gate (#11567)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:11:00 -03:00
Guhan K
5eebee5dcc fix(sse): do not 502 Claude Code /model probes (content:[] + max_tokens) (#11568)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:10:55 -03:00
Nguyen Thanh Dat
608c90f592 fix(cache): expire a semantic-cache row on its TTL, not at UTC midnight (#11559) (#11573)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:10:51 -03:00
Nguyen Thanh Dat
b61a530a33 fix(api): reconnect the /v1/models background-refresh scheduler (#11551) (#11574)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:10:47 -03:00
Amar Tinawi
6ffa012c3e test(chatcore): guard the response tool-name alias recovery (#11575)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:10:43 -03:00
Nguyen Thanh Dat
eccb66042c docs(env): document OMNIROUTE_CORPUS_CACHE_SIZE so Docs Gates goes green again (#11576)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:10:38 -03:00
Nguyen Thanh Dat
5c1a882b95 chore(stryker): register the two test files covering three mutated modules (#11577)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:10:34 -03:00
Nguyen Thanh Dat
8cb2d0c047 test(authz): pin the GET-exemption set by membership, not by count (#11531) (#11580)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:10:29 -03:00
Nguyen Thanh Dat
1ea13454d5 fix(check): await the now-async getExecutor in the known-symbols gate (#11421) (#11582)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:10:24 -03:00
Nguyen Thanh Dat
0a792216d6 test(build): pin the bundler flag to OMNIROUTE_USE_TURBOPACK, the contract it implements (#11583)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:10:20 -03:00
AStupidBear
4449ba3173 fix(opencode): hydrate Proxy Pool references for no-auth connections (#11584)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:10:16 -03:00
Nguyen Thanh Dat
fe12208e30 test(claude-web): anchor on the alias wiring, not on how the map builds it (#11585)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:10:12 -03:00
Nguyen Thanh Dat
f40f6e18da fix(pack): register bin/cli/utils/volatileEnvPath.mjs as a required artifact path (#11437) (#11588)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:10:08 -03:00
Nguyen Thanh Dat
91ede70bcb i18n(vi): translate the five reliability-column strings (#11589)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:10:04 -03:00
Nguyen Thanh Dat
954d58b528 test(models): pin qwen3.8-max resolution per provider, not across all four (#11590)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:09:59 -03:00
Nguyen Thanh Dat
88939a35d1 test(executors): drop the tool-truncation assertion #11444 removed the code for (#11591)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:09:55 -03:00
Nguyen Thanh Dat
b9fd4aa00b test(token-health): pin the real terminal-skip boundary, not the pre-exemption one (#11592)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:09:51 -03:00
Nguyen Thanh Dat
ff1ed20bb8 test(models): follow #11503's Gemini -high alias retarget (#11593)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:09:47 -03:00
Paco Cartones
5cc213e46b fix(dashboard): preserve combo success percentage scale (#11595)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:09:43 -03:00
solstxce
5d7d4cb953 fix(providers): route duckduckgo-web via duck.ai and self-heal stale model catalog (#11597)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:09:38 -03:00
Paco Cartones
bd4a7629b6 fix(ui): make console log controls accessible (#11599)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
2026-08-26 08:09:34 -03:00
Meet shah
bfac589529 fix(autoCombo): lifecycle veto, deterministic capability merge, variant inheritance in models_dev_tier (#11508) (#11598)
Merged via /merge-batch (lote 2026-08-26, v3.8.51). As três correções mecânicas do #11508 aplicadas com clareza (veto de lifecycle, merge determinístico entre providers, herança de variante). Validado no worktree combinado: 5/5 testes focados passando, typecheck/complexity/changelog/file-size verdes (achados de lint pré-existentes confirmados via sonda contra o tip puro). Encontrei um bug real no próprio teste novo (profundidade de import relativo errada, `../../src/...` em vez de `../../../src/...`, causando ERR_MODULE_NOT_FOUND) e enviei a correção para o seu branch antes do merge — obrigado pela contribuição.
2026-08-26 08:09:21 -03:00
dependabot[bot]
e0ea3f92e1 deps: bump the development group across 1 directory with 8 updates (#11428)
Validated in a combined dependabot worktree off release/v3.8.51 tip alongside #11426 and #11440 — a fresh npm install of all three combined (2437 packages, 0 vulnerabilities) plus full-suite validation:
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity, check:cycles — all OK, including through the @types/node 22→26 major jump
- npm run lint — 0 errors (after also draining an unrelated stale-suppressions cascade, see #11596)
- npm run test:vitest — 451/452 pass; the 1 failure (auto/glm materialization) is a pre-existing timing-flaky test, reproduced 11/11 pass ×3 in isolation, unrelated to this bump
- Node runtime unaffected — v24.16.0 unchanged, only the type definitions moved

8 development-group updates. The bun 1.3.14→1.4.0 + @types/bun bump aligns with the Bun-native infrastructure work merged earlier today (#11468/#11470/#11471/#11482), which was built against Bun 1.4+ assumptions.
2026-08-26 00:38:38 -03:00
dependabot[bot]
64ffddf5e5 deps: bump the production group across 1 directory with 11 updates (#11440)
Validated in a combined dependabot worktree off release/v3.8.51 tip alongside #11426 and #11428 — a fresh npm install of all three combined (2437 packages, 0 vulnerabilities) plus full-suite validation:
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity, check:cycles — all OK
- npm run lint — 0 errors (after also draining an unrelated stale-suppressions cascade, see #11596)
- npm run test:vitest — 451/452 pass; the 1 failure (auto/glm materialization) is a pre-existing timing-flaky test, reproduced 11/11 pass ×3 in isolation, unrelated to this bump

11 production-group updates (aws-sdk bedrock-runtime, dompurify, fumadocs-core/ui, jose, lucide-react, marked, mermaid, next, onnxruntime-node, wreq-js), all patch/minor within their existing major lines.
2026-08-26 00:38:26 -03:00
dependabot[bot]
a9dd1dd0fe deps: bump electron from 43.4.0 to 43.4.1 in /electron (#11426)
Validated in a combined dependabot worktree off release/v3.8.51 tip alongside #11440 and #11428 — a fresh npm install of all three combined (2437 packages, 0 vulnerabilities) plus full-suite validation:
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity, check:cycles — all OK
- npm run lint — 0 errors (after also draining an unrelated stale-suppressions cascade, see #11596)
- npm run test:vitest — 451/452 pass; the 1 failure (auto/glm materialization) is a pre-existing timing-flaky test, reproduced 11/11 pass ×3 in isolation, unrelated to this bump

Electron patch bump, upstream release notes list only crash fixes and stability backports.
2026-08-26 00:38:16 -03:00
Diego Rodrigues de Sa e Souza
f69cd9f3bd chore(quality): prune stale eslint-suppressions entries (#11596)
Verified in an isolated worktree off a fresh npm install: 228 → 0 lint errors, purely from removing the stale-suppression count-mismatch cascade. See PR body for the separate main-checkout node_modules finding.
2026-08-26 00:37:38 -03:00
Chewji
6eb7bf0ba9 fix(command-code): fallback to /alpha/generate for Go plan without Provider API access (#11455)
Validated in a combined sub-batch worktree off release/v3.8.51 tip. This PR's branch also carried ~88 already-merged commits from a stale rebase (phantom-diff); cherry-picked only the genuine value commit. That commit's own test file (command-code-executor.test.ts) predated #11421's async getExecutor() change and had 7 test failures from unresolved-Promise call sites (`.execute()` on a still-pending getExecutor() Promise, and in one case on execute() itself not being awaited) — fixed all 7 call sites to properly await both async calls, verified 16/16 pass, and pushed both the cherry-pick and the fix to this branch.
- Focused tests: command-code-executor.test.ts (16/16) + provider-validation-specialty.test.ts — 140/140 combined, part of sub-batch's full run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for tracing the Go-plan 403 to the v3.8.50 endpoint migration and building a clean fallback that preserves the new Provider-tier path while restoring CLI compatibility for Go plan.
2026-08-25 21:15:24 -03:00
adevwithpurpose
ae22c157f4 fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards (#11464)
Validated in a combined sub-batch worktree off release/v3.8.51 tip. This PR's branch carried ~100 already-merged commits from a stale rebase (phantom-diff — every commit but the last matches history already on the release tip); cherry-picked only the genuine value commit (the flex-wrap fix itself) and pushed that to this branch.
- Focused tests: quota-card-expanded-footer-flex-wrap.test.ts, quota-card-expanded-sort-collapse.test.ts, quota-card-grid-mobile-7072.test.ts — part of sub-batch's 165/165 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for the clear root cause (extra View credits button + missing flex-wrap on the footer + overflow-hidden on the outer Card clipping the trailing Refresh now button).
2026-08-25 21:12:50 -03:00
Justin Hong
b84ae072ea fix(cli): write jcode settings as [providers.omniroute] in config.toml (#11484)
Validated in a combined sub-batch worktree off release/v3.8.51 tip. Its second commit reimplemented wsPath.ts's sanitizeLiveWsPort/resolveLiveWsUrl to fix a build break it hit — that exact break is already fixed on the current tip via #11502 (the original #11388 implementation restored verbatim). Conflicted against that; resolved by keeping the tip's established implementation and dropping the redundant reimplementation (both are functionally equivalent; the tip's carries the original #11331 doc comments and precedence contract). Pushed the same resolution to this branch.
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff
- Focused test: cli-settings-jcode.test.ts (integration) — part of sub-batch's 165/165 run

Thanks for the real end-to-end verification (production server, real ~/.jcode/config.toml, jcode --provider-profile omniroute serving a completion, jcode provider-doctor passing) — the managed-block approach (marker-delimited, preserves user-authored config, 409s instead of corrupting on a hand-written conflicting table) is exactly the right shape for this integration.
2026-08-25 21:11:50 -03:00
KaspaPulse
be6cbe7de5 fix(sessions): preserve legacy data when exclusive projection fails (#11469)
Validated in a combined sub-batch worktree off release/v3.8.51 tip.
- Focused test: exclusive-session-observability.test.ts — part of sub-batch's 165/165 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for the narrow failure boundary and the privacy-conscious warning (proven not to leak the caught error's sensitive fields) — a projection failure discarding valid legacy data was a real regression from #11389.
2026-08-25 21:08:29 -03:00
Syed Raheemuddin
721ee2a038 refactor(local-corpus): implement dynamic root resolution and LRU cache (#11491)
Validated in a combined sub-batch worktree off release/v3.8.51 tip. Also removed an unused variable (idx2) from the new test — pushed to this branch.
- Focused test: local-corpus-lru-cache.test.ts — 4/4 pass, part of sub-batch's 165/165 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff (after the idx2 fix)

Thanks for the LRU cache and path-traversal guard — preventing an unconfigured workspace path from indexing system root is a real safety improvement.
2026-08-25 21:08:05 -03:00
Nguyễn Viết Tuấn
892f359305 build(bun): allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback (#11471)
Validated in a combined 4-PR batch worktree off release/v3.8.51 tip (last of the Bun-native cluster; conflicted against the already-merged #11482's Dockerfile.bun hunk in the shared worktree — resolved by taking this PR's configurable ARG/ENV shape, which is exactly what it's designed to replace, and pushed the same resolution to this branch).
- Focused test: resolve-next-build-bundler-flag.test.mjs — 3/3 pass, part of batch's 5/5 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for validating with both node --test and bun test — good practice given the dual-runtime surface this touches.
2026-08-25 20:35:41 -03:00
Nguyễn Viết Tuấn
7b7196f05d fix(docker-bun): strip Node SQLite addon from runtime (#11482)
Validated in a combined 4-PR batch worktree off release/v3.8.51 tip (complements #11468 and #11470, both merged first). Conflicted against #11470's Dockerfile.bun hunk in the shared worktree — resolved additively (both the ownership hardening and the addon-stripping RUN block coexist, ordered so the strip runs as root before USER bun) and pushed the same resolution to this branch.
- Focused test: bun-runtime-native-addon.test.mjs — 1/1 pass, part of batch's 5/5 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for the isolated-probe root-causing (better-sqlite3 specifically, ruling out keytar/onnxruntime-node/sqlite-vec/tls-client-node/wreq-js/sharp) plus the production ARM64 evidence.
2026-08-25 20:34:05 -03:00
Nguyễn Viết Tuấn
a8f9024c20 fix(docker-bun): make Bun image install and SQLite startup reliable (#11470)
Validated in a combined 4-PR batch worktree off release/v3.8.51 tip (Bun-native SQLite infrastructure cluster; complements #11468 already merged).
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK (Docker build itself not executed in this environment; reviewed the Dockerfile.bun diff for correctness)
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for the two concrete failure modes fixed (missing postinstall.mjs during layer-cached bun install, ARM64 NAPI crash avoided by using the native bun:sqlite smoke check instead of rebuilding better-sqlite3) plus the non-root USER bun hardening.
2026-08-25 20:31:57 -03:00
Nguyễn Viết Tuấn
9ad90fe7a3 fix(bun): use native bun:sqlite during startup to avoid N-API crash (#11468)
Validated in a combined 4-PR batch worktree off release/v3.8.51 tip (Bun-native SQLite infrastructure cluster from the same contributor).
- Focused test: bunSqliteAdapter.test.ts — part of batch's 5/5 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for tracing this to the pre-boot better-sqlite3 require — a native NAPI abort that JS try/catch cannot recover from is exactly the kind of failure mode that needs the guard moved earlier, and aligning bootstrap/sync-env with the already-preferred bun:sqlite driver is the right fix.
2026-08-25 20:31:34 -03:00
Markus Hartung
3f6a881b6c fix(logging): raise chat-log array truncation cap to 1000 and unify duplicate implementations (#11499)
Validated in a combined 2-PR batch worktree off release/v3.8.51 tip (companion fix to #11473, merged first).
- Focused tests: chat-log-array-tail-items-default, chatcore-log-truncation, request-logger-bounded-clone, request-logger-bounded-idempotence, repro-7847-bound-client-raw-request — part of batch's 46/46 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for unifying the two independently-drifted truncation caps onto one configurable source — measured evidence that the storage ceiling comes from retention days, not the per-item cap, makes the 128→1000 raise a clear correctness improvement.
2026-08-25 20:11:56 -03:00
Markus Hartung
fff62f10a7 fix(responses-continuation): fail closed on a log-truncated stored input/output array (#11473)
Validated in a combined 2-PR batch worktree off release/v3.8.51 tip.
- Focused test: responses-continuation-store.test.ts — part of batch's 46/46 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for the live-verified root cause — a truncation sentinel getting forwarded upstream as a real Responses-API item, breaking the turn with a genuine 400, is exactly the kind of defect that's easy to miss without production traffic to reproduce against.
2026-08-25 20:11:46 -03:00
Dizzle
d3c395bbcf fix(free-tier): serve the Radar overlay from /api/free-tier/summary (#11550)
Validated in a combined 4-PR batch worktree off release/v3.8.51 tip. Also removed an unused `crypto` import from the new test file (one-line lint fix, 228→229 unrelated-drift comparison confirmed it was the only new finding) — pushed to this branch.
- Focused tests: free-tier-summary-radar-overlay.test.ts (9/9) + free-model-catalog/free-catalog-2026-07-expansion/free-providers-batch-2026-07 — part of batch's 60/60 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity, check:docs-counts-sync — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff (after the crypto-import fix)

Thanks for this — the community/live feed entitlement distinction (never re-publishing paid feed content to anonymous callers) mirrors #9686's treatment carefully, and the catalogUpdatedAt honesty (null over a fabricated download-time stand-in) is the right call.
2026-08-25 19:49:00 -03:00
Dizzle
3863dab149 fix(chat): stop forwarding redundant provider-node routing segments to model lookup (#11557)
Validated in a combined 4-PR batch worktree off release/v3.8.51 tip.
- Focused test: model-connid-prefix-normalization-6772.test.ts — part of batch's 60/60 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity, check:docs-counts-sync — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for closing this with production-log-shaped test cases — a deterministic upstream 404 loop from a duplicated routing segment is exactly the kind of defect that's easy to miss without real traffic shapes in the test suite.
2026-08-25 19:48:49 -03:00
Dizzle
ffd8984a59 docs(routing): LKGP sticks at provider level — align copy with routerStrategy contract (#11555)
Validated in a combined 4-PR batch worktree off release/v3.8.51 tip. Also removed a leftover changelog placeholder duplicate (0000-lkgp-doc-provider-stickiness.md, byte-identical content to the correctly-numbered 11555- fragment minus the PR link) — pushed to this branch.
- Focused test: router-strategies.test.ts — part of batch's 60/60 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity, check:docs-counts-sync — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for the docs-accuracy follow-through — aligning the LKGP copy with what routerStrategy.ts actually does (provider-level, not target/model) closes a real gap between prose and behavior.
2026-08-25 19:48:40 -03:00
Dizzle
6787fc9aa9 docs: real PR links for merged changelog fragments; fix stale quality comment (#11553)
Validated in a combined 4-PR batch worktree off release/v3.8.51 tip.
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity, check:docs-counts-sync — all OK
- Comment-only + changelog metadata fix, no runtime surface

Thanks for closing the placeholder-link loop before the release aggregator baked them into CHANGELOG.md — good timing call in the PR notes.
2026-08-25 19:48:29 -03:00
Bob.Hou
64a28b559c fix(combo): enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) (#11411)
Validated in a combined 5-PR batch worktree off release/v3.8.51 tip.
- Focused test: universal-quota-aware-routing.test.ts — part of batch's 115/115 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for closing the gap where reset-aware/reset-window/headroom strategies were silently degenerating to round-robin for generic providers because the fetcher registry was never populated at startup — a subtle but real routing-quality regression.
2026-08-25 19:25:57 -03:00
Bob.Hou
98cec759ae fix(oauth): allow expired token retry before deactivation and activate expired retry loop (#P0) (#11414)
Validated in a combined 5-PR batch worktree off release/v3.8.51 tip.
- Focused tests: token-health-check-retry-deactivation.test.ts + token-health-check-kimi.test.ts + cursor-renewal.test.ts — part of batch's 115/115 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for restoring the retry-budget gate before deactivation — premature deactivation on a single transient refresh failure is a real P0-class defect for OAuth-connected accounts.
2026-08-25 19:25:48 -03:00
Bob.Hou
62b3f9d36d perf(core): eliminate async waterfalls across home, cache, and db operations (#11396) (#11512)
Validated in a combined 5-PR batch worktree off release/v3.8.51 tip.
- Focused test: perf-waterfall-elimination.test.ts — part of batch's 115/115 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for parallelizing these independent DB/settings reads — straightforward, low-risk perf hygiene with parity tests.
2026-08-25 19:25:38 -03:00
Bob.Hou
891c1af0ea fix(providers): remove duplicate 128-tool truncation in OpencodeExecutor (#11444) (#11518)
Validated in a combined 5-PR batch worktree off release/v3.8.51 tip (opencode.ts overlapped with already-merged #11409 — git auto-merged cleanly, no manual resolution needed).
- Focused test: opencode-tools-no-truncation.test.ts — part of batch's 115/115 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for catching the leftover duplicate truncation from #6193 — a silent 128-tool cap breaking subagent execution on oc/* models is a nasty one to trace back.
2026-08-25 19:25:28 -03:00
Bob.Hou
1ad06d2059 fix(translator): preserve exact tool name casing for Claude tool_use payloads (#11487) (#11520)
Validated in a combined 5-PR batch worktree off release/v3.8.51 tip.
- Focused test: claude-tool-name-casing.test.ts — part of batch's 115/115 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for tracing this to the REVERSE_MAP fallback — a client-dispatch-breaking casing bug is exactly the kind of thing that's easy to miss without a targeted test.
2026-08-25 19:25:18 -03:00
Webman
0023a9ec04 fix: make release/v3.8.50 CI gates green (run 32786966560 root causes) (#11450)
Validated in a combined 3-PR batch worktree off release/v3.8.51 tip. This PR conflicted against today's accumulated merges (mostly pure provider-count drift: 353 vs its 352 snapshot across 51 docs/i18n/SVG files — resolved to the release's current 353, confirmed byte-identical besides the count on diff). Two real code conflicts:
- src/lib/usage/providerLimits.ts: this PR's `syntheticCooldownOutlivedByRealWindows()` is genuinely new (didn't exist on the tip; a caller already referencing it elsewhere in the file confirmed it was required) — kept in full.
- tests/unit/providers-constants-split.test.ts: both sides' running-count comments land at the same 233 via different additions (this PR's volcengine-agent/coding-plan vs the v3.8.50 back-merge's Synthetic + Kilo Gateway, both already present in providers.ts) — combined as sequential history, no functional change.

Resolution pushed to the PR branch and re-validated:
- Focused tests: 8134-github-t5-fallback-filter, cc-compatible-provider, cli-oneproxy-commands, hard-session-lease-bypass-inventory, llm-selector-custom-vision-models, model-capabilities-registry, openapi-coverage, provider-limits-recovery, providers-constants-split, repro-glm-iso-reset-24h-cap, startup-stale-cooldown-recovery, memory-pipeline, security-hardening, skills-pipeline — part of batch's 165/165 node:test run; glmCodingProviderConfig.test.ts (vitest) 10/10
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity, check:docs-counts-sync — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for this — root-causing all 18 failed jobs from a single CI run with gate-by-gate evidence (including the harder-to-spot ones like the antigravity BYOP legacy-ack misread and the reserved-alias `cc` guard) is exactly the kind of base-red drain this release needs.
2026-08-25 18:55:18 -03:00
Webman
9862f129e2 fix(electron): make the packaged Windows build pass the #7592 cold-restart smoke (#11443)
Validated in a combined 3-PR batch worktree off release/v3.8.51 tip.
- Focused test: electron-smoke-script.test.ts — part of batch's 165/165 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity, check:docs-counts-sync — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Real hardware validation on a fresh packaged Windows build (Hard Rule #18) surfacing and fixing 5 genuine defects (GNU-tar drive-letter parsing, missing asar file, electron-builder's node_modules extraResources drop, USERPROFILE-derived userData path, unreachable driver-log assertion) is exactly the kind of investigation this repo needs more of. Thank you.
2026-08-25 18:54:20 -03:00
Webman
700819a29b fix(ci): boot protocol E2E on the peer-stamped custom server with preserved open bootstrap (#11535) (#11549)
Validated in a combined 3-PR batch worktree off release/v3.8.51 tip.
- Focused test: protocol-e2e-server-stamping-11535.test.ts — part of batch's 165/165 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity, check:docs-counts-sync — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for the meticulous root-causing here — three distinct issues (server flavor, HOST pin, open-bootstrap env leak) traced to exact line numbers with live-boot before/after evidence.
2026-08-25 18:54:09 -03:00
Paijo
7cfabfc5c9 perf(executors): lazy-load the executor registry — defer class imports + construction to first use (#11220) (#11421)
Validated in a combined 3-PR batch worktree off release/v3.8.51 tip (a sibling PR from the same author, #11495, was held out — a typecheck error in zai-web.ts only reproduced with this PR + #11495 boarded together, and cleared without #11495; isolated this PR alone confirmed clean on its own too, so the interaction belonged to #11495's side — see its comment).
- Golden lock: executor-map-golden.test.ts — passes byte-identical (same keys, classes, provider identities, dispatch guards)
- Focused tests part of batch's 94/94 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for the measured, careful methodology here — the golden-lock contract plus the isolated DATA_DIR benchmarking make this an easy PR to trust despite the wide surface (72 files).
2026-08-25 18:22:46 -03:00
Paijo
026d26edb7 feat(providers): derive + surface expiry for JWT-bearing web cookies (#11497) (#11505)
Validated in a combined 3-PR batch worktree off release/v3.8.51 tip (a sibling PR from the same author, #11495, was held out — see its own comment for the isolated finding, unrelated to this diff).
- Focused test: web-cookie-expiry.test.ts — part of batch's 94/94 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for closing a real trust gap — operators deserve to know a cookie is about to expire before a live request fails.
2026-08-25 18:22:34 -03:00
Paijo
8a8d0cf336 fix(providers): stop silently dropping reasoning effort on opencode families (#10788) (#11409)
Validated in a combined 3-PR batch worktree off release/v3.8.51 tip (a sibling PR from the same author, #11495, was held out after an interaction-only typecheck error was isolated to it — reproduced clean without it, see #11495's own comment).
- Focused tests: opencode-go-effort-aliases-6922.test.ts, opencode-go-effort-aliases-8353.test.ts, chatcore-upstream-body.test.ts — part of batch's 94/94 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Thanks for tracing this all the way to the wire format — forwarding the aliased id verbatim instead of injecting a field the non-DeepSeek families never had is exactly the right fix.
2026-08-25 18:22:23 -03:00
Dizzle
ee4e37154d feat(rankings): show what each provider actually served (#11546)
Validated in a combined 3-PR batch worktree off release/v3.8.51 tip.
- Focused tests, run with the correct vitest config (tests/unit/ui/*.tsx needs `--config vitest.config.ts`, not node:test — my invocation error, not the PR's): free-provider-rankings-page-usage.test.tsx + free-provider-rankings-page-authtype-6915.test.tsx — 9/9 pass; freeProviderRankings-usage-display.test.ts — pass
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK
- Full-repo lint: one flagged line in this file (91:5, react-hooks/set-state-in-effect on the mount-time fetchRankings() call) confirmed pre-existing and untouched by this diff — this PR's changes are confined to the fetch body's URL params and the new table column

Thanks for closing a real trust gap — an ELO-only ranking calling a 100%-error provider "healthy" is exactly the kind of thing a reliability column should catch, and the no-data-vs-0% distinction is the right call.
2026-08-25 16:31:11 -03:00
Dizzle
60eb0806df chore(free-models): derive the free-tier regime sets from the regime table (#11537)
Validated in a combined 3-PR batch worktree off release/v3.8.51 tip.
- Focused tests: free-regime-traits-derived-sets.test.ts, free-model-catalog.test.ts — pass; vitest autoCombo suite 108/111 (3 pre-existing timing-flaky failures unrelated to this diff, reproduced identically on the pure release/v3.8.51 tip in an isolated probe — auto/glm and Cerebras-rotation timeouts, none of the files this PR touches)
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK

Thanks for collapsing the four duplicated regime answers into one table-driven source — the `satisfies Record<...>` trick turning a missing answer into a compile error is a nice touch.
2026-08-25 16:30:59 -03:00
Dizzle
a63b3e21b9 fix(docs): Auto-Combo factor count is read from DEFAULT_WEIGHTS (#11545)
Validated in a combined 3-PR batch worktree off release/v3.8.51 tip. The prerequisite sibling (159→160 migrations) was already covered by #11502 — closed the redundant #11543 separately.
- New check:docs-counts gate: proven fail-before/pass-after per the PR's own methodology, re-verified here (PASS, 2 unrelated soft-drift notices on cloud-agent/A2A doc counts, non-blocking)
- Focused tests: check-docs-counts-sync.test.ts, combo-matrix/auto.test.ts, lkgp-enabled-context-11181.test.ts — pass
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK

Thanks for replacing seventeen hand-maintained factor-count claims with one source-derived gate.
2026-08-25 16:30:49 -03:00
MumuTW
ae7a843e86 fix(autoCombo,sse): catalog hygiene — retire dead FITNESS_TABLE rows, fix 7 BUILT_IN_ALIASES targets, add model-lifecycle gate (#11503) (#11507)
Validated in a combined 4-PR batch worktree off release/v3.8.51 tip. This PR's diff overlapped taskFitness.ts and autoCombo.test.ts with the already-merged #11492/#11506 — git's merge auto-resolved both hunks cleanly (non-overlapping layers: #11492/#11506 touch layer 2 arena lookup, this PR touches layer 4 static-table hygiene); verified no conflict markers remained and re-ran the full suite after boarding.
- npm run check:model-lifecycle — PASS, 68 retired ids, 1327 catalog ids, 0 violations (re-ran with the correct `node --import tsx/esm` loader after an initial bare-node invocation mistakenly failed on path-alias resolution — that was my invocation error, not the gate)
- Focused tests: fitness-table-hygiene-11503.test.ts, taskFitness-pattern-order-8603.test.ts, model-deprecation-aliases-11503.test.ts, check-model-lifecycle-gate.test.ts, model-deprecation.test.ts, autoCombo.test.ts — part of batch's 126/126 vitest + 246/246 node:test runs
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity, check:cycles — all OK
- Full-repo lint: 228 problems remaining, all pre-existing dashboard react-hooks/* findings unrelated to this diff (zero errors in any file this PR touches)

Thanks for this — genuinely thorough methodology (segment-boundary matching, provider-scoped alias guard, offline lifecycle gate with a documented burn-down list for the 6 remaining catalog offenders).
2026-08-25 13:52:23 -03:00
MumuTW
a15af27c78 fix(intelligence): synthesize base-model arena rows from effort/harness variants; drop MODEL_ALIAS_MAP (#11504) (#11506)
Validated in a combined 4-PR batch worktree off release/v3.8.51 tip (stacked on #11492, merged first).
- TDD-first: the new base-model-synthesis describe block failed 5/5 pre-fix, passes now
- Focused test: tests/unit/arena-elo-sync.test.ts — 56/56 pass, part of batch's 246/246 node:test + 126/126 vitest runs
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity, check:cycles — all OK

Thanks for closing the arena-lookup gap for harness/effort-annotated leaderboard rows and retiring MODEL_ALIAS_MAP's cross-generation score copying in favor of scoresAs + registry-owned aliases.
2026-08-25 13:51:56 -03:00
MumuTW
78d10da84c fix(autoCombo): inherit task fitness from base model for effort/alias variants (#11489) (#11492)
Validated in a combined 4-PR batch worktree off release/v3.8.51 tip.
- Focused tests: scoresAs-11489.test.ts + task-fitness-scores-as-11489.test.ts + autoCombo.test.ts — pass as part of batch's 126/126 vitest + 246/246 node:test runs
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity, check:cycles — all OK

Thanks for closing the gap where effort/alias variants fell back to the wildcard score instead of inheriting their base model's task fitness.
2026-08-25 13:51:27 -03:00
MumuTW
17e4ddfc77 chore(release): clear release/v3.8.51 base-red gates — docs count, stryker list, lockfile host, stale suppressions, 7 lint regressions (#11502)
Validated in a combined 4-PR batch worktree off release/v3.8.51 tip.
- Every fix individually confirmed against the pristine tip, no runtime behavior change
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity, check:cycles — all OK
- Full-repo lint: 503 → 228 problems, confirming this PR's diagnosis of the exit-2 stale-suppressions + orphaned-code causes; the remaining 228 are pre-existing dashboard react-hooks/* findings this PR never claimed to touch
- node --test tests/unit/combo-routing-engine.test.ts, providers-constants-split.test.ts, and the providerLimits/videoBridge importers — all pass as part of the batch's 246/246 node:test run

Thanks for the meticulous base-red triage — this directly explains and fixes the largest lint-drift finding from the prior merge-batch session.
2026-08-25 13:51:18 -03:00
Paco Cartones
28601b456e fix(relay): strip unsafe forwarded headers (#11533)
Validated in a combined 10-PR batch worktree off release/v3.8.51 tip.
- Focused test: tests/unit/generated-relay-header-denylist.test.ts — 3/3 contract pass plus 30/30 related SSRF tests
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity gates — all OK
- Full-repo lint: 503 pre-existing problems confirmed identical on the pure release/v3.8.51 tip — unrelated to this diff

⚠️ base-red inherited: #11449

Thanks for applying one canonical header denylist across the generated Cloudflare/Vercel/Deno relays so hop-by-hop, framing, and proxy-auth/relay-control headers stop leaking upstream.
2026-08-25 13:12:50 -03:00
Paco Cartones
436ce4869f fix(tailscale): validate ports and quote MSI paths (#11534)
Validated in a combined 10-PR batch worktree off release/v3.8.51 tip.
- Focused test: tests/unit/tailscale-validation.test.ts — 2/2 pass plus 13/13 related Tailscale tests
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity gates — all OK
- Full-repo lint: 503 pre-existing problems confirmed identical on the pure release/v3.8.51 tip — unrelated to this diff

⚠️ base-red inherited: #11449

Thanks for rejecting out-of-range Tailscale ports and escaping apostrophes before they reach the Windows MSI installer PowerShell command.
2026-08-25 13:12:38 -03:00
Paco Cartones
e48ffd38d3 fix(authz): restrict tunnel process routes locally (#11531)
Validated in a combined 10-PR batch worktree off release/v3.8.51 tip.
- Focused tests: tests/unit/authz/route-guard-tunnel-processes-local-only.test.ts + tests/unit/authz/spawn-capable-prefixes-client-safe.test.ts — 6+ pass
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity gates — all OK
- Full-repo lint: 503 pre-existing problems confirmed identical on the pure release/v3.8.51 tip — unrelated to this diff

⚠️ base-red inherited: #11449

Thanks for closing the process-spawning tunnel routes to local-only (Hard Rule #15/#17 territory) while preserving authenticated remote read access to status endpoints.
2026-08-25 13:12:34 -03:00
Paco Cartones
30026b2e96 fix(idempotency): fingerprint Responses request semantics (#11532)
Validated in a combined 10-PR batch worktree off release/v3.8.51 tip.
- Focused test: tests/unit/idempotency-fusion-collision.test.ts — 8/8 pass
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity gates — all OK
- Full-repo lint: 503 pre-existing problems confirmed identical on the pure release/v3.8.51 tip — unrelated to this diff

⚠️ base-red inherited: #11449

Thanks for closing the idempotency-collision gap between unrelated Responses requests.
2026-08-25 13:12:18 -03:00
Paco Cartones
3804ffb6ff fix(router): preserve selected connection identity (#11530)
Validated in a combined 10-PR batch worktree off release/v3.8.51 tip.
- Focused test: tests/unit/router-strategies.test.ts — 22/22 pass
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity gates — all OK
- Full-repo lint: 503 pre-existing problems confirmed identical on the pure release/v3.8.51 tip — unrelated to this diff

⚠️ base-red inherited: #11449

Thanks for carrying the winning connectionId through cost/latency/SLA/LKGP so duplicate accounts dispatch through the connection actually ranked.
2026-08-25 13:12:15 -03:00
Paco Cartones
eb9b4cec54 fix(ci): skip stale release Docker publishes (#11527)
Validated in a combined 10-PR batch worktree off release/v3.8.51 tip. Fixes #11523.
- Focused test: tests/unit/build/docker-next-channel-8576.test.ts — 7/7 pass
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity gates — all OK
- Full-repo lint: 503 pre-existing problems confirmed identical on the pure release/v3.8.51 tip — unrelated to this diff

⚠️ base-red inherited: #11449

Thanks for treating Docker publishes from non-default frozen release branches as an expected no-op.
2026-08-25 13:12:02 -03:00
Paco Cartones
57461af5cf fix(duckduckgo): preserve streamed chunk boundaries (#11528)
Validated in a combined 10-PR batch worktree off release/v3.8.51 tip.
- Focused test: tests/unit/duckduckgo-stream-chunks.test.ts — 2/2 pass
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity gates — all OK
- Full-repo lint: 503 pre-existing problems confirmed identical on the pure release/v3.8.51 tip — unrelated to this diff

⚠️ base-red inherited: #11449

Thanks for keeping the streaming decoder/line-buffer intact across DuckDuckGo transport chunk boundaries.
2026-08-25 13:11:58 -03:00
Paco Cartones
1a8c6d13a1 fix(responses): preserve json_object response format (#11529)
Validated in a combined 10-PR batch worktree off release/v3.8.51 tip.
- Focused test: tests/unit/translator-openai-responses-req.test.ts — 58/58 pass
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity gates — all OK
- Full-repo lint: 503 pre-existing problems confirmed identical on the pure release/v3.8.51 tip — unrelated to this diff

⚠️ base-red inherited: #11449

Thanks for restoring json_object symmetry between the Chat Completions and Responses translators.
2026-08-25 13:11:45 -03:00
Paco Cartones
987b67420a fix(kimi-web): align health probe with executor domain (#11521)
Validated in a combined 10-PR batch worktree off release/v3.8.51 tip. Fixes #11515.
- Focused test: tests/unit/kimi-web-validation-11515.test.ts — 37/37 pass; live unauthenticated probe confirms www.kimi.ai/api/user returns the expected 401 boundary
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity gates — all OK
- Full-repo lint: 503 pre-existing problems confirmed identical on the pure release/v3.8.51 tip — unrelated to this diff

⚠️ base-red inherited: #11449

Thanks for aligning the health probe with the executor's actual domain plus the live verification.
2026-08-25 13:11:41 -03:00
Paco Cartones
a8dbf7bbb8 fix(cli): preserve existing admin password during setup (#11522)
Validated in a combined 10-PR batch worktree off release/v3.8.51 tip. Fixes #11494.
- Focused test: tests/unit/cli-setup-command.test.ts — 10/10 pass
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity gates — all OK
- Full-repo lint: 503 pre-existing problems confirmed identical on the pure release/v3.8.51 tip (isolated probe) — unrelated to this diff (bin/cli only)

⚠️ base-red inherited: #11449

Thanks for preventing INITIAL_PASSWORD from silently overwriting an operator's already-set admin password.
2026-08-25 13:11:28 -03:00
Diego Rodrigues de Sa e Souza
a179ffed5b feat(resilience): add hierarchical concurrency admission (#11493)
Cherry-pick of the 3 value commits from #11323 (by @RaviTharuma), dropping 17 already-merged rebase-baggage commits. Validated in a combined-batch worktree off release/v3.8.51 tip:
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity gates — all OK
- Focused tests, isolated re-run flake-free: accountSemaphore.test.ts 11/11, chatcore-hierarchical-admission.test.ts 2/2, resilience-settings-provider-quota-overrides.test.ts 12/12, i18n-vi-completeness.test.ts 5/5
- lint clean once isolated from the unrelated ESLint-10 suppressions regression carried by a separate PR in the batch

Closes #7778. Full credit to @RaviTharuma for the design and implementation.
2026-08-25 07:19:09 -03:00
Nguyen Thanh Dat
23fa2709a9 fix(db): keep tokenExpiresAt when a connection is created (#11368)
Validated in a combined-batch worktree off release/v3.8.51 tip alongside #11259 and the cherry-picked #11323 successor (#11493):
- Focused test: tests/unit/oauth-connection-tokenexpiresat-5326.test.ts — 3/3 pass, exercises the round-trip through createProviderConnection/getProviderConnections
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity gates — all OK
- One-string allowlist fix, TDD-proven (fails on base with tokenExpiresAt: null, passes with the change), mutation-checked

Thanks for restoring #5326's fix end to end — clean, minimal, well-tested.
2026-08-25 07:18:58 -03:00
Paijo
6435f618f4 fix(quota-share): release the winner's reserved in-flight slot (#11371) (#11408)
Merged into release/v3.8.51. Batch review caught that the slot-release replaced the #5923 recordComboFailure call on the no-executable-targets path (pin auto-clear would freeze); restored both effects side by side + regression guard in combo-routing-engine.test.ts (proven RED without the fix, GREEN after — 211/211 across the focused combo/quota/opencode battery, quota-share-strategy 31/31 on the final branch head). Thanks @oyi77!
2026-08-25 04:14:10 -03:00
Praveen K Palaniswamy
b39e5ecb20 feat(routing): subscription-first auto groupings (auto/subscription, auto/thrifty) [defer to 3.8.51] (#11146)
Merged into release/v3.8.51 via batch validation: subscription-ladder + free-regime vitest suites green (32/32) on the combined tree, check:provider-consistency OK (353 canonical providers), static gates green (virtualFactory.ts frozen at merge size with dated rebaseline). Also pushed a docs commit marking rungBudgetUsd as not-yet-enforced per review, and synced the branch onto the updated release tip. Strong opt-in design failing closed where money is involved — thanks @yourspraveen!
2026-08-25 01:44:24 -03:00
Burak Bayır
a0ceccc6f0 feat(search): add Xquik X search provider (#11370)
Merged into release/v3.8.51 via batch validation: xquik provider suites green on the combined tree (193 node:test assertions incl. your 7 new cases), check:provider-consistency OK (353 canonical providers), static gates green. Well-scoped fallbackOnly X-provider with clean citation building — thanks @kriptoburak!
2026-08-25 01:39:39 -03:00
Tuan Dinh
14ca809924 fix(providers): send Responses API payload during OpenAI-compatible validation (#11454)
Merged into release/v3.8.51 via batch validation: provider-validation-specialty suite green on the combined tree (193 node:test assertions across the batch's focused files), static gates green. Right fix for the Responses-API probe body (#11453) — thanks @tuandinh0801!
2026-08-25 01:39:19 -03:00
KaspaPulse
c8ca024e29 feat(dashboard): surface exclusive managed leases in Sessions view (#11389)
Merged into release/v3.8.51 via batch validation: exclusive-session-observability unit+UI suites green on the combined tree, static gates green. Nice additive observability layer over the #10362 lease backend — thanks @KaspaPulse!
2026-08-25 01:38:59 -03:00
Aman
613fc71e98 feat(providers): publish web-session credential contract (#11340)
Merged into release/v3.8.51 via batch validation: node:test web-session-contract suite green on the combined tree, static gates green (file-size/complexity/cognitive/changelog/typecheck). Clean credential-contract surface — thanks @Zartharas!
2026-08-25 01:38:39 -03:00
Dizzle
c472dbccc4 fix(free-models): stop reporting discontinued Pollinations models as free (#11441)
Merged into release/v3.8.51 via batch validation (combined tree): vitest autoCombo suite 32/32 incl. free-regime-not-read-by-predicate, static gates green (file-size / complexity / cognitive / changelog-integrity / typecheck:core). Compiler-checked FREE_REGIME_TRAITS table eliminating the discontinued-as-free routing bug — excellent work, thanks @maxmad64bis!
2026-08-25 01:37:45 -03:00
Diego Rodrigues de Sa e Souza
d82b68274c fix(sse): exact-domain cookie match and origin-equality URL assertions (#11452)
Clears CodeQL js/incomplete-url-substring-sanitization alerts #860-#865:

- volcengineConsoleAutoLogin: cookie domain filter now uses an exact/
  dot-suffix helper (isVolcengineCookieDomain) instead of substring
  includes(), rejecting look-alike hosts like volcengine.com.evil.test
- security-s1-s2-s4 tests: agent-card/agent.json URL assertions compare
  parsed origin equality instead of startsWith prefix

Co-authored-by: Markus Hartung <mail@hartmark.se>
2026-08-24 23:46:45 -03:00
Dizzle
943b9aaa84 fix(cli): flag a .env that lives inside the installed package (#11437)
Retargetado para release/v3.8.51 (release/v3.8.50 está congelada — freeze issue #11439). Resolvido o mesmo conflito não-relacionado em src/shared/utils/wsPath.ts (mesma causa do #11436 — refactor já mergeado na branch depois do fork deste PR; o diff real deste PR — bin/cli/utils/volatileEnvPath.mjs + bin/omniroute.mjs — ficou intacto) e revalidado: typecheck:core limpo, 12/12 testes focados passando.

Companion do #11436, decisão pura testável isoladamente, sem mudança de comportamento fora do caso volátil. Obrigado pela contribuição!
2026-08-24 20:10:08 -03:00
Dizzle
6e8fc94732 fix(cli): stop pre-filling the secrets the server owns (#11436)
Retargetado para release/v3.8.51 (release/v3.8.50 está congelada — freeze issue #11439). Resolvido um conflito de merge não-relacionado em src/shared/utils/wsPath.ts (originado de um refactor já mergeado nessa branch depois do fork deste PR; o diff real deste PR — scripts/dev/sync-env.mjs + tests/unit/sync-env.test.ts — ficou intacto) e revalidado: typecheck:core limpo, 13/13 testes focados passando.

Segue o precedente correto do #1622 (STORAGE_ENCRYPTION_KEY) para os dois secrets restantes que a postinstall preenchia por engano, defeituando o mecanismo de ensureSecrets(). Obrigado pela contribuição!
2026-08-24 20:04:11 -03:00
Xiangzhe
66ecc09050 chore(release): restore the living [3.8.51] CHANGELOG section
The cycle-open commit's root CHANGELOG hunk was lost in the rebase onto the
branch tip (the 42 i18n mirrors kept theirs, so the root and the mirrors had
drifted apart). Re-inserts the section with the three canonical headings.
2026-08-24 20:00:10 -03:00
Xiangzhe
65a1808f84 chore(release): open v3.8.51 development cycle
Completes the 0a.0b cut for the parallel-cycle model: the branch already existed
(cut from the v3.8.50 tip) but had never been bumped. Bumps package.json x3,
openapi.yaml and the lockfile, adds the living [3.8.51] CHANGELOG section with
the three canonical headings so aggregate-changelog.mjs cannot mis-target an
older published section, and syncs the 42 i18n mirrors.
2026-08-24 19:59:24 -03:00
Dizzle
a166752138 fix(radar): keep the feed's build date in the catalog cache (#11435)
Retargetado para release/v3.8.51 (release/v3.8.50 está congelada — freeze issue #11439). Validado em lote combinado (batch-0824h2) contra o tip de release/v3.8.51: typecheck:core limpo, gates estáticos + migration-numbering OK, 127/127 testes focados passando (8/8 do PR entre migration-163 e radar-feed-cache-generated-at).

Migração limpa (ADD COLUMN nullable, sem backfill necessário), aditiva na API, mantém "unknown" honesto para linhas antigas. Obrigado pela contribuição!
2026-08-24 19:57:24 -03:00
Markus Hartung
04dba0460e fix(responses-continuation): recover a real id/output for passthrough and translate-mode replies (#11434)
Retargetado para release/v3.8.51 (release/v3.8.50 está congelada — freeze issue #11439). Validado em lote combinado (batch-0824h2, junto de #11435/#11436/#11437) contra o tip de release/v3.8.51: typecheck:core limpo, gates estáticos OK, 127/127 testes focados passando.

Investigação sólida com repro real via container isolado, três causas independentes identificadas e corrigidas com testes de regressão dedicados para cada uma. Obrigado pela contribuição!
2026-08-24 19:57:12 -03:00
3296 changed files with 104575 additions and 48530 deletions

View File

@@ -45,6 +45,16 @@ INITIAL_PASSWORD=CHANGEME
# executor's on-disk thread-sticky session cache. Leave unset to rely on DATA_DIR.
# OMNIROUTE_DATA_DIR=/var/lib/omniroute
# Directory the runtime plugin scanner reads, overriding the home-derived default (#11827).
# Used by: src/lib/plugins/scanner.ts — getDefaultPluginDir(); it is also the root the
# plugin manager installs into. Set it in Docker/K8s to point straight at the bind-mounted
# plugin tree, instead of moving HOME (which changes every other HOME-relative behaviour)
# just to relocate the scan path. Unset = <HOME>/.omniroute/plugins, and
# /tmp/.omniroute/plugins when the process exports no home at all.
# Distinct from the CLI-only variable in section 9 that points the omniroute-cmd-* command
# loader (bin/cli/plugins.mjs) at a package tree — this one drives the server-side scanner.
# OMNIROUTE_PLUGINS_DIR=/opt/omniroute/plugins
# Escape hatch for the test-context DATA_DIR guard (#10428). A test run that never
# chose a DATA_DIR is redirected to a throwaway temp dir so it cannot open the
# operator's real database. Set to 1 only for a deliberate run against the real
@@ -71,6 +81,11 @@ INITIAL_PASSWORD=CHANGEME
# Never set this for the running server. Used by: src/lib/buildPhase.ts, src/lib/db/core.ts
# OMNIROUTE_BUILDING=1
# Skip the optional native-dependency prebuild check for exotic vendored trees.
# This does not make a missing dependency buildable. Used by: scripts/check/check-native-deps.mjs
# Default: 0 | Set to 1 only when native dependencies are supplied out of band.
# OMNIROUTE_SKIP_NATIVE_DEP_CHECK=0
# Encryption key for SQLite database encryption at rest.
# Used by: src/lib/db/encryption.ts — encrypts the entire SQLite database.
# Generate: openssl rand -hex 32 | Leave empty to disable DB encryption.
@@ -403,8 +418,17 @@ ALLOW_API_KEY_REVEAL=false
# OMNIROUTE_CHAT_LARGE_BODY_BYTES=262144
# Actual-byte hard cap enforced during bounded ingestion. Default 52428800 (50 MB).
# OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES=52428800
# Maximum heavyweight requests simultaneously admitted in one process. Default 1.
# Legacy request-COUNT cap (#503-fanout). Now binds only when explicitly set here —
# left unset, heavyweight admission is gated by OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES below
# instead (an auto-derived byte budget), fixing coding-agent fan-out (multiple
# subagents/CLIs) collapsing to an effective concurrency of ~1 and 503ing.
# OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT=1
# Override for the auto-derived ingest byte budget (#503-fanout). Default: 25% of the
# process's effective memory ceiling (V8 heap limit, or the tighter cgroup/container
# limit) divided by an 8x transient-amplification factor, clamped between 8 MiB and
# 2 GiB; explicit overrides are clamped to the same safe range. Read
# chatAdmission.maxInflightBytes/budgetSource at /api/monitoring/health before overriding.
# OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES=134217728
# Heap-pressure shed ratio (heapUsed/heap_size_limit) for the structural admission gate
# (#10183, #10268): a second concurrent heavyweight request past OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT
# is only shed with a retryable 503 when the heap is ALSO under this much pressure — on a
@@ -603,11 +627,9 @@ CLOUD_URL=
# Default: http://localhost:20128
NEXT_PUBLIC_BASE_URL=http://localhost:20128
# Browser-facing OmniRoute origin for generated assets in API responses.
# Highest-priority public origin override; also used by non-dashboard public-origin validation.
# Used by: chatgpt-web image generation cache URLs (/v1/chatgpt-web/image/<id>).
# Set this when OpenWebUI or another relay reaches OmniRoute by an internal URL
# but the user's browser must fetch images from a LAN, tunnel, or public origin.
# Highest-priority OmniRoute public origin override, also used by non-dashboard
# public-origin validation. Set it when external clients reach OmniRoute through
# a stable LAN, tunnel, or public origin that differs from its internal URL.
# Do not include /v1; if included accidentally it will be normalized away.
# OMNIROUTE_PUBLIC_BASE_URL=http://192.168.0.15:20128
@@ -620,28 +642,6 @@ NEXT_PUBLIC_BASE_URL=http://localhost:20128
# Used by: open-sse/config/providerPluginManifestUrl.ts. Defaults to http.
# OMNIROUTE_PUBLIC_PROTOCOL=http
# Max wait time for an async chatgpt-web image to land via the celsius
# WebSocket, in milliseconds. Default 180000 (3 minutes). Increase during
# upstream queue-deep windows ("Lots of people are creating images right now").
# OMNIROUTE_CGPT_WEB_IMAGE_TIMEOUT_MS=180000
# Total in-memory byte budget for the chatgpt-web image cache (used to serve
# /v1/chatgpt-web/image/<id>), in megabytes. Default 256. Lower this if you
# run OmniRoute on a memory-constrained host; raise it if image generation
# is heavy and clients are racing the 30-minute TTL.
# OMNIROUTE_CGPT_WEB_IMAGE_CACHE_MAX_MB=256
# Overall wait budget for a chatgpt-web GPT-5.5 Pro background-poll handoff,
# in milliseconds. Default 1200000 (20 minutes). Pro reasoning runs are slow
# and complete out-of-band, so OmniRoute polls until the answer lands or this
# budget elapses. Raise it if Pro requests time out before finishing.
# OMNIROUTE_CGPT_WEB_PRO_TIMEOUT_MS=1200000
# Interval between chatgpt-web GPT-5.5 Pro background-poll attempts, in
# milliseconds. Default 4000 (4 seconds). Lower for snappier completion at the
# cost of more upstream polling; raise to reduce request volume.
# OMNIROUTE_CGPT_WEB_PRO_POLL_INTERVAL_MS=4000
# Public cloud URL — client-side mirror of CLOUD_URL.
NEXT_PUBLIC_CLOUD_URL=
@@ -673,21 +673,11 @@ NEXT_PUBLIC_CLOUD_URL=
# open-sse/services/usage.ts.
#OMNIROUTE_CROF_USAGE_URL=https://crof.ai/usage_api/
#OMNIROUTE_CODEWHISPERER_BASE_URL=https://codewhisperer.us-east-1.amazonaws.com
#OMNIROUTE_OPENCODE_QUOTA_URL=https://opencode.ai/zen/go/v1/quota
# OpenCode Go has no public quota API — this has no default and stays
# unset unless you explicitly opt in to a self-hosted/mirrored endpoint:
#OMNIROUTE_OPENCODE_GO_QUOTA_URL=
#OMNIROUTE_OPENCODE_GO_DASHBOARD_URL=https://opencode.ai/workspace
# Official OpenCode Go usage endpoint, authenticated with the connection API key.
# Override only for relays or test fixtures.
#OMNIROUTE_OPENCODE_QUOTA_URL=https://opencode.ai/zen/go/v1/usage
#OMNIROUTE_OLLAMA_CLOUD_USAGE_URL=https://ollama.com/settings
# OpenCode Go dashboard quota scraping. Prefer configuring these per connection
# in Dashboard → Providers → OpenCode Go. Env vars are useful for headless
# deployments or shared server defaults. The cookie is sensitive.
#OPENCODE_GO_WORKSPACE_ID=wrk_...
#OMNIROUTE_OPENCODE_GO_WORKSPACE_ID=wrk_...
#OPENCODE_GO_AUTH_COOKIE=auth=...
#OMNIROUTE_OPENCODE_GO_AUTH_COOKIE=auth=...
# OpenCode Go/Zen VPS egress (#5997): on a datacenter VPS, Cloudflare in front of
# opencode.ai/zen/go 403s chat requests that lack OpenCode CLI identity headers.
# When your clients don't already send them, set this to synthesize the CLI headers
@@ -825,6 +815,10 @@ NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=true
# Windsurf has no default binary — set this to enable binary detection for it.
# CLI_WINDSURF_BIN=windsurf
# CLI_AUGGIE_BIN=auggie
# CLI_5DIVE_BIN=5dive
# 5dive keeps root-owned auth profiles under a system state dir (its own STATE_DIR,
# default /var/lib/5dive); override here when it lives elsewhere.
# CLI_5DIVE_STATE_DIR=/var/lib/5dive
# AUGGIE_BIN=auggie
# ── ZCode (Z.ai GLM coding-plan CLI) local provider ──
@@ -916,6 +910,11 @@ NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=true
# web_fetch). Default: 60000. Used by: open-sse/mcp-server/fetchTimeout.ts
# OMNIROUTE_MCP_UPSTREAM_TIMEOUT_MS=60000
# Maximum number of local-corpus index instances cached in memory.
# Used by: src/lib/localCorpus/configured.ts — bounds the LRU cache of
# LocalCorpusIndex objects (one per indexed root directory). Default: 5.
# OMNIROUTE_CORPUS_CACHE_SIZE=5
# Model catalog sync interval in hours.
# Used by: src/shared/services/modelSyncScheduler.ts — periodic model refresh.
# Default: 24
@@ -979,6 +978,11 @@ PROVIDER_LIMITS_SYNC_SPACING_MS=1500
# Used by: src/lib/jobs/budgetResetJob.ts. Floor: 10000.
#OMNIROUTE_BUDGET_RESET_JOB_INTERVAL_MS=600000
# Cron expression for the call-log export job (destinations configured in the
# dashboard under Integrations > Log export). Default: hourly, on the hour.
# Used by: src/lib/jobs/logExportJob.ts. Timezone: UTC.
#OMNIROUTE_LOG_EXPORT_CRON=0 * * * *
# Emergency budget-exhaustion fallback (set false or 0 to disable the reroute to
# nvidia/openai/gpt-oss-120b when a request fails with a 402 budget error).
# Used by: open-sse/services/emergencyFallback.ts. Default: enabled.
@@ -1023,6 +1027,13 @@ PROVIDER_LIMITS_SYNC_SPACING_MS=1500
# to disable the check. Used by: src/lib/db/migrationRunner.ts. Default: 50.
#OMNIROUTE_MAX_PENDING_MIGRATIONS=50
# Working directory for the check:install-upgrade release gate. It builds two ~3 GB
# install trees plus a ~275 MB tarball, so it needs roughly 12 GB — more than the
# 12 GB RAM-backed tmpfs that /tmp is on the self-hosted runner, where it exhausted
# the tmpfs and npm silently truncated the package. Defaults to <repo>/.install-upgrade
# on real disk. Used by: scripts/check/check-install-upgrade.mjs. Default: <repo>/.install-upgrade.
#OMNIROUTE_INSTALL_UPGRADE_WORKDIR=/var/tmp/omniroute-install-upgrade
# Trust user-managed RTK project filter rules without strict signature checks.
# Used by: open-sse/services/compression/engines/rtk/filterLoader.ts. Default: 0.
#OMNIROUTE_RTK_TRUST_PROJECT_FILTERS=0
@@ -1242,17 +1253,6 @@ GITHUB_OAUTH_CLIENT_ID=Iv1.b507a08c87ecfe98
# VISION_BRIDGE_BASE_URL=
# VISION_BRIDGE_API_KEY=
# ── Raycast Pro (local auto-import) ──
# Raycast Pro AI is a reverse-engineered, unofficial API — local/personal use
# only (no OAuth client_id/secret; token is captured via macOS Auto-Import
# from the Keychain + local Raycast SQLite DB, or pasted manually). These
# vars are optional manual overrides used by open-sse/services/raycast.ts
# and the direct-probe benchmark script scripts/raycast/usage-benchmark.mjs.
# RAYCAST_BEARER_TOKEN=
# RAYCAST_DEVICE_ID=
# RAYCAST_AID=
# RAYCAST_SIG_SECRET=
# ─────────────────────────────────────────────────────────────────────────────
# ⚠️ GOOGLE OAUTH (Antigravity) & OTHER PROVIDERS — REMOTE SERVERS
# ─────────────────────────────────────────────────────────────────────────────
@@ -1469,28 +1469,25 @@ CURSOR_USER_AGENT="Cursor/3.4"
# FIRECRAWL_BASE_URL=https://api.firecrawl.dev
# FIRECRAWL_TIMEOUT_MS=30000 # Per-request timeout (default: 30000 = 30s)
# ── ChatGPT TLS sidecar (Firefox-fingerprinted client) ──
# Used by: open-sse/services/chatgptTlsClient.ts — wire-level timeout for
# the bogdanfinn/tls-client koffi binding and the JS-side grace window
# layered on top of it when the native library is wedged.
# ── ChatGPT browser transport (Firefox-fingerprinted client) ──
# Used by: open-sse/services/chatgptTlsClient.ts — native wreq-js request
# timeout and the JS-side hard-deadline grace layered on top of it.
# OMNIROUTE_CHATGPT_TLS_TIMEOUT_MS=60000
# OMNIROUTE_CHATGPT_TLS_GRACE_MS=10000
# Max wait for the FIRST streamed byte from the ChatGPT TLS sidecar before the
# request is aborted as a dead stream, in milliseconds. Default 30000 (30s).
# Raise it if upstream cold-starts routinely exceed the window.
# Max wait for the FIRST streamed byte before switching from direct streaming
# to a buffered response, in milliseconds. Default 30000 (30s). The request's
# hard deadline continues to apply while the buffered body is read.
# OMNIROUTE_CHATGPT_STREAM_FIRST_BYTE_TIMEOUT_MS=30000
# ── Claude TLS sidecar (Chromium-fingerprinted client) ──
# Used by: open-sse/services/claudeTlsClient.ts — wire-level timeout for
# the bogdanfinn/tls-client koffi binding and the JS-side grace window
# layered on top of it when the native library is wedged.
# ── Claude browser transport (Chromium-fingerprinted client) ──
# Used by: open-sse/services/claudeTlsClient.ts — native wreq-js request
# timeout and the JS-side hard-deadline grace layered on top of it.
# OMNIROUTE_CLAUDE_TLS_TIMEOUT_MS=60000
# OMNIROUTE_CLAUDE_TLS_GRACE_MS=10000
# ── Perplexity TLS sidecar (Firefox-fingerprinted client) ──
# Used by: open-sse/services/perplexityTlsClient.ts — wire-level timeout for
# the bogdanfinn/tls-client koffi binding and the JS-side grace window
# layered on top of it when the native library is wedged.
# ── Perplexity browser transport (Firefox-fingerprinted client) ──
# Used by: open-sse/services/perplexityTlsClient.ts — native wreq-js request
# timeout and the JS-side hard-deadline grace layered on top of it.
# OMNIROUTE_PPLX_TLS_TIMEOUT_MS=30000
# OMNIROUTE_PPLX_TLS_GRACE_MS=10000
@@ -1502,18 +1499,16 @@ CURSOR_USER_AGENT="Cursor/3.4"
# meta-commentary. Set to 1/true/yes/on to restore the old behavior.
# OMNIROUTE_PPLX_SEARCH_HINT=0
# ── Grok web TLS sidecar (Chrome-fingerprinted client) ──
# Used by: open-sse/services/grokTlsClient.ts — wire-level timeout for the
# bogdanfinn/tls-client koffi binding and the JS-side grace window layered on
# top of it when the native library is wedged.
# ── Grok web browser transport (Chrome-fingerprinted client) ──
# Used by: open-sse/services/grokTlsClient.ts — native wreq-js request timeout
# and the JS-side hard-deadline grace layered on top of it.
# OMNIROUTE_GROK_TLS_TIMEOUT_MS=60000
# OMNIROUTE_GROK_TLS_GRACE_MS=10000
# ── Notion web TLS sidecar (Chrome-fingerprinted client) ──
# Used by: open-sse/services/notionTlsClient.ts — wire-level timeout for the
# bogdanfinn/tls-client koffi binding and the JS-side grace window layered on
# top of it when the native library is wedged. The notion-web executor raises
# the wire timeout per-request to 180000 for long generations.
# ── Notion web browser transport (Chrome-fingerprinted client) ──
# Used by: open-sse/services/notionTlsClient.ts — native wreq-js request timeout
# and the JS-side hard-deadline grace layered on top of it. The notion-web
# executor raises the native timeout per-request to 180000 for long generations.
# OMNIROUTE_NOTION_TLS_TIMEOUT_MS=30000
# OMNIROUTE_NOTION_TLS_GRACE_MS=10000
@@ -1774,6 +1769,7 @@ APP_LOG_TO_FILE=true
# Custom directory for CLI plugin discovery (omniroute-cmd-* packages).
# Default: ~/.omniroute/plugins/ Override in dev/CI to point at a local plugin tree.
# CLI-only: the server-side plugin scanner is pointed by OMNIROUTE_PLUGINS_DIR (section 2).
# OMNIROUTE_PLUGIN_PATH=
# ── Prompt cache (system prompt deduplication) ──
@@ -1788,6 +1784,13 @@ APP_LOG_TO_FILE=true
# SEMANTIC_CACHE_MAX_BYTES=4194304 # Max total cache size in bytes (default: 4 MB)
# SEMANTIC_CACHE_TTL_MS=1800000 # Cache entry TTL (default: 30 minutes)
# ── Local corpus index cache ──
# How many local-corpus roots keep a live in-memory index at once. The cache is
# LRU: reaching the limit evicts the least-recently-used root's index, which is
# then rebuilt on its next query. Clamped to a minimum of 1; a non-numeric value
# falls back to the default. Used by: src/lib/localCorpus/configured.ts
# OMNIROUTE_CORPUS_CACHE_SIZE=5
# ── In-memory log buffers ──
# Maximum recent stream events kept in memory for the Dashboard live view.
# STREAM_HISTORY_MAX=50
@@ -1920,12 +1923,6 @@ APP_LOG_TO_FILE=true
# Base backoff after a transient 408 response (ms); five attempts maximum.
# ADOBE_FIREFLY_SUBMIT_BASE_DELAY_MS=8000
# ── Microsoft Designer Web (Image Generation) ──
# Polling config for the microsoft-designer-web submit-then-poll image job.
# Used by: open-sse/handlers/imageGeneration/providers/designerWeb.ts
# DESIGNER_WEB_POLL_TIMEOUT_MS=60000 # Max wait for job completion (default: 60s)
# DESIGNER_WEB_POLL_INTERVAL_MS=2000 # Poll frequency (default: 2s)
# ── Adobe Firefly (Image Upscale) ──
# Base delay (ms) for the submit-retry exponential backoff when Adobe Firefly's
# upscale job submission is rate-limited. Used by:
@@ -1983,6 +1980,26 @@ APP_LOG_TO_FILE=true
# Custom path to cloudflared binary for tunnel management.
# Used by: src/lib/cloudflaredTunnel.ts
# CLOUDFLARED_BIN=/usr/local/bin/cloudflared
#
# Transport protocol for the tunnel. One of: http2 (default), quic, auto.
# CLOUDFLARED_PROTOCOL=http2
#
# ── Named / persistent tunnel (stable hostname) ──
# By default OmniRoute runs an ephemeral quick tunnel (random *.trycloudflare.com
# URL that changes on every restart). To bind a stable, named hostname instead,
# create a locally-managed tunnel with the cloudflared CLI:
# cloudflared tunnel login
# cloudflared tunnel create <name>
# cloudflared tunnel route dns <name> ai.example.com
# then write a ~/.cloudflared/config.yml with `tunnel:`, `credentials-file:`, and
# `ingress:` rules routing your hostname to http://localhost:<PORT> (default 20128).
# Point OmniRoute at that config to switch into named-tunnel mode — it runs
# `cloudflared tunnel --config <path> run`.
# CLOUDFLARED_CONFIG=/home/you/.cloudflared/config.yml
# CLOUDFLARED_HOSTNAME is optional — when unset, OmniRoute reads the public hostname
# from the config's first ingress rule. Set it to override what is reported as
# publicUrl/apiUrl.
# CLOUDFLARED_HOSTNAME=ai.example.com
# ── Search cache ──
# TTL for search API response caching (Perplexity, Brave, etc.).
@@ -2021,6 +2038,8 @@ APP_LOG_TO_FILE=true
# CLIPROXYAPI_HOST=127.0.0.1
# CLIPROXYAPI_PORT=5544
# CLIPROXYAPI_CONFIG_DIR=~/.cli-proxy-api
# Data-plane key fallback; the cliproxyapi_api_key setting takes precedence.
# CLIPROXYAPI_API_KEY=
# Management key for an externally managed instance. Embedded instances use
# OmniRoute's encrypted service key.
# CLIPROXYAPI_MANAGEMENT_KEY=
@@ -2124,6 +2143,12 @@ APP_LOG_TO_FILE=true
# Used by: open-sse/services/rateLimitManager.ts
# RATE_LIMIT_MAX_WAIT_MS=15000
# Limiter-managed execution backstop (Bottleneck `expiration`): bounds a job's
# post-dispatch execution, never queue wait. Must stay ABOVE upstream
# fetch-start timeouts on non-incremental gateways. Default: 600000 (10 min)
# Used by: open-sse/services/rateLimitManager.ts
# RATE_LIMIT_EXECUTION_MAX_WAIT_MS=600000
# Rate limit queue admission cap: reject with 429 queue_full once this many requests
# are already queued (0 = disabled/unbounded, the default). Used by: open-sse/services/rateLimitManager.ts
# RATE_LIMIT_MAX_QUEUE_DEPTH=0
@@ -2525,11 +2550,6 @@ APP_LOG_TO_FILE=true
# Used by: src/lib/jobs/backupScheduleJob.ts
# OMNIROUTE_BACKUP_SCHEDULE_JOB_INTERVAL_MS=30000
# ── TLS sidecar override ──
# Used by: open-sse/services/chatgptTlsClient.ts tests. Production deployments
# should leave this unset; the sidecar is auto-managed.
# OMNIROUTE_TLS_PROXY_URL=
# ── Skills sandbox (experimental) ──
# Used by: src/lib/skills/builtins.ts. All values support comma lists where
# noted in the source.
@@ -3026,3 +3046,17 @@ QUOTA_STORE_DRIVER=sqlite
# without a configured budget are always considered affordable. Requires the
# provider_quota_state table (migration 148).
# OMNIROUTE_QUOTA_AWARE_ROUTING=0
# ─── LOCAL CORPUS (opt-in document index) ───
# Size of the in-memory LRU index cache for the local document corpus used by
# corpus-aware retrieval. Higher values keep more index entries hot.
# Used by: src/lib/localCorpus/configured.ts
# OMNIROUTE_CORPUS_CACHE_SIZE=5
# Service-worker cache-busting id for the PWA shell (#11779). NEXT_PUBLIC_SW_BUILD_ID is
# derived at build time from OMNIROUTE_SW_BUILD_ID, then SOURCE_VERSION (set by some PaaS
# builders), then the git SHA — override only when the build cannot see git. Used by:
# next.config.mjs, scripts/build/assembleStandalone.mjs, src/shared/components/PwaRegister.tsx.
#OMNIROUTE_SW_BUILD_ID=2026-08-28T12-00-00
#SOURCE_VERSION=abcdef0123456789
#NEXT_PUBLIC_SW_BUILD_ID=abcdef0123456789

View File

@@ -0,0 +1,36 @@
name: API Route Typecheck
on:
pull_request:
branches:
- main
- "release/**"
types: [opened, synchronize, reopened, ready_for_review]
push:
branches: [main]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
api-typecheck:
name: API Route Typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: "24"
cache: npm
- uses: ./.github/actions/npm-ci-retry
- name: Reject new API-route TypeScript diagnostics
run: node scripts/check/check-api-typecheck.mjs
- name: API typecheck gate unit tests
run: node --import tsx/esm --test tests/unit/build/check-api-typecheck.test.ts

View File

@@ -1,9 +1,16 @@
name: Build App
# Manual-only since #11946. The hosted 7 GB runner can no longer build this tree — 19 of
# the last 30 runs died with "The runner has received a shutdown signal" (VM out of
# memory) ~8 min into `next build`, release/v3.8.51 itself included, even with the 10 GB
# swapfile below. Triggered on `push: branches: ["**"]` it painted every branch and every
# PR red while producing an artefact nothing downloads. The bundle is validated where a
# build actually fits:
# - main: ci.yml `Build` (self-hosted omni-build pool) on every merge
# - release/**: nightly-release-green.yml (same pool, continuous)
# Dispatch this workflow by hand when a hosted build artefact is genuinely needed.
on:
workflow_dispatch:
push:
branches: ["**"]
permissions:
contents: read

View File

@@ -93,6 +93,7 @@ jobs:
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-node@v7
with:
@@ -108,8 +109,11 @@ jobs:
.eslintcache
.eslintcache-complexity
key: eslint-${{ runner.os }}-${{ hashFiles('eslint.config.mjs', 'eslint.complexity-ratchets.config.mjs', 'config/quality/eslint-suppressions.json', 'package-lock.json') }}
restore-keys: |
eslint-${{ runner.os }}-
# No restore-keys fallback on purpose (#11600, P-II.1 of the v3.8.50 postmortem): a
# cache built under a different suppressions file / lint config / lockfile reports
# stale per-file verdicts, which is exactly how 215 pre-existing errors stayed
# invisible for a whole cycle. Exact key or a cold full lint (~13 min) — never a
# partial cache from another configuration.
# Single ESLint inventory (JSON) — quality-gate reuses the artifact instead of
# a second cold full-tree pass for eslintWarnings ratchet counts.
- name: ESLint (JSON report)
@@ -126,6 +130,8 @@ jobs:
- run: npm run check:route-validation:t06
- run: npm run check:any-budget:t11
- run: npm run check:provider-consistency
- run: npm run check:model-lifecycle
- run: npm run check:provider-asset-provenance
- run: npm run check:fetch-targets
- run: npm run check:deps
- run: npm run check:file-size
@@ -206,8 +212,11 @@ jobs:
.eslintcache
.eslintcache-complexity
key: eslint-${{ runner.os }}-${{ hashFiles('eslint.config.mjs', 'eslint.complexity-ratchets.config.mjs', 'config/quality/eslint-suppressions.json', 'package-lock.json') }}
restore-keys: |
eslint-${{ runner.os }}-
# No restore-keys fallback on purpose (#11600, P-II.1 of the v3.8.50 postmortem): a
# cache built under a different suppressions file / lint config / lockfile reports
# stale per-file verdicts, which is exactly how 215 pre-existing errors stayed
# invisible for a whole cycle. Exact key or a cold full lint (~13 min) — never a
# partial cache from another configuration.
# Coverage mergeada (coverage-summary.json) p/ o ratchet de cobertura.
# continue-on-error: o artifact pode não existir se a job test-coverage foi
# SKIPPED (shard flaky). Nesse caso collect-metrics pula coverage.* (ausente sem
@@ -606,13 +615,24 @@ jobs:
# Dynamic runner: when the release captain flips the USE_VPS_RUNNER repo var to
# 'true' (scripts/vps/release-runner-up.sh does it after the self-hosted VM is
# online), the heavy jobs run on the dedicated 32-core VPS runners (label
# omni-release) instead of queueing on the 20-concurrent-job hosted pool.
# omni-build) instead of queueing on the 20-concurrent-job hosted pool.
# Safety: fork PRs NEVER reach the self-hosted runner — the expression falls
# back to ubuntu-latest unless the PR head repo is this repository (push /
# dispatch events are own-origin by definition). Any failure path (VM down,
# var unset/false) also falls back to ubuntu-latest.
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","omni-release"]') || 'ubuntu-latest' }}
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","omni-build"]') || 'ubuntu-latest' }}
needs: changes
# The .113 pool runs ONE next-build with room to spare and two at the edge: the
# box has 31 GB and a single next-build peaks at 1416 GB RSS. On 2026-08-28
# 13:50Z the kernel OOM-killed main's build while a PR build ran beside it
# (five Build jobs had been queued by a burst of PRs). Two lanes: main keeps
# its own so a release is never queued behind PR traffic; PR builds serialize
# among themselves. docker-publish.yml's amd64 leg joins `heavy-build-main`
# so a :next image build waits beside this artefact instead of becoming the
# third heavy (#11976). GitHub keeps one running + one pending per group.
concurrency:
group: heavy-build-${{ github.ref == 'refs/heads/main' && 'main' || 'pr' }}
cancel-in-progress: false
if: ${{ github.event_name != 'pull_request' || (needs.changes.outputs.code == 'true' && github.event.pull_request.draft == false) }}
steps:
- uses: actions/checkout@v7
@@ -646,14 +666,14 @@ jobs:
# Keep standalone/node_modules intact: package/electron jobs consume the
# Next-traced standalone tree and must not replace it with root node_modules.
run: |
tar -czf /tmp/e2e-build.tar.gz \
tar -czf "$RUNNER_TEMP/e2e-build.tar.gz" \
--exclude='.build/next/cache' \
.build/next
- name: Upload Next.js build for downstream jobs
uses: actions/upload-artifact@v7
with:
name: next-build
path: /tmp/e2e-build.tar.gz
path: ${{ runner.temp }}/e2e-build.tar.gz
retention-days: 1
package-artifact:
@@ -676,10 +696,14 @@ jobs:
uses: actions/download-artifact@v8
with:
name: next-build
path: /tmp/
# Workspace-relative on purpose: the matrix below includes windows-latest, whose
# default shell is pwsh, where $RUNNER_TEMP is empty (it is $env:RUNNER_TEMP) —
# #11896's first cut broke the Electron smoke on exactly that. A relative path
# works in bash and pwsh alike; hosted workspaces are ephemeral.
path: next-build-artifact
- name: Extract Next.js build artifact
run: |
tar -xzf /tmp/e2e-build.tar.gz
tar -xzf next-build-artifact/e2e-build.tar.gz
# build:cli consumes the downloaded .build/next standalone artifact and assembles dist/;
# it only rebuilds if the downloaded standalone artifact is missing.
- run: npm run build:cli
@@ -767,10 +791,14 @@ jobs:
uses: actions/download-artifact@v8
with:
name: next-build
path: /tmp/
# Workspace-relative on purpose: the matrix below includes windows-latest, whose
# default shell is pwsh, where $RUNNER_TEMP is empty (it is $env:RUNNER_TEMP) —
# #11896's first cut broke the Electron smoke on exactly that. A relative path
# works in bash and pwsh alike; hosted workspaces are ephemeral.
path: next-build-artifact
- name: Extract Next.js build artifact
run: |
tar -xzf /tmp/e2e-build.tar.gz
tar -xzf next-build-artifact/e2e-build.tar.gz
- name: Install Electron dependencies
working-directory: electron
run: npm install --no-audit --no-fund
@@ -957,7 +985,11 @@ jobs:
# 10min was sized before #7114 added the lcov reporter (Codecov/Sonar need it);
# merging 8 shard JSONs + text+json+lcov now takes ~10-12min — three consecutive
# release-tip runs died at exactly 10m as job-timeout "cancelled" (2026-07-15/16).
timeout-minutes: 20
# 30, not 20 (2026-08-29): the informational Codecov upload below hung for the rest of
# the budget on two consecutive main runs (33207760653, 33215115341); the job ended
# `cancelled` and dragged the whole run's conclusion to `cancelled` although every
# blocking job was green. The upload step now has its own ceiling; this is headroom.
timeout-minutes: 30
needs: test-unit
if: ${{ !cancelled() && needs.test-unit.result == 'success' && !contains(github.event.pull_request.labels.*.name, 'hotfix') }}
env:
@@ -1036,6 +1068,10 @@ jobs:
# (if-no-files-found: warn) — Sonar consumes the same file.
- name: Upload coverage to Codecov (informational)
if: always()
# Informational means informational: its own ceiling and continue-on-error, so a
# stalled upload can neither eat the job's budget nor turn a green job cancelled.
timeout-minutes: 5
continue-on-error: true
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
files: coverage/lcov.info
@@ -1230,10 +1266,14 @@ jobs:
uses: actions/download-artifact@v8
with:
name: next-build
path: /tmp/
# Workspace-relative on purpose: the matrix below includes windows-latest, whose
# default shell is pwsh, where $RUNNER_TEMP is empty (it is $env:RUNNER_TEMP) —
# #11896's first cut broke the Electron smoke on exactly that. A relative path
# works in bash and pwsh alike; hosted workspaces are ephemeral.
path: next-build-artifact
- name: Extract Next.js build artifact
run: |
tar -xzf /tmp/e2e-build.tar.gz
tar -xzf next-build-artifact/e2e-build.tar.gz
# WS4.1: duration-balanced shards (LPT over config/quality/e2e-timings.json).
# Measured skew of plain --shard was 14× (24m47s vs 1m47s) — E2E was the CI
# critical path. The balancer self-verifies completeness and exits non-zero on

View File

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

View File

@@ -1,7 +1,15 @@
name: DAST smoke (PR)
# PRs into main only since #11946. The job's "Build CLI bundle" step is a backend-only
# `next build`; on the hosted 7 GB runner it fits main's tree (~5.5 min) but dies on
# release/v3.8.51 (VM shutdown ~7 min in, before the server even starts), and because the
# job is continue-on-error the result was a permanently red advisory check on every
# release PR — noise, not signal. DAST coverage for release/** lives on the nightly rail
# (nightly-schemathesis.yml, nightly-llm-security.yml); dispatch this workflow by hand
# to smoke a release branch on demand.
on:
workflow_dispatch:
pull_request:
branches: ["main", "release/**"]
branches: ["main"]
# Runner-cost guard (#8084): the CLI-bundle build alone is 6-11min; a docs-only PR
# cannot change DAST behavior, so skip the whole workflow for pure docs/markdown
# changes. Any code path in the diff still runs the full smoke.

View File

@@ -26,6 +26,14 @@ on:
type: boolean
default: false
# One publish per ref. A merge storm used to fan out 8 concurrent hosted builds,
# every one OOM-killing `npm run build` inside BuildKit (#11976). The :next
# channel only needs the newest SHA; cancel-in-progress is the same pattern as
# quality.yml / nightly-release-green.
concurrency:
group: docker-publish-${{ github.ref }}
cancel-in-progress: true
# Least-privilege default: read-only at the top level; the build and merge jobs that
# push to GHCR grant packages: write themselves (Scorecard TokenPermissions).
permissions:
@@ -68,6 +76,16 @@ jobs:
"$EVENT_NAME" "$REF_TYPE" "$REF_NAME" "$INPUT_VERSION" "$DEFAULT_BRANCH")
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
# Frozen release branches keep receiving coordination commits after the
# next cycle becomes the default branch. They must not overwrite :next,
# but that expected no-op is not a workflow failure.
if [ "$VERSION" = "skip" ]; then
echo "promote_latest=false" >> "$GITHUB_OUTPUT"
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "Skipping Docker publish from non-default release branch: $REF_NAME"
exit 0
fi
# 2) Decide whether to promote :latest. Floating channels are never
# eligible, and the helper independently fails closed for non-semver.
PROMOTE="false"
@@ -108,7 +126,23 @@ jobs:
name: Build Docker (${{ matrix.platform }})
needs: prepare
if: needs.prepare.outputs.skip != 'true'
runs-on: ${{ matrix.runner }}
# amd64: the .113 omni-build pool (31 GB / 32 cores, ONE listener since
# #12048). Hosted ubuntu-24.04 is ~7 GB and dies ResourceExhausted (#11976).
# Falls back to hosted when USE_VPS_RUNNER is off. arm64: no ARM box — stay
# on GitHub's ubuntu-24.04-arm.
# Webpack on BOTH arches: Turbopack on omniroute-113-6 hit
# TurbopackInternalError "there must be a path to a root" after 26 min
# (run 33253576569). The same tree's arm64 webpack build on hosted ARM
# succeeded (run 33264823398). Dockerfile already documents webpack as the
# Docker escape hatch (OMNIROUTE_USE_TURBOPACK=0).
runs-on: ${{ matrix.arch == 'amd64' && (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-build"]') || 'ubuntu-24.04') || 'ubuntu-24.04-arm' }}
# Share the 1-slot omni-build ceiling (#12048) with ci.yml `Build` /
# npm-publish. Same group as main's Build so a :next publish waits beside
# the artefact instead of sitting next to it. arm64 is hosted — its own
# group, cancelled by the workflow-level concurrency.
concurrency:
group: ${{ matrix.arch == 'amd64' && 'heavy-build-main' || format('docker-publish-arm-{0}', github.ref) }}
cancel-in-progress: ${{ matrix.arch != 'amd64' }}
permissions:
contents: read
packages: write
@@ -117,10 +151,8 @@ jobs:
matrix:
include:
- platform: linux/amd64
runner: ubuntu-24.04
arch: amd64
- platform: linux/arm64
runner: ubuntu-24.04-arm
arch: arm64
env:
IMAGE_NAME: diegosouzapw/omniroute
@@ -133,6 +165,9 @@ jobs:
ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/v{0}', inputs.version) || '' }}
fetch-depth: 0
- name: Assert Docker Engine
run: docker info
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
@@ -156,12 +191,14 @@ jobs:
context: .
target: runner-base
platforms: ${{ matrix.platform }}
build-args: |
OMNIROUTE_USE_TURBOPACK=0
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
tags: |
${{ env.IMAGE_NAME }}
${{ env.GHCR_IMAGE_NAME }}
cache-from: type=gha,scope=docker-${{ matrix.arch }}
cache-to: type=gha,scope=docker-${{ matrix.arch }},mode=max
cache-to: type=gha,scope=docker-${{ matrix.arch }},mode=max,ignore-error=true
no-cache: false
env:
DOCKER_BUILDKIT_INLINE_CACHE: 1
@@ -173,48 +210,68 @@ jobs:
context: .
target: runner-web
platforms: ${{ matrix.platform }}
build-args: |
OMNIROUTE_USE_TURBOPACK=0
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
tags: |
${{ env.IMAGE_NAME }}
${{ env.GHCR_IMAGE_NAME }}
cache-from: type=gha,scope=docker-web-${{ matrix.arch }}
cache-to: type=gha,scope=docker-web-${{ matrix.arch }},mode=max
cache-to: type=gha,scope=docker-web-${{ matrix.arch }},mode=max,ignore-error=true
no-cache: false
env:
DOCKER_BUILDKIT_INLINE_CACHE: 1
- name: Build and push BUN base platform image by digest
id: build-bun-base
# Bun is a best-effort compatibility target, not a supported runtime
# (AGENTS.md -> Environment). Its `bun run build` has been OOM-killing on
# both arches; letting that sink the whole publish means the SUPPORTED
# runner-base / runner-web images never reach the registry either. The
# image is still built and pushed whenever it succeeds — only its power to
# block the release is removed.
continue-on-error: true
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
with:
context: .
file: Dockerfile.bun
target: runner-base
platforms: ${{ matrix.platform }}
build-args: |
OMNIROUTE_USE_TURBOPACK=0
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
tags: |
${{ env.IMAGE_NAME }}
${{ env.GHCR_IMAGE_NAME }}
cache-from: type=gha,scope=docker-bun-base-${{ matrix.arch }}
cache-to: type=gha,scope=docker-bun-base-${{ matrix.arch }},mode=max
cache-to: type=gha,scope=docker-bun-base-${{ matrix.arch }},mode=max,ignore-error=true
no-cache: false
env:
DOCKER_BUILDKIT_INLINE_CACHE: 1
- name: Build and push BUN web platform image by digest
id: build-bun-web
# Bun is a best-effort compatibility target, not a supported runtime
# (AGENTS.md -> Environment). Its `bun run build` has been OOM-killing on
# both arches; letting that sink the whole publish means the SUPPORTED
# runner-base / runner-web images never reach the registry either. The
# image is still built and pushed whenever it succeeds — only its power to
# block the release is removed.
continue-on-error: true
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
with:
context: .
file: Dockerfile.bun
target: runner-web
platforms: ${{ matrix.platform }}
build-args: |
OMNIROUTE_USE_TURBOPACK=0
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
tags: |
${{ env.IMAGE_NAME }}
${{ env.GHCR_IMAGE_NAME }}
cache-from: type=gha,scope=docker-bun-web-${{ matrix.arch }}
cache-to: type=gha,scope=docker-bun-web-${{ matrix.arch }},mode=max
cache-to: type=gha,scope=docker-bun-web-${{ matrix.arch }},mode=max,ignore-error=true
no-cache: false
env:
DOCKER_BUILDKIT_INLINE_CACHE: 1
@@ -230,8 +287,15 @@ jobs:
mkdir -p /tmp/digests/base /tmp/digests/web /tmp/digests/bun-base /tmp/digests/bun-web
touch "/tmp/digests/base/${DIGEST_BASE#sha256:}"
touch "/tmp/digests/web/${DIGEST_WEB#sha256:}"
touch "/tmp/digests/bun-base/${DIGEST_BUN_BASE#sha256:}"
touch "/tmp/digests/bun-web/${DIGEST_BUN_WEB#sha256:}"
# Empty when the (non-blocking) bun build produced no image. `if` blocks,
# not `[ -n ] && touch`: under `set -e` a failing AND-list aborts the step,
# which is precisely the case being handled here.
if [ -n "$DIGEST_BUN_BASE" ]; then
touch "/tmp/digests/bun-base/${DIGEST_BUN_BASE#sha256:}"
fi
if [ -n "$DIGEST_BUN_WEB" ]; then
touch "/tmp/digests/bun-web/${DIGEST_BUN_WEB#sha256:}"
fi
- name: Upload base digests
uses: actions/upload-artifact@v7
@@ -254,7 +318,11 @@ jobs:
with:
name: digests-bun-base-${{ matrix.arch }}
path: /tmp/digests/bun-base/*
if-no-files-found: error
# `ignore`, not `error`: the bun build is non-blocking, so an absent
# digest is the expected outcome of a failed/skipped bun image — the
# manifest step already treats these tags as optional. Leaving `error`
# here just relocates the blocker from the manifest to the upload.
if-no-files-found: ignore
retention-days: 1
- name: Upload bun-web digests
@@ -262,7 +330,11 @@ jobs:
with:
name: digests-bun-web-${{ matrix.arch }}
path: /tmp/digests/bun-web/*
if-no-files-found: error
# `ignore`, not `error`: the bun build is non-blocking, so an absent
# digest is the expected outcome of a failed/skipped bun image — the
# manifest step already treats these tags as optional. Leaving `error`
# here just relocates the blocker from the manifest to the upload.
if-no-files-found: ignore
retention-days: 1
merge:
@@ -320,6 +392,9 @@ jobs:
merge-multiple: true
- name: Download bun-base digests
# Non-blocking: the bun image is best-effort, so its artifact may not
# exist at all. The manifest step treats these tags as optional.
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: digests-bun-base-*
@@ -327,6 +402,9 @@ jobs:
merge-multiple: true
- name: Download bun-web digests
# Non-blocking: the bun image is best-effort, so its artifact may not
# exist at all. The manifest step treats these tags as optional.
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: digests-bun-web-*
@@ -338,7 +416,7 @@ jobs:
set -euo pipefail
create_manifest() {
local image="$1" suffix="$2" dir="$3"
local image="$1" suffix="$2" dir="$3" optional="${4:-}"
local tags=(-t "${image}:${VERSION}${suffix}")
if [ "$PROMOTE_LATEST" = "true" ]; then
tags+=(-t "${image}:latest${suffix}")
@@ -348,6 +426,10 @@ jobs:
refs+=("${image}@sha256:$(basename "$digest_file")")
done < <(find "$dir" -type f | sort)
if [ "${#refs[@]}" -eq 0 ]; then
if [ -n "$optional" ]; then
echo "::warning::No image digests in $dir — skipping optional tag ${image}:${VERSION}${suffix}" >&2
return 0
fi
echo "No image digests in $dir" >&2
exit 1
fi
@@ -356,15 +438,15 @@ jobs:
create_manifest "${IMAGE_NAME}" "" /tmp/digests/base
create_manifest "${IMAGE_NAME}" "-web" /tmp/digests/web
create_manifest "${IMAGE_NAME}" "-bun" /tmp/digests/bun-base
create_manifest "${IMAGE_NAME}" "-web-bun" /tmp/digests/bun-web
create_manifest "${IMAGE_NAME}" "-bun" /tmp/digests/bun-base optional
create_manifest "${IMAGE_NAME}" "-web-bun" /tmp/digests/bun-web optional
- name: Create GHCR manifest
run: |
set -euo pipefail
create_manifest() {
local image="$1" suffix="$2" dir="$3"
local image="$1" suffix="$2" dir="$3" optional="${4:-}"
local tags=(-t "${image}:${VERSION}${suffix}")
if [ "$PROMOTE_LATEST" = "true" ]; then
tags+=(-t "${image}:latest${suffix}")
@@ -374,6 +456,10 @@ jobs:
refs+=("${image}@sha256:$(basename "$digest_file")")
done < <(find "$dir" -type f | sort)
if [ "${#refs[@]}" -eq 0 ]; then
if [ -n "$optional" ]; then
echo "::warning::No image digests in $dir — skipping optional tag ${image}:${VERSION}${suffix}" >&2
return 0
fi
echo "No image digests in $dir" >&2
exit 1
fi
@@ -382,8 +468,8 @@ jobs:
create_manifest "${GHCR_IMAGE_NAME}" "" /tmp/digests/base
create_manifest "${GHCR_IMAGE_NAME}" "-web" /tmp/digests/web
create_manifest "${GHCR_IMAGE_NAME}" "-bun" /tmp/digests/bun-base
create_manifest "${GHCR_IMAGE_NAME}" "-web-bun" /tmp/digests/bun-web
create_manifest "${GHCR_IMAGE_NAME}" "-bun" /tmp/digests/bun-base optional
create_manifest "${GHCR_IMAGE_NAME}" "-web-bun" /tmp/digests/bun-web optional
- name: Inspect image
if: needs.prepare.outputs.version != 'main'
@@ -442,11 +528,14 @@ jobs:
severity: CRITICAL
ignore-unfixed: true
exit-code: "1"
# Explicit: the advisory scan above already points at it, and the blocking
# gate must honour the same accepted-risk list (#12084).
trivyignores: .trivyignore
- 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.7
uses: github/codeql-action/upload-sarif@v4.37.8
with:
sarif_file: trivy-results.sarif
category: trivy-image

View File

@@ -4,12 +4,21 @@ on:
push:
tags:
- "v*"
# A dispatch builds the ref it is dispatched ON (`gh workflow run … --ref v3.8.50` rebuilds
# that tag; `--ref main` builds the repaired line). The ref is deliberately NOT an input:
# CodeQL flags an input-controlled checkout next to the npm cache on the default branch as
# cache poisoning (actions/cache-poisoning/poisonable-step), and `github.ref` is trusted.
workflow_dispatch:
inputs:
version:
description: "Release version (e.g., v1.6.8)"
required: true
type: string
publish_npm:
description: "Also run the npm publish leg (turn off when re-attaching desktop assets to a release whose npm package already shipped)"
required: false
default: true
type: boolean
# Least-privilege default: read-only at the top level; each job grants the writes it
# needs (build/release upload assets, publish-npm forwards npm provenance / packages
@@ -76,6 +85,9 @@ jobs:
- uses: actions/checkout@v7
with:
persist-credentials: false
# workflow_dispatch: build the tag being (re)built, not the dispatching branch. On a
# tag push this resolves to the same commit.
ref: ${{ needs.validate.outputs.version }}
- name: Setup Node
uses: actions/setup-node@v7
with:
@@ -161,6 +173,9 @@ jobs:
- uses: actions/checkout@v7
with:
persist-credentials: false
# workflow_dispatch: build the tag being (re)built, not the dispatching branch. On a
# tag push this resolves to the same commit.
ref: ${{ needs.validate.outputs.version }}
- name: Setup Node
uses: actions/setup-node@v7
with:
@@ -222,7 +237,7 @@ jobs:
# optionals (@img/sharp-*, @img/sharp-libvips-*, @ngrok/ngrok-*,
# fsevents) carry linux forks. Replace them with the forks this
# leg's own `npm ci` resolved, then assert every bundled native
# (koffi triplets, better-sqlite3 prebuilds, wreq-js, onnxruntime)
# (better-sqlite3 prebuilds, wreq-js, onnxruntime)
# can service this leg's platform/arch before packaging starts.
run: |
node scripts/build/standaloneBundle.mjs restore --archive web-bundle.tar.gz
@@ -347,6 +362,8 @@ jobs:
with:
persist-credentials: false
fetch-depth: 0
# Source archives + SBOM come from the tag being released, not the dispatching branch.
ref: ${{ needs.validate.outputs.version }}
# `merge-multiple` is deliberately OFF. It resolves same-name collisions by ARRIVAL
# ORDER, and the two macOS jobs each emit their own `latest-mac.yml` listing only their
@@ -404,7 +421,14 @@ jobs:
tag_name: ${{ needs.validate.outputs.version }}
draft: false
prerelease: false
generate_release_notes: true
# NEVER. Phase 3 of the release flow creates the GitHub Release with the curated
# notes seconds after pushing the tag, so by the time this step runs (1-2 h of
# builds later) the body already exists — and `true` APPENDS GitHub's
# auto-generated "What's Changed" block to it (v3.8.48 shipped that way; the
# v3.8.50 re-attach dispatch added +1,416 chars to a 121 KB body, run
# 33238093090). A curated body sits ~3 KB under the 125,000-char cap, so the
# append can also turn this step RED and leave the release with no assets.
generate_release_notes: false
fail_on_unmatched_files: false
files: |
release-assets/*.dmg
@@ -462,11 +486,20 @@ jobs:
publish-npm:
name: Publish to npm
needs: [validate, release]
# A re-dispatch that only re-attaches desktop assets must not publish the npm package again.
if: ${{ github.event_name != 'workflow_dispatch' || inputs.publish_npm }}
permissions:
# Must be `write`, not `read`: this job calls the reusable npm-publish.yml whose
# `publish` job needs `contents: write` (gh release upload — attach the SBOM, #3874).
# A reusable workflow's job cannot request more permission than the caller grants,
# so a `read` here makes GitHub reject the run at startup (startup_failure).
#
# `actions: read` for the same reason: the called `publish` job downloads the next-build
# artefact and requests it. v3.8.50 (run 33005490476) died at startup with "The nested
# job 'publish' is requesting 'actions: read', but is only allowed 'actions: none'" — and
# because `release` lives in this same workflow, the tag shipped with ZERO assets. Keep
# this block a superset of every job's permissions in npm-publish.yml.
actions: read
contents: write
id-token: write # npm provenance (forwarded to the reusable workflow)
packages: write # publish to npm.pkg.github.com

View File

@@ -10,7 +10,10 @@ permissions:
jobs:
promptfoo-guard:
name: promptfoo — injection guard (block mode, no secret)
runs-on: ubuntu-latest
# #11965: this job runs a backend-only `next build`; the hosted 7 GB runner cannot build
# release/v3.8.51 (VM shutdown ~7 min in), so it targets the box's light pool (`omni-light`:
# two listeners, jobs ≤ ~6 GB). Falls back to hosted when USE_VPS_RUNNER is off.
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-light"]')) || 'ubuntu-latest' }}
steps:
- uses: actions/checkout@v7
with:
@@ -46,7 +49,10 @@ jobs:
garak:
name: garak probes (skip without provider secret)
runs-on: ubuntu-latest
# #11965: this job runs a backend-only `next build`; the hosted 7 GB runner cannot build
# release/v3.8.51 (VM shutdown ~7 min in), so it targets the box's light pool (`omni-light`:
# two listeners, jobs ≤ ~6 GB). Falls back to hosted when USE_VPS_RUNNER is off.
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-light"]')) || 'ubuntu-latest' }}
# NOTE: the `secrets` context is NOT available in a job-level `if:` — referencing
# it there makes GitHub reject the file on push (startup_failure on every push).
# Map the secret into a job-level env and gate each step on a presence check, so

View File

@@ -68,7 +68,7 @@ jobs:
# this runs on the dedicated VPS runner — clean env (no operator OMNIROUTE_API_KEY,
# no local noauth CLIs => zero machine-specific false positives) and no contention.
# Nightly cron normally finds the var false (VM off) and falls back to hosted.
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-release"]')) || 'ubuntu-latest' }}
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-build"]')) || 'ubuntu-latest' }}
env:
JWT_SECRET: ci-nightly-secret-with-sufficient-length-for-validation
API_KEY_SECRET: ci-nightly-api-key-secret-long
@@ -196,6 +196,26 @@ jobs:
gh issue create --repo "$GITHUB_REPOSITORY" --title "$TITLE" --label base-red --body-file issue-body.md
fi
- name: Close tracking issue when the branch is green again
if: steps.validate.outputs.exit == '0'
env:
GH_TOKEN: ${{ github.token }}
TARGET: ${{ steps.branch.outputs.target }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
set -euo pipefail
# The open/update step above is the UPWARD half of the loop; without this
# step a stale "not green" issue outlives the fix and every base-green check
# (`AGENTS.md` → "Base-green check") keeps stamping new PRs as base-red inherited.
TITLE="🔴 Release branch not green: ${TARGET}"
EXISTING=$(gh issue list --repo "$GITHUB_REPOSITORY" --state open \
--search "in:title $TITLE" --json number --jq '.[0].number' 2>/dev/null || echo "")
if [ -n "$EXISTING" ]; then
gh issue close "$EXISTING" --repo "$GITHUB_REPOSITORY" --reason completed \
--comment "✅ \`${TARGET}\` is release-green again at \`${GITHUB_SHA:0:9}\` — ${RUN_URL}. Auto-closed by Release-Green (continuous)."
echo "Closed issue #$EXISTING"
fi
- name: Upload report artifact
if: always()
uses: actions/upload-artifact@v7
@@ -217,7 +237,7 @@ jobs:
# On a push, only run for a push to main — a push to release/* is handled by
# release-green above. Schedule/dispatch always run (they also sweep main).
if: ${{ github.event_name != 'push' || github.ref_name == 'main' }}
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-release"]')) || 'ubuntu-latest' }}
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-build"]')) || 'ubuntu-latest' }}
env:
JWT_SECRET: ci-nightly-secret-with-sufficient-length-for-validation
API_KEY_SECRET: ci-nightly-api-key-secret-long
@@ -294,6 +314,25 @@ jobs:
gh issue create --repo "$GITHUB_REPOSITORY" --title "$TITLE" --label base-red --body-file issue-body.md
fi
- name: Close tracking issue when the branch is green again
if: steps.validate.outputs.exit == '0'
env:
GH_TOKEN: ${{ github.token }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
set -euo pipefail
# The open/update step above is the UPWARD half of the loop; without this
# step a stale "not green" issue outlives the fix and every base-green check
# (`AGENTS.md` → "Base-green check") keeps stamping new PRs as base-red inherited.
TITLE="🔴 main branch not green"
EXISTING=$(gh issue list --repo "$GITHUB_REPOSITORY" --state open \
--search "in:title $TITLE" --json number --jq '.[0].number' 2>/dev/null || echo "")
if [ -n "$EXISTING" ]; then
gh issue close "$EXISTING" --repo "$GITHUB_REPOSITORY" --reason completed \
--comment "✅ \`main\` is main-green again at \`${GITHUB_SHA:0:9}\` — ${RUN_URL}. Auto-closed by Release-Green (continuous)."
echo "Closed issue #$EXISTING"
fi
- name: Upload report artifact
if: always()
uses: actions/upload-artifact@v7
@@ -384,6 +423,13 @@ jobs:
# on `improvements`, complexity-ratchets only when `.improved`), and both exit
# non-zero while the branch is over baseline — which is exactly when there is
# nothing to bank. Their exit code is not the signal; the verifier below is.
# Velocity phase (quality-baseline.json `_policy`, relax-baselines.mjs): the caps
# were raised on purpose, so banking the measured shrink would silently undo the
# 20% headroom every night. Pause the downward ratchet until the phase closes.
if node -e 'process.exit(require("./config/quality/quality-baseline.json")._policy?.phase === "velocity" ? 0 : 1)'; then
echo "Velocity phase active — ratchet banking paused (see docs/architecture/QUALITY_GATES.md → Velocity phase)."
exit 0
fi
set +e
node scripts/check/check-file-size.mjs --update
node scripts/check/check-complexity-ratchets.mjs --update
@@ -445,3 +491,72 @@ jobs:
gh pr create --repo "$GITHUB_REPOSITORY" --base "$TARGET" --head "$BANK_BRANCH" \
--title "chore(quality): bank ratchet shrinks (${TARGET})" --body-file pr-body.md
fi
# ── Baseline headroom (velocity phase, 2026-08-30 → v4.0) ──────────────────────
# The ratchets only speak when a baseline is crossed. With every baseline loosened by
# 20% (scripts/quality/relax-baselines.mjs) the question is how fast the budget is
# being consumed — this job measures each gate the way CI does and posts the headroom
# table to one living issue, so a budget that fills in a week is visible before the
# first red PR. Advisory: never fails the workflow.
baseline-headroom:
name: Baseline headroom
if: ${{ github.event_name != 'push' }}
timeout-minutes: 60
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-light"]')) || 'ubuntu-latest' }}
permissions:
contents: read
issues: write
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version: "24"
cache: npm
- uses: ./.github/actions/npm-ci-retry
- name: Measure headroom on ${{ github.ref_name }}
run: |
set -euo pipefail
node scripts/quality/baseline-headroom.mjs \
--json reports/quality/headroom.json --md reports/quality/headroom.md
cat reports/quality/headroom.md >> "$GITHUB_STEP_SUMMARY"
- name: Upload headroom report
if: always()
uses: actions/upload-artifact@v7
with:
name: baseline-headroom-${{ github.run_id }}
path: reports/quality/headroom.*
retention-days: 90
- name: Post to the living issue
env:
GH_TOKEN: ${{ github.token }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
set -euo pipefail
TITLE="📈 Baseline headroom (velocity phase)"
BAD=$(node -e 'const r=require("./reports/quality/headroom.json").rows;console.log(r.filter(x=>x.status==="critical"||x.status==="warn").length)')
{
echo "Branch: \`${GITHUB_REF_NAME}\` · run: ${RUN_URL}"
echo ""
cat reports/quality/headroom.md
} > headroom-comment.md
EXISTING=$(gh issue list --repo "$GITHUB_REPOSITORY" --state open \
--search "in:title $TITLE" --json number --jq '.[0].number' 2>/dev/null || echo "")
if [ -z "$EXISTING" ]; then
EXISTING=$(gh issue create --repo "$GITHUB_REPOSITORY" --title "$TITLE" --label quality-gate-finding \
--body "Living tracker for the velocity-phase baseline budget (docs/architecture/QUALITY_GATES.md → Velocity phase). One comment per nightly run; the newest comment is the current state." \
| grep -oE '[0-9]+$')
fi
gh issue comment "$EXISTING" --repo "$GITHUB_REPOSITORY" --body-file headroom-comment.md
if [ "$BAD" != "0" ]; then
gh issue edit "$EXISTING" --repo "$GITHUB_REPOSITORY" --add-label "headroom-alert" 2>/dev/null || true
else
gh issue edit "$EXISTING" --repo "$GITHUB_REPOSITORY" --remove-label "headroom-alert" 2>/dev/null || true
fi

View File

@@ -78,7 +78,10 @@ jobs:
a11y:
name: A11y axe (nightly, freeze-and-alert)
runs-on: ubuntu-latest
# #11965: this job runs a backend-only `next build`; the hosted 7 GB runner cannot build
# release/v3.8.51 (VM shutdown ~7 min in), so it targets the box's light pool (`omni-light`:
# two listeners, jobs ≤ ~6 GB). Falls back to hosted when USE_VPS_RUNNER is off.
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-light"]')) || 'ubuntu-latest' }}
# The Playwright webServer (`start` mode) builds Next via build-next-isolated.mjs and
# boots the standalone server itself (waits on /api/monitoring/health, 15min webServer
# timeout). Unlike the per-PR test-e2e job, this nightly job has no pre-built artifact,

View File

@@ -10,7 +10,10 @@ permissions:
jobs:
schemathesis:
name: Schemathesis — OpenAPI contract fuzz (advisory)
runs-on: ubuntu-latest
# #11965: this job runs a backend-only `next build`; the hosted 7 GB runner cannot build
# release/v3.8.51 (VM shutdown ~7 min in), so it targets the box's light pool (`omni-light`:
# two listeners, jobs ≤ ~6 GB). Falls back to hosted when USE_VPS_RUNNER is off.
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-light"]')) || 'ubuntu-latest' }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v7

View File

@@ -23,11 +23,12 @@ on:
- next
- historic
publish_mode:
description: "staged = npm stage publish (owner approves with 2FA after the staged boot-verify); direct = legacy immediate publish (emergency fallback only)"
description: "auto = publish through npm Trusted Publishing (OIDC, no token, no 2FA prompt — the default); staged = npm stage publish (owner approves with 2FA); direct = legacy token publish (emergency fallback only)"
required: false
default: "staged"
default: "auto"
type: choice
options:
- auto
- staged
- direct
workflow_call:
@@ -62,11 +63,15 @@ jobs:
# mid-"Creating an optimized production build" while v3.8.48 had still fit in 16min.
# This job never runs on `pull_request`, so the fork-safety clause is always true here;
# it is kept verbatim so the expression stays greppable against ci.yml.
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","omni-release"]') || 'ubuntu-latest' }}
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","omni-build"]') || 'ubuntu-latest' }}
outputs:
version: ${{ steps.resolve.outputs.version }}
tag: ${{ steps.resolve.outputs.tag }}
skip: ${{ steps.resolve.outputs.skip }}
permissions:
actions: read # find + download the CI run's next-build artifact for this SHA
contents: write # gh release upload (attach SBOM to the GitHub Release)
id-token: write # npm provenance
id-token: write # npm provenance (GitHub Packages step)
packages: write # publish to npm.pkg.github.com
steps:
- name: Checkout
@@ -200,8 +205,11 @@ jobs:
exit 0
fi
RUN=""
# $RUNNER_TEMP, never /tmp: on the .113 pool /tmp is a 12 GB tmpfs (RAM). Parking
# this 1.3 GB artefact there took 2732 min of the 76-min publish job — the
# same bytes upload from disk in 2 min. RUNNER_TEMP is per-runner and on disk.
for candidate in $CANDIDATES; do
if gh run download "$candidate" --repo "$REPO" --name next-build --dir /tmp/next-build 2>/dev/null; then
if gh run download "$candidate" --repo "$REPO" --name next-build --dir "$RUNNER_TEMP/next-build" 2>/dev/null; then
RUN="$candidate"
break
fi
@@ -211,8 +219,8 @@ jobs:
echo "::notice::none of the candidate runs still carries next-build (1-day retention) — falling back to a full build"
exit 0
fi
tar -xzf /tmp/next-build/e2e-build.tar.gz -C .
rm -rf /tmp/next-build
tar -xzf "$RUNNER_TEMP/next-build/e2e-build.tar.gz" -C .
rm -rf "$RUNNER_TEMP/next-build"
if [ -f .build/next/standalone/server.js ]; then
echo "✅ standalone tree restored from CI run $RUN — build:cli will skip next build"
else
@@ -226,6 +234,28 @@ jobs:
JWT_SECRET: ci-build-secret-with-sufficient-length-for-validation
run: npm run build:cli
# `build:cli` assembles dist/ but does NOT write dist/BUILD_SHA — only
# `build:release` does, by calling write-build-sha.mjs. The #10427 provenance
# guard inside check:pack-artifact rejects an artifact with no SHA (and rejects
# it even under OMNIROUTE_ALLOW_CANARY_BUILD=1: what cannot be identified cannot
# be vouched for). Without this step the build+validate pair in this job is
# structurally incompatible and fails 100% of the time — the same gap that was
# fixed in ci.yml's Package Artifact job.
- name: Stamp dist/BUILD_SHA for the provenance guard (#10427)
if: steps.resolve.outputs.skip != 'true'
env:
OMNIROUTE_BUILD_SHA: ${{ github.sha }}
run: |
export OMNIROUTE_BUILD_SHA="${OMNIROUTE_BUILD_SHA:0:7}"
node scripts/build/write-build-sha.mjs
# The guard checks ancestry against origin/main by default, which is correct
# here (a release tag is cut from main), but the ref has to exist locally for
# `git merge-base` to resolve it.
- name: Fetch main for the provenance probe
if: steps.resolve.outputs.skip != 'true'
run: git fetch --no-tags --depth=50 origin +refs/heads/main:refs/remotes/origin/main
- name: Validate npm package artifact
if: steps.resolve.outputs.skip != 'true'
run: npm run check:pack-artifact
@@ -243,11 +273,20 @@ jobs:
if-no-files-found: error
- name: Attach SBOM to GitHub Release
if: steps.resolve.outputs.skip != 'true' && github.event_name == 'release'
# Not only on the `release` event: the v3.8.50 package shipped through a
# workflow_dispatch (staged publish, 11 attempts) and this step was skipped, so the
# GitHub Release carried no SBOM until it was attached by hand from the run's
# `sbom-npm` artifact. Attach whenever a release for the published tag exists.
if: steps.resolve.outputs.skip != 'true' && (github.event_name == 'release' || github.event_name == 'workflow_dispatch')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
run: gh release upload "$TAG" sbom-npm.cdx.json --clobber
TAG: ${{ github.event_name == 'release' && github.ref_name || format('v{0}', inputs.version) }}
run: |
if ! gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo "::notice::no GitHub Release for $TAG yet — SBOM stays on the sbom-npm workflow artifact"
exit 0
fi
gh release upload "$TAG" sbom-npm.cdx.json --repo "$GITHUB_REPOSITORY" --clobber
# WS1.2/WS1.3 (#7065 class): the artifact that is about to be published must
# BOOT. build:cli already assembled dist/ above; this packs+installs+boots the
@@ -265,7 +304,12 @@ jobs:
# a staged package that is never approved simply expires, with no `npm deprecate` needed.
- name: Prove clean-install AND upgrade-over-previous both boot
if: steps.resolve.outputs.skip != 'true'
timeout-minutes: 30
# 60, not 30. This gate was added in #8953 and the 2026-08-27 v3.8.50 publish
# was the FIRST run to ever reach it — every earlier attempt died upstream, so
# its budget had never been measured against a real run. It then blew the limit
# on its debut: `npm pack` alone took 24m37s, leaving 5 minutes for two installs
# and two boots. 30 was a guess; 60 is sized to the one measurement we have.
timeout-minutes: 60
run: npm run check:install-upgrade
# WS1.3 (D2, v3.8.49 plan): STAGED publishing by default — `npm stage publish`
@@ -287,41 +331,33 @@ jobs:
fi
npm --version
- name: Publish to npm (staged — owner approves with 2FA)
if: steps.resolve.outputs.skip != 'true' && (github.event_name != 'workflow_dispatch' || inputs.publish_mode != 'direct')
# The registry upload itself moved to the `stage-npm` job below: npm REFUSES
# `--provenance` from a self-hosted runner (422 "Unsupported GitHub Actions
# runner environment"), and the heavy verification above cannot move to a
# hosted one (16 GB is not enough for build:cli's next-build fallback — see
# this job's runs-on comment). So this job proves the bytes and hands them
# over; a tiny hosted job does the upload.
- name: Pack the verified tarball for the upload job
if: steps.resolve.outputs.skip != 'true'
env:
VERSION: ${{ steps.resolve.outputs.version }}
TAG: ${{ steps.resolve.outputs.tag }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
set -euo pipefail
# Always pass --tag explicitly. Defense in depth: even if VERSION is
# accidentally an older release, the historic tag will NOT claim `@latest`.
npm stage publish --provenance --access public --tag "$TAG"
{
echo "## 📦 omniroute@$VERSION STAGED (not yet installable)"
echo ""
echo "The exact bytes are parked on the registry. To release them:"
echo '```'
echo "npm stage list omniroute # find the stage id"
echo "npm stage approve <id> # owner 2FA — THE publish"
echo '```'
echo "To verify the staged bytes first: npm stage download <id> → run"
echo "scripts/check/check-pack-boot.mjs against them (see RELEASE_CHECKLIST)."
echo "To discard: npm stage reject <id>."
} >> "$GITHUB_STEP_SUMMARY"
echo "✅ Staged omniroute@$VERSION (dist-tag=$TAG) — awaiting owner 'npm stage approve'"
# --ignore-scripts: prepublishOnly would re-run build:cli-api && build:cli,
# rebuilding bytes this job has already built, validated and boot-smoked.
npm pack --ignore-scripts
TARBALL="omniroute-${VERSION}.tgz"
test -f "$TARBALL" || { echo "expected $TARBALL to exist after npm pack" >&2; ls -la ./*.tgz || true; exit 1; }
echo "packed $TARBALL ($(du -h "$TARBALL" | cut -f1))"
- name: Publish to npm (DIRECT — emergency fallback)
if: steps.resolve.outputs.skip != 'true' && github.event_name == 'workflow_dispatch' && inputs.publish_mode == 'direct'
env:
VERSION: ${{ steps.resolve.outputs.version }}
TAG: ${{ steps.resolve.outputs.tag }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
set -euo pipefail
npm publish --provenance --access public --tag "$TAG"
echo "✅ Published omniroute@$VERSION (dist-tag=$TAG) [DIRECT mode]"
- name: Hand the tarball to the hosted publish job
if: steps.resolve.outputs.skip != 'true'
uses: actions/upload-artifact@v7
with:
name: npm-tarball
path: omniroute-${{ steps.resolve.outputs.version }}.tgz
retention-days: 1
if-no-files-found: error
- name: Publish to GitHub Packages
if: steps.resolve.outputs.skip != 'true'
@@ -338,6 +374,117 @@ jobs:
|| echo "⚠️ omniroute@${VERSION} might already be published on GitHub Packages."
echo "✅ Action finished for GitHub Packages"
# npm REFUSES `--provenance` from a self-hosted runner:
# 422 Unprocessable Entity - Error verifying sigstore provenance bundle:
# Unsupported GitHub Actions runner environment: "self-hosted".
# Only "github-hosted" runners are supported when publishing with provenance.
# v3.8.49 published fine because it predates USE_VPS_RUNNER being turned on
# (2026-08-02); v3.8.50 was the first release after it, so this had been latent
# for four weeks. Dropping --provenance was not an option: 3.8.49 carries a
# SLSA attestation and 3.8.50 must not regress that.
# The `publish` job cannot simply move to a hosted runner either — 16 GB is not
# enough for build:cli's next-build fallback. So it keeps proving the bytes and
# this job, which needs no memory at all, performs the upload.
stage-npm:
needs: publish
if: needs.publish.outputs.skip != 'true'
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # npm provenance — the whole reason this job is separate
steps:
- name: Download the tarball the publish job proved
uses: actions/download-artifact@v8
with:
name: npm-tarball
path: .
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: ${{ env.NPM_PUBLISH_NODE_VERSION }}
registry-url: https://registry.npmjs.org
- name: Ensure npm supports staged publishing
run: |
set -euo pipefail
CUR=$(npm --version)
if ! node -e "const [a,b]='$(npm --version)'.split('.').map(Number); process.exit(a>11||(a===11&&b>=15)?0:1)"; then
# Pinned exact version (supply-chain: never float @latest in a publish
# job); bump deliberately when a newer npm is required.
echo "npm $CUR < 11.15 — installing pinned npm 11.15.0 for staged publishing"
npm install -g --ignore-scripts npm@11.15.0
fi
npm --version
# Trusted Publishing (OIDC): npm mints a short-lived credential for THIS run from
# GitHub's id-token — no NPM_TOKEN secret, no 2FA prompt, provenance included, and
# it is the bypass npm sanctions now that tokens which skip 2FA are being retired
# (gh.io/npm-gat-bypass2fa-deprecation). Requires the package's Trusted Publisher to
# be configured on npmjs.com (owner: diegosouzapw/OmniRoute, workflow
# npm-publish.yml) and a github-hosted runner — which is why this job exists.
# Without that configuration `npm publish` fails with ENEEDAUTH: re-dispatch with
# publish_mode=staged or direct. Automatic publishing was the flow up to v3.8.48;
# v3.8.49 moved to staged (WS1.3) to keep a leaked token from publishing alone —
# OIDC gives the same guarantee without the manual approve.
- name: Publish to npm (Trusted Publishing / OIDC — automatic)
if: github.event_name != 'workflow_dispatch' || inputs.publish_mode == 'auto'
env:
VERSION: ${{ needs.publish.outputs.version }}
TAG: ${{ needs.publish.outputs.tag }}
run: |
set -euo pipefail
TARBALL="omniroute-${VERSION}.tgz"
test -f "$TARBALL" || { echo "tarball $TARBALL did not arrive from the publish job" >&2; ls -la; exit 1; }
# Deliberately NO NODE_AUTH_TOKEN in this step: npm >= 11.5 detects the GitHub
# OIDC token itself. Always pass --tag explicitly (defense in depth: an older
# VERSION can never claim `@latest`).
npm publish "$TARBALL" --provenance --access public --tag "$TAG" --ignore-scripts
echo "✅ Published omniroute@$VERSION (dist-tag=$TAG) via Trusted Publishing"
- name: Publish to npm (staged — owner approves with 2FA)
# Only on an explicit request now: Trusted Publishing below is the default.
if: github.event_name == 'workflow_dispatch' && inputs.publish_mode == 'staged'
env:
VERSION: ${{ needs.publish.outputs.version }}
TAG: ${{ needs.publish.outputs.tag }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
set -euo pipefail
TARBALL="omniroute-${VERSION}.tgz"
test -f "$TARBALL" || { echo "tarball $TARBALL did not arrive from the publish job" >&2; ls -la; exit 1; }
# Always pass --tag explicitly. Defense in depth: even if VERSION is
# accidentally an older release, the historic tag will NOT claim `@latest`.
# --ignore-scripts: publishing a built tarball must never re-run
# prepublishOnly (build:cli-api && build:cli) on this small runner.
npm stage publish "$TARBALL" --provenance --access public --tag "$TAG" --ignore-scripts
{
echo "## 📦 omniroute@$VERSION STAGED (not yet installable)"
echo ""
echo "The exact bytes are parked on the registry. To release them:"
echo '```'
echo "npm stage list omniroute # find the stage id"
echo "npm stage approve <id> # owner 2FA — THE publish"
echo '```'
echo "To verify the staged bytes first: npm stage download <id> → run"
echo "scripts/check/check-pack-boot.mjs against them (see RELEASE_CHECKLIST)."
echo "To discard: npm stage reject <id>."
} >> "$GITHUB_STEP_SUMMARY"
echo "✅ Staged omniroute@$VERSION (dist-tag=$TAG) — awaiting owner 'npm stage approve'"
- name: Publish to npm (DIRECT — emergency fallback)
if: github.event_name == 'workflow_dispatch' && inputs.publish_mode == 'direct'
env:
VERSION: ${{ needs.publish.outputs.version }}
TAG: ${{ needs.publish.outputs.tag }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
set -euo pipefail
TARBALL="omniroute-${VERSION}.tgz"
test -f "$TARBALL" || { echo "tarball $TARBALL did not arrive from the publish job" >&2; ls -la; exit 1; }
npm publish "$TARBALL" --provenance --access public --tag "$TAG" --ignore-scripts
echo "✅ Published omniroute@$VERSION (dist-tag=$TAG) [DIRECT mode]"
publish-opencode-plugin:
runs-on: ubuntu-latest
permissions:

View File

@@ -61,13 +61,25 @@ jobs:
name: Build (advisory)
needs: changes
# FORK PRs ONLY. build.yml's `Fast Production Build` triggers on `push: branches: ["**"]`
# (#11946, 2026-08-29: build.yml is now workflow_dispatch-only — the hosted runner cannot
# build this tree in any profile, 8/8 recent fork PRs included — so own-origin PRs rely on
# ci.yml `Build` after merge to main and on nightly-release-green for release/**.)
# and runs `build:release` — a superset of this job — so for an own-origin branch this job
# was building the same tree twice. A fork contributor pushes to THEIR repo, so that push
# never fires here, and this is the only pre-merge build signal they get. Measured
# 2026-08-14: 72 of the last 100 PRs into release/** came from forks, so the fork case is
# the majority of the traffic, not the exception — this job earns its place, it just should
# not duplicate build.yml for the own-origin 28%.
if: ${{ github.event_name != 'pull_request' || ((github.event.pull_request.draft == false || startsWith(github.head_ref, 'mergify/merge-queue/')) && needs.changes.outputs.code == 'true' && github.event.pull_request.head.repo.full_name != github.repository) }}
# Disabled 2026-08-29 (#11976 follow-up). `continue-on-error: true` still
# reports a GitHub check FAILURE, so every fork PR into release/** was born
# with a red "Build (advisory)" even when every required gate was green
# (sweep-reds, 41 PRs). Hosted ubuntu-latest cannot finish `npm run build`
# on this tree — VM shutdown ~6 min in, same class as build.yml going
# workflow_dispatch-only in #11962. Pre-merge build signal for release/**
# is nightly-release-green (omni-build); for main it is ci.yml `Build`.
# Restore this job when a runner that actually fits the tree is wired here.
# Bare `false` (not `${{ false }}`) — zizmor obfuscation flags the expression form.
if: false
# PINNED to hosted — this was the last job in THIS workflow still on the USE_VPS_RUNNER
# switch (ci.yml's Build, nightly-release-green and npm-publish keep it, so the variable
# stays meaningful), and with USE_VPS_RUNNER=true it produced NO signal at all here.
@@ -189,8 +201,11 @@ jobs:
.eslintcache
.eslintcache-complexity
key: eslint-${{ runner.os }}-${{ hashFiles('eslint.config.mjs', 'eslint.complexity-ratchets.config.mjs', 'config/quality/eslint-suppressions.json', 'package-lock.json') }}
restore-keys: |
eslint-${{ runner.os }}-
# No restore-keys fallback on purpose (#11600, P-II.1 of the v3.8.50 postmortem): a
# cache built under a different suppressions file / lint config / lockfile reports
# stale per-file verdicts, which is exactly how 215 pre-existing errors stayed
# invisible for a whole cycle. Exact key or a cold full lint (~13 min) — never a
# partial cache from another configuration.
# Security scanners — same hardened install as ci.yml quality-extended
# (gh release download = authenticated, 5000 req/hr; curl to api.github.com
# is rate-limited to 60/hr and silently no-ops when throttled). The blocking
@@ -269,11 +284,11 @@ jobs:
run: |
set -uo pipefail
gates=(
provider-consistency fetch-targets deps file-size error-helper
provider-consistency provider-asset-provenance fetch-targets deps file-size error-helper
migration-numbering public-creds db-rules known-symbols
route-guard-membership test-discovery test-runner-api
mutation-test-coverage any-budget:t11 build-scope pack-policy
complexity-ratchets
complexity-ratchets model-lifecycle
cycles lockfile duplication dead-code type-coverage compression-budget
# #8781: open-sse workspace typecheck gate — the workspace imports @/ which
# escapes to src/ via undeclared path aliases. See check-open-sse-typecheck.mjs.
@@ -460,6 +475,12 @@ jobs:
# cache restore saturating the link), while the tests themselves tied, 2m54 vs 2m31. So
# self-hosted is strictly worse here and there is nothing to configure.
runs-on: ubuntu-latest
# A shard finishes in ~10 min. Without a ceiling a hung test process holds the PR for
# GitHub's 6 h default: on 2026-08-28 shard 1/4 sat 64 min without a line of output
# (twice, same spot — a timing race, gone on the third run) while the other three
# shards were long green. 30 min = 3x the normal wall-clock; a shard that needs more
# is a hang, not a slow run, and a fast red with a re-run beats a silent 6 h hold.
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
@@ -524,8 +545,11 @@ jobs:
.eslintcache
.eslintcache-complexity
key: eslint-${{ runner.os }}-${{ hashFiles('eslint.config.mjs', 'eslint.complexity-ratchets.config.mjs', 'config/quality/eslint-suppressions.json', 'package-lock.json') }}
restore-keys: |
eslint-${{ runner.os }}-
# No restore-keys fallback on purpose (#11600, P-II.1 of the v3.8.50 postmortem): a
# cache built under a different suppressions file / lint config / lockfile reports
# stale per-file verdicts, which is exactly how 215 pre-existing errors stayed
# invisible for a whole cycle. Exact key or a cold full lint (~13 min) — never a
# partial cache from another configuration.
- name: ESLint (baseline congelado — warning novo = vermelho)
# lint:json writes the report; --max-warnings 0 keeps no-new-warnings policy.
run: npm run lint:json -- --max-warnings 0

View File

@@ -4,12 +4,15 @@ on:
schedule:
- cron: "27 7 * * 1"
push:
branches: ["main"]
# Scorecard only accepts the DEFAULT branch — here the active release/vX.Y.Z,
# not `main`. The job below guards on it so a push to any other branch skips.
branches: ["main", "release/**"]
permissions: read-all
jobs:
analysis:
if: ${{ github.event_name != 'push' || github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:

3
.gitignore vendored
View File

@@ -293,3 +293,6 @@ docker-compose.yml.bak
# Ad-hoc test sandboxes (never tracked — may contain local DBs)
/.sandbox/
.aider*
# check:install-upgrade work trees (~12 GB, disposable)
/.install-upgrade/

40
.mailmap Normal file
View File

@@ -0,0 +1,40 @@
# .mailmap — canonical author identities for git log/shortlog/blame.
#
# Why this file exists: between 2026-08-13 and 2026-08-26 this checkout carried a
# `git config --local` whose user.name was one contributor's ("Xiangzhe" / @xz-dev)
# and whose user.email was ANOTHER contributor's (@backryun). Every commit produced
# on this machine in that window was therefore signed with @backryun's address —
# 237 commits, all in the -0300 timezone, while @backryun's own work commits from
# +0900 and continued normally throughout. The local override was removed on
# 2026-08-26; this file repairs the RECORD without rewriting published history
# (those commits live on release/v3.8.50 and release/v3.8.51, which other sessions
# and open PRs build on — a rewrite would force-push both and orphan the v3.8.50 tag).
#
# Format: Canonical Name <canonical@email> Commit Name <commit@email>
# --- Maintainer: several addresses used over the project's life ---
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> <diegosouza.pw@gmail.com>
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> <diegosouza.pw@outlook.com>
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> <diegosouzapw@users.noreply.github.com>
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Diego Souza <8016841+diegosouzapw@users.noreply.github.com>
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> <diego.souza.pw@gmail.com>
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> <souzamiriamrodrigues790@gmail.com>
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> <diego.souza@cdwasolutions.com.br>
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> <diegosouzapw@devbox.local>
# --- The misattribution window: name Xiangzhe + @backryun's email, from -0300.
# These are maintainer/session commits, NOT @backryun's contributions.
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Xiangzhe <bakryun0718@proton.me>
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Xiangzhe <diegosouza.pw@gmail.com>
# --- Xiangzhe (@xz-dev) — a distinct contributor; keep their own work intact ---
Xiangzhe <32761048+xz-dev@users.noreply.github.com> <xz-dev@users.noreply.github.com>
Xiangzhe <32761048+xz-dev@users.noreply.github.com> <xiangzhedev@gmail.com>
# --- @backryun's own alternate addresses (their real work, kept intact) ---
backryun <24198422+backryun@users.noreply.github.com> <bakryun0718@proton.me>
backryun <24198422+backryun@users.noreply.github.com> <backryun@daonlab.local>
backryun <24198422+backryun@users.noreply.github.com> <busan011@ormbiz.co.kr>
backryun <24198422+backryun@users.noreply.github.com> <backryun@users.noreply.github.com>

View File

@@ -19,4 +19,12 @@
# Keep this list SHORT and reviewed every release. Prefer fixing (rebuild on a
# patched base / bump the dep) over suppressing. Stale entries are debt.
#
# (No accepted-risk suppressions at present — ignore-unfixed covers the noise.)
# CVE-2025-68121 — Go stdlib crypto/tls (session-resumption certificate validation)
# inside the PREBUILT bogdanfinn/tls-client v1.15.1 .so that tls-client-node's
# postinstall downloads (built with go 1.24.1; fixed in 1.24.13). No upstream
# rebuild exists (v1.15.1 is still the latest release) and nothing in this repo
# can bump it. The binary is only loaded by the browser-TLS web-provider
# executors (claude-web / grok-web / lmarena / perplexity-web / notion-web),
# whose handshakes go through utls. Tracking issue: #12084. Revisit at the next
# tls-client release or base-image bump and BEFORE the v3.8.51 tag (2026-09-15).
CVE-2025-68121

View File

@@ -23,7 +23,7 @@
"scripts": {
"build": "tsup",
"clean": "rm -rf dist",
"test": "node --import tsx/esm --test tests/scaffold.test.ts tests/auth.test.ts tests/options-schema.test.ts tests/multi-instance.test.ts tests/fetch-interceptor.test.ts tests/provider.test.ts tests/gemini-sanitize.test.ts tests/combos.test.ts tests/config-shim.test.ts tests/features.test.ts tests/feature-defaults.test.ts tests/usable-combo.test.ts tests/disk-snapshot-perms.test.ts tests/fork-features.test.ts tests/auto-combo-context.test.ts tests/provider-id-routing.test.ts tests/management-read-token.test.ts tests/auto-sync.test.ts tests/model-allowlist.test.ts tests/log-level.test.ts tests/effort-tier-variants.test.ts",
"test": "node --import tsx/esm --test tests/scaffold.test.ts tests/auth.test.ts tests/options-schema.test.ts tests/multi-instance.test.ts tests/fetch-interceptor.test.ts tests/provider.test.ts tests/gemini-sanitize.test.ts tests/combos.test.ts tests/config-shim.test.ts tests/features.test.ts tests/feature-defaults.test.ts tests/usable-combo.test.ts tests/disk-snapshot-perms.test.ts tests/fork-features.test.ts tests/auto-combo-context.test.ts tests/provider-id-routing.test.ts tests/management-read-token.test.ts tests/auto-sync.test.ts tests/model-allowlist.test.ts tests/log-level.test.ts tests/effort-tier-variants.test.ts tests/naming.test.ts tests/free-budget-magnitude.test.ts",
"prepublishOnly": "npm run clean && npm run build && npm test"
},
"keywords": [

View File

@@ -23,27 +23,11 @@ const ALIAS_UPPER_MAX_CHARS = 5;
// ── Auto Combo Types ─────────────────────────────────────────────────────
export type AutoVariant =
| "coding"
| "fast"
| "cheap"
| "offline"
| "smart"
| "lkgp";
export type AutoVariant = "coding" | "fast" | "cheap" | "offline" | "smart" | "lkgp";
export const AUTO_VARIANTS: AutoVariant[] = [
"coding",
"fast",
"cheap",
"offline",
"smart",
"lkgp",
];
export const AUTO_VARIANTS: AutoVariant[] = ["coding", "fast", "cheap", "offline", "smart", "lkgp"];
export const AUTO_VARIANT_DESCRIPTIONS: Record<
AutoVariant | "default",
string
> = {
export const AUTO_VARIANT_DESCRIPTIONS: Record<AutoVariant | "default", string> = {
default: "Best provider via scoring",
coding: "Quality-first for code tasks",
fast: "Latency-optimized routing",
@@ -83,24 +67,15 @@ function titleCaseAlias(alias: string): string {
* 3. Neither → undefined.
*/
export function shortProviderLabel(
enrichment:
| { providerDisplayName?: string; providerAlias?: string }
| undefined,
enrichment: { providerDisplayName?: string; providerAlias?: string } | undefined
): string | undefined {
if (!enrichment) return undefined;
const raw =
typeof enrichment.providerDisplayName === "string"
? enrichment.providerDisplayName.trim()
: "";
typeof enrichment.providerDisplayName === "string" ? enrichment.providerDisplayName.trim() : "";
if (raw.length > 0 && raw.length <= PROVIDER_LABEL_MAX_CHARS) return raw;
const alias =
typeof enrichment.providerAlias === "string"
? enrichment.providerAlias.trim()
: "";
const alias = typeof enrichment.providerAlias === "string" ? enrichment.providerAlias.trim() : "";
if (alias.length > 0) {
return alias.length <= ALIAS_UPPER_MAX_CHARS
? alias.toUpperCase()
: titleCaseAlias(alias);
return alias.length <= ALIAS_UPPER_MAX_CHARS ? alias.toUpperCase() : titleCaseAlias(alias);
}
// Long displayName with no alias to fall back on: keep the long label
// rather than dropping the provider prefix entirely.
@@ -131,10 +106,33 @@ export function normaliseFreeLabel(name: string): string {
// ── Free Budget Formatting ────────────────────────────────────────────────
/** Scales, largest first, so the unit is chosen by descending magnitude. */
const TOKEN_UNITS = [
[1e9, "B"],
[1e6, "M"],
[1e3, "K"],
] as const;
/**
* Format a token count as a short magnitude string: `25M`, `1.5K`, `999`.
*
* The unit has to be picked from the value that will actually be *printed*,
* not from the raw input. `toFixed(1)` rounds to the nearest tenth, so at the
* K scale 999_950 and above render as `1000.0` — and by then the M branch has
* already been skipped, producing `1000K` for a number that is `1M`. The same
* carry turns just under a billion into `1000M`. When the rounded value reaches
* the next scale, re-render at that scale instead.
*/
function fmtTokens(n: number): string {
if (n >= 1e9) return (n / 1e9).toFixed(1).replace(/\.0$/, "") + "B";
if (n >= 1e6) return (n / 1e6).toFixed(1).replace(/\.0$/, "") + "M";
if (n >= 1e3) return (n / 1e3).toFixed(1).replace(/\.0$/, "") + "K";
for (let i = 0; i < TOKEN_UNITS.length; i++) {
const [scale, suffix] = TOKEN_UNITS[i]!;
if (n < scale) continue;
const value = Number((n / scale).toFixed(1));
// `Number()` also drops a trailing `.0`, which the previous regex did.
if (value < 1000 || i === 0) return `${value}${suffix}`;
const [nextScale, nextSuffix] = TOKEN_UNITS[i - 1]!;
return `${Number((n / nextScale).toFixed(1))}${nextSuffix}`;
}
return String(n);
}
@@ -184,15 +182,11 @@ export function formatFreeBudget(params: {
*/
export function formatAutoComboName(
variant: AutoVariant | undefined,
candidateCount?: number,
candidateCount?: number
): string {
const label = variant
? variant.charAt(0).toUpperCase() + variant.slice(1)
: "Default";
const label = variant ? variant.charAt(0).toUpperCase() + variant.slice(1) : "Default";
const count =
typeof candidateCount === "number" && candidateCount > 0
? ` (${candidateCount}p)`
: "";
typeof candidateCount === "number" && candidateCount > 0 ? ` (${candidateCount}p)` : "";
return `Auto: ${label}${count}`;
}

View File

@@ -0,0 +1,78 @@
/**
* Magnitude-crossover regression for the free-budget suffix
* (`formatFreeBudget` -> `fmtTokens` in @omniroute/opencode-plugin/src/naming.ts).
*
* `fmtTokens` picked its unit from the raw input and then rounded with
* `toFixed(1)`. Rounding can carry a value into the next magnitude *after* that
* branch has been skipped, so 999_950..999_999 rendered as "1000K" rather than
* "1M", and just under a billion rendered as "1000M" rather than "1B".
*
* These budgets are not always round numbers: `monthlyTokens` is derived from the
* remote Radar feed (`tokensPerMonth`) and can be replaced wholesale by a
* user-local override, so the crossover band is reachable with real data.
*
* Kept in its own file rather than added to naming.test.ts so this does not
* collide with the coverage being added for `formatFreeBudget` in #11660.
*/
import { test } from "node:test";
import assert from "node:assert/strict";
import { formatFreeBudget } from "../src/naming.js";
/** `recurring-daily` is the shortest path from a token count to a rendered suffix. */
const daily = (monthlyTokens: number) =>
formatFreeBudget({ freeType: "recurring-daily", monthlyTokens }).replace(" tokens/day", "");
test("fmtTokens: a rounded K value that reaches 1000 is promoted to M", () => {
// 999_950 is the true boundary, not 999_999: toFixed(1) rounds to the nearest
// tenth, so 999.95K is the first value that carries to "1000.0".
assert.equal(daily(999_950), "1M");
assert.equal(daily(999_999), "1M");
});
test("fmtTokens: a rounded M value that reaches 1000 is promoted to B", () => {
assert.equal(daily(999_950_000), "1B");
assert.equal(daily(999_999_999), "1B");
});
test("fmtTokens: values just below the rounding boundary keep their own unit", () => {
// The promotion must not fire early — 999.9K still rounds to 999.9, not 1000.
assert.equal(daily(999_949), "999.9K");
assert.equal(daily(999_499), "999.5K");
assert.equal(daily(999_499_999), "999.5M");
});
test("fmtTokens: ordinary magnitudes are unchanged", () => {
assert.equal(daily(0), "0");
assert.equal(daily(999), "999");
assert.equal(daily(1_000), "1K");
assert.equal(daily(1_500), "1.5K");
assert.equal(daily(1_000_000), "1M");
assert.equal(daily(1_500_000), "1.5M");
assert.equal(daily(25_000_000), "25M");
assert.equal(daily(1_234_567), "1.2M");
assert.equal(daily(1_000_000_000), "1B");
assert.equal(daily(2_500_000_000), "2.5B");
});
test("fmtTokens: B is the top unit, so a carry there has nowhere to go", () => {
// Deliberately pinned: promoting past B would need a unit that does not exist,
// so "1000B" is the intended output rather than an oversight.
assert.equal(daily(999_999_999_999), "1000B");
});
test("formatFreeBudget: the promotion applies to every token-bearing branch", () => {
assert.equal(
formatFreeBudget({ freeType: "recurring-monthly", monthlyTokens: 999_999 }),
"1M tokens/month"
);
assert.equal(
formatFreeBudget({ freeType: "recurring-credit", creditTokens: 999_999 }),
"1M credits"
);
assert.equal(
formatFreeBudget({ freeType: "one-time-initial", creditTokens: 999_999 }),
"1M credits (one-time)"
);
});

View File

@@ -0,0 +1,81 @@
/**
* Tests for `formatFreeBudget` (@omniroute/opencode-plugin/src/naming.ts):
* formats a free-tier model's budget info into a short human-readable
* suffix, branching on `freeType`.
*/
import { test } from "node:test";
import assert from "node:assert/strict";
import { formatFreeBudget, type FreeModelFreeType } from "../src/naming.js";
test("formatFreeBudget: recurring-daily formats tokens/day", () => {
assert.equal(
formatFreeBudget({ freeType: "recurring-daily", monthlyTokens: 25_000_000 }),
"25M tokens/day"
);
});
test("formatFreeBudget: recurring-monthly formats tokens/month", () => {
assert.equal(
formatFreeBudget({ freeType: "recurring-monthly", monthlyTokens: 1_000_000 }),
"1M tokens/month"
);
});
test("formatFreeBudget: recurring-credit formats credits", () => {
assert.equal(
formatFreeBudget({ freeType: "recurring-credit", creditTokens: 10_000_000 }),
"10M credits"
);
});
test("formatFreeBudget: one-time-initial formats credits with (one-time) suffix", () => {
assert.equal(
formatFreeBudget({ freeType: "one-time-initial", creditTokens: 1_000_000 }),
"1M credits (one-time)"
);
});
test("formatFreeBudget: keyless has no token/credit args", () => {
assert.equal(formatFreeBudget({ freeType: "keyless" }), "(keyless)");
});
test("formatFreeBudget: discontinued has no token/credit args", () => {
assert.equal(formatFreeBudget({ freeType: "discontinued" }), "(discontinued)");
});
test("formatFreeBudget: missing token/credit counts default to 0", () => {
assert.equal(
formatFreeBudget({ freeType: "recurring-daily" }),
"0 tokens/day"
);
});
test("formatFreeBudget: unrecognised freeType falls through to the default branch", () => {
// `freeType` is populated from catalog data at runtime, so a value the
// build doesn't know about is reachable even though TypeScript treats the
// `default:` arm as dead code for a well-typed caller.
assert.equal(
formatFreeBudget({ freeType: "some-future-type" as FreeModelFreeType }),
""
);
});
test("formatFreeBudget: sub-1K token count is not abbreviated", () => {
assert.equal(
formatFreeBudget({ freeType: "recurring-daily", monthlyTokens: 500 }),
"500 tokens/day"
);
});
test("formatFreeBudget: the 999_999 rounding wart is fixed — promotes to 1M", () => {
// `toFixed(1)` rounds 999999/1e3 up to "1000.0" before the `>= 1e6` threshold
// check has a chance to apply. fmtTokens now promotes a rounded-up "1000" in
// any unit to the next unit up, so this correctly reads "1M" instead of the
// old "1000K" wart.
assert.equal(
formatFreeBudget({ freeType: "recurring-daily", monthlyTokens: 999_999 }),
"1M tokens/day"
);
});

View File

@@ -46,7 +46,7 @@ Repository map and Reference Documentation sections below.
## Project at a Glance
**OmniRoute** — unified AI proxy/router. One endpoint, 352 LLM providers, auto-fallback.
**OmniRoute** — unified AI proxy/router. One endpoint, 351 LLM providers, auto-fallback.
| Layer | Location | Purpose |
| ------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -56,7 +56,7 @@ Repository map and Reference Documentation sections below.
| Translators | `open-sse/translator/` | Format conversion (OpenAI↔Claude↔Gemini) |
| Transformer | `open-sse/transformer/` | Responses API ↔ Chat Completions |
| Services | `open-sse/services/` | Combo routing, rate limits, caching, etc |
| Database | `src/lib/db/` | SQLite domain modules (159 migrations) |
| Database | `src/lib/db/` | SQLite domain modules (167 migrations) |
| Domain/Policy | `src/domain/` | Policy engine, cost rules, fallback logic |
| MCP Server | `open-sse/mcp-server/` | 110 tools (44 canonical + memory/skill/GitHub/pool/gamification/plugin/Notion/Obsidian/local-corpus/RTK modules), 3 transports (stdio / SSE / Streamable HTTP), 33 scopes |
| A2A Server | `src/lib/a2a/` | JSON-RPC 2.0 agent protocol |
@@ -83,7 +83,7 @@ Client → /v1/chat/completions (Next.js route)
API routes follow a consistent pattern: `Route → CORS preflight → Zod body validation → Optional auth (extractApiKey/isValidApiKey) → API key policy enforcement → Handler delegation (open-sse)`. No global Next.js middleware — interception is route-specific.
**Combo routing** (`open-sse/services/combo.ts`): 19 public strategies (priority, weighted, fill-first, round-robin, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline). Each target calls `handleSingleModel()` which wraps `handleChatCore()` with per-target error handling and circuit breaker checks. The `fusion` strategy is the exception: it fans out to a panel of models in parallel, then a judge model synthesizes one final answer (`open-sse/services/fusion.ts`). See `docs/routing/AUTO-COMBO.md` for the 14-factor Auto-Combo scoring + the full strategy table and `docs/architecture/RESILIENCE_GUIDE.md` for the 3 resilience layers.
**Combo routing** (`open-sse/services/combo.ts`): 19 public strategies (priority, weighted, fill-first, round-robin, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline). Each target calls `handleSingleModel()` which wraps `handleChatCore()` with per-target error handling and circuit breaker checks. The `fusion` strategy is the exception: it fans out to a panel of models in parallel, then a judge model synthesizes one final answer (`open-sse/services/fusion.ts`). See `docs/routing/AUTO-COMBO.md` for the 15-factor Auto-Combo scoring + the full strategy table and `docs/architecture/RESILIENCE_GUIDE.md` for the 3 resilience layers.
---
@@ -197,7 +197,7 @@ baseCooldownMs * 2 ** failureIndex;
The anti-thundering-herd guard prevents concurrent failures on the same connection from
repeatedly extending the cooldown or double-incrementing `backoffLevel`.
Terminal states are not cooldowns. `banned`, `expired`, and `credits_exhausted` are
Terminal states are not cooldowns. `banned`, `expired` (which becomes terminal only after N bounded retries via `EXPIRED_RETRY_MAX`), and `credits_exhausted` are
intended to stay unavailable until credentials/settings change or an operator resets
them. Do not overwrite terminal states with transient cooldown state.
@@ -289,8 +289,7 @@ When creating _any_ validation tests or one-off logic scripts, default to `scrip
### Database
- **Always** go through `src/lib/db/` domain modules — **never** write raw SQL in routes or handlers
- **Never** add logic to `src/lib/localDb.ts` (re-export layer only)
- **Never** barrel-import from `localDb.ts` — import specific `db/` modules instead
- **Never** barrel-import from `localDb.ts` — import specific `src/lib/db/*` modules
- DB singleton: `getDbInstance()` from `src/lib/db/core.ts` (WAL journaling)
- Migrations: `src/lib/db/migrations/` — versioned SQL files, idempotent, run in transactions
@@ -355,8 +354,7 @@ Documentation must describe verified behavior, not plausible behavior.
1. Create `src/lib/db/yourModule.ts` — import `getDbInstance` from `./core.ts`
2. Export CRUD functions for your domain table(s)
3. Add migration in `src/lib/db/migrations/` if new tables needed
4. Re-export from `src/lib/localDb.ts` (add to the re-export list only)
5. Write tests
4. Write tests
### Adding a New MCP Tool
@@ -399,6 +397,9 @@ Documentation must describe verified behavior, not plausible behavior.
- Eval suite: `src/lib/evals/` → docs: `docs/frameworks/EVALS.md`
- Skill (sandbox): `src/lib/skills/` → docs: `docs/frameworks/SKILLS.md`
- Webhook event: `src/lib/webhookDispatcher.ts` → docs: `docs/frameworks/WEBHOOKS.md`
- Log-export destination: add `src/lib/logExport/destinations/<name>.ts` + one line in
`src/lib/logExport/registry.ts` → docs: `docs/frameworks/LOG-EXPORT.md`. The runner, REST layer
and dashboard form all read the registry, so nothing else changes.
---
@@ -411,7 +412,7 @@ For any non-trivial change, read the matching deep-dive first:
| Repo navigation | `docs/architecture/REPOSITORY_MAP.md` |
| Architecture | `docs/architecture/ARCHITECTURE.md` |
| Engineering reference | `docs/architecture/CODEBASE_DOCUMENTATION.md` |
| Auto-Combo (14-factor scoring, 19 strategies) | `docs/routing/AUTO-COMBO.md` |
| Auto-Combo (15-factor scoring, 19 strategies) | `docs/routing/AUTO-COMBO.md` |
| Resilience (3 mechanisms) | `docs/architecture/RESILIENCE_GUIDE.md` |
| Reasoning replay | `docs/routing/REASONING_REPLAY.md` |
| Skills framework | `docs/frameworks/SKILLS.md` |
@@ -424,6 +425,7 @@ For any non-trivial change, read the matching deep-dive first:
| Evals | `docs/frameworks/EVALS.md` |
| Compliance / audit | `docs/security/COMPLIANCE.md` |
| Webhooks | `docs/frameworks/WEBHOOKS.md` |
| Log export (call logs → BigQuery/…) | `docs/frameworks/LOG-EXPORT.md` |
| Authorization pipeline | `docs/architecture/AUTHZ_GUIDE.md` |
| Stealth (TLS / fingerprint) | `docs/security/STEALTH_GUIDE.md` |
| Agent protocols (A2A / ACP / Cloud) | `docs/frameworks/AGENT_PROTOCOLS_GUIDE.md` |
@@ -594,6 +596,18 @@ inside your feature branch (a base-red fix is its own freeze-gated `fix/release-
PR); and if you must open a PR anyway, add `⚠️ base-red inherited: #<issue>` to the PR body so
reviewers and CI babysitters do not chase ghosts.
### Sync-back landings are fast-forward, never squash
A `main → release/vX+1` sync-back (Phase 5 of `/generate-release`, or any later "bring main's
post-release commits over" PR) must reach the release branch as the merge commit it already is:
`git merge-base --is-ancestor origin/release/vX+1 <head>` then
`git push origin <head>:refs/heads/release/vX+1` (GitHub marks the PR merged). Squash-merging it
drops `main` from the release branch's ancestry and the next sync-back re-conflicts on every file
main touched (551 conflicts on the v3.8.50 → v3.8.51 sync before the two-step merge). After
landing, `git merge-base --is-ancestor origin/main origin/release/vX+1` must be true — and check
that `config/quality/eslint-suppressions.json` / `quality-baseline.json` carried main's freezes
(they merge as "ours" silently). Details: `.agents/skills/generate-release/phases/phase-5-next-cycle.md`.
---
## Upstream contributions
@@ -645,6 +659,10 @@ procedures are in [`docs/architecture/QUALITY_GATES.md`](docs/architecture/QUALI
`npm run quality:ratchet -- --update` when a metric genuinely improves.
- Job `test-vitest` runs `npm run test:vitest` (MCP tools, autoCombo, cache) — blocking.
`test:vitest:ui` has been blocking since PR #7127.
- **Velocity phase (2026-08-30 → v4.0)**: every numeric baseline is loosened by 20% and
`--require-tighten` is advisory (`quality-baseline.json` → `_policy`); the nightly
`baseline-headroom` job tracks how much of the budget is left in the issue
"📈 Baseline headroom". See `docs/architecture/QUALITY_GATES.md` → "Velocity phase".
**Allowlist policy (short form):** Fix the cause; use the allowlist only for pre-existing
violations you cannot fix in the same PR. Add a comment with justification + issue number.
@@ -656,7 +674,7 @@ the stale-enforcement added in Fase 6A.3.
## Hard Rules
1. Never commit secrets or credentials
2. Never add logic to `localDb.ts`
2. Never barrel-import from `localDb.ts` — import specific `src/lib/db/*` modules
3. Never use `eval()` / `new Function()` / implied eval
4. Never commit directly to `main`
5. Never write raw SQL in routes — use `src/lib/db/` modules

View File

@@ -89,10 +89,83 @@
- **feat(cli):** run `omniroute serve --tray` as a detached desktop process after server and tray readiness, with graphical login auto-start support.
- **feat(routing):** add client-, provider-, and model-neutral exclusive managed session connection leases with API-key-bound generation fencing, durable SQLite ownership, explicit allowlist policy, and bounded 429 capacity retry semantics.
## [3.8.51] — TBD
_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
### ✨ New Features
### 🐛 Bug Fixes
### 📝 Maintenance
---
## [3.8.50] — 2026-08-25
_Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._
### 📊 Release by the numbers
| | |
| --- | ---: |
| 👥 People who contributed | **248** |
| 📝 Commits in the cycle | **1,714** |
| 🔀 Pull requests referenced | **1,666** |
| 📋 Changelog entries | **1,182** |
| 🙌 Contributors credited in entries | **256** |
| 🤖 Automated dependency commits | 22 |
**Entries by type**
| Type | Count |
| --- | ---: |
| 🐛 Fixes | 779 |
| ✨ Features | 169 |
| 📚 Docs | 29 |
| 🧹 Chore | 27 |
| 🧪 Tests | 15 |
| ♻️ Refactor | 5 |
| ⚡ Performance | 3 |
| providers | 2 |
| 🔒 Security | 2 |
| ⚙️ CI | 2 |
| deps | 2 |
| maint | 2 |
### 🏆 Top 25 contributors this cycle
_By commits in `ed2db6cb19..v3.8.50`, author identities consolidated via `.mailmap`. Bots excluded._
| # | Contributor | Commits |
| ---: | --- | ---: |
| 🥇 | diegosouzapw | 738 |
| 🥈 | backryun | 88 |
| 🥉 | Dizzle | 66 |
| 4 | Ravi Tharuma | 52 |
| 5 | Markus Hartung | 48 |
| 6 | Bob.Hou | 42 |
| 7 | Rouzbeh† | 38 |
| 8 | Xiangzhe | 31 |
| 9 | Paco Cartones | 28 |
| 10 | Nguyen Thanh Dat | 23 |
| 11 | Aman | 22 |
| 12 | Will Gordon | 19 |
| 13 | 小妍儿 ✨ | 17 |
| 14 | adevwithpurpose | 16 |
| 15 | Andrew B. | 10 |
| 16 | NOXX - Commiter | 10 |
| 17 | ignamiranda | 10 |
| 18 | Jonathan Bailey | 9 |
| 19 | Ke Jin | 9 |
| 20 | Austin Liu | 8 |
| 21 | Chewji | 8 |
| 22 | Prudhvi Vuda | 7 |
| 23 | benzntech | 7 |
| 24 | rinseaid | 7 |
| 25 | stanley | 7 |
### ✨ New Features
- **feat(search):** first-class X Search provider (`x-search`) on `POST /v1/search` and MCP `omniroute_x_search` using SuperGrok / xAI server-side `x_search`. Explicit provider or `search_type: "x"` only — never auto-selected for web. Reuses `xai-oauth` / `xao` / `xai` credentials. Not the X Developer Platform MCP. ([#10985](https://github.com/diegosouzapw/OmniRoute/issues/10985))
- **feat(core):** add Layer A capability filter at router (#5696)
@@ -1111,6 +1184,12 @@ _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `e
- **fix(models):** health-check-excluded models are hidden from the `/v1/models` catalog ([#10026](https://github.com/diegosouzapw/OmniRoute/issues/10026) — thanks @ritheshcn25)
- **fix(skills):** the CLI skills left stale by the quota subcommands are regenerated ([#10698](https://github.com/diegosouzapw/OmniRoute/issues/10698))
- **fix(mcp):** CLI MCP call protocol issues were resolved ([#10960](https://github.com/diegosouzapw/OmniRoute/issues/10960) — thanks @YunyunZhai)
- **fix(dashboard):** expert mode in the Combo Builder can type a provider/model pair by hand again ([#8875](https://github.com/diegosouzapw/OmniRoute/pull/8875)) — [#8285](https://github.com/diegosouzapw/OmniRoute/pull/8285) (global model search) replaced the "Manual model" block positionally with the new search panel, removing the only way to enter a model that is not in the catalog. The state and handlers behind it survived as dead code, so neither typecheck nor lint noticed the loss. The block is restored above the search panel, unchanged from its pre-#8285 form, and guarded by `tests/e2e/combos-flow.spec.ts` ("expert mode shows a single-page combo form with manual model entry").
- **fix(sse):** a combo step pinned to an explicit connection (or a request pinned with `x-omniroute-connection`) is honored on fallback instead of silently rotating to a sibling account ([#8875](https://github.com/diegosouzapw/OmniRoute/pull/8875)) — the generic account-fallback branch excluded the pinned connection after an upstream failure and re-selected another account of the same provider, so a priority combo repeating one provider/model with two different fixed accounts ran **both** attempts under the first step; the second step and its own pin never executed, and per-step attribution (`comboStepId` / `comboExecutionKey`) was wrong. Rotation is now gated on there being no forced connection, matching the stream-readiness, pre-response-timeout and account-semaphore branches. Cooldown recording is unchanged, and unpinned selection still skips burned connections.
- **fix(cli):** the local CLI sees the full `/api/monitoring/health` payload again — `version` included — restoring the `check:pack-boot` release gate. [#11040](https://github.com/diegosouzapw/OmniRoute/pull/11040) reduced that route to a liveness-only view for non-management callers (GHSA-mvf8-qc78-5mxm), but the route is classified PUBLIC and `runAuthzPipeline` strips the machine-token header for every route class — so the PUBLIC policy stamped `anonymous` and the loopback CLI could never be recognized as a management principal. The PUBLIC policy now stamps the same loopback-gated `local-cli-token` subject the MANAGEMENT policy already did; anonymous callers still get liveness only.
- **fix(search):** a configured search connection (Serper, Brave, Tavily…) is now used instead of being silently passed over for the free `duckduckgo-free` fallback ([#11524](https://github.com/diegosouzapw/OmniRoute/issues/11524)) — when no provider was named explicitly and the cheapest auto-selected one had no credentials, the last-resort loop ran first and `duckduckgo-free` (cost 0, no auth) always won it with empty credentials. On `/v1/responses` the call then returned `success: true` with **zero results**, so web search looked healthy while the paid connection the operator had set up was never called. Credentialed regular providers are now swept before the last-resort loop, and fallback-only providers can no longer outrank a configured one on price.
- **fix(api):** `/v1/models` no longer blocks the stale response while it rebuilds the catalog ([#11551](https://github.com/diegosouzapw/OmniRoute/issues/11551)) — the route has been passing a `scheduleBackgroundRefresh` option since #10198, but the parameter had already been removed from `getUnifiedModelsResponse()`, so the object was silently dropped and the stale-while-revalidate rebuild still ran on a `setTimeout(..., 0)`. The builder is essentially synchronous under the App Router, so it pinned the event loop **before** the cached response was flushed — the [#8728](https://github.com/diegosouzapw/OmniRoute/pull/8728) guarantee did not actually exist for operators with large catalogs. `catalogCache` now schedules through `after()` (with a macrotask fallback outside a request scope) and the option is threaded end to end. The extra argument was invisible to CI because `typecheck:core` is a curated allowlist and `next.config.mjs` sets `ignoreBuildErrors: true`.
- **fix(sse):** a universal handoff whose summary comes back unusable is no longer regenerated on every single model switch, which was burning paid quota on upstream calls whose answers were thrown away ([#11552](https://github.com/diegosouzapw/OmniRoute/issues/11552)) — nothing is persisted when the summary does not parse, so the next switch in the same session re-issued the same full-history summarization request and discarded it again, forever. With a switch-heavy combo strategy (weighted, random, round-robin, p2c) that landed on a large share of requests: measured at n=200, roughly **one request in four carried an extra discarded upstream call**, and that traffic skewed a weighted 70/30 combo to an observed 0.895 share for one provider even though the share actually delivered to the client was a correct 0.70. There is now an exponential back-off per (session, combo) — 5 min up to 1 h, cleared on the first successful handoff, capped at 500 tracked keys. A transient upstream failure is deliberately **not** tracked, so it still retries immediately. After the fix: 201 upstream calls for 200 requests, and the measured share matches the delivered one.
### 📝 Maintenance
@@ -1355,6 +1434,10 @@ _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `e
- **docs(i18n):** localization contributions — a complete Persian user guide ([#11254](https://github.com/diegosouzapw/OmniRoute/issues/11254)), improved and completed Turkish documentation ([#11237](https://github.com/diegosouzapw/OmniRoute/issues/11237)), the Italian README restored ([#11246](https://github.com/diegosouzapw/OmniRoute/issues/11246)), a Farsi README ([#10777](https://github.com/diegosouzapw/OmniRoute/issues/10777) — thanks @farshidrezaei), a `SETUP_GUIDE.md` correction ([#10490](https://github.com/diegosouzapw/OmniRoute/issues/10490) — thanks @realize000), a `python_requests.py` example fix ([#10731](https://github.com/diegosouzapw/OmniRoute/issues/10731) — thanks @pandaaaa1990), and a retranslation of the CLI reference and integrations guide across all 42 locales
- **chore(repo):** repository hygiene — the self-referential `_tasks` symlink was untracked and `.gitignore` anchored so a `_tasks` symlink can never be tracked again, `.source/dynamic.ts`, `.source`, `/output/` and the Playwright CLI artifact directory were ignored, an initial `.cbmignore` was added for codebase-memory indexing, unused `.source/dynamic.ts` and `source.config.mjs` files were removed, the stray unresolved conflict marker in `ENVIRONMENT.md` was cleaned up, and the Open Collective sponsorship link was removed from the README
- **chore(release):** localized `llm.txt` mirrors and the v3.8.50 base quality docs were synchronized, and the 363 `changelog.d` fragments were aggregated into this section
- **fix(ci):** the pack-artifact provenance gate now checks the branch under test instead of `origin/main` ([#8875](https://github.com/diegosouzapw/OmniRoute/pull/8875)) — the guard added for [#10427](https://github.com/diegosouzapw/OmniRoute/issues/10427) is correct at publish time (that workflow runs on `main`), but in a `pull_request` context the head is by construction not an ancestor of `main` and the shallow checkout never fetches it, so the probe always answered false and the job failed 100% of the time. It became visible only once it stopped being cancelled behind Build. Pre-merge it resolves the ref from `refs/pull/<N>/head`, which exists on origin even for fork PRs.
- **test(dashboard):** the proxy-registry e2e smoke flow opens the toolbar's "More actions" overflow menu before clicking bulk assign ([#8875](https://github.com/diegosouzapw/OmniRoute/pull/8875)) — [#9870](https://github.com/diegosouzapw/OmniRoute/pull/9870) moved the action into that menu, which only renders its items while open, so the locator never resolved and the test burned its full 180 s budget. Every existing assertion is unchanged.
- **test(api):** the `/v1/models` e2e check accepts the auth gate introduced by [#9320](https://github.com/diegosouzapw/OmniRoute/pull/9320) ([#8875](https://github.com/diegosouzapw/OmniRoute/pull/8875)) — the catalog now requires auth whenever management auth is configured, and the harness boots with `INITIAL_PASSWORD` set, so the endpoint had been answering 401 since 2026-08-04 and the check was red the whole time, hidden behind a cancelled job. It now mirrors the sibling `/api/providers` check: assert the catalog shape when the catalog is served, otherwise pin the gate by status **and** error type so an unrelated 401 cannot pass for the deliberate one.
- **chore(quality):** refreshed the combos-page ESLint suppressions after the manual-model restore ([#8875](https://github.com/diegosouzapw/OmniRoute/pull/8875)) — three `no-unused-vars` suppressions existed only because #8285 had deleted the JSX consuming that state; with the block back they are live again, and a stale suppression makes ESLint exit 2, which is what actually turned the Lint job red. The 7 `react-hooks/set-state-in-effect` plus 1 `react-hooks/immutability` errors in the same file are pre-existing (reproducible on the file's `091e2ba4da` content) and surfaced only because touching the file evicted it from the restored `.eslintcache`; they are frozen here and tracked separately rather than refactored mid-release.
### 🙌 Contributors
@@ -3033,10 +3116,6 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
### 🙌 Contributors
Thanks to everyone whose work landed in v3.8.49:

View File

@@ -103,25 +103,11 @@ RUN test -f package-lock.json \
# node-gyp comes from npm's own bundled copy (deterministic, already in the image)
# instead of `npx --yes`, which would install an arbitrary registry version
# on-demand and run its lifecycle scripts (Sonar docker:S6505).
#
# tls-client-node (chatgpt-web/claude-web/grok-web/lmarena/perplexity-web TLS
# impersonation) hits the same --ignore-scripts wall: its own postinstall.js
# fetches a platform .so/.dylib/.dll from the bogdanfinn/tls-client GitHub
# Releases API and is never invoked when npm ci skips lifecycle scripts. Unlike
# better-sqlite3 above, that script never throws on failure — it only
# `console.warn`s and exits 0 — so a rate-limited or offline build would
# otherwise succeed silently with an empty bin/ and only fail at first request
# in production (TlsClientUnavailableError, #7802). Run it explicitly here so
# a broken/rate-limited fetch fails the BUILD loudly instead of shipping a
# broken image.
RUN --mount=type=cache,id=s/92ca8a61-c1ba-421f-a389-d48ac7258c2d-npm-cache,target=/root/.npm \
npm ci --include=optional --no-audit --no-fund --legacy-peer-deps --ignore-scripts \
&& (cd node_modules/better-sqlite3 \
&& node /usr/local/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js rebuild) \
&& node -e "require('better-sqlite3')(':memory:').close()" \
&& node node_modules/tls-client-node/scripts/postinstall.js \
&& (test -n "$(find node_modules/tls-client-node/bin -mindepth 1 -print -quit 2>/dev/null)" \
|| (echo "tls-client-node native binary missing after postinstall — GitHub API fetch likely rate-limited or failed (#7802)" >&2 && exit 1))
&& node -e "require('better-sqlite3')(':memory:').close()"
# Build with Turbopack (stable in Next 16, the repo default). The v3.8.27-era
# TurbopackInternalError panic ("entered unreachable code: there must be a path to a
@@ -184,19 +170,29 @@ ENV NODE_OPTIONS="--max-old-space-size=${OMNIROUTE_BUILD_MEMORY_MB}"
# silently leaving no standalone bundle. Next derives the worker count from
# CIRCLE_NODE_TOTAL (workers = N-1). (#10060)
#
# Lowered 8 → 3 (7 workers → 2). Every page-data worker inherits NODE_OPTIONS
# above, so the ceiling is per PROCESS, not per build: 7 workers on a 16 GB
# GitHub runner (ubuntu-24.04 / ubuntu-24.04-arm, 4 vCPU) exhausted the host and
# buildkit failed the whole step with `ResourceExhausted: ... cannot allocate
# memory`. The compile phase always finished ("✓ Compiled successfully in
# 4.2min"); the kernel killed the build right after "Collecting page data using
# 7 workers". It was intermittent for a while and went 100% on 2026-08-22, which
# is what a threshold being crossed by ordinary codebase growth looks like.
# tests/unit/docker-build-memory-budget.test.ts does the arithmetic and fails if
# either knob is raised past what a 16 GB runner holds. 2 workers also stops
# oversubscribing the runner's 4 vCPU, which 7 did. Override for a big builder:
# `--build-arg OMNIROUTE_BUILD_WORKERS=8`.
ARG OMNIROUTE_BUILD_WORKERS=3
# Lowered 8 → 3 (7 workers → 2) in #11419, then 3 → 2 (2 workers → 1) in #7518.
# Every page-data worker inherits NODE_OPTIONS above, so the ceiling is per
# PROCESS, not per build: 7 workers on a 16 GB GitHub runner (ubuntu-24.04 /
# ubuntu-24.04-arm, 4 vCPU) exhausted the host and buildkit failed the whole
# step with `ResourceExhausted: ... cannot allocate memory`. The compile phase
# always finished ("✓ Compiled successfully in 4.2min"); the kernel killed the
# build right after "Collecting page data using N workers".
#
# #11419's first fix (8 → 3) modeled the per-worker peak as an INFERENCE
# (2560 MB, guessed from "7 workers didn't fit") and assumed the parent
# process's RSS tracked the V8 heap ceiling. Both assumptions were wrong: a
# live VPS reproduction (issue #7518, dmesg OOM-killer report) measured the
# real per-process RSS directly at ~4.5 GB, independent of the NODE_OPTIONS
# heap flag (Turbopack itself is native/Rust, outside the V8 heap) — and it
# applies to the parent process too, not just workers. 2 workers (3 processes
# × 4.5 GB = 13.5 GB) still didn't fit the 12.288 GB (75%) budget on a 16 GB
# runner, matching the still-live publish failures after #11419 merged. 1
# worker (2 processes × 4.5 GB = 9 GB) fits with headroom to spare.
# tests/unit/docker-build-memory-budget.test.ts does the arithmetic against
# the measured figure and fails if either knob is raised past what a 16 GB
# runner holds. Override for a big builder: `--build-arg
# OMNIROUTE_BUILD_WORKERS=8`.
ARG OMNIROUTE_BUILD_WORKERS=2
ENV CIRCLE_NODE_TOTAL=${OMNIROUTE_BUILD_WORKERS}
COPY . ./

View File

@@ -19,7 +19,14 @@ RUN apt-get update \
FROM base AS builder
WORKDIR /app
COPY . .
# Cache dependency layer
COPY package.json bun.lock* pnpm-workspace.yaml* ./
COPY open-sse/package.json ./open-sse/package.json
COPY packages/ ./packages/
# Root postinstall helpers needed during bun install lifecycle
COPY scripts/build/ ./scripts/build/
COPY scripts/dev/sync-env.mjs ./scripts/dev/sync-env.mjs
# Fast Bun native package install
RUN bun install --include=optional --quiet
@@ -29,13 +36,14 @@ RUN if [ -d "node_modules/better-sqlite3" ]; then \
(cd node_modules/better-sqlite3 && bunx node-gyp rebuild); \
fi
# Fetch tls-client-node native binary if script exists
RUN if [ -f "node_modules/tls-client-node/scripts/postinstall.js" ]; then \
bun node_modules/tls-client-node/scripts/postinstall.js || true; \
fi
# Smoke check native database driver used by Bun (bun:sqlite)
RUN bun -e "import { Database } from 'bun:sqlite'; const db = new Database(':memory:'); db.query('SELECT 1 AS ok').get(); db.close(); console.log('bun:sqlite smoke: OK');"
# Disable Turbopack for Bun builder stage (Turbopack V8 internal worker bindings require Node)
ENV OMNIROUTE_USE_TURBOPACK=0
COPY . .
# Turbopack is supported on Bun 1.4 + Next 16.3; override via --build-arg OMNIROUTE_USE_TURBOPACK=0 if needed
ARG OMNIROUTE_USE_TURBOPACK=1
ENV OMNIROUTE_USE_TURBOPACK=${OMNIROUTE_USE_TURBOPACK}
ARG OMNIROUTE_BASE_PATH=""
ENV OMNIROUTE_BASE_PATH=$OMNIROUTE_BASE_PATH
@@ -65,6 +73,7 @@ RUN apt-get update \
libsecret-1-0 \
ca-certificates \
curl \
sqlite3 \
&& rm -rf /var/lib/apt/lists/*
ENV NODE_ENV=production
@@ -76,11 +85,23 @@ ENV DATA_DIR=/app/data
RUN mkdir -p /app/data
COPY --from=builder /app/.build/next/standalone ./
COPY --from=builder /app/node_modules/better-sqlite3 ./node_modules/better-sqlite3
ENV OMNIROUTE_MIGRATIONS_DIR=/app/migrations
COPY --from=builder /app/scripts/dev/healthcheck.mjs ./healthcheck.mjs
# Bun uses bun:sqlite. Remove every standalone/vendor copy of the Node-only
# addon so no traced chunk can dlopen it and abort the process before fallback.
RUN find /app \
-path '*/node_modules/better-sqlite3' \
-prune \
-exec rm -rf '{}' + \
&& test -z "$(find /app -type f -name 'better_sqlite3.node' -print -quit)"
RUN chown -R bun:bun /app /app/data
USER bun
EXPOSE 20128
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
@@ -141,6 +162,7 @@ RUN apt-get update \
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
ENV PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium
# Return to the base image non-root user after the apt install (mirrors the
# Node Dockerfile runner-web stage, which re-asserts USER node).
# Drop back to default non-root user
USER bun
ENTRYPOINT ["bun", "dev/run-standalone.mjs"]

View File

@@ -1,447 +0,0 @@
---
title: "Provider Reference"
version: 3.8.50
lastUpdated: 2026-08-21
---
# Provider Reference
> **Auto-generated** from `src/shared/constants/providers.ts` — do not edit by hand.
> Regenerate with: `npm run gen:provider-reference`
> **Last generated:** 2026-08-21
Total providers: **349**. See category breakdown below.
## Categories
- **Free** — free tier with API key (configured via dashboard)
- **No-auth** — public endpoints that require no key or sign-in at all
- **OAuth** — sign-in flow handled by OmniRoute, no API key needed
- **Web cookie** — wraps the provider's web app via cookie auth
- **API key** — paid provider configured via API key (free credits may apply)
- **Local** — runs on the user's machine (Ollama, LM Studio, vLLM, etc.)
- **Search** — web search providers
- **Audio** — audio-only providers (TTS/STT)
- **Upstream proxy** — providers that proxy to other providers
- **Cloud agent** — long-running coding agents (Codex Cloud, Devin, Jules)
- **System** — OmniRoute-internal providers (loopback, etc.)
Additional tags: `image`, `video`, `aggregator`, `enterprise`, `embed/rerank`, `self-hosted`.
`Tool calling` (where shown): `native` — real function-calling API; `emulated` — the `tools` array is prompt-emulated via `webTools.ts` (regex-parsed `<tool>{...}</tool>` blocks); `none``tools` is currently silently dropped. See #7286.
Use the dashboard at `/dashboard/providers` to enable, configure, and test each provider.
---
## No-auth Providers (no key required) (11)
| ID | Alias | Name | Tags | Website | Notes | Tool calling |
|----|-------|------|------|---------|-------|--------------|
| `aihorde` | `horde` | AI Horde | No-auth | [link](https://aihorde.net) | No API key required — uses AI Horde's documented anonymous key. Adding a free aihorde.net key is optional and only buys higher queue priority (kudos). | — |
| `auggie` | `aug` | Augment (Auggie CLI) | No-auth | [link](https://augmentcode.com) | No API key stored by OmniRoute. Install the Auggie CLI and run `auggie login` on this machine, then OmniRoute spawns it locally for each request. | — |
| `chipotle` | `pepper` | Chipotle Pepper AI (Free) | No-auth | [link](https://amelia.chipotle.com) | No credentials required. Uses Chipotle's public support chatbot via reverse-engineered SockJS/STOMP protocol. | — |
| `cloudflare-playground` | `cfp` | Cloudflare AI Playground | No-auth | [link](https://playground.ai.cloudflare.com) | No credentials required — anonymous browser sessions over a reverse-engineered cf_agent WebSocket protocol (Playwright transport). | — |
| `devin-cli-agentic` | `dva` | Devin CLI Agentic Bridge | No-auth | [link](https://docs.devin.ai/work-with-devin/devin-cli) | Authentication is owned by the official Devin CLI in its isolated bridge volume. | emulated |
| `duckduckgo-web` | `ddgw` | DuckDuckGo AI Chat | No-auth | [link](https://duckduckgo.com/duckchat) | No credentials required — DuckDuckGo AI Chat is anonymous and free. | emulated |
| `felo-web` | `felo` | Felo | No-auth | [link](https://felo.ai) | No credentials required — Felo is a free, no-signup chat/search aggregator. | — |
| `opencode` | `oc` | OpenCode Free | No-auth | [link](https://opencode.ai) | No API key required — uses OpenCode's public free endpoint. | — |
| `theoldllm` | `tllm` | The Old LLM (Free) | No-auth | [link](https://theoldllm.vercel.app) | No credentials required. The executor auto-generates access tokens via an embedded Playwright browser instance. | — |
| `veoaifree-web` | `veo-free` | Veo AI Free | No-auth, video | [link](https://veoaifree.com) | No auth required. Rate limited to 6 requests/hour per IP. | — |
| `zcode` | `zc` | ZCode (GLM Coding Plan) | No-auth | [link](https://zcode.z.ai) | No API key stored by OmniRoute. The local ZCode app-server uses the existing builtin:zai-coding-plan login. | — |
## OAuth Providers (25)
| ID | Alias | Name | Tags | Website | Notes |
|----|-------|------|------|---------|-------|
| `agy` | `agy` | Antigravity CLI | OAuth | [link](https://antigravity.google) | Import your Antigravity CLI (`agy`) login (paste/upload its token file), auto-detect a local CLI login, or sign in with Google. Shares the Antigravity backend (incl. Claude models). |
| `amazon-q` | `aq` | Amazon Q | OAuth | [link](https://aws.amazon.com/q/developer/) | Uses the same AWS Builder ID or imported refresh-token flow as Kiro, but keeps Amazon Q connections separate. |
| `antigravity` | — | Antigravity | OAuth | — | — |
| `claude` | `cc` | Claude Code | OAuth | — | — |
| `cline` | `cl` | Cline | OAuth | — | — |
| `clinepass` | `cp` | ClinePass | OAuth | [link](https://cline.bot/cline-pass) | ClinePass is Cline's $9.99/mo subscription bundling 10 open coding models. Sign in with your Cline account (same login as the Cline CLI/IDE), or paste a direct ClinePass API key (app.cline.bot → Settings → API Keys). A ClinePass subscription unlocks the cline-pass/* models. Reuses the Cline WorkOS OAuth flow. |
| `codebuddy-cn` | `cbcn` | CodeBuddy CN | OAuth | [link](https://copilot.tencent.com) | Tencent CodeBuddy CN (copilot.tencent.com). Sign in via the official CLI device-code flow, or paste a direct API key (sent as Authorization: Bearer). Catalog: GLM / Kimi / MiniMax / DeepSeek / Hunyuan. |
| `codex` | `cx` | OpenAI Codex | OAuth | — | — |
| `cursor` | `cu` | Cursor IDE | OAuth | — | — |
| `devin-cli` | `dv` | Devin CLI | OAuth | [link](https://cli.devin.ai) | Requires the Devin CLI binary. Run `devin auth login` to authenticate, or provide your WINDSURF_API_KEY. Install: https://cli.devin.ai |
| `devin-desktop` | — | Devin Desktop | OAuth | [link](https://devin.ai) | Paste an existing Devin API key from an authenticated Devin session. Key export availability and steps vary by Devin version and account. |
| `ghe-copilot` | `ghe-copilot` | GitHub Enterprise Copilot | OAuth | — | Enter your GHE instance URL (e.g., https://ghe.company.com) in provider settings, then authenticate via device flow. |
| `github` | `gh` | GitHub Copilot | OAuth | — | — |
| `gitlab-duo` | `gitlab-duo` | GitLab Duo | OAuth | [link](https://docs.gitlab.com/user/duo_agent_platform/code_suggestions/) | GitLab Duo OAuth is not configured. Register an OAuth application at https://gitlab.com/-/profile/applications with redirect URI http://localhost:20128/callback and scopes "ai_features read_user", then set GITLAB_DUO_OAUTH_CLIENT_ID (and optionally GITLAB_DUO_OAUTH_CLIENT_SECRET) and restart. |
| `grok-cli` | `gc` | Grok Build | OAuth | — | Sign in with your browser, or paste your ~/.grok/auth.json (or the JWT access token) from the Grok Build CLI; refresh_token is rotated automatically either way. |
| `kilocode` | `kc` | Kilo Code | OAuth | — | — |
| `kimi-coding` | `kmc` | Kimi Code CLI | OAuth | [link](https://www.kimi.com/code?aff=omniroute) | Sign in with the same Kimi account used by Kimi Code CLI. OmniRoute uses the CLI OAuth flow and Kimi Coding Plan endpoints. |
| `kiro` | `kr` | Kiro AI | OAuth | — | Free tier: 50 credits/month (~25K100K tokens). ⚠️ Kiro ToS prohibits third-party proxy/harness use. |
| `openference` | `of` | Openference | OAuth | [link](https://openference.com) | Sign in with your Openference account to route requests through api.openference.com. An active plan is required for inference — OAuth may authenticate but return 402 without one. |
| `qoder` | `if` | Qoder | OAuth | — | — |
| `raycast` | `rc` | Raycast Pro AI | OAuth | [link](https://raycast.com/ai) | Unofficial integration — uses your Raycast Pro subscription via credentials from the macOS app (Auto-Import or manual capture). May break on Raycast updates. Not for redistribution; personal use only. |
| `trae` | `tr` | Trae | OAuth | [link](https://trae.ai) | Trae is an AI-native IDE by ByteDance (SOLO remote agent). Authorize via trae.ai in the popup, or sign in at solo.trae.ai and paste the Cloud-IDE-JWT (sent as 'Authorization: Cloud-IDE-JWT <token>', ~14-day lifetime) as the access token; web_id/biz_user_id/user_unique_id/scope/tenant/region propagate via providerSpecificData. No headless refresh for pasted tokens — re-paste on expiry. |
| `xai-oauth` | `xao` | xAI OAuth (Grok) | OAuth | [link](https://x.ai) | Sign in with xAI to use api.x.ai models such as Grok 4.5. This is separate from Grok Build JWT sessions, which use cli-chat-proxy.grok.com and grok-build model aliases. |
| `zed` | `zd` | Zed IDE | OAuth | [link](https://zed.dev) | Zed stores LLM provider credentials (OpenAI, Anthropic, Google, Mistral, xAI) in the OS keychain. Use the Import button below to discover and import them automatically. |
| `zed-hosted` | — | Zed Hosted Models | OAuth | [link](https://zed.dev) | Sign in with your Zed account (native-app sign-in). OmniRoute generates a one-time RSA keypair and opens zed.dev to authorize it — on a remote/headless install, copy the resulting 127.0.0.1 callback URL from your browser's address bar and paste it back here. Distinct from the 'Zed IDE' credential-import entry above: this proxies chat completions through Zed's own hosted model aggregator (cloud.zed.dev), fronting Anthropic/OpenAI/Google/xAI models under your Zed plan. |
## Web Cookie Providers (35)
| ID | Alias | Name | Tags | Website | Notes | Tool calling |
|----|-------|------|------|---------|-------|--------------|
| `adapta-web` | `adp-web` | Adapta.org (Adapta One Web) | Web cookie | [link](https://agent.adapta.one) | Paste your __client cookie value from .clerk.agent.adapta.one (DevTools → Application → Cookies) | emulated |
| `adobe-firefly` | `firefly` | Adobe Firefly (Image/Video) | Web cookie | [link](https://firefly.adobe.com) | RECOMMENDED: firefly.adobe.com signed-in → F12 → Network → click firefly-3p.ff.adobe.io (generate-async or models/discovery) → Request Headers → Authorization → copy the token AFTER 'Bearer ' (starts with eyJ…). Cookie-only from firefly.adobe.com mints a GUEST token → 401/403; only multi-domain IMS cookies (adobelogin.com) or that Bearer JWT work. Unofficial/experimental media + Limits. | — |
| `blackbox-web` | `bb-web` | Blackbox Web (Subscription) | Web cookie | [link](https://app.blackbox.ai) | Paste your __Secure-authjs.session-token value or full cookie header from app.blackbox.ai | emulated |
| `chatgpt-web` | `cgpt-web` | ChatGPT Web (Plus/Pro) | Web cookie | [link](https://chatgpt.com) | Paste your __Secure-next-auth.session-token cookie value from chatgpt.com | emulated |
| `chatgpt-web-codex` | `cgpt-codex` | ChatGPT Web (Codex) | Web cookie | [link](https://chatgpt.com) | Paste the full ChatGPT Cookie header. OmniRoute verifies it in an isolated headless browser profile. | native |
| `claude-web` | `cw` | Claude Web | Web cookie | [link](https://claude.ai) | Paste your session cookie from claude.ai | none |
| `conol-web` | `cnl` | Conol (Unofficial/Experimental) | Web cookie | [link](https://conol.ai) | Use browser sign-in, or paste the full Cookie header from conol.ai. The __Secure-better-auth.session_token cookie is required. | — |
| `copilot-m365-web` | `m365copilot` | Microsoft 365 Copilot (BizChat) | Web cookie | [link](https://m365.cloud.microsoft/chat) | Sign in at m365.cloud.microsoft/chat, then open DevTools → Network → filter 'WS' → click the Chathub WebSocket connection. Copy both the access_token query parameter AND the account-specific Chathub path segment from its request URL (wss://…/Chathub/<path>?…&access_token=…). It is NOT an Authorization: Bearer header on an XHR/Fetch request. The token is short-lived; this is an unofficial integration. Optional: store a refresh_token in providerSpecificData.refreshToken (any Microsoft device-code/refresh flow for the substrate.office.com/sydney scopes) and OmniRoute pre-flight-refreshes the access token itself — otherwise re-capture after every ~75 min expiry. | — |
| `copilot-web` | `copilot` | Microsoft Copilot Web | Web cookie | [link](https://copilot.microsoft.com) | Paste the access_token from an authenticated copilot.microsoft.com request (DevTools → Network → Authorization), or export a HAR while logged in | — |
| `deepseek-web` | `ds-web` | DeepSeek Web | Web cookie | [link](https://chat.deepseek.com) | Paste your userToken from chat.deepseek.com — DevTools → Application → Local Storage → userToken | emulated |
| `doubao-web` | `db` | Dola Web (ByteDance) | Web cookie | [link](https://www.dola.com) | Paste the full Cookie header from www.dola.com. It should include sessionid, ttwid, and s_v_web_id. If s_v_web_id is unavailable, fp=verify_... from a chat/completion request URL can be used as a fallback. | — |
| `gemini-business` | `gembiz` | Gemini Business (Enterprise) | Web cookie | [link](https://business.gemini.google) | From your enterprise account: open business.gemini.google/home/cid/{your-cid}, then copy __Secure-1PSID and __Secure-1PSIDTS cookies from DevTools → Application → Cookies. Paste as a cookie header below. | — |
| `gemini-web` | `gweb` | Gemini Web (Free) | Web cookie | [link](https://gemini.google.com) | Paste your __Secure-1PSID cookie value from gemini.google.com. Optionally add __Secure-1PSIDTS separated by semicolon. | emulated |
| `grok-web` | `gw` | Grok Web (Subscription) | Web cookie | [link](https://grok.com) | Paste the full grok.com cookie line from DevTools → Application → Cookies. Include both `sso` and `sso-rw` (e.g. `sso=...; sso-rw=...`) — Grok's anti-bot rejects `sso` on its own. | — |
| `hailuo-web` | `hailuo-web` | Hailuo Web (MiniMax) | Web cookie | [link](https://hailuo.ai) | Open hailuo.ai, log in, then open DevTools → Application → Local Storage → copy the "_token" value. device_id/uuid fingerprint fields are derived automatically; if requests fail, re-capture _token (sessions can expire). | — |
| `huggingchat` | `huggingchat` | HuggingChat (Free) | Web cookie | [link](https://huggingface.co/chat) | Paste the full Cookie header from huggingface.co/chat (DevTools → Network → /chat/conversation → Request Headers → Cookie). It should include hf-chat and may also include token / aws-waf-token. | — |
| `hyperagent` | `ha` | HyperAgent (Unofficial/Experimental) | Web cookie | [link](https://hyperagent.com) | Paste the full Cookie header from hyperagent.com (DevTools → Network → any request → Request Headers → Cookie). Session cookies power chat + billing usage. | — |
| `inner-ai` | `in-ai` | Inner.ai (Subscription) | Web cookie | [link](https://app.innerai.com) | Paste your token cookie and email separated by a space: open DevTools → Application → Cookies → .innerai.com, copy the token value, then append a space and your Inner.ai login email. Example: eyJhbG... user@example.com | emulated |
| `kimi-web` | `kimi-web` | Kimi Web | Web cookie | [link](https://www.kimi.com/code?aff=omniroute) | Paste access_token from www.kimi.com DevTools → Application → Local Storage. A legacy kimi-auth cookie is also accepted. | — |
| `lmarena` | `lma` | Arena (Free) | Web cookie | [link](https://arena.ai) | Paste the full Cookie header from arena.ai (DevTools → Network → request → Cookie). Include arena-auth-prod-v1.0/.1… and cf_clearance/__cf_bm when present. OmniRoute uses Chrome TLS impersonation; if Arena still 403s, set providerSpecificData.recaptchaV3Token from a live browser session. | — |
| `microsoft-designer-web` | `msdesigner` | Microsoft Designer (Image Generation) | Web cookie | [link](https://designer.microsoft.com) | Sign in at designer.microsoft.com, then open DevTools → Network, generate an image, and find the request to DallE.ashx?action=GetDallEImagesCogSci. Copy the value of its Authorization: Bearer header (the access_token — no 'Bearer ' prefix). The token is short-lived; this is an unofficial, reverse-engineered integration. | — |
| `muse-spark-web` | `ms-web` | Muse Spark Web (Meta AI) | Web cookie | [link](https://www.meta.ai) | Paste your ecto_1_sess cookie AND the ecto1:... WS auth token from meta.ai. Capture the ecto1: token in DevTools → Network → WS → the clippy request's Authorization query param. Example: ecto_1_sess=4240a308...NVDg0; ecto1:ABCD... | emulated |
| `notion-web` | `nw` | Notion AI Web (Unofficial/Experimental) | Web cookie | [link](https://www.notion.so) | Paste only the token_v2 cookie VALUE from app.notion.com (DevTools → Application → Cookies → token_v2). Do not paste token_v2= or the full Cookie header. Workspace is auto-detected; space_id / notion_user_id are optional. | — |
| `perplexity-web` | `pplx-web` | Perplexity Web (Pro/Max) | Web cookie | [link](https://www.perplexity.ai) | Paste your __Secure-next-auth.session-token cookie value from perplexity.ai | emulated |
| `poe-web` | `poe` | Poe Web (Subscription) | Web cookie | [link](https://poe.com) | Paste your p-b cookie value from poe.com (DevTools → Application → Cookies → p-b) | — |
| `promptql` | `pql` | PromptQL (Unofficial/Experimental) | Web cookie | [link](https://prompt.ql.app) | Paste the Bearer JWT from prompt.ql.app DevTools → Network → graphql → Authorization (token only). Optional projectId + session Cookie for refresh. | — |
| `qwen-web` | `qwen-web` | Qwen Web (Free) | Web cookie | [link](https://chat.qwen.ai) | Open chat.qwen.ai, log in, then open DevTools → Application → Local Storage → copy the "token" value (or use tongyi_sso_ticket cookie as Bearer token). | emulated |
| `t3-web` | `t3chat` | t3.chat (Pro/Free) | Web cookie | [link](https://t3.chat) | Open t3.chat in your browser, log in, then open DevTools → Application → Local Storage → https://t3.chat. Copy the value of 'convex-session-id'. Also open DevTools → Network, copy the Cookie header from any request. Paste both values here. See provider setup docs for a step-by-step guide. | emulated |
| `tencent-aistudio-web` | `tasw` | Tencent AI Studio (Free) | Web cookie | [link](https://aistudio.tencent.ai) | Log in to aistudio.tencent.ai, open DevTools -> Network, copy any request Cookie header containing session tokens. | — |
| `tinycms-web` | `tcw` | TinyCMS Web (Free/Sub) | Web cookie | [link](https://site.tinycms.xyz) | Go to site.tinycms.xyz, open DevTools → Application → Local Storage, copy the value of 'app-config-uuid' (starts with 'R'), and paste it here. | — |
| `v0-vercel-web` | `v0-vercel-web` | v0 Vercel Web (Code Gen) | Web cookie | [link](https://v0.dev) | Paste your session cookie from v0.dev (DevTools → Application → Cookies) | — |
| `venice-web` | `ven` | Venice Web (Privacy) | Web cookie | [link](https://venice.ai) | Paste your session cookie from venice.ai (DevTools → Application → Cookies) | — |
| `yuanbao-web` | `ybw` | Tencent Yuanbao (Free) | Web cookie | [link](https://yuanbao.tencent.com) | Log in to yuanbao.tencent.com, then paste the full Cookie header (DevTools → Network → any /api request → Request Headers → Cookie). It must contain hy_user and hy_token. | — |
| `zai-web` | `zw` | Z.ai Web | Web cookie | [link](https://chat.z.ai) | Copy the "token" value from chat.z.ai → DevTools → Application → Local Storage. Do not copy cookies; OmniRoute handles the per-request CAPTCHA through its browser transport. | — |
| `zenmux-free` | `zmf` | ZenMux Free (Web) | Web cookie | [link](https://zenmux.ai) | Login at zenmux.ai, then export all cookies using EditThisCookie or Cookie-Editor and paste the full Cookie header string here. Refresh every ~30 days. | — |
## API Key Providers (paid / paid-with-free-credits) (233)
| ID | Alias | Name | Tags | Website | Notes |
|----|-------|------|------|---------|-------|
| `360ai` | `360ai` | 360 AI | API key | [link](https://ai.360.cn) | Get API key at ai.360.cn |
| `agentrouter` | `agentrouter` | AgentRouter | API key, aggregator | [link](https://agentrouter.org) | $200 free credits on signup - multi-model routing gateway |
| `agnes` | `agnes` | Agnes AI | API key, video | [link](https://agnes-ai.com) | Get API key at agnes-ai.com |
| `ai21` | `ai21` | AI21 Labs | API key | [link](https://www.ai21.com) | $10 trial credits on signup (valid 3 months), no credit card required |
| `aimlapi` | `aiml` | AI/ML API | API key, aggregator | [link](https://aimlapi.com) | Free tier paused (2026) — AI/ML API is now pay-as-you-go only (min $20 top-up); no recurring free credits. |
| `ainative` | `ainative` | AINative Studio | API key | [link](https://ainative.studio) | Create a free API key at ainative.studio (no card), then paste it here as a Bearer token. |
| `aion` | `aion` | Aion Labs | API key | [link](https://www.aionlabs.ai) | Create a free API key at aionlabs.ai (no card), then paste it here as a Bearer token. |
| `alibaba` | `ali` | Alibaba Cloud Model Studio | API key | [link](https://bailian.console.alibabacloud.com/) | — |
| `alibaba-cn` | `ali-cn` | Alibaba (China) | API key | [link](https://dashscope.console.aliyun.com/) | — |
| `ant-ling` | `ling` | Ant Ling / Ring (inclusionAI) | API key | [link](https://developer.ant-ling.com/en/docs/) | Register and create an API key at the Ant Ling API console (https://chat.ant-ling.com/open), then paste it here. OmniRoute routes chat traffic to https://api.ant-ling.com/v1/chat/completions; the provider is OpenAI-compatible and also exposes an Anthropic-compatible surface. |
| `anthropic` | `anthropic` | Anthropic | API key | [link](https://platform.claude.com) | — |
| `anyapi` | `anyapi` | AnyAPI AI | API key, aggregator | [link](https://anyapi.ai) | Free plan: 100,000 ANY Tokens/day and 100 RPM for eligible Free/Basic models; no credit card required. |
| `api-airforce` | `af` | Api.airforce | API key | [link](https://api.airforce) | 55 free tier models including Grok-3, Claude 3.7, Qwen3, Kimi-K2, Gemini 2.5 Flash, DeepSeek-V3 |
| `arcee-ai` | `arcee` | Arcee AI | API key | [link](https://arcee.ai) | Get API key at arcee.ai |
| `auriko` | `auriko` | Auriko | API key, aggregator | [link](https://www.auriko.ai) | Free plan publishes 1,000 Platform RPM and 10,000 BYOK RPM. Platform inference still passes through provider cost; this is not a free-token pool or unlimited free inference. |
| `azure-ai` | `azure-ai` | Azure AI Foundry | API key, enterprise | [link](https://learn.microsoft.com/azure/ai-foundry) | Use your Azure AI Foundry key. Base URL can be https://<resource>.services.ai.azure.com/openai/v1/ or https://<resource>.openai.azure.com/openai/v1/. |
| `azure-openai` | `azure` | Azure OpenAI | API key, enterprise | [link](https://azure.microsoft.com/products/ai-services/openai-service) | Use your Azure OpenAI API key. Base URL should be your resource endpoint, for example https://my-resource.openai.azure.com. |
| `bai` | `bai` | b.ai | API key | [link](https://b.ai) | Bearer API key for the b.ai OpenAI-compatible LLM gateway (distinct from TheB.AI). Create a key at https://docs.b.ai, then use https://api.b.ai/v1 as the OpenAI-compatible base URL. |
| `baichuan` | `baichuan` | Baichuan | API key | [link](https://www.baichuan-ai.com/) | Get API key at platform.baichuan-ai.com |
| `baidu` | `baidu` | Baidu (ERNIE) | API key | [link](https://ernie.baidu.com/) | Get API key at console.bce.baidu.com |
| `bailian-coding-plan` | `bcp` | Alibaba Token Plan | API key | [link](https://www.alibabacloud.com/help/en/model-studio/token-plan-overview) | — |
| `baseten` | `baseten` | Baseten | API key | [link](https://baseten.co) | $30 free trial credits for GPU inference |
| `bazaarlink` | `bzl` | BazaarLink | API key | [link](https://bazaarlink.ai) | Use your BazaarLink API key (starts with sk-bl-) in Authorization: Bearer <key>. OpenAI SDK works with base URL https://bazaarlink.ai/api/v1. Models use provider/model-name format. |
| `bedrock` | `bedrock` | Amazon Bedrock | API key, enterprise | [link](https://aws.amazon.com/bedrock) | Use your Amazon Bedrock API key and configure the AWS region where your models are enabled (for example eu-west-2). OmniRoute calls Bedrock's native Converse API directly. |
| `black-forest-labs` | `bfl` | Black Forest Labs | API key, image | [link](https://blackforestlabs.ai) | — |
| `blackbox` | `bb` | Blackbox AI | API key | [link](https://blackbox.ai) | Limited free access is available through Blackbox; model availability and account limits apply |
| `bluesminds` | `bm` | BluesMinds | API key | [link](https://www.bluesminds.com) | Free daily pi credits — supports 200+ models including GPT-4o, GPT-4.1, Claude Sonnet 4.5, Gemini 2.0 Flash, DeepSeek V4, Qwen, Kimi K2 |
| `byteplus` | `bpm` | BytePlus ModelArk | API key | [link](https://console.byteplus.com/ark) | — |
| `bytez` | `bytez` | Bytez | API key | [link](https://bytez.com) | $1 free credits, refreshes every 4 weeks |
| `cerebras` | `cerebras` | Cerebras | API key | [link](https://inference.cerebras.ai) | Free Trial: 1M tokens/day, 30K TPM, 5 RPM — no credit card. |
| `charm-hyper` | `charm-hyper` | Charm Hyper | API key | [link](https://hyper.charm.land) | 100 free monthly Hypercredits on signup |
| `chat-oripe` | `chat-oripe` | Chat Oripe | API key, aggregator | [link](https://api.oriper.com) | Official metadata advertises 2M tokens/month, but the public site and documentation were blocked during audit; treat the quota and brand mapping as unconfirmed. |
| `chatanywhere` | `chatanywhere` | ChatAnywhere | API key, aggregator | [link](https://chatanywhere.tech) | Personal, educational or research use only: public documentation cites 10,000 points/day and 200 requests/day per IP/key; do not use for commercial traffic. |
| `cheaperinference` | `cinf` | Cheaper Inference | API key | [link](https://cheaperinference.com/?utm_source=omniroute) | — |
| `chenzk` | `chenzk` | Chenzk API | API key | [link](https://chenzk.top) | — |
| `chutes` | `chutes` | Chutes.ai | API key, aggregator | [link](https://chutes.ai) | Bearer API key for the Chutes OpenAI-compatible gateway. |
| `clarifai` | `clarifai` | Clarifai | API key, enterprise | [link](https://docs.clarifai.com) | Use your Clarifai PAT or app-specific API key. OmniRoute targets the OpenAI-compatible endpoint at https://api.clarifai.com/v2/ext/openai/v1 and authenticates with Authorization: Key <token>. |
| `cloudcode-one` | `cloudcode-one` | CloudCode.ONE | API key, aggregator | [link](https://cloudcode.one) | Published free models include glm-4.7-flash and glm-4.6v-flash; no numeric quota is published, and key creation may require credit or a coupon. |
| `cloudflare-ai` | `cf` | Cloudflare Workers AI | API key | [link](https://developers.cloudflare.com/workers-ai) | Requires API Token AND Account ID (found at dash.cloudflare.com) |
| `clova-studio` | `clova` | Naver CLOVA Studio | API key | [link](https://api.ncloud-docs.com/docs/en/ai-naver-clovastudio-summary) | — |
| `codestral` | `codestral` | Codestral | API key | [link](https://mistral.ai) | — |
| `cohere` | `cohere` | Cohere | API key | [link](https://cohere.com) | Free Trial: 1,000 API calls/month for testing, no credit card required |
| `command-code` | `cmd` | Command Code | API key | [link](https://commandcode.ai/) | Use a Command Code API key. Requests are sent to Command Code's /alpha/generate endpoint. |
| `coze` | `coze` | Coze | API key | [link](https://coze.com) | Get API key at coze.com/open/api |
| `crof` | `crof` | CrofAI | API key | [link](https://crof.ai) | — |
| `cursor-api` | `cua` | Cursor API | API key | [link](https://cursor.com/dashboard/api) | Paste a Cursor user API key (crsr_...) from cursor.com/dashboard/api. OmniRoute exchanges it for a session token on demand; no IDE or cursor-agent install is needed. Usage bills to the Cursor plan that owns the key. |
| `dahl` | `dahl` | Dahl | API key | [link](https://inference.dahl.global) | Click 'Add Account' to auto-generate a token, or add a manual API key. |
| `databricks` | `databricks` | Databricks | API key, enterprise | [link](https://www.databricks.com) | — |
| `datarobot` | `datarobot` | DataRobot | API key, enterprise | [link](https://docs.datarobot.com) | Use your DataRobot API token. Optional Base URL can be the account root (for LLM Gateway) or a deployment URL under /api/v2/deployments/<id>. |
| `deepai` | `deepai` | DeepAI | API key, image | [link](https://deepai.org) | Use your DeepAI API key. Get one at deepai.org — requires a Pro subscription ($9.99/mo). |
| `deepinfra` | `deepinfra` | DeepInfra | API key | [link](https://deepinfra.com) | Free signup credits for API testing and model exploration |
| `deepseek` | `ds` | DeepSeek | API key | [link](https://platform.deepseek.com) | 5M free tokens on signup - no credit card required |
| `dgrid` | `dgrid` | DGrid | API key | [link](https://dgrid.ai) | DGrid Free Models Router: 10 requests/minute and 100 requests/day. A $5 lifetime top-up unlocks up to 20 requests/minute and 1,000 requests/day. |
| `dify` | `dify` | Dify | API key | [link](https://dify.ai) | Get API key from your Dify instance. |
| `digitalocean` | `digitalocean` | DigitalOcean | API key | [link](https://docs.digitalocean.com/products/ai-platform/) | — |
| `dit` | `dai` | DIT.ai | API key | [link](https://dit.ai) | Use your dit.ai API key in Authorization: Bearer <key>. Fully OpenAI-compatible — a drop-in replacement, just change the base URL to https://api.dit.ai/v1. |
| `doubao` | `doubao` | Doubao | API key | [link](https://doubao.com) | Get API key at console.volcengine.com |
| `dxnt` | `dxnt` | DXNT / DX Token | API key, aggregator | [link](https://www.dxnt.com) | Free accounts are documented at 100 calls/day; the quota may increase through invitations and can vary by account. |
| `electronhub` | `electronhub` | Electron Hub | API key, aggregator | [link](https://www.electronhub.ai) | Free plan: 5 RPM, $0.25 weekly credits and 10 Neutrinos/day for :free models; family budgets also apply. |
| `empower` | `empower` | Empower | API key, aggregator | [link](https://docs.empower.dev) | Bearer API key for the Empower OpenAI-compatible endpoint. |
| `factory` | `factory` | Factory | API key | [link](https://factory.ai) | Bearer API key for the Factory OpenAI-compatible gateway. |
| `fal-ai` | `fal` | Fal.ai | API key, image | [link](https://fal.ai) | — |
| `fastrouter` | `fastrouter` | FastRouter | API key, aggregator | [link](https://fastrouter.ai) | Models with the :free suffix allow 10 requests/day per organization and model; availability may change. |
| `featherless-ai` | `featherless` | Featherless AI | API key | [link](https://featherless.ai) | Free tier available — no credit card required |
| `fenayai` | `fenayai` | FenayAI | API key, aggregator | [link](https://fenayai.com) | Bearer API key for the FenayAI OpenAI-compatible gateway. |
| `fireworks` | `fireworks` | Fireworks AI | API key | [link](https://fireworks.ai) | $1 free starter credits on signup for API testing |
| `free-ai` | `free-ai` | Free.ai | API key, aggregator | [link](https://free.ai) | 30,000 tokens/day cover self-hosted models after email verification. Usage beyond the pool can bill at raw cost, and premium external models are paid. |
| `freeaiapikey` | `faik` | FreeAIAPIKey | API key | [link](https://freeaiapikey.com) | — |
| `freebuff` | `freebuff` | Freebuff | API key | [link](https://freebuff.com) | Enter Freebuff / Codebuff Auth Token (obtained via CLI login or automated harvester). |
| `freeinference` | `freeinference` | FreeInference | API key, aggregator | [link](https://freeinference.org) | Free research access without a card; non-Harvard applicants require manual approval and no numeric quota is publicly guaranteed. |
| `freemodel-dev` | `fmd` | FreeModel.dev | API key | [link](https://freemodel.dev) | $300 free credits on signup — no credit card required. Access GPT-5.4 and GPT-5.5 (OpenAI's latest flagship models) through an OpenAI-compatible API. |
| `freetheai` | `fta` | FreeTheAi | API key, aggregator | [link](https://freetheai.xyz) | Join the FreeTheAi Discord to get your free API key. |
| `friendliai` | `friendli` | FriendliAI | API key | [link](https://friendli.ai) | Free tier for serverless inference — no credit card required |
| `g4f-gemini` | `g4fgem` | g4f.space — Gemini | API key, aggregator | [link](https://g4f.space) | No auth required. Free tier is limited to 5 requests/minute — sign up at g4f.dev/members.html for higher limits. |
| `g4f-groq` | `g4fgroq` | g4f.space — Groq | API key, aggregator | [link](https://g4f.space) | No auth required. Free tier is limited to 5 requests/minute — sign up at g4f.dev/members.html for higher limits. |
| `g4f-nvidia` | `g4fnv` | g4f.space — NVIDIA | API key, aggregator | [link](https://g4f.space) | No auth required. Free tier is limited to 5 requests/minute — sign up at g4f.dev/members.html for higher limits. |
| `g4f-ollama` | `g4foll` | g4f.space — Ollama | API key, aggregator | [link](https://g4f.space) | No auth required. Free tier is limited to 5 requests/minute — sign up at g4f.dev/members.html for higher limits. |
| `g4f-pollinations` | `g4fpol` | g4f.space — Pollinations | API key, aggregator | [link](https://g4f.space) | No auth required. Free tier is limited to 5 requests/minute — sign up at g4f.dev/members.html for higher limits. |
| `galadriel` | `galadriel` | Galadriel | API key | [link](https://galadriel.com) | ⚠️ **DEPRECATED.** api.galadriel.ai no longer resolves (sweep 2026-06-19); the inference API appears discontinued. |
| `gemini` | `gemini` | Gemini (Google AI Studio) | API key | [link](https://aistudio.google.com) | Free tier available through Google AI Studio; current per-model quotas and regional limits apply |
| `getgoapi` | `ggo` | GoAPI | API key, aggregator | [link](https://api.getgoapi.com) | — |
| `gigachat` | `gigachat` | GigaChat (Sber) | API key | [link](https://developers.sber.ru) | — |
| `gitlab` | `gitlab` | GitLab Duo PAT | API key | [link](https://docs.gitlab.com/user/duo_agent_platform/code_suggestions/) | GitLab personal access token for the public Code Suggestions API. Configure a self-hosted base URL when not using gitlab.com. |
| `gitlawb` | `glb` | Gitlawb Opengateway (MiMo) | API key | [link](https://opengateway.gitlawb.com) | Free MiMo (xiaomi/mimo-v2.5) revoked 2026-05 — Opengateway is now a pay-as-you-go credit gateway; no recurring free model. |
| `gitlawb-gmi` | `glb-gmi` | Gitlawb Opengateway (GMI Cloud) | API key | [link](https://opengateway.gitlawb.com) | Free Nemotron promo ended 2026-06 — the GMI Cloud route is now pay-as-you-go credit only. |
| `glm` | `glm` | GLM Coding | API key | [link](https://z.ai/subscribe) | — |
| `glm-cn` | `glmcn` | GLM Coding (China) | API key | [link](https://open.bigmodel.cn) | — |
| `glmt` | `glmt` | GLM Thinking | API key | [link](https://open.bigmodel.cn) | — |
| `groq` | `groq` | Groq | API key | [link](https://groq.com) | Free tier: 30 RPM / 14.4K RPD — no credit card |
| `hackclub` | `hc` | Hackclub AI | API key, aggregator | [link](https://ai.hackclub.com) | Sign in with your Hack Club account at ai.hackclub.com. |
| `haiper` | `hp` | Haiper | API key, video | [link](https://haiper.ai) | Get API key at haiper.ai/haiper-api |
| `hcnsec` | `hcnsec` | Huancheng Public API | API key | [link](https://api.hcnsec.cn) | Get API key at api.hcnsec.cn |
| `helixmind` | `helixmind` | HelixMind | API key, aggregator | [link](https://helixmind.online) | Previously circulated 3 RPM/50 RPD and no-card claims were not confirmed during the 2026-08-02 audit; current quota and billing require account verification. |
| `helyxai` | `helyxai` | Helyx AI | API key, aggregator | [link](https://helyxai.space) | Operational Free plan documents 100,000 tokens/day; the site's separate 2M+ marketing claim conflicts and is not treated as a quota guarantee. |
| `heroku` | `heroku` | Heroku AI | API key, enterprise | [link](https://www.heroku.com) | — |
| `huggingface` | `hf` | HuggingFace | API key | [link](https://huggingface.co) | Free Inference API for thousands of models (Whisper, VITS, SDXL…) |
| `hyperbolic` | `hyp` | Hyperbolic | API key | [link](https://hyperbolic.xyz) | $1-5 trial credits on signup for serverless inference |
| `ideogram` | `ideo` | Ideogram | API key | [link](https://ideogram.ai) | Get API key at ideogram.ai/docs/api |
| `iflytek` | `iflytek` | iFlytek Spark | API key | [link](https://xinghuo.xfyun.cn) | Get API key at console.xfyun.cn |
| `inception` | `inception` | Inception | API key | [link](https://docs.inceptionlabs.ai) | 10M free tokens on signup, no credit card required. |
| `inference-net` | `inet` | Inference.net | API key | [link](https://inference.net) | $25 free credits on signup plus research grants available |
| `internlm` | `internlm` | InternLM (Intern-S1) | API key | [link](https://internlm.intern-ai.org.cn/) | Free monthly quota ~1M input / 3M output tokens (~10 RPM) |
| `jina-ai` | `jina` | Jina AI (Foundation API) | API key, embed/rerank | [link](https://jina.ai) | Bearer API key for api.jina.ai — embeddings, rerank, classify, segment, and search. Dashboard keys take precedence over JINA_AI_API_KEY. This is not the Reader / r.jina.ai card and does not fetch URLs. |
| `jina-reader` | `jr` | Jina Reader (r.jina.ai) | API key | [link](https://jina.ai/reader) | Bearer API key for r.jina.ai URL-to-markdown (/v1/web/fetch only). Does not serve /v1/embeddings or /v1/rerank. The same Jina token as Foundation API works; OmniRoute reuses a jina-ai dashboard key or JINA_AI_API_KEY when this card is empty. |
| `kenari` | `kenari` | Kenari | API key | [link](https://kenari.id) | Use your Kenari API key (kn-...) in Authorization: Bearer <key>. Fully OpenAI-compatible. API base URL: https://kenari.id/v1. |
| `kie` | `kie` | KIE.AI | API key | [link](https://kie.ai) | — |
| `kilo-gateway` | `kg` | Kilo Gateway | API key, aggregator | [link](https://kilo.ai) | — |
| `kimi` | `kimi` | Kimi (Legacy Moonshot API) | API key | [link](https://platform.kimi.ai?aff=omniroute) | — |
| `kimi-coding-apikey` | `kmca` | Kimi Code API Key | API key | [link](https://www.kimi.com/code?aff=omniroute) | — |
| `lambda-ai` | `lambda` | Lambda AI | API key | [link](https://lambda.ai) | — |
| `laozhang` | `lz` | LaoZhang AI | API key, aggregator | [link](https://api.laozhang.ai) | — |
| `leonardo` | `leo` | Leonardo AI | API key, video | [link](https://leonardo.ai) | Get API key at leonardo.ai/developer |
| `liquid` | `liquid` | Liquid AI | API key | [link](https://liquid.ai) | Get API key at liquid.ai |
| `literouter` | `literouter` | LiteRouter | API key, aggregator | [link](https://literouter.com) | Free model variants use the :free suffix; daily credit limits vary by model and free input is capped at 5,000 tokens. |
| `llamagate` | `llamagate` | LlamaGate | API key | [link](https://llamagate.ai) | — |
| `llm-kiwi` | `llmkiwi` | LLM.Kiwi | API key, aggregator | [link](https://llm.kiwi) | Free plan exposes auto and hrLLM; the published 40 requests/hour limit applies to hrLLM. |
| `llm7` | `llm7` | LLM7.io | API key | [link](https://llm7.io) | Use any non-empty key (for example 'unused'). If older built-in models return model_unavailable, use Available Models → Import from /models or Auto-Sync; verified live model: gemini-3.1-flash-lite. |
| `llmgateway` | `llmgateway` | LLM Gateway | API key, aggregator | [link](https://llmgateway.io) | Hosted Free plan: free-priced models are limited to 5 requests per 10 minutes when the account has no credits. |
| `logfare` | `logfare` | Logfare | API key, aggregator | [link](https://logfare.ai) | Create a free account at https://logfare.ai/register (username/password, no email verification) to get an instant API key, then paste it here as a Bearer token. |
| `longcat` | `lc` | LongCat AI | API key | [link](https://longcat.chat/platform/docs) | Free: one-time 10M-token grant after account signup + KYC verification (LongCat-2.0). One-time only — not a recurring daily/monthly allowance. |
| `magnific` | `freepik` | Magnific | API key, image | [link](https://www.magnific.com) | Get an API key at magnific.com/user/api-keys (header x-magnific-api-key). Legacy Freepik developer keys still work. |
| `maritalk` | `maritalk` | Maritalk | API key | [link](https://www.maritaca.ai) | — |
| `meganova-ai` | `meganova-ai` | MegaNova AI | API key, aggregator | [link](https://meganova.ai) | Free signup without a card. Published Tier 1 per-model quotas total 550 requests/day; they are not a shared global pool, and paid overage can apply if enabled. |
| `meta-llama` | `meta` | Meta Llama API | API key | [link](https://llama.developer.meta.com) | — |
| `minimax` | `minimax` | Minimax Coding | API key, video | [link](https://www.minimax.io) | — |
| `minimax-cn` | `minimax-cn` | Minimax (China) | API key | [link](https://www.minimaxi.com) | — |
| `mistral` | `mistral` | Mistral | API key | [link](https://mistral.ai) | Free Experiment tier: rate-limited access to all models, no credit card required |
| `mixedbread` | `mxbai` | Mixedbread AI | API key | [link](https://www.mixedbread.com) | Bearer API key for the Mixedbread embeddings API. |
| `mixlayer` | `mixlayer` | Mixlayer | API key, aggregator | [link](https://www.mixlayer.com) | The qwen/qwen3.5-4b-free model is free for prototyping and rate-limited; no fixed public RPM or daily quota is confirmed. |
| `mnn-ai` | `mnn-ai` | MNN AI | API key, aggregator | [link](https://mnnai.ru) | Free plan: $1 monthly credits, 10 RPM and access only to models marked Free. |
| `modal` | `mdl` | Modal | API key, enterprise | [link](https://modal.com/docs) | Use the bearer token that protects your Modal deployment, if enabled. Base URL should point to your OpenAI-compatible Modal app, for example https://<workspace>--<app>.modal.run/v1. |
| `modelscope` | `ms` | ModelScope | API key | [link](https://modelscope.cn) | Free tier via ModelScope API-Inference — Alibaba account required. |
| `monsterapi` | `monster` | MonsterAPI | API key | [link](https://monsterapi.ai) | ⚠️ **DEPRECATED.** Monster API shuttered operations on 2026-06-30. Use alternative OpenAI-compatible providers. |
| `moonshot` | `moonshot` | Kimi | API key | [link](https://platform.kimi.ai?aff=omniroute) | — |
| `morph` | `morph` | Morph | API key | [link](https://morphllm.com) | Free tier: 250K credits/month, $0 |
| `muse-code` | `mc` | Muse Code (Meta) | API key | [link](https://github.com/meta-llama/llama-stack) | Use your META_API_KEY env var as a Bearer token. Muse Code CLI uses the OpenAI Responses API wire format (POST /responses). |
| `naga-ac` | `naga` | Naga.ac | API key, aggregator | [link](https://naga.ac) | Get API key at naga.ac — Google/GitHub/Discord signup available. |
| `naga-ai` | `naga-ai` | Naga AI | API key, aggregator | [link](https://naga.ac) | Models marked :free are publicly listed, but no numeric quota is confirmed. Naga's policy warns that free-tier prompts and outputs may be collected or used for training. |
| `nanogpt` | `nanogpt` | NanoGPT | API key | [link](https://nano-gpt.com) | — |
| `nara` | `nara` | NaraRouter | API key | [link](https://bynara.id) | Get a free API key via NaraRouter's Telegram channel, then paste it here as a Bearer token. |
| `navy` | `navy` | NavyAI | API key | [link](https://api.navy) | Create a free API key from the NavyAI dashboard, then paste it here as a Bearer token. |
| `nebius` | `nebius` | Nebius AI | API key | [link](https://nebius.com) | ~$1 trial credits on signup for API testing |
| `nlpcloud` | `nlpc` | NLP Cloud | API key | [link](https://docs.nlpcloud.com) | Use your NLP Cloud API key in Authorization: Token <key>. OmniRoute targets the chatbot endpoint on https://api.nlpcloud.io/v1/gpu/<model>/chatbot by default. |
| `nomic` | `nomic` | Nomic | API key | [link](https://nomic.ai) | Get API key at atlas.nomic.ai |
| `nous-research` | `nous` | Nous Research | API key | [link](https://portal.nousresearch.com/help) | Use your Nous Portal API key. OmniRoute targets the official OpenAI-compatible inference endpoint at https://inference-api.nousresearch.com/v1. |
| `novita` | `novita` | Novita AI | API key, video, aggregator | [link](https://novita.ai) | $0.50 trial credits on signup (valid about 1 year) |
| `nscale` | `nscale` | nScale | API key | [link](https://nscale.com) | $5 free credits on signup for inference testing |
| `nube` | `nube` | Nube.sh | API key | [link](https://nube.sh) | — |
| `nvidia` | `nvidia` | NVIDIA NIM | API key | [link](https://build.nvidia.com) | Free dev access: ~40 RPM, 70+ models (Kimi K2.5, GLM 4.7, DeepSeek V3.2...) |
| `oci` | `oci` | OCI Generative AI | API key, enterprise | [link](https://www.oracle.com/artificial-intelligence/generative-ai) | Use your OCI Generative AI API key or IAM bearer token. Base URL can be https://inference.generativeai.<region>.oci.oraclecloud.com/openai/v1/. |
| `ofoxai` | `ofoxai` | OfoxAI | API key, aggregator | [link](https://ofox.ai) | The current catalog advertises 10+ free models without a public numeric quota; review upstream provenance, retention and training terms before production use. |
| `ollama-cloud` | `ollamacloud` | Ollama Cloud | API key | [link](https://ollama.com/settings/keys) | — |
| `openadapter` | `oad` | OpenAdapter | API key | [link](https://openadapter.dev) | Use your OpenAdapter API key in Authorization: Bearer sk-cv-<key>. Fully OpenAI-compatible. API base URL: https://api.openadapter.in/v1. |
| `openai` | `openai` | OpenAI | API key | [link](https://platform.openai.com) | — |
| `opencode-go` | `opencode-go` | OpenCode Go | API key | [link](https://opencode.ai/go) | — |
| `opencode-zen` | `opencode-zen` | OpenCode Zen | API key | [link](https://opencode.ai/zen) | — |
| `openference-api` | `ofa` | Openference API | API key | [link](https://openference.com) | Free plan: 3-day trial with open-source models — no credit card required |
| `openrouter` | `openrouter` | OpenRouter | API key, aggregator | [link](https://openrouter.ai) | Free models at $0/token with :free suffix - 20 RPM / 200 RPD |
| `openvecta` | `openvecta` | OpenVecta | API key | [link](https://openvecta.com) | Free credits on signup for OpenAI-compatible inference across LLMs, embeddings, and reasoning models |
| `orcarouter` | `orcarouter` | OrcaRouter | API key | [link](https://www.orcarouter.ai) | — |
| `ovhcloud` | `ovh` | OVHcloud AI | API key | [link](https://www.ovhcloud.com) | — |
| `perplexity` | `pplx` | Perplexity | API key | [link](https://www.perplexity.ai) | — |
| `piapi` | `pi` | PiAPI | API key, aggregator | [link](https://piapi.ai) | — |
| `pioneer` | `pn` | Pioneer AI | API key | [link](https://pioneer.ai) | $75 free usage credits — no credit card required |
| `plamo` | `plamo` | PLaMo | API key | [link](https://plamo.preferredai.jp/api) | — |
| `poe` | `poe` | Poe | API key, aggregator | [link](https://creator.poe.com/api-reference) | Bearer API key for the Poe OpenAI-compatible API. |
| `poixe-ai` | `poixe-ai` | Poixe AI | API key, aggregator | [link](https://poixe.com) | Current public free limits are small and model-group specific: 2 RPM/5 RPD for large-cup models and 20 RPM/50 RPD for small-cup models. |
| `pollinations` | `pol` | Pollinations AI | API key, video | [link](https://pollinations.ai) | Anonymous/keyless access to the documented free models is best-effort. Local v3.8.50 verification (2026-07-31) returned 401 via OmniRoute and Cloudflare 1010 on direct upstream probes from the same network. Premium models still require a Pollinations API key from enter.pollinations.ai. |
| `poolside` | `poolside` | Poolside | API key | [link](https://poolside.ai) | Laguna S 2.1 and XS 2.1 are free during Preview; no public numeric quota is published. |
| `predibase` | `predibase` | Predibase | API key | [link](https://predibase.com) | ⚠️ **DEPRECATED.** serving.app.predibase.com no longer resolves (sweep 2026-06-19); the managed serving API appears discontinued. |
| `publicai` | `publicai` | PublicAI | API key | [link](https://publicai.co) | Requires an API key — one-time signup credit, then paid |
| `qianfan` | `qianfan` | Baidu Qianfan | API key | [link](https://cloud.baidu.com/product-s/qianfan_home) | — |
| `qiniu` | `qiniu` | Qiniu | API key | [link](https://www.qiniu.com) | — |
| `qwen-cloud` | `qwc` | Qwen Cloud | API key | [link](https://www.qwencloud.com/) | — |
| `qwen-cloud-token-plan` | `qct` | Qwen Cloud Token Plan | API key | [link](https://www.qwencloud.com/pricing/token-plan) | — |
| `recraft` | `recraft` | Recraft | API key, image | [link](https://recraft.ai) | — |
| `regolo` | `regolo` | Regolo AI | API key | [link](https://regolo.ai) | Get your Regolo API key from regolo.ai, then paste it here as a Bearer token. |
| `reka` | `reka` | Reka | API key | [link](https://docs.reka.ai/chat/overview) | Use your Reka API key. OmniRoute supports the OpenAI-compatible base URL https://api.reka.ai/v1 and sends both Authorization and X-Api-Key headers for compatibility. |
| `requesty` | `requesty` | Requesty | API key | [link](https://requesty.ai) | Free tier ~200 requests/day - multi-model routing gateway (300+ models) |
| `routeway` | `routeway` | Routeway | API key | [link](https://routeway.ai) | Create a free API key at routeway.ai, then paste it here as a Bearer token. |
| `runwayml` | `runway` | Runway | API key, video | [link](https://docs.dev.runwayml.com) | Use your Runway API key in Authorization: Bearer <key>. OmniRoute targets the current Runway API at https://api.dev.runwayml.com/v1 and sends the required X-Runway-Version header automatically. |
| `sambanova` | `samba` | SambaNova | API key | [link](https://sambanova.ai) | $5 free credits on signup (30-day validity), no credit card required |
| `sap` | `sap` | SAP Generative AI Hub | API key, enterprise | [link](https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/generative-ai-hub-in-sap-ai-core) | Use your SAP AI Core bearer token. Base URL can be your AI_API_URL root or a deploymentUrl from Generative AI Hub. |
| `sarvam` | `sarvam` | Sarvam AI | API key | [link](https://docs.sarvam.ai) | ₹1,000 in free signup credits — never expire |
| `scaleway` | `scw` | Scaleway AI | API key | [link](https://www.scaleway.com/en/docs/ai-data/generative-apis/) | 1M free tokens for new accounts — EU/GDPR compliant (Paris), Qwen3 235B & Llama 70B |
| `sealion` | `sealion` | SEA-LION | API key | [link](https://sea-lion.ai) | Sign in at sea-lion.ai with Google (no card, no region wall), create an API key, then paste it here. |
| `segmind` | `segmind` | Segmind | API key, image, video | [link](https://segmind.com) | Use your Segmind API key in the x-api-key header. OmniRoute targets https://api.segmind.com/v1/<model> and returns the generated image/video bytes directly. |
| `sensenova` | `sensenova` | SenseNova | API key | [link](https://platform.sensenova.cn) | Get API key at platform.sensenova.cn |
| `siliconflow` | `siliconflow` | SiliconFlow | API key | [link](https://cloud.siliconflow.com) | $1 free credits plus currently listed $0 models after identity verification; availability and limits may change |
| `snowflake` | `snowflake` | Snowflake Cortex | API key, enterprise | [link](https://www.snowflake.com) | — |
| `sparkdesk` | `sparkdesk` | SparkDesk | API key | [link](https://xinghuo.xfyun.cn) | Get API key at console.xfyun.cn |
| `speka` | `speka` | Speka AI | API key, aggregator | [link](https://speka.me) | Free plan: $1 monthly usage, 10 RPM, one API key and access to open models and the playground; no card required. |
| `stability-ai` | `stability` | Stability AI | API key, image | [link](https://stability.ai) | — |
| `stepfun` | `stepfun` | StepFun | API key | [link](https://stepfun.com) | Get API key at platform.stepfun.com |
| `sumopod` | `sumopod` | SumoPod | API key | [link](https://ai.sumopod.com) | Use your SumoPod API key (sk-...) in Authorization: Bearer <key>. Fully OpenAI-compatible. API base URL: https://ai.sumopod.com/v1. |
| `suno` | `suno` | Suno | API key | [link](https://suno.ai) | Paste session cookie from suno.ai (Clerk auth) |
| `synthetic` | `synthetic` | Synthetic | API key, aggregator | [link](https://synthetic.new) | — |
| `tabitoken` | `tabitoken` | TabiToken | API key, aggregator | [link](https://tabitoken.com) | — |
| `tencent` | `tencent` | Tencent Hunyuan | API key | [link](https://hunyuan.tencent.com) | Get API key at console.cloud.tencent.com |
| `thebai` | `thebai` | TheB.AI | API key, aggregator | [link](https://theb.ai) | Bearer API key for the TheB.AI OpenAI-compatible gateway. |
| `tinyfish` | `tf` | TinyFish Fetch | API key | [link](https://docs.tinyfish.ai/fetch-api) | X-API-Key from agent.tinyfish.ai/api-keys |
| `together` | `together` | Together AI | API key, video | [link](https://www.together.ai) | — |
| `token-kiosk` | `tk` | Token Kiosk | API key | [link](https://agent-router.gaib.ai) | Use your Token Kiosk API key in Authorization: Bearer <key>. Fully OpenAI-compatible gateway. API base URL: https://agent-router.gaib.ai/v1. |
| `tokenreply` | `tokenreply` | TokenReply | API key, aggregator | [link](https://www.tokenreply.com) | Free-tagged models have model- and campaign-specific daily limits; no fixed global free quota is published. |
| `tokenrouter` | `trk` | TokenRouter | API key | [link](https://tokenrouter.com) | Use your TokenRouter API key in Authorization: Bearer <key>. Fully OpenAI-compatible. API base URL: https://api.tokenrouter.com/v1. |
| `topaz` | `topaz` | Topaz | API key, image | [link](https://topazlabs.com) | — |
| `typhoon` | `typhoon` | Typhoon | API key | [link](https://docs.opentyphoon.ai) | Free API key with a 5 req/s and 200 req/m rate limit. |
| `udio` | `udio` | Udio | API key | [link](https://udio.com) | Paste session cookie from udio.com (Supabase auth) |
| `uncloseai` | `unc` | UncloseAI | API key | [link](https://uncloseai.com) | No auth required. API accepts any non-empty string as key for identification. If older built-in models return 404, use Available Models → Import from /models or Auto-Sync; verified live model: solidrust/Hermes-3-Llama-3.1-8B-AWQ. |
| `unorouter` | `unorouter` | UnoRouter | API key, aggregator | [link](https://unorouter.ai) | Models with the :free suffix do not debit balance; limit is 1 request/minute per free model per user. |
| `upstage` | `upstage` | Upstage | API key | [link](https://www.upstage.ai) | — |
| `v0-vercel` | `v0` | v0 (Vercel) | API key | [link](https://v0.dev) | — |
| `venice` | `venice` | Venice.ai | API key | [link](https://venice.ai) | — |
| `vercel-ai-gateway` | `vag` | Vercel AI Gateway | API key, aggregator | [link](https://vercel.com/docs/ai-gateway) | — |
| `vertex` | `vertex` | Vertex AI | API key, enterprise | [link](https://cloud.google.com/vertex-ai) | Provide Service Account JSON or OAuth access_token |
| `vertex-partner` | `vp` | Vertex AI Partners | API key, enterprise | [link](https://cloud.google.com/vertex-ai) | Provide the same Service Account JSON used for Vertex AI partner models. |
| `void-ai` | `void-ai` | Void AI | API key, aggregator | [link](https://voidai.app) | The public model catalog marks some models with a free plan requirement, but access is conditional and no numeric quota is confirmed. |
| `volcengine` | `volcengine` | Volcengine | API key | [link](https://www.volcengine.com) | — |
| `voyage-ai` | `voyage` | Voyage AI | API key, embed/rerank | [link](https://www.voyageai.com) | Bearer API key for Voyage AI embeddings and rerank APIs. |
| `wafer` | `wafer` | Wafer AI | API key | [link](https://wafer.ai) | — |
| `wandb` | `wandb` | Weights & Biases Inference | API key | [link](https://wandb.ai) | — |
| `watsonx` | `watsonx` | IBM watsonx.ai Gateway | API key, enterprise | [link](https://www.ibm.com/products/watsonx-ai) | Use your watsonx bearer token. Base URL can be https://<region>.ml.cloud.ibm.com/ml/gateway/v1/ or a self-managed /ml/gateway/v1 endpoint. |
| `writer` | `writer` | Writer | API key | [link](https://dev.writer.com) | — |
| `x5lab` | `x5lab` | X5Lab | API key | [link](https://x5lab.dev) | Use your X5Lab API key (x5-...) in Authorization: Bearer <key>. Fully OpenAI-compatible. API base URL: https://api.x5lab.dev/v1. |
| `xai` | `xai` | xAI (Grok) | API key | [link](https://x.ai) | Use an official xAI API key, or sign in with xAI OAuth. Grok Build JWT sessions remain a separate provider. |
| `xiaomi-mimo` | `mimo` | Xiaomi MiMo | API key | [link](https://mimo.mi.com) | — |
| `xiaomi-mimo-token-plan` | `mimotp` | Xiaomi MiMo Token Plan | API key | [link](https://mimo.mi.com) | — |
| `yi` | `yi` | Yi (01.AI) | API key | [link](https://01.ai) | Get API key at platform.lingyiwanwu.com |
| `yolo-auto` | `yolo-auto` | Yolo-Auto | API key, aggregator | [link](https://yolo-auto.com) | Free API access is request-limited and intended for testing; no numeric daily quota is published and free access is not promised indefinitely. |
| `zai` | `zai` | Z.AI | API key | [link](https://open.bigmodel.cn) | — |
| `zenmux` | `zm` | ZenMux | API key | [link](https://zenmux.ai) | Use your ZenMux API key in Authorization: Bearer <key>. ZenMux is fully OpenAI-compatible. Base URL: https://zenmux.ai/api/v1. |
| `zerolimitai` | `zerolimitai` | ZeroLimitAI | API key, aggregator | [link](https://www.zerolimitai.com) | Temporary free trial is advertised, but official pages conflict between 3 and 7 days; a 100-calls/day claim is not treated as permanent. |
| `zylo-api` | `zylo` | Zylo API | API key, aggregator | [link](https://zyloai.net) | Basic plan: 10 RPM, 7,200 requests/day and 200,000 tokens/day; limited to Basic text models. |
## Local Providers (14)
| ID | Alias | Name | Tags | Website | Notes |
|----|-------|------|------|---------|-------|
| `comfyui` | `comfyui` | ComfyUI | Local | [link](https://github.com/comfyanonymous/ComfyUI) | No API key required. Configure the local ComfyUI base URL (default: http://localhost:8188). |
| `docker-model-runner` | `dmr` | Docker Model Runner | Local, self-hosted | [link](https://docs.docker.com/ai/model-runner/) | API key optional. Configure the local Docker Model Runner OpenAI-compatible base URL (default: http://localhost:12434/v1). |
| `lemonade` | `lemonade` | Lemonade Server | Local, self-hosted | [link](https://lemonade-server.ai) | API key optional. Configure the local Lemonade OpenAI-compatible base URL (default: http://localhost:13305/api/v1). |
| `llama-cpp` | `llamacpp` | llama.cpp | Local, self-hosted | [link](https://github.com/ggml-org/llama.cpp) | API key optional (use any value, e.g. sk-no-key-required). Configure the llama-server OpenAI-compatible base URL (default: http://127.0.0.1:8080/v1). Note: if Llamafile is also installed, both default to port 8080 — run only one at a time or override the port. |
| `llamafile` | `llamafile` | Llamafile | Local, self-hosted | [link](https://github.com/Mozilla-Ocho/llamafile) | API key optional. Configure the local Llamafile OpenAI-compatible base URL (default: http://127.0.0.1:8080/v1). |
| `lm-studio` | `lmstudio` | LM Studio | Local, self-hosted | [link](https://lmstudio.ai) | API key optional. Configure the local LM Studio OpenAI-compatible base URL (default: http://localhost:1234/v1). |
| `mlx-gemma` | `mlx-gemma` | MLX Gemma 26B | Local, self-hosted | [link](https://github.com/ml-explore/mlx) | No API key required. Runs mlx-lm server locally on port 11435. Requires uv and mlx-lm installed. Model: mlx-community/gemma-4-26B-A4B-it-qat-q4_0-mlx-aligned (~15.9GB peak memory). |
| `mlx-qwen` | `mlx-qwen` | MLX Qwen 3.8 27B | Local, self-hosted | [link](https://github.com/ml-explore/mlx) | No API key required. Runs mlx-lm server locally on port 11436. Requires uv and mlx-lm installed. Model: maglun/Qwen3.8-27B-MLX-Mixed-3.80bpw (~13.1GB peak memory). |
| `ollama-local` | `ollama` | Ollama | Local, self-hosted | [link](https://ollama.com) | No API key required. Ollama runs locally — configure its OpenAI-compatible base URL (default: http://localhost:11434/v1) and make sure Ollama is running before connecting. |
| `oobabooga` | `ooba` | oobabooga | Local, self-hosted | [link](https://github.com/oobabooga/text-generation-webui) | API key optional. Configure the local oobabooga OpenAI-compatible base URL (default: http://localhost:5000/v1). |
| `sdwebui` | `sdwebui` | SD WebUI | Local | [link](https://github.com/AUTOMATIC1111/stable-diffusion-webui) | No API key required. Configure the local WebUI base URL (default: http://localhost:7860). |
| `triton` | `triton` | NVIDIA Triton | Local, self-hosted | [link](https://developer.nvidia.com/triton-inference-server) | API key optional. Configure the Triton OpenAI-compatible base URL (default: http://localhost:8000/v1). |
| `vllm` | `vllm` | vLLM | Local, self-hosted | [link](https://github.com/vllm-project/vllm) | API key optional. Configure the local vLLM OpenAI-compatible base URL (default: http://localhost:8000/v1). |
| `xinference` | `xinference` | XInference | Local, self-hosted | [link](https://inference.readthedocs.io) | API key optional. Configure the local XInference OpenAI-compatible base URL (default: http://localhost:9997/v1). |
## Search Providers (13)
| ID | Alias | Name | Tags | Website | Notes |
|----|-------|------|------|---------|-------|
| `brave-search` | `brave-search` | Brave Search | Search | [link](https://brave.com/search/api) | Subscription token from Brave Search API dashboard |
| `exa-search` | `exa-search` | Exa Search | Search | [link](https://exa.ai) | API key from dashboard.exa.ai |
| `firecrawl` | `fc` | Firecrawl | Search | [link](https://firecrawl.dev) | API key from firecrawl.dev/app/api-keys (or set your self-hosted Firecrawl base URL) |
| `google-pse-search` | `google-pse` | Google Programmable Search | Search | [link](https://developers.google.com/custom-search/v1/overview) | Requires a Google API key and your Programmable Search Engine ID (cx) |
| `linkup-search` | `linkup` | Linkup Search | Search | [link](https://docs.linkup.so) | Bearer API key from the Linkup dashboard |
| `ollama-search` | `ollama-search` | Ollama Search | Search | [link](https://ollama.com/settings/keys) | Same API key as Ollama Cloud (from ollama.com/settings/keys) |
| `perplexity-search` | `pplx-search` | Perplexity Search | Search | [link](https://docs.perplexity.ai/guides/search-quickstart) | Same API key as Perplexity (pplx-...) |
| `searchapi-search` | `searchapi` | SearchAPI | Search | [link](https://www.searchapi.io/docs/google) | API key from SearchAPI (query param or Bearer auth) |
| `searxng-search` | `searxng` | SearXNG Search | Search | [link](https://docs.searxng.org) | API key is optional. Set your SearXNG base URL. Some instances may require a bearer token for access. |
| `serper-search` | `serper-search` | Serper Search | Search | [link](https://serper.dev) | API key from serper.dev dashboard |
| `tavily-search` | `tavily-search` | Tavily Search | Search | [link](https://tavily.com) | API key from app.tavily.com (format: tvly-...) |
| `x-search` | `x_search` | X Search (Grok) | Search | [link](https://docs.x.ai/developers/tools/x-search) | SuperGrok OAuth (xai-oauth) or xAI API key. This is Grok X Search, not the X Developer MCP. |
| `youcom-search` | `youcom-search` | You.com Search | Search | [link](https://you.com/business/api/) | X-API-Key from the You.com platform dashboard |
## Audio-only Providers (12)
| ID | Alias | Name | Tags | Website | Notes |
|----|-------|------|------|---------|-------|
| `assemblyai` | `aai` | AssemblyAI | Audio | [link](https://assemblyai.com) | — |
| `aws-polly` | `polly` | AWS Polly | Audio | [link](https://aws.amazon.com/polly/) | Use AWS Secret Access Key as API key; set providerSpecificData.accessKeyId and optional region. |
| `cartesia` | `cartesia` | Cartesia | Audio | [link](https://cartesia.ai) | — |
| `deepgram` | `dg` | Deepgram | Audio | [link](https://deepgram.com) | — |
| `elevenlabs` | `el` | ElevenLabs | Audio | [link](https://elevenlabs.io) | — |
| `fishaudio` | `fishaudio` | Fish Audio | Audio | [link](https://fish.audio) | — |
| `gladia` | `gladia` | Gladia | Audio | [link](https://gladia.io) | — |
| `inworld` | `inworld` | Inworld | Audio | [link](https://inworld.ai) | — |
| `playht` | `playht` | PlayHT | Audio | [link](https://play.ht) | — |
| `rev-ai` | `revai` | Rev AI | Audio | [link](https://www.rev.ai) | — |
| `soniox` | `sx` | Soniox | Audio | [link](https://soniox.com) | — |
| `speechmatics` | `sm` | Speechmatics | Audio | [link](https://www.speechmatics.com) | Free tier — 8 hours/month, no credit card required. Batch (async) mode only. |
## Upstream Proxy Providers (2)
| ID | Alias | Name | Tags | Website | Notes |
|----|-------|------|------|---------|-------|
| `9router` | `nr` | 9router | Upstream proxy | [link](https://www.npmjs.com/package/9router) | — |
| `cliproxyapi` | `cpa` | CLIProxyAPI | Upstream proxy | [link](https://github.com/router-for-me/CLIProxyAPI) | — |
## Cloud Agent Providers (3)
| ID | Alias | Name | Tags | Website | Notes |
|----|-------|------|------|---------|-------|
| `codex-cloud` | `codex-cloud` | Codex Cloud | Cloud agent | [link](https://openai.com/codex) | OpenAI API key with Codex Cloud task access. |
| `devin` | `devin` | Devin | Cloud agent | [link](https://devin.ai) | Devin API key for cloud agent sessions. |
| `jules` | `jules` | Google Jules | Cloud agent | [link](https://jules.google) | Jules API key for creating and managing cloud coding tasks. |
## System Providers (1)
| ID | Alias | Name | Tags | Website | Notes |
|----|-------|------|------|---------|-------|
| `auto` | `auto` | Auto (Zero-Config) | System | — | — |
## Sources of truth
- Catalog: [`src/shared/constants/providers.ts`](../../src/shared/constants/providers.ts)
- Registry (per-model details): [`open-sse/config/providerRegistry.ts`](../../open-sse/config/providerRegistry.ts)
- Executors: [`open-sse/executors/`](../../open-sse/executors/) (106 implementations)
- Translators: [`open-sse/translator/`](../../open-sse/translator/)
## See Also
- [FREE_TIERS.md](./FREE_TIERS.md) — curated free-tier guide
- [USER_GUIDE.md](../guides/USER_GUIDE.md) — provider setup walkthrough
- [ARCHITECTURE.md](../architecture/ARCHITECTURE.md) — overall architecture

View File

@@ -7,7 +7,7 @@
# 🚀 OmniRoute — The Free AI Gateway
<img src="./docs/diagrams/readme-hero.svg" width="100%" alt="OmniRoute — Never stop coding. Every AI tool → 352 providers — 90+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini with auto-fallback. RTK + Caveman stacked compression saves 1595% tokens (~89% avg) — never hit limits. 352 AI providers · 90+ free tiers · ~1.51B free tokens/mo · 19 routing strategies · $0 to start."/>
<img src="./docs/diagrams/readme-hero.svg" width="100%" alt="OmniRoute — Never stop coding. Every AI tool → 351 providers — 90+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini with auto-fallback. RTK + Caveman stacked compression saves 1595% tokens (~89% avg) — never hit limits. 351 AI providers · 90+ free tiers · ~1.51B free tokens/mo · 19 routing strategies · $0 to start."/>
</div>
@@ -17,9 +17,9 @@
</div>
> Stacking free tiers by hand is painful — dozens of SDKs, dozens of rate limits, and no idea how much you actually have. OmniRoute catalogs **455 free-tier entries across 40 recurring pool keys** and computes the token headline from the **20 pools with a published positive monthly budget**, deduplicated by shared pool. The result stays visible on the dashboard (`/dashboard/free-tiers`).
> Stacking free tiers by hand is painful — dozens of SDKs, dozens of rate limits, and no idea how much you actually have. OmniRoute catalogs **445 free-tier entries across 39 recurring pool keys** and computes the token headline from the **20 pools with a published positive monthly budget**, deduplicated by shared pool. The result stays visible on the dashboard (`/dashboard/free-tiers`).
<img src="./docs/diagrams/free-tier-budget.svg" width="100%" alt="OmniRoute free-tier budget card: ~1.51B free tokens per month steady, up to ~2.13B in the first month with signup credits, from 40 documented recurring pool keys covering 455 cataloged free-tier entries behind one endpoint. Honest pool-deduped math — each shared pool counted once, including 20 recurring pools with a published positive monthly token budget; 15 providers are marked avoid in the terms-risk catalog so you decide. Budget bar includes Mistral 1B, LLM7 150M, Nara 150M, Gemini 60M and smaller pools, plus first-month signup credits and permanently-free no-token-cap providers surfaced separately so they never inflate the headline. Live used/remaining on /dashboard/free-tiers."/>
<img src="./docs/diagrams/free-tier-budget.svg" width="100%" alt="OmniRoute free-tier budget card: ~1.51B free tokens per month steady, up to ~2.13B in the first month with signup credits, from 39 documented recurring pool keys covering 445 cataloged free-tier entries behind one endpoint. Honest pool-deduped math — each shared pool counted once, including 20 recurring pools with a published positive monthly token budget; 13 providers are marked avoid in the terms-risk catalog so you decide. Budget bar includes Mistral 1B, LLM7 150M, Nara 150M, Gemini 60M and smaller pools, plus first-month signup credits and permanently-free no-token-cap providers surfaced separately so they never inflate the headline. Live used/remaining on /dashboard/free-tiers."/>
> Animated summary of the live `/dashboard/free-tiers` page. Full methodology (pool dedupe, credit tiers, provider terms): **[docs/reference/FREE_TIERS.md](docs/reference/FREE_TIERS.md)**.
>
@@ -63,7 +63,7 @@
| | v3.8.49 | **v3.8.50** | `v3.8.51+` |
| ------------------------- | :-----: | :-----------------------: | :---------: |
| 🌐 Providers | 290 | **352** | more queued |
| 🌐 Providers | 290 | **350** | more queued |
| 🧠 Unique chat model IDs | 1185 | **1312** | — |
| 🖼️ Modality Bridge | — | 🆕 vision + audio + video | — |
| 📡 Radar free catalog | — | 🆕 opt-in | — |
@@ -101,7 +101,7 @@
<tr>
<td align="right"><b>⚙️ Features</b></td>
<td align="center"><a href="#-combos--the-flagship">🎯 Combos</a></td>
<td align="center"><a href="#-352-ai-providers--154-catalog-marked-free">🌐 Providers</a></td>
<td align="center"><a href="#-351-ai-providers--154-catalog-marked-free">🌐 Providers</a></td>
<td align="center"><a href="#-full-cli--a2a--mcp">🔌 CLI &amp; MCP</a></td>
</tr>
<tr>
@@ -189,7 +189,7 @@
</div>
<img src="./docs/diagrams/works-zero-config.svg" width="100%" alt="Works the second you install it — zero config. Three steps: 1. Install — npm i -g omniroute, server boots on localhost:20128. 2. Point your tool at http://localhost:20128/v1 — any OpenAI-compatible tool (Claude Code, Cursor, Cline). 3. It answers — call model auto for an instant reply, with no API key, no signup, no configuration. Keyless free providers OpenCode Free and Felo are pre-wired into the auto combo, so a fresh install responds out of the box."/>
<img src="./docs/diagrams/works-zero-config.svg" width="100%" alt="Works the second you install it — zero config. Three steps: 1. Install — npm i -g omniroute, server boots on localhost:20128. 2. Point your tool at http://localhost:20128/v1 — any OpenAI-compatible tool (Claude Code, Cursor, Cline). 3. It answers — call model auto for an instant reply, with no API key, no signup, no configuration. Keyless provider OpenCode Free is pre-wired into the auto combo, so a fresh install responds out of the box."/>
```bash
# Fresh install, zero credentials — `auto` already works:
@@ -198,7 +198,7 @@ curl http://localhost:20128/v1/chat/completions \
-d '{"model":"auto","messages":[{"role":"user","content":"Hello!"}]}'
```
<sub>Prefer a specific free backend? Call it directly, e.g. `oc/…` (OpenCode Free) or `felo/…` (Felo). Then graduate to `auto` and let OmniRoute pick.</sub>
<sub>Prefer a specific free backend? Call `oc/…` (OpenCode Free) directly. Then graduate to `auto` and let OmniRoute pick.</sub>
<sub>📦 Copy-paste quickstart scripts for **Python, Node.js, PHP, and cURL** → [`examples/quickstart/`](examples/quickstart/)</sub>
@@ -210,7 +210,7 @@ curl http://localhost:20128/v1/chat/completions \
</div>
<img src="./docs/diagrams/promise-pillars.svg" width="100%" alt="The Promise — One endpoint and 352 providers. Automatic fallback keeps routing while another healthy target is available. Six pillars: resilient fallback across 352 providers · up to 95% token savings on eligible workloads · $0 to start with 90+ free tiers and 56 recurring/keyless free-forever providers · 35 CLI/agent integrations through one config · OpenAI, Claude, Gemini and Responses API compatibility at /v1 · production controls including circuit breakers, TLS stealth, MCP 110 tools, A2A, memory, guardrails, evals and 39,000+ static test declarations across 5,100+ tracked test files."/>
<img src="./docs/diagrams/promise-pillars.svg" width="100%" alt="The Promise — One endpoint and 351 providers. Automatic fallback keeps routing while another healthy target is available. Six pillars: resilient fallback across 351 providers · up to 95% token savings on eligible workloads · $0 to start with 90+ free tiers and 56 recurring/keyless free-forever providers · 35 CLI/agent integrations through one config · OpenAI, Claude, Gemini and Responses API compatibility at /v1 · production controls including circuit breakers, TLS stealth, MCP 110 tools, A2A, memory, guardrails, evals and 39,000+ static test declarations across 5,100+ tracked test files."/>
<br/>
<br/>
@@ -266,7 +266,7 @@ curl http://localhost:20128/v1/chat/completions \
<tr>
<td align="center" width="150">
<a href="https://cheaperinference.com/?utm_source=omniroute">
<img src="public/providers/cheaperinference.svg" width="64" alt="Cheaper Inference"/>
<img src="./public/providers/cli-generic.svg" width="64" alt="Cheaper Inference"/>
</a>
<br/><b>Cheaper Inference</b><br/><sub>cheaperinference.com</sub><br/><br/>
<img src="https://img.shields.io/badge/Open_Source_Friend-31f889?style=flat-square&labelColor=04170d" alt="Open Source Friend"/>
@@ -292,7 +292,7 @@ curl http://localhost:20128/v1/chat/completions \
<tr>
<td align="center" width="120">
<a href="https://agentrouter.org/register?aff=70LM">
<img src="public/providers/agentrouter.png" width="32" alt="AgentRouter"/>
<img src="./public/providers/cli-generic.svg" width="32" alt="AgentRouter"/>
</a>
<br/><sub><b>AgentRouter</b></sub><br/><sub>agentrouter.org</sub>
</td>
@@ -424,7 +424,7 @@ All **19** strategies — mix & match per combo step:
<tr>
<td align="center">16</td>
<td nowrap><code>lkgp</code></td>
<td>Last-Known-Good Path — sticky to the last successful target</td>
<td>Last-Known-Good Path — pins to the last successful provider, then falls back to rules</td>
</tr>
<tr>
<td align="center">17</td>
@@ -461,7 +461,7 @@ All **19** strategies — mix & match per combo step:
</div>
<img src="./docs/diagrams/comparison-table.svg" width="100%" alt="What sets OmniRoute apart — a dated feature snapshot vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 352 providers, 90+ free tiers built in, 19 routing strategies, 12-engine token compression, built-in MCP server with 110 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA and 43 i18n UI locales. OmniRoute is MIT-licensed and self-hostable. Competitor capabilities and counts may change; see the linked methodology."/>
<img src="./docs/diagrams/comparison-table.svg" width="100%" alt="What sets OmniRoute apart — a dated feature snapshot vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 351 providers, 90+ free tiers built in, 19 routing strategies, 12-engine token compression, built-in MCP server with 110 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA and 43 i18n UI locales. OmniRoute is MIT-licensed and self-hostable. Competitor capabilities and counts may change; see the linked methodology."/>
<sub>📊 Full methodology &amp; per-feature detail vs 9router, OpenRouter, CLIProxyAPI &amp; LiteLLM → [`docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md`](docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md)</sub>
@@ -557,9 +557,9 @@ the current catalog at **[radar.omniroute.online/planos](https://radar.omniroute
- **🧠 Memory you control** — off by default, opt-in int8 vector quantization + typed decay, per-request `x-omniroute-no-memory`. → [Memory](docs/frameworks/MEMORY.md)
- **🛡️ Security** — prompt-injection guard on every LLM route (red-team suite), opt-in credential-masking guardrail (redacts leaked API keys/secrets in both directions), free DuckDuckGo last-resort web search, and an optional OIDC login gate for the dashboard (password login always stays available). → [Guardrails](docs/security/GUARDRAILS.md)
- **🖼️ New endpoints** — `/v1/ocr` (Mistral OCR) and `/v1/audio/translations` (Whisper-style) round out the media surface. → [API Reference](docs/reference/API_REFERENCE.md)
- **🎨 Image / video / audio generation** — one API for media: xAI Grok Imagine & Novita AI video, ComfyUI, Freepik, Adobe Firefly, Microsoft Designer, Segmind, EdgeTTS. → [API Reference](docs/reference/API_REFERENCE.md)
- **🎨 Image / video / audio generation** — one API for media: xAI Grok Imagine & Novita AI video, ComfyUI, Magnific, Adobe Firefly, Segmind, and speech providers such as ElevenLabs. → [API Reference](docs/reference/API_REFERENCE.md)
- **🌍 Deployment & ops** — reverse-proxy `basePath`, browser-language auto-detect, per-key device tracking, root-less MITM trust, zh-TW localization. → [Environment](docs/reference/ENVIRONMENT.md)
- **🤝 More providers & agents** — Cursor Cloud Agent, Grok Build (xAI) with browser + OAuth login, Ollama first-class card, Claude Opus 5 & Sonnet 5, Kimi official partnership (Code/Web/Moonshot), Zed, Requesty, SenseNova, Yuanbao, Agnes AI… and a refreshed **352-provider catalog**. → [Providers](docs/reference/PROVIDER_REFERENCE.md)
- **🤝 More providers & agents** — Cursor Cloud Agent, Grok Build (xAI) with browser + OAuth login, Ollama first-class card, Claude Opus 5 & Sonnet 5, Kimi official partnership (Code/Web/Moonshot), Zed, Requesty, SenseNova, Yuanbao, Agnes AI… and a refreshed **351-provider catalog**. → [Providers](docs/reference/PROVIDER_REFERENCE.md)
- **📡 Routing transparency** — every response carries an `X-OmniRoute-Decision` header naming the strategy/provider/latency that served it, a new `cache-optimized` combo strategy + Auto-Combo `cacheAffinity` factor route repeat requests back to the connection holding the cached prefix, and a read-only `/v1/auto-combo/{channel}/candidates` endpoint exposes an `auto/*` channel's live candidate pool. → [Auto-Combo](docs/routing/AUTO-COMBO.md)
- **⚡ Local performance & infra** — one-click local Redis, Cloudflare Workers / Deno Deploy relay deployers, Bifrost & Mux as supervised embedded services. → [Embedded Services](docs/frameworks/EMBEDDED-SERVICES.md)
@@ -577,8 +577,8 @@ the current catalog at **[radar.omniroute.online/planos](https://radar.omniroute
<td align="center" width="76"><a href="https://github.com/anthropics/claude-code"><img src="./public/providers/claude.svg" width="40" alt="Claude Code"/><br/><sub><b>Claude Code</b></sub><br/><sub>                           </sub></a></td>
<td align="center" width="76"><a href="https://github.com/openai/codex"><img src="./public/providers/codex.svg" width="40" alt="Codex CLI"/><br/><sub><b>Codex CLI</b></sub><br/><sub>                           </sub></a></td>
<td align="center" width="76"><picture><source media="(prefers-color-scheme:dark)" srcset="https://cdn.jsdelivr.net/npm/@lobehub/icons-static-png@1.91.0/dark/cline.png"/><img src="https://cdn.jsdelivr.net/npm/@lobehub/icons-static-svg@1.91.0/icons/cline.svg" width="40" alt="Cline"/></picture><br/><sub><b>Cline</b></sub><br/><sub>                           </sub></td>
<td align="center" width="76"><a href="https://github.com/Kilo-Org/kilocode"><img src="./public/providers/kilocode.svg" width="40" alt="Kilo Code"/><br/><sub><b>Kilo Code</b></sub><br/><sub>                           </sub></a></td>
<td align="center" width="76"><a href="https://github.com/Zoo-Code-Org/Zoo-Code"><img src="./public/providers/zoocode.png" width="40" alt="Zoo Code"/><br/><sub><b>Zoo Code</b></sub><br/><sub>                           </sub></a></td>
<td align="center" width="76"><a href="https://github.com/Kilo-Org/kilocode"><img src="./public/providers/cli-generic.svg" width="40" alt="Kilo Code"/><br/><sub><b>Kilo Code</b></sub><br/><sub> </sub></a></td>
<td align="center" width="76"><a href="https://github.com/Zoo-Code-Org/Zoo-Code"><img src="./public/providers/cli-generic.svg" width="40" alt="Zoo Code"/><br/><sub><b>Zoo Code</b></sub><br/><sub> </sub></a></td>
<td align="center" width="76"><img src="./public/providers/continue.svg" width="40" alt="Continue"/><br/><sub><b>Continue</b></sub><br/><sub>                           </sub></td>
</tr>
<tr>
@@ -587,10 +587,10 @@ the current catalog at **[radar.omniroute.online/planos](https://radar.omniroute
<td align="center" width="76"><img src="./public/providers/cli-generic.svg" width="40" alt="jcode"/><br/><sub><b>jcode</b></sub><br/><sub>                           </sub></td>
<td align="center" width="76"><img src="./public/providers/deepseek.svg" width="40" alt="DeepSeek TUI"/><br/><sub><b>DeepSeek TUI</b></sub><br/><sub>                           </sub></td>
<td align="center" width="76"><img src="./public/providers/cli-generic.svg" width="40" alt="CodeWhale"/><br/><sub><b>CodeWhale</b></sub><br/><sub>                           </sub></td>
<td align="center" width="76"><a href="https://github.com/anomalyco/opencode"><picture><source media="(prefers-color-scheme:dark)" srcset="https://cdn.jsdelivr.net/npm/@lobehub/icons-static-png@1.91.0/dark/opencode.png"/><img src="https://cdn.jsdelivr.net/npm/@lobehub/icons-static-svg@1.91.0/icons/opencode.svg" width="40" alt="OpenCode"/></picture><br/><sub><b>OpenCode</b></sub><br/><sub>                           </sub></a></td>
<td align="center" width="76"><a href="https://github.com/anomalyco/opencode"><img src="./public/providers/cli-generic.svg" width="40" alt="OpenCode"/><br/><sub><b>OpenCode</b></sub><br/><sub>                           </sub></a></td>
</tr>
<tr>
<td align="center" width="76"><img src="./public/providers/droid.svg" width="40" alt="Factory Droid"/><br/><sub><b>Factory Droid</b></sub><br/><sub>                           </sub></td>
<td align="center" width="76"><img src="./public/providers/cli-generic.svg" width="40" alt="Factory Droid"/><br/><sub><b>Factory Droid</b></sub><br/><sub>                           </sub></td>
<td align="center" width="76"><img src="./public/providers/copilot.svg" width="40" alt="GitHub Copilot CLI"/><br/><sub><b>Copilot CLI</b></sub><br/><sub>                           </sub></td>
<td align="center" width="76"><img src="./public/providers/cursor.svg" width="40" alt="Cursor CLI"/><br/><sub><b>Cursor CLI</b></sub><br/><sub>                           </sub></td>
<td align="center" width="76"><img src="./public/providers/cli-generic.svg" width="40" alt="Smelt"/><br/><sub><b>Smelt</b></sub><br/><sub>                           </sub></td>
@@ -612,7 +612,7 @@ the current catalog at **[radar.omniroute.online/planos](https://radar.omniroute
<b> also works with</b> · Kiro · Command Code · Antigravity · Windsurf · AMP · <b>any OpenAI-compatible tool</b>
</div>
<sub>📖 Per-tool setup for all 35 tools (26 CLI Code's + 9 CLI Agents) → [`docs/reference/CLI-TOOLS.md`](docs/reference/CLI-TOOLS.md) · 🧩 OpenCode plugin → [`@omniroute/opencode-provider`](https://www.npmjs.com/package/@omniroute/opencode-provider)</sub>
<sub>📖 Per-tool setup for all 36 tools (26 CLI Code's + 10 CLI Agents) → [`docs/reference/CLI-TOOLS.md`](docs/reference/CLI-TOOLS.md) · 🧩 OpenCode plugin → [`@omniroute/opencode-provider`](https://www.npmjs.com/package/@omniroute/opencode-provider)</sub>
</div>
@@ -642,11 +642,11 @@ of your shell history. → [CLI Integrations](docs/guides/CLI-INTEGRATIONS.md)
<div align="center">
## 🌐 352 AI Providers — 154 Catalog-Marked Free
## 🌐 351 AI Providers — 154 Catalog-Marked Free
</div>
> **352 registered providers** across the canonical chat, media, search, local, cloud-agent and system collections, including **154 carrying `hasFree: true` discovery metadata**. The chat model registry covers **268 providers / 2,566 distinct provider-model pairs / 1,312 raw model IDs**; the separate free-budget catalog has **455 per-model rows**, **40 recurring pools** and **56 recurring/keyless free-forever providers**. These are different denominators by design; definitions and pool-deduped calculations live in the [Provider Reference](docs/reference/PROVIDER_REFERENCE.md) and [Free Tiers](docs/reference/FREE_TIERS.md).
> **351 registered providers** across the canonical chat, media, search, local, cloud-agent and system collections, including **154 carrying `hasFree: true` discovery metadata**. The chat model registry covers **268 providers / 2,566 distinct provider-model pairs / 1,312 raw model IDs**; the separate free-budget catalog has **455 per-model rows**, **40 recurring pools** and **56 recurring/keyless free-forever providers**. These are different denominators by design; definitions and pool-deduped calculations live in the [Provider Reference](docs/reference/PROVIDER_REFERENCE.md) and [Free Tiers](docs/reference/FREE_TIERS.md).
<div align="center">
@@ -687,8 +687,8 @@ of your shell history. → [CLI Integrations](docs/guides/CLI-INTEGRATIONS.md)
<table>
<tr>
<td align="center" width="150"><img src="./public/providers/opencode.svg" width="42" alt="OpenCode Zen"/><br/><b>OpenCode Zen</b><br/><sub>DeepSeek V4, Nemotron 3<br/>No token cap</sub></td>
<td align="center" width="150"><img src="./public/providers/kilocode.svg" width="42" alt="Kilo Code"/><br/><b>Kilo Code</b><br/><sub>Auto-router, Tencent Hy3<br/>Free forever</sub></td>
<td align="center" width="150"><img src="./public/providers/cli-generic.svg" width="42" alt="OpenCode Zen"/><br/><b>OpenCode Zen</b><br/><sub>DeepSeek V4, Nemotron 3<br/>No token cap</sub></td>
<td align="center" width="150"><img src="./public/providers/cli-generic.svg" width="42" alt="Kilo Code"/><br/><b>Kilo Code</b><br/><sub>Auto-router, Tencent Hy3<br/>Free forever</sub></td>
<td align="center" width="150"><img src="./public/providers/requesty.svg" width="42" alt="Requesty"/><br/><b>Requesty</b><br/><sub>GPT-OSS 120B, Nemotron<br/>Free forever</sub></td>
<td align="center" width="150"><img src="https://cdn.jsdelivr.net/npm/@lobehub/icons-static-svg@1.91.0/icons/siliconcloud-color.svg" width="42" alt="SiliconFlow"/><br/><b>SiliconFlow</b><br/><sub>DeepSeek V3.2 / R1<br/>Free tier</sub></td>
<td align="center" width="150"><img src="./public/providers/zhipu.svg" width="42" alt="Z.AI GLM"/><br/><b>Z.AI GLM</b><br/><sub>GLM-4.7 / 4.5-Flash<br/>Free forever</sub></td>
@@ -1202,7 +1202,7 @@ Métricas canônicas em 2026-08-24: **1.029 vídeos únicos** · **11.132.922 vi
<tr><td nowrap><b>Runtime</b></td><td>Node.js 22.x / 24.x LTS — <code>&gt;=22.22.2 &lt;23 || &gt;=24.0.0 &lt;27</code></td></tr>
<tr><td nowrap><b>Language</b></td><td>TypeScript 6.0 — <b>100% TypeScript</b> across <code>src/</code> and <code>open-sse/</code> (zero <code>any</code> in core since v2.0)</td></tr>
<tr><td nowrap><b>Framework</b></td><td>Next.js 16 + React 19 + Tailwind CSS 4</td></tr>
<tr><td nowrap><b>Database</b></td><td>better-sqlite3 (SQLite, WAL journaling) + LowDB (JSON legacy) — 120 domain modules, 159 migrations</td></tr>
<tr><td nowrap><b>Database</b></td><td>better-sqlite3 (SQLite, WAL journaling) + LowDB (JSON legacy) — 120 domain modules, 167 migrations</td></tr>
<tr><td nowrap><b>Memory</b></td><td>SQLite FTS5 full-text + int8-quantized vector embeddings, typed decay</td></tr>
<tr><td nowrap><b>Schemas</b></td><td>Zod 4 — MCP tool I/O validation + API contracts</td></tr>
<tr><td nowrap><b>Protocols</b></td><td>MCP (stdio / HTTP / SSE) + A2A v0.3 (JSON-RPC 2.0 + SSE)</td></tr>
@@ -1265,7 +1265,7 @@ Métricas canônicas em 2026-08-24: **1.029 vídeos únicos** · **11.132.922 vi
<tr><td nowrap><b><a href="docs/architecture/RESILIENCE_GUIDE.md">Resilience Guide</a></b></td><td>Circuit breakers, cooldowns, queue, anti-thundering herd, TLS spoofing</td></tr>
<tr><td nowrap><b><a href="docs/routing/AUTO-COMBO.md">Auto-Combo Engine</a></b></td><td>15-factor scoring, mode packs, self-healing</td></tr>
<tr><td nowrap><b><a href="docs/ops/PROXY_GUIDE.md">Proxy Guide</a></b></td><td>3-level proxy system, 1proxy marketplace, registry CRUD</td></tr>
<tr><td nowrap><b><a href="docs/reference/FREE_TIERS.md">Free Tiers</a></b></td><td>Consolidated directory: 40 documented recurring pools / 455 cataloged free-tier entries</td></tr>
<tr><td nowrap><b><a href="docs/reference/FREE_TIERS.md">Free Tiers</a></b></td><td>Consolidated directory: 39 documented recurring pools / 445 cataloged free-tier entries</td></tr>
<tr><td nowrap><b><a href="docs/guides/FEATURES.md">Features Gallery</a></b></td><td>Visual dashboard tour with screenshots</td></tr>
<tr><td nowrap><b><a href="docs/architecture/CODEBASE_DOCUMENTATION.md">Codebase Documentation</a></b></td><td>Beginner-friendly codebase walkthrough</td></tr>
</table>
@@ -1621,7 +1621,7 @@ OmniRoute stands on the shoulders of giants. It started as a fork of **[9router]
<table>
<tr><th align="left">Project</th><th align="center">⭐</th><th align="left">How it inspired OmniRoute</th></tr>
<tr><td nowrap><b><a href="https://github.com/chouzz/llm-interceptor">llm-interceptor</a></b></td><td align="center">66</td><td>MITM interception/analysis of coding-assistant ↔ LLM traffic — our Traffic Inspector ports its SSE merge, conversation normalization, host passthrough and secret masking. The upstream's complete license text is still under provenance review.</td></tr>
<tr><td nowrap><b><a href="https://github.com/chouzz/llm-interceptor">llm-interceptor</a></b></td><td align="center">66</td><td>MITM interception/analysis of coding-assistant ↔ LLM traffic informed early Traffic Inspector requirements. Four previously derived modules — SSE merging, conversation normalization, secret masking and header sanitization — have been replaced by independent clean-room implementations based on public protocol standards. The two host-passthrough surfaces (<code>passthrough.ts</code> and <code>_internal/bypass.cjs</code>) remain OmniRoute-internal implementations classified independently; they were not rewritten as part of that replacement.</td></tr>
<tr><td nowrap><b><a href="https://github.com/InterceptSuite/ProxyBridge">ProxyBridge</a></b></td><td align="center">5,995</td><td>Transparent per-process proxy routing — inspired our crash-safe MITM teardown, socket idle-timeouts, <code>/proc</code> process attribution and TPROXY capture.</td></tr>
</table>

View File

@@ -24,3 +24,305 @@ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPO
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
## wreq-js 3.0.0
OmniRoute distributes `wreq-js` and its seven platform-specific native addons from
[`wreq-js@3.0.0`](https://www.npmjs.com/package/wreq-js/v/3.0.0).
MIT License
Copyright (c) 2025 will-work-for-meal
Copyright (c) 2025 Oleksandr Herasymov
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
## blackwell-systems/gcf-typescript
The generic-profile codec in
`open-sse/services/compression/engines/headroom/gcf/{decode_generic,generic,index,scalar}.ts`
is adapted from
[`blackwell-systems/gcf-typescript`](https://github.com/blackwell-systems/gcf-typescript/tree/00972f2dc781477eb6d369e62edfe03ad4112a07),
commit `00972f2dc781477eb6d369e62edfe03ad4112a07`. The license below is reproduced
from that commit's
[`LICENSE`](https://github.com/blackwell-systems/gcf-typescript/blob/00972f2dc781477eb6d369e62edfe03ad4112a07/LICENSE).
MIT License
Copyright (c) 2026 Dayna Blackwell
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
## lipis/flag-icons
The country flag SVGs in `docs/assets/flags/` are copied from the `flags/4x3/` directory of
[`lipis/flag-icons`](https://github.com/lipis/flag-icons/tree/086f7e97d657358203916dbe84f61c2bccaa81eb),
commit `086f7e97d657358203916dbe84f61c2bccaa81eb`. The license below is reproduced
from that commit's
[`LICENSE`](https://github.com/lipis/flag-icons/blob/086f7e97d657358203916dbe84f61c2bccaa81eb/LICENSE).
The MIT License (MIT)
Copyright (c) 2013 Panayiotis Lipiridis
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
## LobeHub provider asset derivatives
Six local provider SVGs contain geometry derived from fixed components in
`@lobehub/icons@5.10.0`. The source package is pinned as follows:
- Tarball:
<https://registry.npmjs.org/@lobehub/icons/-/icons-5.10.0.tgz>
- npm shasum: `add1baced073a60157d39c7820b8d5c1928a1054`
- npm integrity:
`sha512-CIpjkISCLRK7haDtSugGFd0o3odaJts8ewJOkUiEFtns3xvsqbl8i24eowBnjw+yMDQVQyNONlhqTD58YC6Ljg==`
- License file in the fixed tarball: `package/LICENSE`
| Local derivative | Fixed tarball source |
| ------------------------------- | ----------------------------------------- |
| `public/providers/360ai.svg` | `package/es/Ai360/components/Color.js` |
| `public/providers/baichuan.svg` | `package/es/Baichuan/components/Color.js` |
| `public/providers/codex.svg` | `package/es/Codex/components/Color.js` |
| `public/providers/copilot.svg` | `package/es/Copilot/components/Color.js` |
| `public/providers/openclaw.svg` | `package/es/OpenClaw/components/Color.js` |
| `public/providers/stepfun.svg` | `package/es/Stepfun/components/Color.js` |
The fixed tarball contains this license notice:
MIT License
Copyright (c) 2023 LobeHub
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
This package notice applies to the derived SVG geometry identified above. It does not grant rights
in any underlying brand name, logo, or trademark.
## theSVG provider assets
At release snapshot `091589089cd134a94df9f6cdab9ba562b2cefd18`, 65 local provider SVGs were
byte-exact matches for `public/icons/<slug>/default.svg` in the theSVG repository at immutable
commit [`7870bc1c5f657d9accbb7f96cc457b8dd3363ee8`](https://github.com/GLINCKER/thesvg/tree/7870bc1c5f657d9accbb7f96cc457b8dd3363ee8).
The fixed upstream evidence includes its
[`LICENSE`](https://github.com/GLINCKER/thesvg/blob/7870bc1c5f657d9accbb7f96cc457b8dd3363ee8/LICENSE),
[`LEGAL.md`](https://github.com/GLINCKER/thesvg/blob/7870bc1c5f657d9accbb7f96cc457b8dd3363ee8/LEGAL.md),
[`TRADEMARK.md`](https://github.com/GLINCKER/thesvg/blob/7870bc1c5f657d9accbb7f96cc457b8dd3363ee8/TRADEMARK.md),
[`LICENSING.md`](https://github.com/GLINCKER/thesvg/blob/7870bc1c5f657d9accbb7f96cc457b8dd3363ee8/LICENSING.md),
and
[`src/data/icons.json`](https://github.com/GLINCKER/thesvg/blob/7870bc1c5f657d9accbb7f96cc457b8dd3363ee8/src/data/icons.json).
The byte match proves source provenance for the listed files. It does not prove that a registry
claim was authorized by each brand owner, and it does not relicense the logos or their underlying
brand marks. The theSVG source applies its MIT license to its codebase, tooling, and catalog; its
own legal documents separately reserve trademark rights to the respective owners.
The fixed theSVG source contains this license notice:
MIT License
Copyright (c) 2025 thesvg.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
### Byte-exact file scope (65/65)
- `public/providers/alibaba.svg`
- `public/providers/anthropic.svg`
- `public/providers/arcee.svg`
- `public/providers/assemblyai.svg`
- `public/providers/aws.svg`
- `public/providers/azure.svg`
- `public/providers/bailian.svg`
- `public/providers/baseten.svg`
- `public/providers/cerebras.svg`
- `public/providers/cline.svg`
- `public/providers/comfyui.svg`
- `public/providers/continue.svg`
- `public/providers/cursor.svg`
- `public/providers/deepgram.svg`
- `public/providers/deepinfra.svg`
- `public/providers/elevenlabs.svg`
- `public/providers/exa.svg`
- `public/providers/fal.svg`
- `public/providers/fireworks.svg`
- `public/providers/friendli.svg`
- `public/providers/gemini.svg`
- `public/providers/grok.svg`
- `public/providers/groq.svg`
- `public/providers/heroku.svg`
- `public/providers/huggingface.svg`
- `public/providers/hyperbolic.svg`
- `public/providers/ibm.svg`
- `public/providers/inference.svg`
- `public/providers/lambda.svg`
- `public/providers/longcat.svg`
- `public/providers/minimax.svg`
- `public/providers/mistral.svg`
- `public/providers/moonshot.svg`
- `public/providers/morph.svg`
- `public/providers/nebius.svg`
- `public/providers/novita.svg`
- `public/providers/nvidia.svg`
- `public/providers/ollama.svg`
- `public/providers/openai.svg`
- `public/providers/openrouter.svg`
- `public/providers/ovhcloud.svg`
- `public/providers/picoclaw.svg`
- `public/providers/poe.svg`
- `public/providers/pollinations.svg`
- `public/providers/qwen.svg`
- `public/providers/recraft.svg`
- `public/providers/replicate.svg`
- `public/providers/roocode.svg`
- `public/providers/runway.svg`
- `public/providers/sambanova.svg`
- `public/providers/searchapi.svg`
- `public/providers/suno.svg`
- `public/providers/tavily.svg`
- `public/providers/topazlabs.svg`
- `public/providers/trae.svg`
- `public/providers/udio.svg`
- `public/providers/upstage.svg`
- `public/providers/v0.svg`
- `public/providers/vercel.svg`
- `public/providers/vllm.svg`
- `public/providers/volcengine.svg`
- `public/providers/voyage.svg`
- `public/providers/windsurf.svg`
- `public/providers/xai.svg`
- `public/providers/zhipu.svg`
### Upstream registry claims
These are claims recorded by the fixed upstream registry. They have not been independently
verified against an authoritative license or brand-owner notice for every asset, so they are not
independent copyright or trademark clearance.
| Upstream registry claim | Count | Clearance status |
| ----------------------- | ----: | --------------------------------------------------------------------- |
| MIT | 46 | Upstream claim only; original per-asset copyright notices remain HOLD |
| CC0-1.0 | 14 | Upstream claim only; not independently verified with each owner |
| Apache-2.0 | 1 | Upstream claim only; upstream NOTICE remains HOLD |
| brand-use | 2 | Brand terms, not open-source licenses; owner guidelines remain HOLD |
| Custom | 1 | Custom MiniMax claim; terms remain HOLD |
| MISSING | 1 | No matching registry claim for HuggingFace; license remains HOLD |
#### MIT (46)
`alibaba`, `arcee`, `assemblyai`, `aws`, `bailian`, `baseten`, `cerebras`, `comfyui`,
`deepinfra`, `exa`, `fal`, `fireworks`, `friendli`, `gemini`, `grok`, `groq`, `heroku`,
`hyperbolic`, `ibm`, `inference`, `lambda`, `longcat`, `mistral`, `moonshot`, `morph`, `nebius`,
`novita`, `openai`, `picoclaw`, `pollinations`, `qwen`, `recraft`, `roocode`, `runway`,
`sambanova`, `searchapi`, `tavily`, `topazlabs`, `trae`, `udio`, `upstage`, `vllm`, `volcengine`,
`voyage`, `xai`, `zhipu`
<!-- end:MIT -->
#### CC0-1.0 (14)
`anthropic`, `cline`, `cursor`, `deepgram`, `elevenlabs`, `nvidia`, `ollama`, `openrouter`, `poe`,
`replicate`, `suno`, `v0`, `vercel`, `windsurf`
<!-- end:CC0-1.0 -->
#### Apache-2.0 (1)
`continue`
<!-- end:Apache-2.0 -->
#### brand-use (2)
`azure`, `ovhcloud`
<!-- end:brand-use -->
#### Custom (1)
`minimax`
<!-- end:Custom -->
#### MISSING (1)
`huggingface`
<!-- end:MISSING -->
The `continue` Apache-2.0 claim remains HOLD until its authoritative upstream NOTICE obligations
are verified. The `azure` and `ovhcloud` brand-use claims are not open-source licenses and remain
subject to owner guidelines. `minimax` remains HOLD under custom terms. `huggingface` remains HOLD
because its matching file has no entry or license claim in the fixed registry.
### Trademark and affiliation disclaimer
All brand names, logos, and trademarks are the property of their respective owners. OmniRoute uses
these assets nominatively to identify provider integrations. There is no affiliation, sponsorship,
or endorsement by the respective owners. Copyright provenance and source license claims do not
provide trademark clearance; users should follow each owner's official brand guidelines.

View File

@@ -16,7 +16,7 @@ export function register_combos(parent) {
});
tag.command("post-api-combos")
.description("Create routing combo")
.option("--body <jsonOrPath>", "JSON body or @path/to/file.json")
.requiredOption("--body <jsonOrPath>", "JSON body or @path/to/file.json")
.action(async (opts, cmd) => {
const gOpts = cmd.optsWithGlobals();
let url = "/api/combos";
@@ -44,7 +44,7 @@ export function register_combos(parent) {
tag.command("put-api-combos-id-")
.description("Update combo")
.requiredOption("--id <id>", "")
.option("--body <jsonOrPath>", "JSON body or @path/to/file.json")
.requiredOption("--body <jsonOrPath>", "JSON body or @path/to/file.json")
.action(async (opts, cmd) => {
const gOpts = cmd.optsWithGlobals();
let url = "/api/combos/{id}";
@@ -62,7 +62,7 @@ export function register_combos(parent) {
tag.command("patch-api-combos-id-")
.description("Update combo")
.requiredOption("--id <id>", "")
.option("--body <jsonOrPath>", "JSON body or @path/to/file.json")
.requiredOption("--body <jsonOrPath>", "JSON body or @path/to/file.json")
.action(async (opts, cmd) => {
const gOpts = cmd.optsWithGlobals();
let url = "/api/combos/{id}";
@@ -99,10 +99,17 @@ export function register_combos(parent) {
});
tag.command("post-api-combos-test")
.description("Test a combo configuration")
.requiredOption("--body <jsonOrPath>", "JSON body or @path/to/file.json")
.action(async (opts, cmd) => {
const gOpts = cmd.optsWithGlobals();
let url = "/api/combos/test";
const res = await apiFetch(url, { method: "POST", baseUrl: gOpts.baseUrl, apiKey: gOpts.apiKey });
let body;
if (opts.body) {
body = opts.body.startsWith("@")
? JSON.parse(readFileSync(opts.body.slice(1), "utf8"))
: JSON.parse(opts.body);
}
const res = await apiFetch(url, { method: "POST", body, baseUrl: gOpts.baseUrl, apiKey: gOpts.apiKey });
const data = res.ok ? await res.json() : await res.text();
emit(data, gOpts);
});

View File

@@ -93,6 +93,16 @@ export const CLI_TARGET_MANIFEST = Object.freeze({
configure: true,
runModel: null,
}),
"5dive": Object.freeze({
// 5dive is a fleet manager, not a coding CLI: it points its own `claude`
// agents at an endpoint. `omniroute run 5dive` would have nothing to
// launch, so this is configure-only.
description: "5dive (agent fleet)",
aliases: Object.freeze(["fivedive", "5dive-cli"]),
run: false,
configure: true,
runModel: null, // travels as the profile's ANTHROPIC_DEFAULT_*_MODEL
}),
});
/**

View File

@@ -39,6 +39,7 @@ export const SETUP_MODULES = {
cline: { module: "./setup-cline.mjs", exportName: "runSetupClineCommand" },
continue: { module: "./setup-continue.mjs", exportName: "runSetupContinueCommand" },
kilo: { module: "./setup-kilo.mjs", exportName: "runSetupKiloCommand" },
"5dive": { module: "./setup-5dive.mjs", exportName: "runSetup5diveCommand" },
};
/**

View File

@@ -24,6 +24,80 @@ function parseHeader(kv) {
return { name: kv.slice(0, eq), value: kv.slice(eq + 1) };
}
function getRootCommand(cmd) {
let curr = cmd;
while (curr.parent) curr = curr.parent;
return curr;
}
function resolveNodeEndpoint(opts, cmd) {
if (opts.endpoint) {
return { endpoint: opts.endpoint, apiFetchOpts: cmd.optsWithGlobals() };
}
if (opts.nodeUrl) {
return { endpoint: opts.nodeUrl, apiFetchOpts: cmd.optsWithGlobals() };
}
// Check if --base-url, --endpoint, or --node-url was explicitly passed after the subcommand
const root = getRootCommand(cmd);
const rawArgs = root.rawArgs || process.argv;
const cmdName = cmd.name();
let subArgsStart = -1;
for (let i = 0; i < rawArgs.length - 1; i++) {
if (rawArgs[i] === "nodes" || rawArgs[i] === "provider-nodes") {
if (rawArgs[i + 1] === cmdName) {
subArgsStart = i + 2;
break;
}
}
}
let explicitSubcommandBaseUrl = undefined;
let serverBaseUrl = undefined;
if (subArgsStart !== -1) {
const preArgs = rawArgs.slice(0, subArgsStart);
for (let i = 0; i < preArgs.length; i++) {
if (preArgs[i] === "--base-url" && i + 1 < preArgs.length) {
serverBaseUrl = preArgs[i + 1];
} else if (preArgs[i].startsWith("--base-url=")) {
serverBaseUrl = preArgs[i].slice("--base-url=".length);
}
}
const subArgs = rawArgs.slice(subArgsStart);
for (let i = 0; i < subArgs.length; i++) {
const arg = subArgs[i];
if (
(arg === "--base-url" || arg === "--endpoint" || arg === "--node-url") &&
i + 1 < subArgs.length
) {
explicitSubcommandBaseUrl = subArgs[i + 1];
} else if (
arg.startsWith("--base-url=") ||
arg.startsWith("--endpoint=") ||
arg.startsWith("--node-url=")
) {
explicitSubcommandBaseUrl = arg.slice(arg.indexOf("=") + 1);
}
}
}
if (explicitSubcommandBaseUrl !== undefined) {
const globals = cmd.optsWithGlobals?.() ?? {};
const apiFetchOpts = { ...globals };
if (serverBaseUrl) {
apiFetchOpts.baseUrl = serverBaseUrl;
} else {
delete apiFetchOpts.baseUrl;
}
return { endpoint: explicitSubcommandBaseUrl, apiFetchOpts };
}
return { endpoint: undefined, apiFetchOpts: cmd.optsWithGlobals() };
}
const nodeSchema = [
{ key: "id", header: "Node ID", width: 22 },
{ key: "provider", header: "Provider", width: 16 },
@@ -70,7 +144,8 @@ export function registerNodes(program) {
nodes
.command("add")
.requiredOption("--provider <p>", t("nodes.add.provider"))
.requiredOption("--base-url <url>", t("nodes.add.baseUrl"))
.option("--endpoint <url>", t("nodes.add.baseUrl"))
.option("--base-url <url>", t("nodes.add.baseUrl"))
.option("--name <n>", t("nodes.add.name"))
.option("--weight <w>", t("nodes.add.weight"), parseInt, 100)
.option("--region <r>", t("nodes.add.region"))
@@ -81,41 +156,57 @@ export function registerNodes(program) {
[]
)
.action(async (opts, cmd) => {
const { endpoint, apiFetchOpts } = resolveNodeEndpoint(opts, cmd);
if (!endpoint) {
process.stderr.write(`error: required option '--endpoint <url>' or '--base-url <url>' not specified\n`);
process.exit(1);
}
const body = {
provider: opts.provider,
baseUrl: opts.baseUrl,
baseUrl: endpoint,
name: opts.name,
weight: opts.weight,
region: opts.region,
enabled: true,
headers: opts.authHeader?.length ? opts.authHeader : undefined,
};
const res = await apiFetch("/api/provider-nodes", { method: "POST", body });
const res = await apiFetch("/api/provider-nodes", {
...apiFetchOpts,
method: "POST",
body,
});
if (!res.ok) {
process.stderr.write(`Error: ${res.status}\n`);
process.exit(1);
}
emit(await res.json(), cmd.optsWithGlobals());
emit(await res.json(), apiFetchOpts);
});
nodes
.command("update <nodeId>")
.option("--endpoint <url>", t("nodes.update.baseUrl"))
.option("--base-url <url>", t("nodes.update.baseUrl"))
.option("--name <n>", t("nodes.update.name"))
.option("--weight <w>", t("nodes.update.weight"), parseInt)
.option("--region <r>", t("nodes.update.region"))
.option("--enabled <b>", t("nodes.update.enabled"), (v) => v === "true")
.action(async (id, opts, cmd) => {
const { endpoint, apiFetchOpts } = resolveNodeEndpoint(opts, cmd);
const body = {};
for (const k of ["baseUrl", "name", "weight", "region", "enabled"]) {
if (endpoint !== undefined) body.baseUrl = endpoint;
for (const k of ["name", "weight", "region", "enabled"]) {
if (opts[k] !== undefined) body[k] = opts[k];
}
const res = await apiFetch(`/api/provider-nodes/${id}`, { method: "PUT", body });
const res = await apiFetch(`/api/provider-nodes/${id}`, {
...apiFetchOpts,
method: "PUT",
body,
});
if (!res.ok) {
process.stderr.write(`Error: ${res.status}\n`);
process.exit(1);
}
emit(await res.json(), cmd.optsWithGlobals());
emit(await res.json(), apiFetchOpts);
});
nodes
@@ -136,18 +227,25 @@ export function registerNodes(program) {
nodes
.command("validate")
.requiredOption("--base-url <url>", t("nodes.validate.baseUrl"))
.option("--endpoint <url>", t("nodes.validate.baseUrl"))
.option("--base-url <url>", t("nodes.validate.baseUrl"))
.requiredOption("--provider <p>", t("nodes.validate.provider"))
.action(async (opts, cmd) => {
const { endpoint, apiFetchOpts } = resolveNodeEndpoint(opts, cmd);
if (!endpoint) {
process.stderr.write(`error: required option '--endpoint <url>' or '--base-url <url>' not specified\n`);
process.exit(1);
}
const res = await apiFetch("/api/provider-nodes/validate", {
...apiFetchOpts,
method: "POST",
body: { baseUrl: opts.baseUrl, provider: opts.provider },
body: { baseUrl: endpoint, provider: opts.provider },
});
if (!res.ok) {
process.stderr.write(`Error: ${res.status}\n`);
process.exit(1);
}
emit(await res.json(), cmd.optsWithGlobals());
emit(await res.json(), apiFetchOpts);
});
nodes

View File

@@ -66,6 +66,7 @@ import { registerSetupClaude } from "./setup-claude.mjs";
import { registerSetupOpencode } from "./setup-opencode.mjs";
import { registerSetupCline } from "./setup-cline.mjs";
import { registerSetupKilo } from "./setup-kilo.mjs";
import { registerSetup5dive } from "./setup-5dive.mjs";
import { registerSetupContinue } from "./setup-continue.mjs";
import { registerSetupCursor } from "./setup-cursor.mjs";
import { registerSetupRoo } from "./setup-roo.mjs";
@@ -152,6 +153,7 @@ export function registerCommands(program) {
registerSetupOpencode(program);
registerSetupCline(program);
registerSetupKilo(program);
registerSetup5dive(program);
registerSetupContinue(program);
registerSetupCursor(program);
registerSetupRoo(program);

View File

@@ -0,0 +1,315 @@
/**
* omniroute setup-5dive — point a 5dive agent fleet at OmniRoute.
*
* 5dive (https://5dive.com) manages a fleet of long-running coding agents, each
* one a systemd unit under its own Unix user. It is not itself a coding CLI, so
* there is nothing for `omniroute run` to launch — this is a configure-only
* target.
*
* Unlike the other recipes, 5dive does not read a config file out of $HOME. Its
* credentials live in AUTH PROFILES under /var/lib/5dive/auth-profiles/<name>/,
* and the supported way to write one is the CLI itself:
*
* 5dive agent auth set claude --provider=<id> --base-url=<url> \
* --api-key=- --auth-profile=<name> --model=<slug>
*
* Four value flags, all four load-bearing (verified against 5dive-cli main,
* 2026-08-27):
* --provider `--base-url` is refused without it, rather than accepted
* and silently dropped. `openai` here is 5dive's BYO id for
* "a custom Anthropic-compatible endpoint", not a vendor
* choice — override with --byo-provider.
* --base-url OmniRoute's Anthropic surface, ROOT url with no /v1.
* --auth-profile BYO credentials are profile-scoped; required for claude.
* --model `openai` has no row in 5dive's built-in endpoint catalog,
* so there are no per-tier model ids to inherit.
*
* The key is handed over on stdin (`--api-key=-`) so it never reaches argv.
*
* Two things this recipe cannot do for you, and says so instead of failing
* obscurely:
* 1. Writing an auth profile is root-only on the 5dive host. We re-exec
* through sudo when we are not root (disable with --no-sudo).
* 2. `agent auth set` writes the profile and restarts the agents bound to it,
* but each seat also carries its OWN runtime model pin, and that pin wins
* over the profile's ANTHROPIC_DEFAULT_*_MODEL. Pass --agent <name> (repeatable)
* to pin the seats too; otherwise we print the command for them.
*/
import { spawn } from "node:child_process";
import { printHeading, printInfo, printSuccess, printError, createPrompt } from "../io.mjs";
import { resolveActiveContext } from "../contexts.mjs";
const DEFAULT_PROFILE = "omniroute";
/** 5dive's `claude` BYO endpoint is the Anthropic surface ROOT — strip a trailing /v1. */
function stripToRoot(url) {
const s = String(url || "").replace(/\/+$/, "");
return s.endsWith("/v1") ? s.slice(0, -3) : s;
}
/** Resolve baseUrl (ROOT, no /v1) + apiKey from flags -> active context -> localhost. */
export function resolveFivediveTarget(opts = {}) {
let baseUrl;
if (opts.remote) baseUrl = stripToRoot(opts.remote);
else {
try {
baseUrl = stripToRoot(
resolveActiveContext(opts.context ?? process.env.OMNIROUTE_CONTEXT)?.baseUrl
);
} catch {
/* no context configured */
}
if (!baseUrl)
baseUrl = `http://localhost:${Number(opts.port ?? process.env.PORT ?? 20128) || 20128}`;
}
let apiKey = opts.apiKey ?? opts["api-key"];
if (!apiKey) {
try {
const c = resolveActiveContext(opts.context ?? process.env.OMNIROUTE_CONTEXT);
apiKey = c?.accessToken || c?.apiKey;
} catch {
/* no context configured */
}
}
if (!apiKey) apiKey = process.env.OMNIROUTE_API_KEY || "";
return { baseUrl, apiKey };
}
/**
* 5dive refuses a base URL before storing it, and the rule is not the obvious
* one: the agent's key rides this URL on every request, so https:// is required
* unless the host is loopback. Reproduce the check here so the operator gets the
* reason at the point of choosing, not a validation error three commands later.
*/
export function validateFivediveBaseUrl(rawUrl) {
const url = String(rawUrl || "");
if (!url) return { ok: false, reason: "A base URL is required." };
if (url.startsWith("https://")) return { ok: true };
if (!url.startsWith("http://")) {
return { ok: false, reason: `Unsupported scheme in '${url}' (expected http:// or https://).` };
}
let host = url.slice("http://".length);
host = host.split("/")[0].split("?")[0];
host = host.startsWith("[") ? `${host.slice(0, host.indexOf("]"))}]` : host.split(":")[0];
if (host === "127.0.0.1" || host === "localhost" || host === "[::1]") return { ok: true };
return {
ok: false,
reason:
`5dive accepts http:// only for a loopback host; '${host}' is off-box, so the agent's ` +
`API key would travel in plaintext. Serve OmniRoute over https:// and pass ` +
`--remote https://${host}...`,
};
}
/** Argv for the profile write. The key is NOT here — it goes in on stdin. */
export function buildFivediveAuthArgs({ baseUrl, profile, model, provider = "openai" }) {
return [
"agent",
"auth",
"set",
"claude",
`--provider=${provider}`,
`--base-url=${baseUrl}`,
"--api-key=-",
`--auth-profile=${profile}`,
`--model=${model}`,
];
}
/** Argv for one seat's runtime model pin, which outranks the profile's env defaults. */
export function buildFivedivePinArgs(agent, model) {
return ["agent", "config", agent, "set", `model=${model}`];
}
/** Prepend sudo when the profile write needs root and we do not have it. */
export function withPrivilege(bin, args, { isRoot, useSudo }) {
if (isRoot || !useSudo) return [bin, args];
return ["sudo", [bin, ...args]];
}
function quote(arg) {
return /^[A-Za-z0-9_@%+=:,./-]+$/.test(arg) ? arg : `'${String(arg).replace(/'/g, "'\\''")}'`;
}
/** Render argv the way an operator would type it. */
export function renderCommand(bin, args) {
return [bin, ...args].map(quote).join(" ");
}
function run(bin, args, stdinPayload) {
return new Promise((resolve) => {
const child = spawn(bin, args, {
// sudo reads its password straight from the tty, so stdin stays free for
// the API key.
stdio: [stdinPayload === undefined ? "inherit" : "pipe", "inherit", "inherit"],
});
child.on("error", (e) => resolve({ code: 1, error: e }));
child.on("close", (code) => resolve({ code: code ?? 1 }));
if (stdinPayload !== undefined && child.stdin) {
child.stdin.end(stdinPayload);
}
});
}
async function fetchModelIds(baseUrl, apiKey) {
try {
const headers = { "Content-Type": "application/json" };
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
const res = await fetch(`${baseUrl}/v1/models`, { headers, signal: AbortSignal.timeout(8000) });
if (!res.ok) return [];
const body = await res.json();
const list = Array.isArray(body) ? body : (body.data ?? body.models ?? []);
return list.map((m) => (typeof m === "string" ? m : m?.id)).filter(Boolean);
} catch {
return [];
}
}
function agentList(opts) {
const raw = opts.agent ?? opts.agents ?? [];
return (Array.isArray(raw) ? raw : [raw]).map((a) => String(a).trim()).filter(Boolean);
}
export async function runSetup5diveCommand(opts = {}) {
const { baseUrl, apiKey } = resolveFivediveTarget(opts);
const dryRun = Boolean(opts.dryRun ?? opts["dry-run"]);
const bin = opts.fivediveBin ?? opts["fivedive-bin"] ?? process.env.CLI_5DIVE_BIN ?? "5dive";
const profile = String(opts.authProfile ?? opts["auth-profile"] ?? opts.name ?? DEFAULT_PROFILE);
// NOT `opts.provider`: the `configure` picker uses that flag for the
// OmniRoute model provider to filter on, and it reaches setup recipes
// verbatim. The 5dive BYO id is its own flag.
const provider = String(opts.byoProvider ?? opts["byo-provider"] ?? "openai");
const agents = agentList(opts);
printHeading("OmniRoute -> 5dive (claude BYO endpoint)");
printInfo(`Server: ${baseUrl}`);
printInfo(`Profile: ${profile}`);
const urlCheck = validateFivediveBaseUrl(baseUrl);
if (!urlCheck.ok) {
printError(urlCheck.reason);
return 2;
}
// 5dive needs one explicit model id: `openai` has no catalog row, so there
// are no per-tier defaults to fall back to.
let model = opts.model;
if (!model) {
const ids = await fetchModelIds(baseUrl, apiKey);
if (ids.length && !opts.yes) {
printInfo(`Examples: ${ids.slice(0, 20).join(", ")}${ids.length > 20 ? " …" : ""}`);
printInfo("A combo id works here too — that is how you get failover across providers.");
const prompt = createPrompt();
try {
model = await prompt.ask("Model or combo id for the 5dive agents");
} finally {
prompt.close();
}
}
}
if (!model) {
printError("A model is required. Pass --model <id> (5dive has no model auto-discovery here).");
return 2;
}
if (!apiKey) {
printError("An OmniRoute API key is required. Pass --api-key, or set OMNIROUTE_API_KEY.");
return 2;
}
const isRoot = typeof process.getuid === "function" ? process.getuid() === 0 : false;
const useSudo = (opts.sudo ?? true) !== false;
const authArgs = buildFivediveAuthArgs({ baseUrl, profile, model, provider });
const [authBin, authArgv] = withPrivilege(bin, authArgs, { isRoot, useSudo });
if (dryRun) {
printInfo("\n[dry-run] would run:");
printInfo(` ${renderCommand(authBin, authArgv)}`);
printInfo(" (the API key is written to that command's stdin, never to argv)");
for (const agent of agents) {
const [pinBin, pinArgv] = withPrivilege(bin, buildFivedivePinArgs(agent, model), {
isRoot,
useSudo,
});
printInfo(` ${renderCommand(pinBin, pinArgv)}`);
}
return 0;
}
if (!isRoot && !useSudo) {
printError(
"Writing a 5dive auth profile needs root on the 5dive host. Re-run as root, drop --no-sudo, " +
"or run this by hand:"
);
printInfo(` ${renderCommand(bin, authArgs)}`);
return 1;
}
const authResult = await run(authBin, authArgv, apiKey);
if (authResult.error?.code === "ENOENT") {
printError(
`Could not find the '${bin}' CLI on this machine. 5dive's verbs run ON the fleet host — ` +
"run this there, or point at the binary with --fivedive-bin."
);
return 1;
}
if (authResult.code !== 0) {
printError(`'${bin} agent auth set' exited ${authResult.code}.`);
return authResult.code;
}
printSuccess(`Auth profile '${profile}' now points at ${baseUrl}`);
// The profile carries ANTHROPIC_DEFAULT_{OPUS,SONNET,HAIKU}_MODEL, but each
// seat's own runtime pin outranks it — a seat still pinned to a stock model id
// fails its first turn with "There's an issue with the selected model".
for (const agent of agents) {
const [pinBin, pinArgv] = withPrivilege(bin, buildFivedivePinArgs(agent, model), {
isRoot,
useSudo,
});
const pinResult = await run(pinBin, pinArgv);
if (pinResult.code !== 0) {
printError(`Could not pin agent '${agent}' to '${model}' (exit ${pinResult.code}).`);
return pinResult.code;
}
printSuccess(`Agent '${agent}' pinned to ${model}`);
}
if (!agents.length) {
printInfo("\nEach seat also carries its own runtime model pin, and it beats the profile:");
printInfo(` ${renderCommand(bin, buildFivedivePinArgs("<agent>", model))}`);
printInfo("Re-run with --agent <name> to have this command apply it for you.");
}
printInfo("\nBind a seat to the profile at creation time with:");
printInfo(` ${renderCommand(bin, ["agent", "create", "<name>", `--auth-profile=${profile}`])}`);
return 0;
}
export function registerSetup5dive(program) {
program
.command("setup-5dive")
.description(
"Point a 5dive agent fleet's claude seats at OmniRoute (writes a 5dive auth profile)"
)
.option("--port <port>", "Local OmniRoute port (ignored when --remote is set)", "20128")
.option("--remote <url>", "Remote OmniRoute URL, e.g. https://omniroute.example.com")
.option("--context <name>", "Named local/remote context")
.option("--api-key <key>", "OmniRoute API key (defaults to the active context/env)")
.option("--model <id>", "OmniRoute model or combo id the agents should use")
.option("--byo-provider <id>", "5dive BYO provider id (default: openai)", "openai")
.option("--auth-profile <name>", "5dive auth profile to write", DEFAULT_PROFILE)
.option(
"--agent <name>",
"Also pin this agent's runtime model (repeatable)",
(value, previous) => [...(previous || []), value],
[]
)
.option("--fivedive-bin <path>", "Path to the 5dive binary (default: 5dive on PATH)")
.option("--no-sudo", "Do not re-exec through sudo when not running as root")
.option("--yes", "Non-interactive: do not prompt (requires --model)")
.option("--dry-run", "Print the commands without running them")
.action(async (opts) => {
const code = await runSetup5diveCommand(opts);
if (code !== 0) process.exit(code);
});
}

View File

@@ -24,9 +24,9 @@ function wantsProviderSetup(opts) {
return opts.addProvider || Boolean(opts.provider) || Boolean(opts.apiKey);
}
async function resolvePassword(opts, prompt, nonInteractive) {
if (opts.password) return opts.password;
if (process.env.INITIAL_PASSWORD) return process.env.INITIAL_PASSWORD;
async function resolvePassword(opts, prompt, nonInteractive, settings) {
if (opts.password !== undefined) return opts.password;
if (!settings.password && process.env.INITIAL_PASSWORD) return process.env.INITIAL_PASSWORD;
if (nonInteractive) return "";
const answer = await prompt.ask("Set an admin password now? [y/N]", "N");
@@ -41,9 +41,9 @@ async function resolvePassword(opts, prompt, nonInteractive) {
}
async function setupPassword(db, opts, prompt, nonInteractive) {
const password = await resolvePassword(opts, prompt, nonInteractive);
const settings = getSettings(db);
const password = await resolvePassword(opts, prompt, nonInteractive, settings);
if (!password) {
const settings = getSettings(db);
if (!settings.password) {
updateSettings(db, { requireLogin: false });
}

View File

@@ -1,4 +1,4 @@
import { printHeading, printInfo, printSuccess, printError } from "../io.mjs";
import { printHeading, printInfo, printSuccess, printError, printWarning } from "../io.mjs";
import { homedir } from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
@@ -6,6 +6,7 @@ import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { t } from "../i18n.mjs";
import { npmBin, npmExecOptions } from "../npm-exec.mjs";
import { readPidFile, isPidRunning } from "../utils/pid.mjs";
const execFileAsync = promisify(execFile);
@@ -79,6 +80,39 @@ export async function createBackup() {
}
}
// #11885: `--apply` installs the new files (npm install -g) and re-reads
// package.json from disk to confirm it, but a long-lived server process keeps
// serving whatever it loaded at its last start — Node caches a `require()`d
// package.json per resolved path for the life of the process. A later
// `omniroute update` then correctly reports "already up to date" (the files
// ARE current) while the running server is still stale, matching the reported
// symptom. `--apply` never restarted anything and its success message ("Run
// `omniroute --version` to verify.") implied the update was already live.
//
// `restart.mjs`'s `runRestartCommand()` stops then re-spawns the server in the
// foreground (via `serve.mjs::runServe`), which can block the calling terminal
// and is a materially bigger behavior change than this fix warrants to invoke
// unconditionally and unattended from `--apply`. Instead, detect whether a
// CLI-managed server is currently running (the same PID file `stop.mjs`/
// `restart.mjs` already trust) and print an explicit, prominent instruction —
// honest about what did and didn't happen — rather than silently assuming.
export async function isServerProcessRunning(deps = { readPidFile, isPidRunning }) {
const pid = deps.readPidFile("server");
return Boolean(pid && deps.isPidRunning(pid));
}
export async function printPostApplyGuidance(latest, deps = { readPidFile, isPidRunning }) {
const running = await isServerProcessRunning(deps);
if (running) {
printWarning(`Files updated to ${latest}, but the running server is still on the old version.`);
printInfo(" Run `omniroute restart` now to apply this update.");
} else {
printInfo(`No running OmniRoute server was detected via the CLI's PID file.`);
printInfo(` Start it with \`omniroute serve\` (or restart your existing process) to run ${latest}.`);
}
printInfo("`omniroute --version` will keep reporting the old version until the process restarts.");
}
export function registerUpdate(program) {
program
.command("update")
@@ -210,8 +244,8 @@ export async function runUpdateCommand(opts = {}) {
console.log(" or reorder PATH so the global bin comes first.");
return 1;
}
printSuccess(`Updated to version ${latest}`);
printInfo("Run `omniroute --version` to verify.");
printSuccess(`Installed omniroute@${latest} to disk.`);
await printPostApplyGuidance(latest);
return 0;
} catch (err) {
printError(`Update failed: ${err.message}`);

View File

@@ -81,3 +81,7 @@ export function printInfo(message) {
export function printError(message) {
console.log(`\x1b[31m✖ ${message}\x1b[0m`);
}
export function printWarning(message) {
console.log(`\x1b[33m⚠ ${message}\x1b[0m`);
}

View File

@@ -26,6 +26,17 @@ let resolvedCached = null;
export async function loadSqliteRuntime() {
if (resolvedCached) return resolvedCached;
if (process.versions.bun) {
try {
const bunSqlite = await import("bun:sqlite");
resolvedCached = {
driver: { kind: "bun-sqlite", Database: bunSqlite.Database },
source: "bun-sqlite",
};
return resolvedCached;
} catch {}
}
const bundled = await tryLoadBundled();
if (bundled) {
resolvedCached = { driver: bundled, source: "bundled" };

View File

@@ -100,31 +100,66 @@ export async function waitForServer(port, timeout = 60000) {
// - "hanging": the request timed out waiting for any response — the
// process accepted the TCP connection but never answered (#6800).
// - "not-listening": nothing is accepting connections on the port at all.
// #11766: probe both IPv4 and IPv6 loopback to handle servers listening on
// either family (or both).
async function pollHealthOnce(port) {
try {
const res = await fetch(`http://127.0.0.1:${port}/api/monitoring/health`, {
signal: AbortSignal.timeout(2000),
});
return res.ok ? "ready" : "fast-reject";
} catch (err) {
if (err?.name === "TimeoutError") return "hanging";
const listening = await isPortListening(port).catch(() => false);
return listening ? "fast-reject" : "not-listening";
}
const hosts = ["127.0.0.1", "::1"];
const outcomes = [];
// Probe both loopback families concurrently
const results = await Promise.all(
hosts.map(async (host) => {
try {
const res = await fetch(`http://${host}:${port}/api/monitoring/health`, {
signal: AbortSignal.timeout(2000),
});
return { host, outcome: res.ok ? "ready" : "fast-reject" };
} catch (err) {
const outcome = err?.name === "TimeoutError" ? "hanging" : "error";
return { host, outcome };
}
})
);
outcomes.push(...results.map((r) => r.outcome));
// If either family is ready, the server is ready
if (outcomes.includes("ready")) return "ready";
// If either family is fast-reject, treat as fast-reject
// (TCP is listening and rejecting, just route not ready yet)
if (outcomes.includes("fast-reject")) return "fast-reject";
// If either family is hanging, server accepted TCP but not answering
// (still booting, must not report as ready per #6800)
if (outcomes.includes("hanging")) return "hanging";
// Both families failed — check if either port is actually listening
// If listening, then errors above are route-level (fast-reject case)
const listening = await isPortListening(port).catch(() => false);
return listening ? "fast-reject" : "not-listening";
}
async function isPortListening(port) {
const net = await import("node:net");
return new Promise((resolve) => {
const socket = net.connect({ host: "127.0.0.1", port, timeout: 1000 });
const finish = (ok) => {
try {
socket.destroy();
} catch {}
resolve(ok);
};
socket.once("connect", () => finish(true));
socket.once("error", () => finish(false));
socket.once("timeout", () => finish(false));
});
// #11766: check both IPv4 and IPv6 loopback. Return true if either is listening.
const hosts = ["127.0.0.1", "::1"];
const results = await Promise.all(
hosts.map(
(host) =>
new Promise((resolve) => {
const socket = net.connect({ host, port, timeout: 1000 });
const finish = (ok) => {
try {
socket.destroy();
} catch {}
resolve(ok);
};
socket.once("connect", () => finish(true));
socket.once("error", () => finish(false));
socket.once("timeout", () => finish(false));
})
)
);
return results.some((ok) => ok);
}

View File

@@ -0,0 +1,37 @@
import { sep } from "node:path";
/**
* A `.env` inside the installed package directory does not survive an update:
* `npm i -g` replaces that directory wholesale, and postinstall recreates the
* file from `.env.example`. The CLI announces every env file it loads without
* distinguishing the ones that last from the one that doesn't.
*
* Returns the warning to print, or null when there is nothing worth saying.
*
* Two conditions, both required, so a development checkout never sees this:
* - the file sits inside the package root, and that root is inside a
* `node_modules` directory — i.e. an installed package, not a checkout,
* where the same path is stable and documented in SETUP_GUIDE.md;
* - the file actually supplied at least one value. First writer wins, so a
* file entirely shadowed by a durable one supplied nothing, and losing it
* costs nothing.
*
* @param {{ envPath: string, packageRoot: string, durableEnvPath: string, suppliedKeys: boolean }} args
* @returns {string | null}
*/
export function describeVolatileEnvWarning({ envPath, packageRoot, durableEnvPath, suppliedKeys }) {
if (!suppliedKeys) return null;
if (envPath === durableEnvPath) return null;
if (!isInsideInstalledPackage(packageRoot)) return null;
if (!envPath.startsWith(packageRoot + sep)) return null;
return (
`${envPath} lives inside the installed package: updating OmniRoute replaces it. ` +
`Move the values you set to ${durableEnvPath}, which updates leave alone.`
);
}
/** True when the path sits under a `node_modules` directory. */
function isInsideInstalledPackage(dir) {
return typeof dir === "string" && dir.split(sep).includes("node_modules");
}

View File

@@ -29,6 +29,7 @@ import { getDefaultDataDir } from "./cli/data-dir.mjs";
import { shouldProvisionStorageKey } from "./cli/utils/storageKeyProvision.mjs";
import { isVersionFastPath } from "./cli/utils/versionFastPath.mjs";
import { parseEnvValue } from "./cli/utils/parseEnvValue.mjs";
import { describeVolatileEnvWarning } from "./cli/utils/volatileEnvPath.mjs";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@@ -91,9 +92,7 @@ function migrateElectronServerEnv(dataDir) {
const serverEnvPath = join(dataDir, "server.env");
if (existsSync(envPath) || !existsSync(serverEnvPath)) return;
writeFileSync(envPath, readFileSync(serverEnvPath, "utf-8"), "utf-8");
console.log(
` \x1b[2m♻ Migrated Electron secrets from ${serverEnvPath} to ${envPath}\x1b[0m`
);
console.log(` \x1b[2m♻ Migrated Electron secrets from ${serverEnvPath} to ${envPath}\x1b[0m`);
} catch {
// Ignore errors migrating server.env — fall back to normal env loading below.
}
@@ -164,6 +163,21 @@ function loadEnvFile() {
const setter = winner ? winner : "the environment";
console.warn(` \x1b[33m⚠ ${key} in ${loser} is ignored, ${setter} set it first\x1b[0m`);
}
// The package directory is replaced by the next `npm i -g`, so a .env kept
// there is silently lost. Say so once, and only when that file actually
// supplied something.
const durableEnvPath = join(process.env.DATA_DIR || getDefaultDataDir(), ".env");
const suppliedKeys = [...keyOrigin.values()].some((origin) => origin === join(ROOT, ".env"));
const volatileWarning = describeVolatileEnvWarning({
envPath: join(ROOT, ".env"),
packageRoot: ROOT,
durableEnvPath,
suppliedKeys,
});
if (volatileWarning && loadedEnvPaths.includes(join(ROOT, ".env"))) {
console.warn(` \x1b[33m⚠ ${volatileWarning}\x1b[0m`);
}
}
loadEnvFile();
@@ -247,16 +261,16 @@ if (shouldProvisionStorageKey(process.argv)) {
const langEnv = process.env.OMNIROUTE_LANG;
const chosen = langArg || langEnv;
if (chosen) {
const { setLocale } = await import(
pathToFileURL(join(ROOT, "bin", "cli", "i18n.mjs")).href
);
const { setLocale } = await import(pathToFileURL(join(ROOT, "bin", "cli", "i18n.mjs")).href);
setLocale(chosen);
}
}
// Register update notifier — checks npm once per 24h, notifies on exit via stderr.
const _pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8"));
const _notifier = updateNotifier ? updateNotifier({ pkg: _pkg, updateCheckInterval: 1000 * 60 * 60 * 24 }) : null;
const _notifier = updateNotifier
? updateNotifier({ pkg: _pkg, updateCheckInterval: 1000 * 60 * 60 * 24 })
: null;
process.on("exit", () => {
if (!_notifier || !_notifier.update) return;
if (process.env.OMNIROUTE_NO_UPDATE_NOTIFIER) return;
@@ -265,7 +279,15 @@ process.on("exit", () => {
const outputIdx = process.argv.indexOf("--output");
const outputVal = outputIdx >= 0 ? process.argv[outputIdx + 1] : null;
if (outputVal === "json" || outputVal === "jsonl" || outputVal === "csv") return;
if (process.argv.some((a) => a.startsWith("--output=json") || a.startsWith("--output=jsonl") || a.startsWith("--output=csv"))) return;
if (
process.argv.some(
(a) =>
a.startsWith("--output=json") ||
a.startsWith("--output=jsonl") ||
a.startsWith("--output=csv")
)
)
return;
if (_notifier.update) {
_notifier.notify({
defer: false,

View File

@@ -0,0 +1 @@
- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556).

View File

@@ -0,0 +1 @@
- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`.

View File

@@ -0,0 +1,3 @@
- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping
large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict
fail-open behavior and per-engine telemetry.

View File

@@ -0,0 +1 @@
- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146))

View File

@@ -0,0 +1 @@
- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas

View File

@@ -0,0 +1 @@
- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369))

View File

@@ -0,0 +1 @@
- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak

View File

@@ -0,0 +1 @@
- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)).

View File

@@ -0,0 +1 @@
- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse

View File

@@ -0,0 +1 @@
- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan

View File

@@ -0,0 +1 @@
- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481)

View File

@@ -0,0 +1 @@
- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546))

View File

@@ -0,0 +1 @@
- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652))

View File

@@ -0,0 +1 @@
- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654).

View File

@@ -0,0 +1,6 @@
- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache
substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail
multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB,
and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for
remote access by default and denies cross-key access with the same response as a nonexistent
handle (no existence oracle).

View File

@@ -0,0 +1 @@
- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15.

View File

@@ -0,0 +1 @@
- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659).

View File

@@ -0,0 +1 @@
- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW)

View File

@@ -0,0 +1 @@
- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White

View File

@@ -0,0 +1 @@
- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis

View File

@@ -0,0 +1 @@
- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843))

View File

@@ -0,0 +1 @@
- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852))

View File

@@ -0,0 +1 @@
- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis

View File

@@ -0,0 +1 @@
- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta

View File

@@ -0,0 +1 @@
- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse

View File

@@ -0,0 +1 @@
- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342)

View File

@@ -0,0 +1 @@
- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only

View File

@@ -0,0 +1 @@
- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#PR_NUMBER](https://github.com/diegosouzapw/OmniRoute/pull/PR_NUMBER))

View File

@@ -0,0 +1 @@
- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#PR_NUMBER](https://github.com/diegosouzapw/OmniRoute/pull/PR_NUMBER))

View File

@@ -0,0 +1,11 @@
- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable
destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with
a persisted per-destination cursor, batched inserts, a config UI rendered from each
destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a
connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship
the request and response payloads shown in the Logs detail pane, including the client and
provider views of each call; this is off by default, and payloads inherit the dashboard's PII
sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination,
using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id,
into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`,
`model` and `status`.

View File

@@ -0,0 +1 @@
- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces.

View File

@@ -0,0 +1,4 @@
- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted
`stage-npm` job publishes with a short-lived credential minted from GitHub's id-token —
no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner
approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`.

View File

@@ -0,0 +1 @@
- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed)

View File

@@ -0,0 +1 @@
- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility.

View File

@@ -0,0 +1 @@
- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore

View File

@@ -0,0 +1 @@
- **fix(github):** proactive credential health now verifies GitHub access tokens through the existing Copilot token exchange, marks only a confirmed `401 Unauthorized` as expired, and leaves rate limits, permission failures, upstream failures, and network errors routable ([#10352](https://github.com/diegosouzapw/OmniRoute/issues/10352)) — thanks @RaviTharuma

View File

@@ -0,0 +1 @@
- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol.

View File

@@ -1 +0,0 @@
- **fix(cli):** the local CLI sees the full `/api/monitoring/health` payload again — `version` included — restoring the `check:pack-boot` release gate. [#11040](https://github.com/diegosouzapw/OmniRoute/pull/11040) reduced that route to a liveness-only view for non-management callers (GHSA-mvf8-qc78-5mxm), but the route is classified PUBLIC and `runAuthzPipeline` strips the machine-token header for every route class — so the PUBLIC policy stamped `anonymous` and the loopback CLI could never be recognized as a management principal. The PUBLIC policy now stamps the same loopback-gated `local-cli-token` subject the MANAGEMENT policy already did; anonymous callers still get liveness only.

View File

@@ -0,0 +1 @@
- **fix(providers):** Antigravity OAuth marks connects with no Cloud Code projectId as degraded instead of a false "Connected"; BYOP detection at connect time, auto-disable of confirmed-missing accounts, and selection-side rotation ([#11284](https://github.com/diegosouzapw/OmniRoute/issues/11284))

View File

@@ -0,0 +1 @@
- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296).

View File

@@ -0,0 +1 @@
- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification.

View File

@@ -0,0 +1 @@
- **fix(db):** group model patterns escape regex metacharacters, so `gpt-4.1*` no longer matches `gpt-4o1-preview` and a pattern like `gpt-4(*` no longer throws `SyntaxError` out of the completion and `/v1/models` paths ([#11311](https://github.com/diegosouzapw/OmniRoute/pull/11311))

View File

@@ -0,0 +1 @@
- **fix(db):** the upstream proxy URL check judges the host by address instead of by spelling, so `http://[::ffff:169.254.169.254]`, `[::ffff:10.0.0.5]`, ULA/link-local and CGNAT targets are refused like their dotted equivalents ([#11319](https://github.com/diegosouzapw/OmniRoute/pull/11319))

View File

@@ -0,0 +1 @@
- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324)

View File

@@ -0,0 +1 @@
- **fix(i18n):** three `pt` strings had dropped their placeholders — the cache tile's subtitle repeated its own label instead of showing `{total}` — and a unit test now enforces placeholder parity with `en` across all locales ([#11325](https://github.com/diegosouzapw/OmniRoute/pull/11325))

View File

@@ -0,0 +1 @@
- **fix(kie):** map the remaining `google-imagen/*` KIE Market catalog ids (`nano-banana`, `nano-banana-pro`, `nano-banana-edit`) to their real, KIE-documented upstream `model` values — `#11225`'s fix only covered `nano-banana-2` ([#11326](https://github.com/diegosouzapw/OmniRoute/pull/11326)).

View File

@@ -0,0 +1 @@
- **fix(security):** `proxy-authorization` and `proxy-authenticate` are refused as upstream/custom headers, so a proxy credential is no longer forwarded to the model provider — the canonical denylist now matches the RFC 7230 §6.1 set the rest of the codebase already strips ([#11328](https://github.com/diegosouzapw/OmniRoute/pull/11328))

View File

@@ -0,0 +1 @@
- **fix(video-bridge):** fall back to the deterministic active-window midpoint when a one-frame scene-aware budget cannot preserve both timeline ends; a real FFmpeg fixture matrix now covers rapid cuts, gradual changes, static and short clips, and detector failure ([#11344](https://github.com/diegosouzapw/OmniRoute/pull/11344)).

View File

@@ -0,0 +1 @@
- **fix(translator):** Codex Responses tool calls translated for Claude clients no longer emit a duplicate `tool_use` block with the same ID and an empty name, preventing Claude Code from terminating with `No such tool available` ([#11347](https://github.com/diegosouzapw/OmniRoute/pull/11347))

View File

@@ -0,0 +1 @@
- **fix(video-bridge):** burn high-contrast timestamps into every bounded contact-sheet cell and add a real-model A/B harness whose promotion verdict stays `HOLD` until token, latency, and quality evidence is actually executed ([#11350](https://github.com/diegosouzapw/OmniRoute/pull/11350))

View File

@@ -0,0 +1 @@
- **fix(video):** fingerprint protected Video Bridge bytes, coalesce concurrent work, and fail open when the bounded TTL/LRU result cache is unavailable or corrupt ([#11362](https://github.com/diegosouzapw/OmniRoute/pull/11362))

View File

@@ -0,0 +1 @@
- **fix(catalog):** keep large `/v1/models` builds responsive by reusing the build-local capability snapshot throughout enrichment and Auto-Combo preparation, yielding cooperatively while constructing virtual candidate pools, and avoiding unrelated synchronous database diagnostics on the cache-TTL read path ([#11367](https://github.com/diegosouzapw/OmniRoute/pull/11367))

View File

@@ -0,0 +1 @@
- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)).

View File

@@ -0,0 +1 @@
- **fix(video):** apply the caption-frame cap after bounded visual deduplication, preserve first/final candidates plus small high-contrast motion and text changes, and version the dedup policy in result-cache identity ([#11382](https://github.com/diegosouzapw/OmniRoute/pull/11382)).

View File

@@ -0,0 +1 @@
- **Live dashboard:** honour the WebSocket port reported by `/api/v1/ws?handshake=1` instead of the port compiled into the bundle, so a `LIVE_WS_PORT` override reaches prebuilt Docker/npm images and Combo Studio Live connects behind a reverse proxy ([#11331](https://github.com/diegosouzapw/OmniRoute/issues/11331)).

View File

@@ -0,0 +1 @@
- **fix(dashboard):** Model Database sync interval slider ticks now match the thumb position — checkpoint-space slider with magnetic snap on release ([#11394](https://github.com/diegosouzapw/OmniRoute/pull/11394)) — thanks @An0nym0us92

View File

@@ -0,0 +1 @@
- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)).

View File

@@ -0,0 +1 @@
- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142.

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