Compare commits

..

18 Commits

Author SHA1 Message Date
diegosouzapw
384ce49a4d chore(deps): patch toml and esbuild in the opencode plugin lockfiles
Two Dependabot alerts on the opencode plugin workspaces, both dev-scope:

- #209 (high) toml@4.1.1 — prototype pollution via `__proto__` key-path
  desynchronization (GHSA-v5mp-jgw5-2x6j), pulled transitively by `effect`
  under `@opencode-ai/plugin`. Patched in 4.1.2.
- #210 (low) esbuild@0.27.7 — arbitrary file read from the dev server on
  Windows (GHSA-g7r4-m6w7-qqqr), nested under `tsup`. Patched in 0.28.1.
  The top-level esbuild was already 0.28.1; only the nested copy lagged.

Both are resolved with an `overrides` entry, reusing the pattern the v1
plugin already applies to esbuild. Neither package reaches the published
runtime — they are build-time only — so this is hygiene, not an exposure fix.

toml 4.1.1 -> 4.3.0, nested esbuild 0.27.7 dropped (single 0.28.2 remains).
`npm install` reports 0 vulnerabilities in both workspaces; both plugins
build and their suites pass (367 and 218 tests).
2026-09-07 11:14:49 -03:00
Bob.Hou
d6f315018a fix(chat): continue after a server-owned tool on Chat Completions (#12867)
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437, ESLint 0 erros nos 152 arquivos alterados, e a suíte vitest:ui completa (2149) verde.

Sobre esta PR especificamente: rodei os **23 arquivos de teste** que ela toca sobre o tip final, depois do merge da base — **392/392**. A migration `174_server_tool_executions.sql` não colide (o tip está em 173, e você já a renumerou em `c35f0fd7`).

O dono foi consultado antes do merge, porque o loop está atrás da flag `SERVER_OWNED_TOOL_LOOP_ENABLED` mas o primeiro send não-streaming mudou de dono sem flag, e a verificação manual em combo com Memory continuava desmarcada. A condição dele foi: entra se os testes focados passarem aqui. Passaram.

O lock de passthrough (`fetchCalls.length === 1`) é a parte que mais me convenceu — o double-dispatch que um `if (stream)` em volta do send existente causaria é exatamente o tipo de regressão que não aparece em teste de comportamento, só em contagem de chamada.

**Três ajustes meus na sua branch:**

1. `tests/unit/chatcore-stream-error-result.test.ts` procurava `"const legResult = await runNonStreamingProviderLeg"`, mas o seu commit final `6077b9dd` passou a reatribuir `legResult` e trocou para `let`. O guard falhava na sua própria branch (confirmei que o arquivo e o `chatCore.ts` eram byte-idênticos ao head da PR, então não era efeito da leva). Passou a aceitar `const|let` — a intenção do guard é o try/catch em volta da chamada, não a palavra-chave.

2. `tests/integration/skills-pipeline.test.ts` foi de 1156 para 1338 linhas e estourou o `testCap` de 1200. Segui o mesmo caminho que você já tinha tomado em `a1d2d20d` para os testes unitários: extraí os três casos do server-owned tool loop para `tests/integration/server-owned-tool-loop-pipeline.test.ts` (259 linhas), com instância própria do harness. O glob `tests/integration/*.test.ts` pega o arquivo novo sem registro adicional. 3/3 verdes isolados.

3. O arquivo novo herdou cinco `any` do original — que só passavam por estarem congelados no `eslint-suppressions.json` sob o nome antigo. Tipei como `Record<string, unknown>`. E `tests/unit/non-streaming-finalization.test.ts` tinha dois argumentos não usados em `trackPendingRequest`, agora prefixados com `_`.

Nada disso toca produção nem enfraquece asserção.
2026-09-07 09:15:00 -03:00
Bob.Hou
ce49d969ca refactor(combo): move handleRoundRobinCombo into roundRobinCombo.ts (#12811)
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437, ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes.

**Sobre a reconstrução da branch.** Esta PR continha os 7 commits do #12746 mais os 3 do round-robin. O dono escolheu mergear os dois em sequência em vez de fechar um como subsumido, então depois que o squash do #12746 entrou eu reconstruí esta branch: cherry-pick de `05059880`, `54168238` e `ddd6bcbf` sobre o tip novo, e force-push. Autoria preservada — os três commits continuam seus (`Minxi Hou <houminxi@gmail.com>`), verificado com `git log --format=%an` antes do push. A PR foi de +4167/−3187 em 14 arquivos para +1281/−1182 em 5, que é o delta real do round-robin.

O `05059880` ("guard round-robin extract before the lift") é o commit que faz esse tipo de extract ser revisável: sem um teste que fixe o contrato antes do movimento, mover 1198 linhas é indistinguível de reescrever 1198 linhas.

Revalidei sobre o tip reconstruído: `round-robin-combo`, `combo-attempt-loop`, `execute-target-attempt`, `execute-target-gates` e `combo-loop-safety-timer-leak-11804` — 24/24 — com typecheck:core limpo e o cap de arquivo OK.
2026-09-07 09:09:51 -03:00
Bob.Hou
6b587d0046 refactor(combo): split executeTarget into gates, attempt, and loop (#12746)
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437, ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes.

Guardar os contratos com testes ANTES de levantar o bloco (`05059880` no irmão, e o `287658f5` marcando os `executeTargetGates` como lift-as-is) é o que torna um refactor deste tamanho auditável. Sem essa ordem, um extract de 3 mil linhas é indistinguível de uma reescrita.

Revalidei depois do merge da base: `combo-attempt-loop`, `execute-target-attempt`, `execute-target-gates` e `combo-loop-safety-timer-leak-11804` — 20/20 — mais typecheck:core limpo e o cap de arquivo OK.

O #12811 entra na sequência logo em seguida, com os três commits do round-robin sobre este.
2026-09-07 09:07:19 -03:00
Bob.Hou
c1b34db50d feat(combo): quota-weighted routing — skip empty accounts, draw by leftover (#12789)
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437, ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes.

Reservar o sorteio antes do próximo `await` (`2cf74acc`) é a parte não-óbvia e a que mais importa: sem isso dois pipelines no mesmo processo observam `inflight=0` na mesma conta e convergem para ela. O comentário no código explica isso melhor do que o commit message.

**Um ajuste meu na sua branch.** O `tests/unit/combo/quota-weighted-strategy.test.ts` era intermitente — falhava em cerca de 1 a cada 5 execuções, alternando entre `A/B isolation: 7 hard-empty…` e `floor=0 puts 0.5% in the main pool`, sempre com dois pares de mesma faixa trocando de posição. A causa é o helper de fixture:

```ts
const iso = (ms = 86_400_000) => new Date(Date.now() + ms).toISOString();
```

Como `iso()` é chamado a cada invocação do fetcher, dois peers que deveriam empatar recebiam `resetAt` com um milissegundo de diferença sempre que o relógio virava entre as duas chamadas. Pressão de reset entra no score, então esse epsilon quebrava o empate e `sortByScoreThenIndex` nunca chegava ao fallback por índice de inserção.

Fixei a base do relógio uma vez só (`CLOCK_BASE`). Nenhuma asserção foi tocada — as garantias de ordem, tamanho e exclusão continuam idênticas. 10/10 execuções verdes depois, e mais 6/6 após o merge da base nesta branch.

Também mergeei a base para resolver `file-size-baseline.json` (aditivo) e `src/domain/quotaCache.ts`, onde o seu placeholder `_providerSpecificData` cedeu lugar à implementação do #12803, que usa o parâmetro de fato.
2026-09-07 09:05:29 -03:00
Bob.Hou
ebdbd2c67d feat(models): live account catalog for Claude, Codex, Copilot, AGY (#12866)
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437, ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes.

O ponto que sustenta a PR é o `models.dev` virar overlay de preço em vez de fonte de catálogo. Um catálogo estático que sobrevive à conta já ter listado ids mais novos é o tipo de defeito que só aparece quando o modelo novo é justamente o que se quer usar.

Nota de integração: `activeSyncedCatalog.ts` colidiu com o #12934 (união dos `customModels` do picker no catálogo de despacho). Como você extraiu o bloco original para `loadConnectionCatalog`, os dois se compõem: a união dos irmãos agy/antigravity primeiro, o `unionCustomModels` por cima. Revalidei com `custom-models-live-catalog-12597`, `live-model-catalog-reconciliation-8926`, `sync-models-degraded-cached-catalog-9683`, `models-dev-catalog-read-gate`, `discovery-class`, `reactive-model-sync` e `l1-oauth-autosync-default` juntos — 48/48 — mais typecheck:core limpo.

Sobre o `autoSync` padrão em Claude/Codex/Copilot com scheduler de 6h: passei isso pelo dono antes de mergear e a decisão foi manter como está.
2026-09-07 09:02:59 -03:00
Bob.Hou
aa35d460dc fix(catalog): union picker customModels into the dispatch-time live catalog (#12597) (#12934)
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437 (ambos sob a baseline), ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes.

A assimetria era exatamente o defeito: o REST do picker já mesclava `customModels`, o despacho não, e o operador via o modelo na tela e tomava 400 na inferência. O overlay só de campos definidos é o detalhe que impede uma escrita esparsa do picker de apagar metadata de capacidade que veio do sync.

Nota de integração: este arquivo colidiu com o #12866, que extraiu o mesmo bloco para `loadConnectionCatalog` e uniu os catálogos irmãos agy/antigravity. Integrei os dois na worktree combinada — a união de irmãos primeiro, o `unionCustomModels` por cima — e a resolução vai junto no merge do #12866.
2026-09-07 09:00:58 -03:00
Bob.Hou
1b97f42ba3 fix(combo): treat a pin-only step as implicit connection allowlist (#12697)
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437 (ambos sob a baseline), ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes. Após o merge da base nesta branch, os 11/11 do `combo-pin-implicit-allowlist` foram revalidados.

A distinção entre pin de step de combo e pin forçado por header (`x-omniroute-connection`) é o que salva a PR de virar uma restrição ampla demais — o header continua permitindo fallback para conexões irmãs, o step não.

Apontar que o `a11930ec4` para a rotação dentro do `handleSingleModel` mas não popula `allowedConnectionIds` no resolve foi a peça que explicou por que os dois são complementares e não redundantes. Sem isso a PR pareceria duplicar um gate que já existia.
2026-09-07 09:00:37 -03:00
Bob.Hou
f12b87c80b fix(claude): extra-usage switch does not skip 5h preflight (#12803)
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437 (ambos sob a baseline), ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes.

`blockExtraUsage: false` significa "pode usar crédito extra", nunca "esconda a conta antes de despachar" — a conta saía da rota justamente quando o crédito extra existia para ser usado. Os 32/32 cobrem os quatro pontos onde a mesma decisão era tomada, e revalidei após o merge da base (32/32 de novo).

Nota de integração: o seu `isQuotaExhaustedForRequest` colidiu com o placeholder `_providerSpecificData` do #12789 na worktree combinada. Ficou a sua implementação, que é a que de fato usa o parâmetro. A base foi mergeada na branch para resolver o `file-size-baseline.json` (aditivo, JSON revalidado).
2026-09-07 08:59:39 -03:00
Bob.Hou
c042a51884 feat(grok-cli): show and redeem banked reset credits on Provider Limits (#12805)
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437 (ambos sob a baseline), ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes.

A decodificação dos campos aninhados 10/20/30 do `GetRemainingResets` ao vivo (último commit) é o que separa isto de um palpite sobre o formato do frame. Mostrar zero em vez de esconder a linha é a escolha certa: crédito zerado é informação, ausência de linha é ambiguidade.
2026-09-07 08:58:12 -03:00
Bob.Hou
d7721559a0 fix(combo): restricted keys listing a combo name no longer skip every member (#12899)
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437 (ambos sob a baseline), ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes.

Regressão de v3.8.50 vinda do #9057, com o sintoma mais enganoso possível: `attempted: 0`. A política já tinha admitido o combo e a checagem era refeita em cada membro interno.

Manter o filtro por prefixo de provider e o `disableNonPublicModels` intactos é o que impede o short-circuit de virar um buraco na allow-list.
2026-09-07 08:57:41 -03:00
Bob.Hou
e8a91173de fix(combos): inherit model_context_overrides onto effort-suffixed targets (#12475) (#12926)
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437 (ambos sob a baseline), ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes.

A busca do sufixo mais longo primeiro (`-xhigh` antes de `-high`) é o detalhe que faz a herança funcionar em vez de quase-funcionar. Manter `getResolvedModelContextOverride` fora do escopo, com o teste existente registrando que aquele caminho continua sem herança, deixa a fronteira explícita.
2026-09-07 08:57:27 -03:00
Bob.Hou
6d6b6027c5 fix(pwa): do not intercept navigations so Chrome can retry HTTP/2 (#12767)
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437 (ambos sob a baseline), ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes.

A separação entre o que é do service worker e o que é do Caddy está certa e é o que torna a PR mergeável: o `respondWith` em navegação é defeito nosso, o `Alt-Svc` mentindo h3 é config de proxy reverso e não tem o que fazer aqui.

O `/dashboardfoo` casando com `startsWith("/dashboard")` é um achado à parte, e o bump de cache v2→v3 é o que faz o worker antigo sair do ar nos clientes que já estão presos.
2026-09-07 08:57:15 -03:00
Bob.Hou
600abe68d0 fix(dashboard): moonshot voucher/cash leftover follows bucket balance (#12733)
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437 (ambos sob a baseline), ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes.

Available 0% com Voucher 100% e Cash 100% não é estado de carteira que exista — foi o sinal certo para puxar o fio. Tratar leftover como booleano só para Available e cravar 100% nos outros dois buckets é o tipo de defeito que passa despercebido enquanto a conta tem saldo.

Os dois testes cobrem os dois lados: o produtor e o caminho até `getQuotaRemainingPercentage` com `isCredits` + CNY.
2026-09-07 08:57:02 -03:00
Bob.Hou
d4d2e68a1f fix(dashboard): pass nodeMap into Runtime QuotaGroup (#12868)
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437 (ambos sob a baseline), ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes.

O `nodeMap` lido do closure de `RuntimePageClient` por uma função de nível de módulo é uma bomba-relógio silenciosa: só explode quando um monitor entra em error/exhausted/alerting, e o teste existente só alimentava listas vazias. Tirar o arquivo do exclude do vitest vale tanto quanto o fix — confirmei aqui que `tests/unit/ui/runtime-page-client.test.tsx` agora roda na `test:vitest:ui` e passa.

A anotação sobre o "内部服务器错误" ser o catálogo RSC da página, e não o crash, poupou o próximo a caçar fantasma.
2026-09-07 08:56:49 -03:00
Bob.Hou
25bc16d87e fix(dashboard): batch delete no longer toasts failure after success (#12711)
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437 (ambos sob a baseline), ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes.

Além do bug do toast, esta PR foi a que derrubou os três base-reds vivos do tip: o fragmento `changelog.d/fixes/reset-aware-model-family.md` sem o `- ` inicial, o registro do `tests/unit/reset-aware-request-scope-12600.test.ts` no `stryker.conf.json` e o `TS2554` do glm. O `check-changelog-integrity` voltou a passar aqui por causa dela.

O diagnóstico do MouseEvent é o que dá o valor: `onConfirm` chegava como handler de clique nativo e `handleBatchDeleteConfirm` tratava qualquer primeiro argumento truthy como callback. O cinto (`typeof`) e o suspensório (o wrap no ConfirmModal) juntos estão certos — só um dos dois deixaria a porta aberta para o próximo caller.
2026-09-07 08:56:36 -03:00
Bob.Hou
d857bd053a fix(glm): drop extra 16th arg to SSE transform helper (#12770)
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437 (ambos sob a baseline), ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes.

O `65536` era o 16º posicional de um helper com 15 parâmetros — `TS2554` vivo no tip (`open-sse/executors/glm.ts:244`, confirmado aqui antes do board). O teste de guarda de aridade é o que impede a reincidência: ele checa a assinatura do helper e o call site, não o comportamento, que é exatamente onde o erro morava.

Obrigado por isolar isso do #12711 em vez de deixar o `glm.ts` viajar junto com pin/combo-split/moonshot.
2026-09-07 08:55:45 -03:00
Diego Rodrigues de Sa e Souza
ce55151ca5 chore(ci): guard commit identity in pre-commit to stop author misattribution (#12772)
* chore(ci): guard commit identity in pre-commit to stop author misattribution

Two windows of commits in this checkout were signed with the wrong identity,
both caused by an identity override left behind by an automated session:
2026-08-13..26 (name "Xiangzhe" + @backryun's e-mail, 237 commits) and
2026-08-29..09-02 (name "Markus Hartung" + the maintainer's e-mail, 59 commits).
The .mailmap repairs the record after the fact; this gate stops the next window.

The gate is opt-in per machine via omniroute.expectedName / expectedEmail — with
no config it exits 0, so contributors who clone the repo are never affected. It
blocks three things: a committer that is not this machine's identity (which is
what BOTH windows looked like — in August neither the name nor the e-mail was
the maintainer's, so checking only their e-mail would have missed it), an author
carrying the maintainer's e-mail under someone else's name, and any address
listed in omniroute.legacyEmail.

Crediting a contributor with `git commit --author="Name <their@email>"` keeps
working, since the rule targets the committer and the maintainer's own address.

* test(ci): isolate the identity gate's test from the ambient git config

The "stays inert when the machine has not opted in" case read the real
global config, so on a machine that HAS opted in (omniroute.expectedEmail
set — the maintainer's own boxes, where this gate matters most) the gate
correctly refused a synthetic contributor identity and the test failed.
It only passed on a clean CI runner.

Neutralising GIT_CONFIG_GLOBAL/GIT_CONFIG_SYSTEM makes the opt-in state
come solely from what the test injects, so the suite is deterministic on
both an opted-in and a clean machine.
2026-09-07 08:35:29 -03:00
286 changed files with 23020 additions and 5306 deletions

View File

@@ -7,6 +7,7 @@ fi
# Cheap, deterministic local gates (re-enabled). Slower checks (i18n drift,
# openapi coverage/security-tiers, env-doc sync) run in CI to keep commits fast.
sh scripts/check/check-git-identity.sh
npx lint-staged
node scripts/check/check-docs-sync.mjs
npm run check:any-budget:t11

View File

@@ -22,7 +22,7 @@
"node": ">=22.22.3"
},
"peerDependencies": {
"@opencode-ai/plugin": "*"
"@opencode-ai/plugin": ">=1.18.29 <2"
}
},
"node_modules/@ai-sdk/provider": {
@@ -39,9 +39,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
"integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
"integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",
"cpu": [
"ppc64"
],
@@ -56,9 +56,9 @@
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
"integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",
"integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",
"cpu": [
"arm"
],
@@ -73,9 +73,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
"integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",
"integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",
"cpu": [
"arm64"
],
@@ -90,9 +90,9 @@
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
"integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",
"integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",
"cpu": [
"x64"
],
@@ -107,9 +107,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
"integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",
"integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",
"cpu": [
"arm64"
],
@@ -124,9 +124,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
"integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",
"integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",
"cpu": [
"x64"
],
@@ -141,9 +141,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
"integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",
"integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",
"cpu": [
"arm64"
],
@@ -158,9 +158,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
"integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",
"integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",
"cpu": [
"x64"
],
@@ -175,9 +175,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
"integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",
"integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",
"cpu": [
"arm"
],
@@ -192,9 +192,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
"integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",
"integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",
"cpu": [
"arm64"
],
@@ -209,9 +209,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
"integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",
"integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",
"cpu": [
"ia32"
],
@@ -226,9 +226,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
"integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",
"integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",
"cpu": [
"loong64"
],
@@ -243,9 +243,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
"integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",
"integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",
"cpu": [
"mips64el"
],
@@ -260,9 +260,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
"integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",
"integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",
"cpu": [
"ppc64"
],
@@ -277,9 +277,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
"integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",
"integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",
"cpu": [
"riscv64"
],
@@ -294,9 +294,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
"integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",
"integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",
"cpu": [
"s390x"
],
@@ -311,7 +311,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.28.1",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",
"integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",
"cpu": [
"x64"
],
@@ -326,9 +328,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
"integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",
"integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",
"cpu": [
"arm64"
],
@@ -343,9 +345,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
"integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",
"integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",
"cpu": [
"x64"
],
@@ -360,9 +362,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
"integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",
"integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",
"cpu": [
"arm64"
],
@@ -377,9 +379,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
"integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",
"integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",
"cpu": [
"x64"
],
@@ -394,9 +396,9 @@
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
"integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",
"integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",
"cpu": [
"arm64"
],
@@ -411,9 +413,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
"integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",
"integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",
"cpu": [
"x64"
],
@@ -428,9 +430,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
"integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",
"integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",
"cpu": [
"arm64"
],
@@ -445,9 +447,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
"integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",
"integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",
"cpu": [
"ia32"
],
@@ -462,9 +464,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
"integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",
"integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",
"cpu": [
"x64"
],
@@ -1180,7 +1182,9 @@
}
},
"node_modules/esbuild": {
"version": "0.28.1",
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
"integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -1191,32 +1195,32 @@
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.28.1",
"@esbuild/android-arm": "0.28.1",
"@esbuild/android-arm64": "0.28.1",
"@esbuild/android-x64": "0.28.1",
"@esbuild/darwin-arm64": "0.28.1",
"@esbuild/darwin-x64": "0.28.1",
"@esbuild/freebsd-arm64": "0.28.1",
"@esbuild/freebsd-x64": "0.28.1",
"@esbuild/linux-arm": "0.28.1",
"@esbuild/linux-arm64": "0.28.1",
"@esbuild/linux-ia32": "0.28.1",
"@esbuild/linux-loong64": "0.28.1",
"@esbuild/linux-mips64el": "0.28.1",
"@esbuild/linux-ppc64": "0.28.1",
"@esbuild/linux-riscv64": "0.28.1",
"@esbuild/linux-s390x": "0.28.1",
"@esbuild/linux-x64": "0.28.1",
"@esbuild/netbsd-arm64": "0.28.1",
"@esbuild/netbsd-x64": "0.28.1",
"@esbuild/openbsd-arm64": "0.28.1",
"@esbuild/openbsd-x64": "0.28.1",
"@esbuild/openharmony-arm64": "0.28.1",
"@esbuild/sunos-x64": "0.28.1",
"@esbuild/win32-arm64": "0.28.1",
"@esbuild/win32-ia32": "0.28.1",
"@esbuild/win32-x64": "0.28.1"
"@esbuild/aix-ppc64": "0.28.2",
"@esbuild/android-arm": "0.28.2",
"@esbuild/android-arm64": "0.28.2",
"@esbuild/android-x64": "0.28.2",
"@esbuild/darwin-arm64": "0.28.2",
"@esbuild/darwin-x64": "0.28.2",
"@esbuild/freebsd-arm64": "0.28.2",
"@esbuild/freebsd-x64": "0.28.2",
"@esbuild/linux-arm": "0.28.2",
"@esbuild/linux-arm64": "0.28.2",
"@esbuild/linux-ia32": "0.28.2",
"@esbuild/linux-loong64": "0.28.2",
"@esbuild/linux-mips64el": "0.28.2",
"@esbuild/linux-ppc64": "0.28.2",
"@esbuild/linux-riscv64": "0.28.2",
"@esbuild/linux-s390x": "0.28.2",
"@esbuild/linux-x64": "0.28.2",
"@esbuild/netbsd-arm64": "0.28.2",
"@esbuild/netbsd-x64": "0.28.2",
"@esbuild/openbsd-arm64": "0.28.2",
"@esbuild/openbsd-x64": "0.28.2",
"@esbuild/openharmony-arm64": "0.28.2",
"@esbuild/sunos-x64": "0.28.2",
"@esbuild/win32-arm64": "0.28.2",
"@esbuild/win32-ia32": "0.28.2",
"@esbuild/win32-x64": "0.28.2"
}
},
"node_modules/fast-check": {
@@ -1786,490 +1790,6 @@
}
}
},
"node_modules/tsup/node_modules/@esbuild/aix-ppc64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
"integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/android-arm": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
"integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/android-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
"integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/android-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
"integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/darwin-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
"integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/darwin-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
"integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/freebsd-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
"integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/freebsd-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
"integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/linux-arm": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
"integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/linux-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
"integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/linux-ia32": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
"integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/linux-loong64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
"integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/linux-mips64el": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
"integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/linux-ppc64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
"integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/linux-riscv64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
"integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/linux-s390x": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
"integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/linux-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
"integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/netbsd-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
"integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/netbsd-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
"integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/openbsd-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
"integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/openbsd-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
"integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/openharmony-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
"integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/sunos-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
"integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/win32-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
"integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/win32-ia32": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
"integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/@esbuild/win32-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
"integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsup/node_modules/esbuild": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
"integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.27.7",
"@esbuild/android-arm": "0.27.7",
"@esbuild/android-arm64": "0.27.7",
"@esbuild/android-x64": "0.27.7",
"@esbuild/darwin-arm64": "0.27.7",
"@esbuild/darwin-x64": "0.27.7",
"@esbuild/freebsd-arm64": "0.27.7",
"@esbuild/freebsd-x64": "0.27.7",
"@esbuild/linux-arm": "0.27.7",
"@esbuild/linux-arm64": "0.27.7",
"@esbuild/linux-ia32": "0.27.7",
"@esbuild/linux-loong64": "0.27.7",
"@esbuild/linux-mips64el": "0.27.7",
"@esbuild/linux-ppc64": "0.27.7",
"@esbuild/linux-riscv64": "0.27.7",
"@esbuild/linux-s390x": "0.27.7",
"@esbuild/linux-x64": "0.27.7",
"@esbuild/netbsd-arm64": "0.27.7",
"@esbuild/netbsd-x64": "0.27.7",
"@esbuild/openbsd-arm64": "0.27.7",
"@esbuild/openbsd-x64": "0.27.7",
"@esbuild/openharmony-arm64": "0.27.7",
"@esbuild/sunos-x64": "0.27.7",
"@esbuild/win32-arm64": "0.27.7",
"@esbuild/win32-ia32": "0.27.7",
"@esbuild/win32-x64": "0.27.7"
}
},
"node_modules/tsx": {
"version": "4.22.3",
"dev": true,

View File

@@ -63,5 +63,8 @@
},
"peerDependencies": {
"@opencode-ai/plugin": ">=1.18.29 <2"
},
"overrides": {
"esbuild": "^0.28.1"
}
}

View File

@@ -1745,9 +1745,9 @@
}
},
"node_modules/toml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/toml/-/toml-4.1.1.tgz",
"integrity": "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/toml/-/toml-4.3.0.tgz",
"integrity": "sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A==",
"dev": true,
"license": "MIT",
"engines": {

View File

@@ -68,6 +68,7 @@
"typescript": "^5.9.3"
},
"overrides": {
"esbuild": "^0.28.1"
"esbuild": "^0.28.1",
"toml": "^4.1.2"
}
}

View File

@@ -46,7 +46,7 @@ Repository map and Reference Documentation sections below.
## Project at a Glance
**OmniRoute** — unified AI proxy/router. One endpoint, 357 LLM providers, auto-fallback.
**OmniRoute** — unified AI proxy/router. One endpoint, 356 LLM providers, auto-fallback.
| Layer | Location | Purpose |
| ------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

View File

@@ -7,19 +7,19 @@
# 🚀 OmniRoute — The Free AI Gateway
<img src="./docs/diagrams/readme-hero.svg" width="100%" alt="OmniRoute — Never stop coding. Every AI tool → 357 providers — 150+ 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. 357 AI providers · 150+ free tiers · ~1.62B 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 → 356 providers — 150+ 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. 356 AI providers · 150+ free tiers · ~1.47B free tokens/mo · 19 routing strategies · $0 to start."/>
</div>
<div align="center">
## 💰 ~1.62B Free Tokens / Month
## 💰 ~1.47B Free Tokens / Month
</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 **483 free-tier entries across 35 recurring pool keys** and computes the token headline from the **17 pools with a published positive monthly budget plus five per-model Groq caps**, deduplicated by shared pool. Quotas that only open after a regional identity check (today: ModelScope) are shown apart, +~6M behind regional identity verification, and never summed into the headline. 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 **444 free-tier entries across 34 recurring pool keys** and computes the token headline from the **16 pools with a published positive monthly budget plus five per-model Groq caps**, deduplicated by shared pool. Quotas that only open after a regional identity check (today: ModelScope) are shown apart, +~6M behind regional identity verification, and never summed into the headline. 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.62B free tokens per month steady, up to ~2.25B in the first month with signup credits, from 35 documented recurring pool keys covering 483 cataloged free-tier entries behind one endpoint. Honest pool-deduped math — each shared pool counted once, including 17 recurring pools with a published positive monthly token budget plus five per-model Groq caps; 13 providers are marked avoid in the terms-risk catalog so you decide. Budget bar includes Mistral 1B, Nara 210M, LLM7 150M, xKiro 150M, Groq 30M (five per-model caps) 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.47B free tokens per month steady, up to ~2.10B in the first month with signup credits, from 34 documented recurring pool keys covering 444 cataloged free-tier entries behind one endpoint. Honest pool-deduped math — each shared pool counted once, including 16 recurring pools with a published positive monthly token budget plus five per-model Groq caps; 13 providers are marked avoid in the terms-risk catalog so you decide. Budget bar includes Mistral 1B, Nara 210M, LLM7 150M, Groq 30M (five per-model caps) 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 | **357** | more queued |
| 🌐 Providers | 290 | **352** | 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="#-357-ai-providers--152-catalog-marked-free">🌐 Providers</a></td>
<td align="center"><a href="#-352-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>
@@ -209,7 +209,7 @@ curl http://localhost:20128/v1/chat/completions \
</div>
<img src="./docs/diagrams/promise-pillars.svg" width="100%" alt="The Promise — One endpoint and 357 providers. Automatic fallback keeps routing while another healthy target is available. Six pillars: resilient fallback across 357 providers · up to 95% token savings on eligible workloads · $0 to start with 150+ free tiers and 53 recurring/keyless free-forever providers · 36 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 356 providers. Automatic fallback keeps routing while another healthy target is available. Six pillars: resilient fallback across 356 providers · up to 95% token savings on eligible workloads · $0 to start with 150+ free tiers and 52 recurring/keyless free-forever providers · 36 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/>
@@ -462,7 +462,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: 357 providers, 150+ 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 42 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: 356 providers, 150+ 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 42 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>
@@ -518,9 +518,9 @@ Pix copia-e-cola:
## 📡 OmniRoute Radar
The main free-tier headline remains **~1.62B tokens/month** from the documented,
The main free-tier headline remains **~1.47B tokens/month** from the documented,
pool-deduplicated catalog above. Temporary provider signup credits can separately lift the first
month to **~2.25B**. Radar is an optional, signed catalog overlay for people who want fresher
month to **~2.10B**. Radar is an optional, signed catalog overlay for people who want fresher
free-model availability between OmniRoute releases; the community catalog and every existing free
feature remain free.
@@ -644,11 +644,11 @@ of your shell history. → [CLI Integrations](docs/guides/CLI-INTEGRATIONS.md)
<div align="center">
## 🌐 357 AI Providers — 152 Catalog-Marked Free
## 🌐 352 AI Providers — 152 Catalog-Marked Free
</div>
> **357 registered providers** across the canonical chat, media, search, local, cloud-agent and system collections, including **152 carrying `hasFree: true` discovery metadata**. The chat model registry covers **229 providers / 2,554 distinct provider-model pairs / 1,283 raw model IDs**; the separate free-budget catalog has **483 per-model rows**, **35 recurring pools** and **53 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).
> **352 registered providers** across the canonical chat, media, search, local, cloud-agent and system collections, including **152 carrying `hasFree: true` discovery metadata**. The chat model registry covers **229 providers / 2,554 distinct provider-model pairs / 1,283 raw model IDs**; the separate free-budget catalog has **444 per-model rows**, **34 recurring pools** and **52 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">
@@ -1307,7 +1307,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>16-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: 35 documented recurring pools / 483 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: 34 documented recurring pools / 444 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>

View File

@@ -0,0 +1 @@
- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789))

View File

@@ -0,0 +1,2 @@
- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866))
- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866))

View File

@@ -0,0 +1 @@
- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic

View File

@@ -0,0 +1 @@
- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi

View File

@@ -0,0 +1,2 @@
- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711))
- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream

View File

@@ -0,0 +1 @@
- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868))

View File

@@ -0,0 +1 @@
- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)).

View File

@@ -0,0 +1 @@
- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi

View File

@@ -0,0 +1 @@
- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi

View File

@@ -0,0 +1 @@
- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection).

View File

@@ -0,0 +1 @@
- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached TransformStream

View File

@@ -0,0 +1 @@
- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3.

View File

@@ -0,0 +1 @@
- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0%

View File

@@ -1 +1 @@
Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider.
- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider.

View File

@@ -0,0 +1 @@
- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged.

View File

@@ -1,5 +1,10 @@
{
"_rebaseline_2026_09_03_12648_xkiro_provider": "PR #12648 (feat/provider-xkiro) own growth: src/shared/constants/providers/apikey/gateways.ts +18 lines on top of #12649 (the xkiro APIKEY_PROVIDERS_GATEWAYS catalog entry with hasFree/freeNote/authHint/apiHint documenting the 5M tokens/day free plan, plus the Prettier reflow of two pre-existing >100-col authHint lines (oneminai, freebuff) that lint-staged enforces on any touch of the file; additive data at the existing registry chokepoint, same god-file no-split rationale as prior gateways.ts rebaselines: #11786 seekai, #10987 logfare, #10531 freebuff). Covered by tests/unit/free-provider-xkiro.test.ts (4/4).",
"_rebaseline_2026_09_06_runtime_quotagroup_nodemap": "Own growth: src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx 1201->1222 (+21, check-file-size split-newline). QuotaGroup is a module-level sibling and was reading nodeMap from RuntimePageClient's closure; that identifier is not in scope, so a quota monitor with status error/exhausted/alerting throws ReferenceError. Fix threads nodeMap as a prop (3 call sites + parameter + ProviderNodeEntry import). Prettier wraps the long import and the three QuotaGroup JSX tags. Covered by tests/unit/ui/runtime-page-client.test.tsx (empty monitors stay green; error+exhausted fixtures mount QuotaGroup).",
"_rebaseline_2026_09_05_claude_extra_usage_preflight": "Own growth: open-sse/services/combo.ts 4080->4084 (+4). buildAutoCandidates now forwards connection.providerSpecificData into evaluateQuotaCutoff so a Claude account with blockExtraUsage=false is not dropped at the 5h bar. Irreducible at the existing cutoff call site; the helper lives in claudeExtraUsage.ts (under cap). Covered by tests/unit/quota-preflight.test.ts.",
"_rebaseline_2026_09_04_12697_combo_pin_allowlist": "PR #12697 own growth: src/sse/handlers/chat.ts 2454->2458 (+4). checkModelAvailable preflight and handleSingleModelChat now call comboPinAllowlist so a pin-only combo step cannot scan the provider pool after 502/429. Helper lives in src/lib/combos/steps.ts under cap. Covered by tests/unit/combo-pin-implicit-allowlist.test.ts (11/11).",
"_rebaseline_2026_09_05_quota_weighted": "feat/quota-weighted-routing own growth: src/app/(dashboard)/dashboard/combos/page.tsx 5066->5080 (+14 = STRATEGY_GUIDANCE_FALLBACK + STRATEGY_RECOMMENDATIONS_FALLBACK entries for quota-weighted; copy is the spec-mandated when/avoid/example and tips, irreducible at the existing fallback maps). Rebased onto 9d1a896c6 where #12671 already grew the same file 5018->5066. Covered by tests/unit/combo/quota-weighted-strategy.test.ts + autocombo-unification.test.ts.",
"_rebaseline_2026_09_03_combo_execute_target_attempt": "Task 3 of handleComboChat split: new leaf open-sse/services/combo/executeTargetAttempt.ts lands at 1205 (check-file-size split-newline; wc -l 1204) above new-file cap 1200. Lift-as-is from combo.ts:1533-2616 retry loop. Pure classify predicates already extracted to executeTargetClassify.ts (54 LOC). Remaining growth is I/O + side effects (handleSingleModel, quality, pin/LKGP, cooldown, lockout) that cannot leave this file without splitting the retry loop mid-request. Frozen at exact LOC so it can only shrink. Covered by tests/unit/combo/execute-target-attempt.test.ts (7/7).",
"_rebaseline_2026_09_06_12696_chat_pipeline_retry_off": "PR #12696 own test growth: tests/integration/chat-pipeline.test.ts 1644->1648 (+4). The upstream-500 structured-error case now sets requestRetry/maxRetryIntervalSec to 0 so the new provider-execution pipeline cannot retry the mock 500 and double-count fetch. Irreducible at the existing seed+fetch mock; covered by the same test.",
"_rebaseline_2026_09_03_reset_aware_model_family": "Own growth: open-sse/services/combo.ts 4036->4041 (+5). buildAutoCandidates now keys the reset-aware quota cache by getQuotaFetchScope and spreads requestedModel onto the connection so Gemini windows stay off a Claude-empty Antigravity account. Irreducible wiring at the existing fetchResetAwareQuotaWithCache call site; the family helper itself lives in antigravityQuotaFamily.ts. Covered by tests/unit/reset-aware-request-scope-12600.test.ts.",
"_rebaseline_2026_09_03_overloaded_not_provider_breaker": "fix/overloaded-not-provider-breaker own growth: open-sse/services/combo.ts 4036->4075 (check-file-size split-newline, +39). Circuit-open pre-skip now records the breaker retryAfter and, when every target was skipped that way, waits the short reset via resolveCircuitOpenWaitDecision (new leaf in comboCooldownRetry.ts) instead of crystallizing ALL_TARGETS_SKIPPED in ~43ms. skippedForCircuitOpen / earliestCircuitOpenRetryMs reset each setTry so a later iteration cannot inherit a stale retryAfter. Irreducible at the existing ALL_TARGETS_SKIPPED chokepoint (same pattern as #7301/#8213 cooldown-wait). Predicate itself lives in circuitBreaker.ts / comboPredicates.ts / chatPredicates.ts, all under cap. Covered by tests/unit/overloaded-not-provider-breaker.test.ts + combo-cooldown-retry.test.ts.",
"_rebaseline_2026_09_03_12649_free_tier_reaudit_gateways": "PR #12649 (fix/free-tier-quota-reaudit) own growth: src/shared/constants/providers/apikey/gateways.ts 1459->1462 (+3 = the nara authHint rewritten for the re-audited 7M/day plan now wraps to two lines, plus the Prettier reflow of two pre-existing >100-col authHint lines (oneminai, freebuff) that lint-staged enforces on any touch of the file; additive text at the existing registry chokepoint, same god-file no-split rationale as prior gateways.ts rebaselines: #11786 seekai, #10987 logfare, #10531 freebuff). Covered by tests/unit/free-tier-reaudit-2026-09.test.ts and tests/unit/free-providers-batch-2026-07.test.ts.",
@@ -210,7 +215,7 @@
"_rebaseline_2026_08_24_video_bridge_fu01_fu03_fu04_result_cache_tests": "PRs #11362 (FU-01 cache hardening) + #11382 (FU-03 visual dedup policy identity) + #11383 (FU-04 focused analysis mode) own test growth: videoBridgeResultCache.test.ts <1000->1040, +40 (sum of three stacked PRs boarded together in the same merge-batch, each adding its own cache-identity assertions on the shared result-cache seam). Owner pre-authorized rebaseline for legitimate PR growth (2026-08-19 directive).",
"_rebaseline_basered_codebuddy_cn": "Base-red fix (#4664 CodeBuddy CN): oauth-providers-config.test.ts 867->870 (+3) to align the EXPECTED provider list/config with the codebuddy-cn provider that #4664 added to the registry without updating this test (it asserts 'exactly once').",
"_rebaseline_pr4613_compatible_provider_groups": "Reconcile #4613 already-merged growth: providers-page-utils.test.ts 1004->1052 (+48, buildCompatibleProviderGroups partition unit test). Fast-gate PR->release does not run check:file-size, so this surfaced post-merge.",
"tests/integration/chat-pipeline.test.ts": 1644,
"tests/integration/chat-pipeline.test.ts": 1648,
"tests/unit/account-fallback-service.test.ts": 2056,
"tests/unit/batch_api.test.ts": 1345,
"tests/unit/cc-compatible-provider.test.ts": 1225,
@@ -426,7 +431,9 @@
"open-sse/mcp-server/server.ts": 1572,
"open-sse/services/accountFallback.ts": 2467,
"open-sse/services/adobeFireflyBrowserLogin.ts": 1401,
"open-sse/services/combo.ts": 4084,
"open-sse/services/combo.ts": 4080,
"open-sse/services/combo/executeTargetAttempt.ts": 1205,
"open-sse/translator/response/openai-responses.ts": 1466,
"open-sse/utils/cursorAgentProtobuf.ts": 1547,
"open-sse/utils/proxyFetch.ts": 1271,
@@ -435,12 +442,12 @@
"open-sse/vendor/codex-chatgpt-web/bridge.ts": 1335,
"src/app/(dashboard)/dashboard/HomePageClient.tsx": 1344,
"src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx": 3186,
"src/app/(dashboard)/dashboard/combos/page.tsx": 5066,
"src/app/(dashboard)/dashboard/combos/page.tsx": 5080,
"src/app/(dashboard)/dashboard/costs/CostOverviewTab.tsx": 1319,
"src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": 2491,
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 1631,
"src/app/(dashboard)/dashboard/providers/page.tsx": 2025,
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1201,
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1222,
"src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": 1475,
"src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": 1271,
"src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": 1607,
@@ -456,9 +463,9 @@
"src/lib/tailscaleTunnel.ts": 1208,
"src/lib/tokenHealthCheck.ts": 1218,
"src/shared/components/RequestLoggerV2.tsx": 1718,
"src/shared/constants/providers/apikey/gateways.ts": 1480,
"src/shared/constants/providers/apikey/gateways.ts": 1462,
"src/shared/services/cliRuntime.ts": 1296,
"src/sse/handlers/chat.ts": 2454,
"src/sse/handlers/chat.ts": 2458,
"src/sse/services/auth.ts": 3450,
"tests/unit/account-fallback-service.test.ts": 2453,
"tests/unit/provider-validation-specialty.test.ts": 4656
@@ -576,7 +583,7 @@
"src/app/(dashboard)/dashboard/health/page.tsx": "1165",
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": "1324",
"src/app/(dashboard)/dashboard/providers/page.tsx": "1944",
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": "1201",
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": "1222",
"src/app/(dashboard)/dashboard/settings/components/PricingTab.tsx": "1019",
"src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": "1470",
"src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": "1123",

View File

@@ -34,7 +34,7 @@ inside GitHub's `<img>` sandbox:
| [combo-always-on.svg](./combo-always-on.svg) | style reference | Animated priority-combo fallback (4 layers, 16s loop). Edit the SVG directly — there is no `.mmd` source. |
| [cli-terminal.svg](./cli-terminal.svg) | README.md (root) | Compact half-height animated terminal (1200×350): 3 real CLI commands cycling with typewriter + scrolling subcommand ticker; first frame = completed providers screen. Edit the SVG directly — there is no `.mmd` source. |
| [compression-pipeline.svg](./compression-pipeline.svg) | README.md (root) | Animated 12-engine compression funnel (8s loop). Edit the SVG directly — there is no `.mmd` source. |
| [free-tier-budget.svg](./free-tier-budget.svg) | README.md (root) | Animated free-tier budget card (~1.62B/mo quantified headline, 17-pool + Groq-caps budget bar, per-pool grid, signup credits, 10s loop). Edit the SVG directly — there is no `.mmd` source. |
| [free-tier-budget.svg](./free-tier-budget.svg) | README.md (root) | Animated free-tier budget card (~1.47B/mo quantified headline, 16-pool + Groq-caps budget bar, per-pool grid, signup credits, 10s loop). Edit the SVG directly — there is no `.mmd` source. |
| [readme-hero.svg](./readme-hero.svg) | README.md (root) | Animated hero card (tagline, live provider/free-access headline, full-width compression bar demo, 6 stat chips). Edit the SVG directly — there is no `.mmd` source. |
| [promise-pillars.svg](./promise-pillars.svg) | README.md (root) | Animated "The Promise" 6-pillar card (12s border-highlight sweep). Edit the SVG directly — there is no `.mmd` source. |
| [why-pain-fix.svg](./why-pain-fix.svg) | README.md (root) | Animated "Why OmniRoute" 10-row pain-vs-fix ledger (15s green row sweep). Edit the SVG directly — there is no `.mmd` source. |

View File

@@ -1,4 +1,4 @@
<svg viewBox="0 0 1200 350" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Animated terminal demoing the OmniRoute CLI: omniroute providers list (357 providers registered, anthropic, codex, glm, kimi shown active), omniroute combo list (always-on priority, cost-saver, fusion-panel, context-relay) and omniroute health (healthy, 18412 requests in 24h, p95 412ms, circuit breakers 24 closed, 1 half-open, 0 open), cycling over 86 top-level commands: providers, oauth, keys, combo, nodes, models, cache, compression, cost, usage, quota, health, resilience, telemetry, logs, audit, mcp, a2a, cloud, memory, skills, eval, doctor, repl, tunnel, backup, sync, webhooks, policy, pricing, translator, simulate and more.">
<svg viewBox="0 0 1200 350" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Animated terminal demoing the OmniRoute CLI: omniroute providers list (356 providers registered, anthropic, codex, glm, kimi shown active), omniroute combo list (always-on priority, cost-saver, fusion-panel, context-relay) and omniroute health (healthy, 18412 requests in 24h, p95 412ms, circuit breakers 24 closed, 1 half-open, 0 open), cycling over 86 top-level commands: providers, oauth, keys, combo, nodes, models, cache, compression, cost, usage, quota, health, resilience, telemetry, logs, audit, mcp, a2a, cloud, memory, skills, eval, doctor, repl, tunnel, backup, sync, webhooks, policy, pricing, translator, simulate and more.">
<desc>Compact animated terminal cycling three real OmniRoute CLI commands with a typewriter effect and a scrolling subcommand ticker; the first frame shows the completed providers-list screen.</desc>
<defs><clipPath id="tickerClip"><rect x="12" y="304" width="1176" height="40"/></clipPath><clipPath id="tw0"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;31;61;92;122;153;184;214;245;245" keyTimes="0;0.012;0.018;0.024;0.030;0.036;0.042;0.048;0.054;1" dur="18s" repeatCount="indefinite"/></rect></clipPath><clipPath id="tw1"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;26;51;76;102;128;153;178;204;204" keyTimes="0;0.348;0.351;0.357;0.363;0.369;0.375;0.381;0.387;1" dur="18s" repeatCount="indefinite"/></rect></clipPath><clipPath id="tw2"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;20;41;61;82;102;122;143;163;163" keyTimes="0;0.678;0.684;0.690;0.696;0.702;0.708;0.714;0.720;1" dur="18s" repeatCount="indefinite"/></rect></clipPath></defs>
<rect width="1200" height="350" fill="#0d1117"/>
@@ -6,7 +6,7 @@
<path d="M 0 34 L 1200 34" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
<circle cx="24" cy="17" r="6" fill="#ff5f56"/><circle cx="46" cy="17" r="6" fill="#ffbd2e"/><circle cx="68" cy="17" r="6" fill="#27c93f"/>
<text x="600" y="22" text-anchor="middle" font-family="Consolas, 'Courier New', monospace" font-size="13" fill="#71717a">omniroute &#8212; 86 top-level commands</text>
<g font-family="Consolas, 'Courier New', monospace" font-size="17"><animate attributeName="opacity" values="1;0;0" keyTimes="0;0.006;1" dur="18s" repeatCount="indefinite"/><text x="64" y="66" fill="#F7F6FC">omniroute providers list</text><text x="40" y="100" font-weight="700" fill="#38bdf8">OmniRoute Providers</text><text x="40" y="128" fill="#a1a1aa">1f3a9c2e&#160;&#160;anthropic&#160;&#160;&#160;Claude Max 20x&#160;&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="154" fill="#a1a1aa">8c2d5b1a&#160;&#160;codex&#160;&#160;&#160;&#160;&#160;&#160;&#160;Codex Pro (team)&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="180" fill="#a1a1aa">f4e0a97b&#160;&#160;glm&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;GLM Coding Plan&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="206" fill="#a1a1aa">03bd6e5f&#160;&#160;kimi&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;Kimi K2 free&#160;&#160;&#160;&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="232" fill="#71717a">&#8230; 353 more providers</text></g><g opacity="1" font-family="Consolas, 'Courier New', monospace" font-size="17">
<g font-family="Consolas, 'Courier New', monospace" font-size="17"><animate attributeName="opacity" values="1;0;0" keyTimes="0;0.006;1" dur="18s" repeatCount="indefinite"/><text x="64" y="66" fill="#F7F6FC">omniroute providers list</text><text x="40" y="100" font-weight="700" fill="#38bdf8">OmniRoute Providers</text><text x="40" y="128" fill="#a1a1aa">1f3a9c2e&#160;&#160;anthropic&#160;&#160;&#160;Claude Max 20x&#160;&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="154" fill="#a1a1aa">8c2d5b1a&#160;&#160;codex&#160;&#160;&#160;&#160;&#160;&#160;&#160;Codex Pro (team)&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="180" fill="#a1a1aa">f4e0a97b&#160;&#160;glm&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;GLM Coding Plan&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="206" fill="#a1a1aa">03bd6e5f&#160;&#160;kimi&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;Kimi K2 free&#160;&#160;&#160;&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="232" fill="#71717a">&#8230; 348 more providers</text></g><g opacity="1" font-family="Consolas, 'Courier New', monospace" font-size="17">
<animate attributeName="opacity" values="1;1;0;0" keyTimes="0;0.315;0.33;1" dur="18s" repeatCount="indefinite"/>
<text x="40" y="66" fill="#22c55e">$</text>
<g clip-path="url(#tw0)"><text x="64" y="66" fill="#F7F6FC">omniroute providers list</text></g>
@@ -14,7 +14,7 @@
<animate attributeName="x" calcMode="discrete" values="64;95;125;156;186;217;248;278;309;309" keyTimes="0.000;0.012;0.018;0.024;0.030;0.036;0.042;0.048;0.054;1" dur="18s" repeatCount="indefinite"/>
<animate attributeName="opacity" values="0;0;1;0.2;1;0.2;1;0;0" keyTimes="0;0.011;0.012;0.022;0.032;0.042;0.052;0.074;1" dur="18s" repeatCount="indefinite"/>
</rect>
<text x="40" y="100" font-weight="700" fill="#38bdf8" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.045;0.047" dur="18s" repeatCount="indefinite"/>OmniRoute Providers</text><text x="40" y="128" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.053;0.055" dur="18s" repeatCount="indefinite"/>1f3a9c2e&#160;&#160;anthropic&#160;&#160;&#160;Claude Max 20x&#160;&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="154" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.061;0.063" dur="18s" repeatCount="indefinite"/>8c2d5b1a&#160;&#160;codex&#160;&#160;&#160;&#160;&#160;&#160;&#160;Codex Pro (team)&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="180" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.069;0.07100000000000001" dur="18s" repeatCount="indefinite"/>f4e0a97b&#160;&#160;glm&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;GLM Coding Plan&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="206" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.077;0.079" dur="18s" repeatCount="indefinite"/>03bd6e5f&#160;&#160;kimi&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;Kimi K2 free&#160;&#160;&#160;&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="232" fill="#71717a" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.085;0.08700000000000001" dur="18s" repeatCount="indefinite"/>&#8230; 353 more providers</text>
<text x="40" y="100" font-weight="700" fill="#38bdf8" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.045;0.047" dur="18s" repeatCount="indefinite"/>OmniRoute Providers</text><text x="40" y="128" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.053;0.055" dur="18s" repeatCount="indefinite"/>1f3a9c2e&#160;&#160;anthropic&#160;&#160;&#160;Claude Max 20x&#160;&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="154" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.061;0.063" dur="18s" repeatCount="indefinite"/>8c2d5b1a&#160;&#160;codex&#160;&#160;&#160;&#160;&#160;&#160;&#160;Codex Pro (team)&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="180" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.069;0.07100000000000001" dur="18s" repeatCount="indefinite"/>f4e0a97b&#160;&#160;glm&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;GLM Coding Plan&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="206" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.077;0.079" dur="18s" repeatCount="indefinite"/>03bd6e5f&#160;&#160;kimi&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;Kimi K2 free&#160;&#160;&#160;&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="232" fill="#71717a" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.085;0.08700000000000001" dur="18s" repeatCount="indefinite"/>&#8230; 348 more providers</text>
</g><g opacity="0" font-family="Consolas, 'Courier New', monospace" font-size="17">
<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.333;0.34800000000000003;0.648;0.663;1" dur="18s" repeatCount="indefinite"/>
<text x="40" y="66" fill="#22c55e">$</text>
@@ -32,7 +32,7 @@
<animate attributeName="x" calcMode="discrete" values="64;84;105;125;146;166;186;207;227;227" keyTimes="0;0.678;0.684;0.690;0.696;0.702;0.708;0.714;0.720;1" dur="18s" repeatCount="indefinite"/>
<animate attributeName="opacity" values="0;0;1;0.2;1;0.2;1;0;0" keyTimes="0;0.677;0.678;0.688;0.698;0.708;0.718;0.74;1" dur="18s" repeatCount="indefinite"/>
</rect>
<text x="40" y="100" font-weight="700" fill="#38bdf8" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.711;0.713" dur="18s" repeatCount="indefinite"/>OmniRoute Health</text><text x="40" y="128" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.719;0.721" dur="18s" repeatCount="indefinite"/>&#160;&#160;Status: <tspan fill='#22c55e'>healthy</tspan>&#160;&#160;&#160;Uptime: 4d 12h 33m</text><text x="40" y="154" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.727;0.729" dur="18s" repeatCount="indefinite"/>&#160;&#160;Requests (24h): 18,412&#160;&#160;&#160;p95: 412ms</text><text x="40" y="180" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.735;0.737" dur="18s" repeatCount="indefinite"/>&#160;&#160;Breakers: <tspan fill='#22c55e'>&#9679; 24 closed</tspan>&#160;&#160;<tspan fill='#f59e0b'>&#9682; 1 half-open</tspan>&#160;&#160;<tspan fill='#ef4444'>&#9675; 0 open</tspan></text><text x="40" y="206" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.743;0.745" dur="18s" repeatCount="indefinite"/>&#160;&#160;Providers: 357 registered&#160;&#160;&#160;150+ free tiers</text><text x="40" y="232" fill="#71717a" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.751;0.753" dur="18s" repeatCount="indefinite"/>&#8230; live: /dashboard &#183; omniroute status</text>
<text x="40" y="100" font-weight="700" fill="#38bdf8" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.711;0.713" dur="18s" repeatCount="indefinite"/>OmniRoute Health</text><text x="40" y="128" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.719;0.721" dur="18s" repeatCount="indefinite"/>&#160;&#160;Status: <tspan fill='#22c55e'>healthy</tspan>&#160;&#160;&#160;Uptime: 4d 12h 33m</text><text x="40" y="154" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.727;0.729" dur="18s" repeatCount="indefinite"/>&#160;&#160;Requests (24h): 18,412&#160;&#160;&#160;p95: 412ms</text><text x="40" y="180" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.735;0.737" dur="18s" repeatCount="indefinite"/>&#160;&#160;Breakers: <tspan fill='#22c55e'>&#9679; 24 closed</tspan>&#160;&#160;<tspan fill='#f59e0b'>&#9682; 1 half-open</tspan>&#160;&#160;<tspan fill='#ef4444'>&#9675; 0 open</tspan></text><text x="40" y="206" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.743;0.745" dur="18s" repeatCount="indefinite"/>&#160;&#160;Providers: 350 registered&#160;&#160;&#160;90+ free tiers</text><text x="40" y="232" fill="#71717a" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.751;0.753" dur="18s" repeatCount="indefinite"/>&#8230; live: /dashboard &#183; omniroute status</text>
</g>
<path d="M 0 300 L 1200 300" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
<g clip-path="url(#tickerClip)"><g font-family="Consolas, 'Courier New', monospace" font-size="14" fill="#71717a">

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,4 +1,4 @@
<svg viewBox="0 0 1200 780" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Comparison table: OmniRoute versus 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute is the only one with the full set: 357 providers, 150+ free providers built-in, 19 routing strategies, 12-engine token compression, a built-in MCP server with 110 tools, A2A protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, desktop/Termux/PWA, 43 UI locales and 100% MIT self-hosted. 9router has free providers, RTK compression and translation but no MCP, A2A, memory, guardrails, cloud agents or stealth. OpenRouter is a hosted SaaS with 400+ models, guardrails and a hosted MCP but is not self-hosted and lacks A2A, memory, cloud agents and stealth. CLIProxyAPI is a light OAuth proxy with two routing strategies. LiteLLM has 100+ providers, A2A and extensive guardrails but no memory, compression, free tier, stealth or cloud agents.">
<svg viewBox="0 0 1200 780" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Comparison table: OmniRoute versus 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute is the only one with the full set: 356 providers, 150+ free providers built-in, 19 routing strategies, 12-engine token compression, a built-in MCP server with 110 tools, A2A protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, desktop/Termux/PWA, 42 UI locales and 100% MIT self-hosted. 9router has free providers, RTK compression and translation but no MCP, A2A, memory, guardrails, cloud agents or stealth. OpenRouter is a hosted SaaS with 400+ models, guardrails and a hosted MCP but is not self-hosted and lacks A2A, memory, cloud agents and stealth. CLIProxyAPI is a light OAuth proxy with two routing strategies. LiteLLM has 100+ providers, A2A and extensive guardrails but no memory, compression, free tier, stealth or cloud agents.">
<desc>Static-header comparison table where each capability row fades in top to bottom; the OmniRoute column is highlighted and shows a check or a leading value in every row, while competitors show a mix of checks, partials and crosses.</desc>
<defs>
<pattern id="gC" width="32" height="32" patternUnits="userSpaceOnUse"><path d="M 32 0 L 0 0 0 32" fill="none" stroke="#ffffff" stroke-opacity="0.05" stroke-width="1"/></pattern>
@@ -23,7 +23,7 @@
<g font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif">
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="0.15s" fill="freeze"/>
<text x="44" y="196" font-size="14.5" fill="#c9d1d9">Providers</text>
<text x="440" y="196" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">357</text>
<text x="440" y="196" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">352</text>
<text x="604" y="196" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">40+</text>
<text x="760" y="196" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">400+*</text>
<text x="916" y="196" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">~5</text>
@@ -32,7 +32,7 @@
<rect x="36" y="212" width="1128" height="42" rx="6" fill="#ffffff" fill-opacity="0.02"/>
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="0.24s" fill="freeze"/>
<text x="44" y="238" font-size="14.5" fill="#c9d1d9">Free providers built-in</text>
<text x="440" y="238" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">150+</text>
<text x="440" y="238" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">90+</text>
<use href="#yes" x="604" y="233"/>
<use href="#mid" x="760" y="233"/>
<use href="#no" x="916" y="233"/>
@@ -117,7 +117,7 @@
<rect x="36" y="632" width="1128" height="42" rx="6" fill="#ffffff" fill-opacity="0.02"/>
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="1.14s" fill="freeze"/>
<text x="44" y="658" font-size="14.5" fill="#c9d1d9">i18n UI locales</text>
<text x="440" y="658" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">43</text>
<text x="440" y="658" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">42</text>
<text x="604" y="658" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">6</text>
<use href="#no" x="760" y="653"/>
<use href="#no" x="916" y="653"/>

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,5 +1,5 @@
<svg viewBox="0 0 1200 842" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OmniRoute free-tier budget: about 1.62 billion free tokens per month steady, up to about 2.25 billion in the first month with signup credits. The catalog contains 483 rows, 476 active and 7 discontinued, grouped into 35 recurring pool keys; 17 pools have a published positive monthly token budget and 18 have a zero, uncapped, or keyless budget. Honest pool-deduped math counts each shared free pool once; 13 providers carry a terms-of-service avoid flag. The 17 quantified pools plus five per-model Groq caps are Mistral 1 billion, Nara 210 million, LLM7 150 million, xKiro 150 million, Groq 30 million across five per-model caps, Cloudflare AI 30 million, API Airforce 24 million, Bluesminds 7.2 million, SambaNova 6 million, Arcee 4.8 million, Navy 4.5 million, BazaarLink 3.6 million, OpenRouter 1.2 million, Cohere 800 thousand, HuggingChat 500 thousand, Morph 400 thousand, Hugging Face 200 thousand, and Kiro 25 thousand. One-time signup credits add about 626 million. Uncapped providers and the OpenRouter top-up boost are shown separately so they do not inflate the headline. A further 6 million behind regional identity verification (ModelScope) is also shown apart. Live usage remains available at /dashboard/free-tiers.">
<desc>Pool-deduplicated chart of the 17 recurring free-token pools with positive published budgets (plus Groq's five per-model caps as one segment), plus signup credits and uncapped providers shown separately.</desc>
<svg viewBox="0 0 1200 842" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OmniRoute free-tier budget: about 1.47 billion free tokens per month steady, up to about 2.10 billion in the first month with signup credits. The catalog contains 444 rows, 437 active and 7 discontinued, grouped into 34 recurring pool keys; 16 pools have a published positive monthly token budget and 18 have a zero, uncapped, or keyless budget. Honest pool-deduped math counts each shared free pool once; 13 providers carry a terms-of-service avoid flag. The 16 quantified pools plus five per-model Groq caps are Mistral 1 billion, Nara 210 million, LLM7 150 million, Groq 30 million across five per-model caps, Cloudflare AI 30 million, API Airforce 24 million, Bluesminds 7.2 million, SambaNova 6 million, Arcee 4.8 million, Navy 4.5 million, BazaarLink 3.6 million, OpenRouter 1.2 million, Cohere 800 thousand, HuggingChat 500 thousand, Morph 400 thousand, Hugging Face 200 thousand, and Kiro 25 thousand. One-time signup credits add about 626 million. Uncapped providers and the OpenRouter top-up boost are shown separately so they do not inflate the headline. A further 6 million behind regional identity verification (ModelScope) is also shown apart. Live usage remains available at /dashboard/free-tiers.">
<desc>Pool-deduplicated chart of the 16 recurring free-token pools with positive published budgets (plus Groq's five per-model caps as one segment), plus signup credits and uncapped providers shown separately.</desc>
<defs>
<pattern id="gridPaperF" width="32" height="32" patternUnits="userSpaceOnUse">
<path d="M 32 0 L 0 0 0 32" fill="none" stroke="#ffffff" stroke-opacity="0.06" stroke-width="1"/>
@@ -61,10 +61,10 @@
<animate attributeName="opacity" values="0;1;1;0" dur="2.4s" begin="1.6s" repeatCount="indefinite"/>
</circle>
</g>
<text x="60" y="228" font-family="Consolas, 'Courier New', monospace" font-size="104" font-weight="800" fill="url(#gradBrandF)">~1.62B</text>
<text x="60" y="228" font-family="Consolas, 'Courier New', monospace" font-size="104" font-weight="800" fill="url(#gradBrandF)">~1.47B</text>
<text x="62" y="266" font-family="Consolas, 'Courier New', monospace" font-size="15" letter-spacing="3" font-weight="700" fill="#a1a1aa">FREE TOKENS / MONTH &#183; <tspan fill="#22c55e">STEADY</tspan></text>
<text x="62" y="298" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="16" fill="#F7F6FC">up to <tspan font-weight="800" fill="#22c55e">~2.25B</tspan> in your first month &#8212; signup credits</text>
<text x="62" y="326" font-family="Consolas, 'Courier New', monospace" font-size="12" fill="#71717a">documented free tiers &#183; <tspan fill="#8b5cf6">35 recurring pools</tspan> &#183; <tspan fill="#8b5cf6">483 catalog entries</tspan> &#183; one endpoint</text>
<text x="62" y="298" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="16" fill="#F7F6FC">up to <tspan font-weight="800" fill="#22c55e">~2.10B</tspan> in your first month &#8212; signup credits</text>
<text x="62" y="326" font-family="Consolas, 'Courier New', monospace" font-size="12" fill="#71717a">documented free tiers &#183; <tspan fill="#8b5cf6">34 recurring pools</tspan> &#183; <tspan fill="#8b5cf6">444 catalog entries</tspan> &#183; one endpoint</text>
<!-- ═══ Panel · The honest math ═══ -->
<rect x="680" y="84" width="460" height="216" rx="14" fill="#161b22" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
@@ -75,7 +75,7 @@
</line>
<text x="836" y="156" font-family="Consolas, 'Courier New', monospace" font-size="11.5" fill="#a1a1aa">every rate limit &#183; 24/7</text>
<text x="836" y="176" font-family="Consolas, 'Courier New', monospace" font-size="11.5" fill="#ef4444" opacity="0.85">we don't publish that</text>
<text x="704" y="240" font-family="Consolas, 'Courier New', monospace" font-size="34" font-weight="800" fill="#22c55e">~1.62B</text>
<text x="704" y="240" font-family="Consolas, 'Courier New', monospace" font-size="34" font-weight="800" fill="#22c55e">~1.47B</text>
<text x="836" y="224" font-family="Consolas, 'Courier New', monospace" font-size="11.5" fill="#a1a1aa">each shared free pool</text>
<text x="836" y="244" font-family="Consolas, 'Courier New', monospace" font-size="11.5" fill="#22c55e">counted once &#10003;</text>
<text x="704" y="280" font-family="Consolas, 'Courier New', monospace" font-size="12" fill="#f59e0b"><tspan font-weight="800">13 providers</tspan> ToS-flagged <tspan fill="#71717a">&#8212; we flag it &#183; you decide</tspan></text>
@@ -85,23 +85,22 @@
<g clip-path="url(#barShapeF)">
<rect x="60" y="372" width="1080" height="18" fill="#1c2230"/>
<g clip-path="url(#barRevF)">
<rect x="60.0" y="372" width="579.0" height="18" fill="#6c5ce7"/>
<rect x="640.0" y="372" width="127.6" height="18" fill="#00b894"/>
<rect x="768.6" y="372" width="93.3" height="18" fill="#0984e3"/>
<rect x="862.9" y="372" width="93.3" height="18" fill="#e17055"/>
<rect x="957.2" y="372" width="24.7" height="18" fill="#fdcb6e"/>
<rect x="982.9" y="372" width="24.7" height="18" fill="#e84393"/>
<rect x="1008.6" y="372" width="21.2" height="18" fill="#00cec9"/>
<rect x="1030.8" y="372" width="11.6" height="18" fill="#d63031"/>
<rect x="1043.4" y="372" width="10.9" height="18" fill="#a29bfe"/>
<rect x="1055.3" y="372" width="10.2" height="18" fill="#55efc4"/>
<rect x="1066.5" y="372" width="10.1" height="18" fill="#74b9ff"/>
<rect x="1077.6" y="372" width="9.6" height="18" fill="#ffeaa7"/>
<rect x="1088.2" y="372" width="8.2" height="18" fill="#fab1a0"/>
<rect x="1097.4" y="372" width="8.0" height="18" fill="#81ecec"/>
<rect x="1106.4" y="372" width="7.8" height="18" fill="#6c5ce7"/>
<rect x="1115.2" y="372" width="7.7" height="18" fill="#00b894"/>
<rect x="1123.9" y="372" width="7.6" height="18" fill="#0984e3"/>
<rect x="60.0" y="372" width="686.0" height="18" fill="#6c5ce7"/>
<rect x="747.0" y="372" width="139.7" height="18" fill="#00b894"/>
<rect x="887.7" y="372" width="99.8" height="18" fill="#0984e3"/>
<rect x="988.5" y="372" width="20.0" height="18" fill="#e17055"/>
<rect x="1009.5" y="372" width="20.0" height="18" fill="#e84393"/>
<rect x="1030.5" y="372" width="16.0" height="18" fill="#00cec9"/>
<rect x="1047.5" y="372" width="7.5" height="18" fill="#d63031"/>
<rect x="1056.0" y="372" width="7.5" height="18" fill="#a29bfe"/>
<rect x="1064.5" y="372" width="7.5" height="18" fill="#55efc4"/>
<rect x="1073.0" y="372" width="7.5" height="18" fill="#74b9ff"/>
<rect x="1081.5" y="372" width="7.5" height="18" fill="#ffeaa7"/>
<rect x="1090.0" y="372" width="7.5" height="18" fill="#fab1a0"/>
<rect x="1098.5" y="372" width="7.5" height="18" fill="#81ecec"/>
<rect x="1107.0" y="372" width="7.5" height="18" fill="#6c5ce7"/>
<rect x="1115.5" y="372" width="7.5" height="18" fill="#00b894"/>
<rect x="1124.0" y="372" width="7.5" height="18" fill="#0984e3"/>
<rect x="1132.5" y="372" width="7.5" height="18" fill="#e17055"/>
</g>
</g>
@@ -116,21 +115,20 @@
<circle cx="66" cy="452" r="5" fill="#6c5ce7"/><text x="78" y="456" fill="#c9d1d9">Mistral <tspan fill="#71717a">1.00B</tspan></text>
<circle cx="346" cy="452" r="5" fill="#00b894"/><text x="358" y="456" fill="#c9d1d9">Nara <tspan fill="#71717a">210M</tspan></text>
<circle cx="626" cy="452" r="5" fill="#0984e3"/><text x="638" y="456" fill="#c9d1d9">LLM7 <tspan fill="#71717a">150M</tspan></text>
<circle cx="906" cy="452" r="5" fill="#e17055"/><text x="918" y="456" fill="#c9d1d9">xKiro <tspan fill="#71717a">150M</tspan></text>
<circle cx="66" cy="482" r="5" fill="#fdcb6e"/><text x="78" y="486" fill="#c9d1d9">Groq <tspan fill="#71717a">30M &#183; 5 caps</tspan></text>
<circle cx="346" cy="482" r="5" fill="#e84393"/><text x="358" y="486" fill="#c9d1d9">Cloudflare AI <tspan fill="#71717a">30M</tspan></text>
<circle cx="626" cy="482" r="5" fill="#00cec9"/><text x="638" y="486" fill="#c9d1d9">API Airforce <tspan fill="#71717a">24M</tspan></text>
<circle cx="906" cy="482" r="5" fill="#d63031"/><text x="918" y="486" fill="#c9d1d9">Bluesminds <tspan fill="#71717a">7.2M</tspan></text>
<circle cx="66" cy="512" r="5" fill="#a29bfe"/><text x="78" y="516" fill="#c9d1d9">SambaNova <tspan fill="#71717a">6M</tspan></text>
<circle cx="346" cy="512" r="5" fill="#55efc4"/><text x="358" y="516" fill="#c9d1d9">Arcee <tspan fill="#71717a">4.8M</tspan></text>
<circle cx="626" cy="512" r="5" fill="#74b9ff"/><text x="638" y="516" fill="#c9d1d9">Navy <tspan fill="#71717a">4.5M</tspan></text>
<circle cx="906" cy="512" r="5" fill="#ffeaa7"/><text x="918" y="516" fill="#c9d1d9">BazaarLink <tspan fill="#71717a">3.6M</tspan></text>
<circle cx="66" cy="542" r="5" fill="#fab1a0"/><text x="78" y="546" fill="#c9d1d9">OpenRouter <tspan fill="#71717a">1.2M</tspan></text>
<circle cx="346" cy="542" r="5" fill="#81ecec"/><text x="358" y="546" fill="#c9d1d9">Cohere <tspan fill="#71717a">800K</tspan></text>
<circle cx="626" cy="542" r="5" fill="#6c5ce7"/><text x="638" y="546" fill="#c9d1d9">HuggingChat <tspan fill="#71717a">500K</tspan></text>
<circle cx="906" cy="542" r="5" fill="#00b894"/><text x="918" y="546" fill="#c9d1d9">Morph <tspan fill="#71717a">400K</tspan></text>
<circle cx="66" cy="572" r="5" fill="#0984e3"/><text x="78" y="576" fill="#c9d1d9">Hugging Face <tspan fill="#71717a">200K</tspan></text>
<circle cx="346" cy="572" r="5" fill="#e17055"/><text x="358" y="576" fill="#c9d1d9">Kiro <tspan fill="#71717a">25K</tspan></text>
<circle cx="906" cy="452" r="5" fill="#e17055"/><text x="918" y="456" fill="#c9d1d9">Groq <tspan fill="#71717a">30M &#183; 5 caps</tspan></text>
<circle cx="66" cy="482" r="5" fill="#e84393"/><text x="78" y="486" fill="#c9d1d9">Cloudflare AI <tspan fill="#71717a">30M</tspan></text>
<circle cx="346" cy="482" r="5" fill="#00cec9"/><text x="358" y="486" fill="#c9d1d9">API Airforce <tspan fill="#71717a">24M</tspan></text>
<circle cx="626" cy="482" r="5" fill="#d63031"/><text x="638" y="486" fill="#c9d1d9">Bluesminds <tspan fill="#71717a">7.2M</tspan></text>
<circle cx="906" cy="482" r="5" fill="#a29bfe"/><text x="918" y="486" fill="#c9d1d9">SambaNova <tspan fill="#71717a">6M</tspan></text>
<circle cx="66" cy="512" r="5" fill="#55efc4"/><text x="78" y="516" fill="#c9d1d9">Arcee <tspan fill="#71717a">4.8M</tspan></text>
<circle cx="346" cy="512" r="5" fill="#74b9ff"/><text x="358" y="516" fill="#c9d1d9">Navy <tspan fill="#71717a">4.5M</tspan></text>
<circle cx="626" cy="512" r="5" fill="#ffeaa7"/><text x="638" y="516" fill="#c9d1d9">BazaarLink <tspan fill="#71717a">3.6M</tspan></text>
<circle cx="906" cy="512" r="5" fill="#fab1a0"/><text x="918" y="516" fill="#c9d1d9">OpenRouter <tspan fill="#71717a">1.2M</tspan></text>
<circle cx="66" cy="542" r="5" fill="#81ecec"/><text x="78" y="546" fill="#c9d1d9">Cohere <tspan fill="#71717a">800K</tspan></text>
<circle cx="346" cy="542" r="5" fill="#6c5ce7"/><text x="358" y="546" fill="#c9d1d9">HuggingChat <tspan fill="#71717a">500K</tspan></text>
<circle cx="626" cy="542" r="5" fill="#00b894"/><text x="638" y="546" fill="#c9d1d9">Morph <tspan fill="#71717a">400K</tspan></text>
<circle cx="906" cy="542" r="5" fill="#0984e3"/><text x="918" y="546" fill="#c9d1d9">Hugging Face <tspan fill="#71717a">200K</tspan></text>
<circle cx="66" cy="572" r="5" fill="#e17055"/><text x="78" y="576" fill="#c9d1d9">Kiro <tspan fill="#71717a">25K</tspan></text>
</g>
<!-- ═══ First-month signup credits ═══ -->

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1,4 +1,4 @@
<svg viewBox="0 0 1200 540" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="The OmniRoute promise: one endpoint and 357 providers. Six pillars. Resilient fallback: automatic routing continues while another healthy target is available. Save up to 95 percent of eligible tokens: RTK plus Caveman stacked compression averages about 89 percent on tool-heavy sessions. Zero dollars to start: 150+ providers with a free tier and 53 recurring or keyless free-forever providers. Every tool works: 36 CLI and agent integration records, including Claude Code, Codex, Cursor, Cline, Copilot and Antigravity, through one config. One endpoint: OpenAI, Claude, Gemini and Responses API translation at /v1. Production controls: circuit breakers, TLS stealth, MCP with 110 tools, A2A, memory, guardrails, evals, and 39,000+ static test declarations across 5,100+ tracked test files.">
<svg viewBox="0 0 1200 540" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="The OmniRoute promise: one endpoint and 356 providers. Six pillars. Resilient fallback: automatic routing continues while another healthy target is available. Save up to 95 percent of eligible tokens: RTK plus Caveman stacked compression averages about 89 percent on tool-heavy sessions. Zero dollars to start: 150+ providers with a free tier and 52 recurring or keyless free-forever providers. Every tool works: 36 CLI and agent integration records, including Claude Code, Codex, Cursor, Cline, Copilot and Antigravity, through one config. One endpoint: OpenAI, Claude, Gemini and Responses API translation at /v1. Production controls: circuit breakers, TLS stealth, MCP with 110 tools, A2A, memory, guardrails, evals, and 39,000+ static test declarations across 5,100+ tracked test files.">
<desc>Animated promise card: six pillar tiles fade in in reading order, then a soft colored border highlight sweeps from tile to tile in a continuous cycle.</desc>
<defs>
<pattern id="gridPaperP" width="32" height="32" patternUnits="userSpaceOnUse">
@@ -21,7 +21,7 @@
<line x1="150" y1="53" x2="1160" y2="53" stroke="#232b38" stroke-width="1.5"/>
</g>
<g>
<text x="40" y="100" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="23" font-weight="600" fill="#c9d1d9">One endpoint. <tspan fill="#a78bfa" font-weight="800">357 providers.</tspan> Never stop building — OmniRoute picks <tspan fill="#7ee787" font-weight="700">the cheapest one that works</tspan>.</text>
<text x="40" y="100" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="23" font-weight="600" fill="#c9d1d9">One endpoint. <tspan fill="#a78bfa" font-weight="800">356 providers.</tspan> Never stop building — OmniRoute picks <tspan fill="#7ee787" font-weight="700">the cheapest one that works</tspan>.</text>
</g>
<g font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif">
@@ -38,7 +38,7 @@
<line x1="3.9" y1="3.9" x2="18.1" y2="18.1"/>
</g>
<text x="102" y="170" font-size="18" font-weight="800" fill="#74b9ff">Never hit limits</text>
<text x="66" y="204" font-size="13.5" fill="#a1a1aa">Auto-fallback across 357 providers in</text>
<text x="66" y="204" font-size="13.5" fill="#a1a1aa">Auto-fallback across 356 providers in</text>
<text x="66" y="226" font-size="13.5" fill="#a1a1aa">milliseconds. Quota out? The next provider</text>
<text x="66" y="248" font-size="13.5" fill="#a1a1aa">takes over while a healthy target remains.</text>
</g>
@@ -73,7 +73,7 @@
<circle cx="6.6" cy="6.6" r="1.4" fill="#fdcb6e" stroke="none"/>
</g>
<text x="862" y="170" font-size="18" font-weight="800" fill="#fdcb6e">$0 to start</text>
<text x="826" y="204" font-size="13.5" fill="#a1a1aa">150+ providers with a free tier, 53 free</text>
<text x="826" y="204" font-size="13.5" fill="#a1a1aa">150+ providers with a free tier, 52 free</text>
<text x="826" y="226" font-size="13.5" fill="#a1a1aa">forever — Qoder, Pollinations, Cloudflare,</text>
<text x="826" y="248" font-size="13.5" fill="#a1a1aa">SiliconFlow… No card needed.</text>
</g>

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -1,4 +1,4 @@
<svg viewBox="0 0 1200 548" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OmniRoute hero: Never stop coding. Every AI tool to 357 providers — 150+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot and Antigravity into free Claude, GPT and Gemini with auto-fallback. RTK + Caveman stacked compression saves 15 to 95 percent of tokens — about 89 percent average on tool-heavy sessions — so you never hit limits. Stats: 357 AI providers, 150+ free tiers, about 1.62B free tokens per month, 15 to 95 percent token savings, 19 routing strategies, zero dollars to start.">
<svg viewBox="0 0 1200 548" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OmniRoute hero: Never stop coding. Every AI tool to 356 providers — 150+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot and Antigravity into free Claude, GPT and Gemini with auto-fallback. RTK + Caveman stacked compression saves 15 to 95 percent of tokens — about 89 percent average on tool-heavy sessions — so you never hit limits. Stats: 356 AI providers, 150+ free tiers, about 1.47B free tokens per month, 15 to 95 percent token savings, 19 routing strategies, zero dollars to start.">
<desc>Animated hero card: a pulse travels the divider line and a compression bar demo repeatedly shrinks a prompt by up to 95 percent; all headline content is static and readable on the first frame.</desc>
<defs>
<pattern id="gridPaperH" width="32" height="32" patternUnits="userSpaceOnUse">
@@ -28,7 +28,7 @@
<text x="48" y="138" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="60" font-weight="800" fill="#e9edf3">Never stop coding<tspan fill="#a855f7">.</tspan></text>
<!-- subheadline -->
<text x="48" y="184" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="25" font-weight="600" fill="#c9d1d9">Every AI tool → <tspan fill="#a78bfa" font-weight="800">357 providers</tspan><tspan fill="#7ee787" font-weight="800">150+ free</tspan> — through one endpoint.</text>
<text x="48" y="184" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="25" font-weight="600" fill="#c9d1d9">Every AI tool → <tspan fill="#a78bfa" font-weight="800">356 providers</tspan><tspan fill="#7ee787" font-weight="800">150+ free</tspan> — through one endpoint.</text>
<!-- plug line -->
<text x="48" y="222" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="16.5" fill="#a1a1aa">Claude Code · Codex · Cursor · Cline · Copilot · Antigravity&#160;&#160;&#160;&#160;<tspan fill="#7ee787" font-weight="700">FREE</tspan> Claude / GPT / Gemini · auto-fallback</text>
@@ -66,13 +66,13 @@
<!-- stat chips -->
<g font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" text-anchor="middle">
<rect x="48" y="448" width="172" height="52" rx="12" fill="#161b22" stroke="#6c5ce7" stroke-opacity="0.55" stroke-width="1.5"/>
<text x="134" y="471" font-size="17" font-weight="800" fill="#a78bfa">357</text>
<text x="134" y="471" font-size="17" font-weight="800" fill="#a78bfa">352</text>
<text x="134" y="490" font-size="11" fill="#a1a1aa">AI PROVIDERS</text>
<rect x="234" y="448" width="172" height="52" rx="12" fill="#161b22" stroke="#22c55e" stroke-opacity="0.55" stroke-width="1.5"/>
<text x="320" y="471" font-size="17" font-weight="800" fill="#7ee787">150+</text>
<text x="320" y="471" font-size="17" font-weight="800" fill="#7ee787">90+</text>
<text x="320" y="490" font-size="11" fill="#a1a1aa">FREE TIERS</text>
<rect x="420" y="448" width="172" height="52" rx="12" fill="#161b22" stroke="#22c55e" stroke-opacity="0.55" stroke-width="1.5"/>
<text x="506" y="471" font-size="17" font-weight="800" fill="#7ee787">~1.62B</text>
<text x="506" y="471" font-size="17" font-weight="800" fill="#7ee787">~1.47B</text>
<text x="506" y="490" font-size="11" fill="#a1a1aa">FREE TOKENS / MO</text>
<rect x="606" y="448" width="172" height="52" rx="12" fill="#161b22" stroke="#e17055" stroke-opacity="0.55" stroke-width="1.5"/>
<text x="692" y="471" font-size="17" font-weight="800" fill="#e17055">1595%</text>

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -252,7 +252,7 @@ Supports **19 routing strategies** (see `src/shared/constants/routingStrategies.
### base.ts (1170 LOC)
The **abstract executor** that all 107 executors extend. It contains:
The **abstract executor** that all 101 executors extend. It contains:
- `buildUrl()` — default URL construction (subclasses override for custom)
- `buildHeaders()` — default headers (auth, content-type)

View File

@@ -1,6 +1,6 @@
# Free Tiers Guide: Understand and Combine Free AI Access
> **TL;DR**: OmniRoute registers 357 provider IDs, with **152 provider-catalog entries marked `hasFree`**. The stricter audited free-model catalog covers **35 recurring pool keys / 483 entries** (476 active + 7 discontinued). Connect several suitable providers for broader fallback capacity; every quota, approval rule, privacy policy, and paid-overage condition still applies.
> **TL;DR**: OmniRoute registers 352 provider IDs, with **152 provider-catalog entries marked `hasFree`**. The stricter audited free-model catalog covers **34 recurring pool keys / 444 entries** (437 active + 7 discontinued). Connect several suitable providers for broader fallback capacity; every quota, approval rule, privacy policy, and paid-overage condition still applies.
---
@@ -161,11 +161,11 @@ The live, pool-deduplicated catalog currently reports:
| Metric | Current audited value | Interpretation |
| ---------------------------------------------------- | -----------------------------------------------: | -------------------------------------------------------------------------------------------------------------------------- |
| Recurring quantified grant | **~1.62B tokens/month** | Shared pools counted once; excludes uncapped providers from the sum |
| First month with signup grants | **~2.25B tokens** | Recurring total plus one-time and recurring credits |
| Audited free-model inventory | **35 recurring pool keys / 483 catalog entries** | 476 active + 7 discontinued; distinct from the 357-provider catalog |
| Recurring/keyless free-forever providers represented | **53** | Unique providers across recurring daily/monthly/credit/uncapped and keyless catalog types, eligibility-gated rows excluded |
| Provider catalog entries marked `hasFree` | **152 / 357** | Broader provider metadata; not all have a quantifiable recurring quota |
| Recurring quantified grant | **~1.47B tokens/month** | Shared pools counted once; excludes uncapped providers from the sum |
| First month with signup grants | **~2.10B tokens** | Recurring total plus one-time and recurring credits |
| Audited free-model inventory | **34 recurring pool keys / 444 catalog entries** | 437 active + 7 discontinued; distinct from the 352-provider catalog |
| Recurring/keyless free-forever providers represented | **52** | Unique providers across recurring daily/monthly/credit/uncapped and keyless catalog types, eligibility-gated rows excluded |
| Provider catalog entries marked `hasFree` | **152 / 352** | Broader provider metadata; not all have a quantifiable recurring quota |
These values are computed from `open-sse/config/freeModelCatalog.ts`; see the
[Free Tiers Reference](../reference/FREE_TIERS.md) for pool deduplication, ToS flags,

View File

@@ -58,8 +58,8 @@ OmniRoute includes a service worker (`sw.js`) that provides intelligent caching:
| **App Shell** | Cache-first | `/`, `/offline`, manifest, and icons are pre-cached on install |
| **Static assets** (CSS, JS, images, fonts) | Network-first with cache fallback | Fetches fresh from the network; falls back to cache if offline |
| **Next.js bundles** (`/_next/`) | Network-first with cache update | Fetches from network and updates cache; serves cached version if offline |
| **Navigation requests** | Network-only with offline fallback | Always fetches from network; shows `/offline` page if network is unavailable |
| **API routes** (`/api/`, `/a2a`, `/dashboard/endpoint`) | Bypass (never cached) | Always goes directly to the server — never intercepted by the service worker |
| **Navigation requests** | Bypass (never intercepted) | Browser owns HTTP/3→HTTP/2 fallback; a dead QUIC socket must not become `Response.error()` |
| **API / dashboard routes** (`/api/`, `/a2a`, `/dashboard`) | Bypass (never cached) | Always goes directly to the server — never intercepted by the service worker |
### Offline Page
@@ -118,7 +118,7 @@ A vanilla service worker (no framework dependencies) with:
- **Install phase**: Pre-caches the app shell (root, offline page, manifest, icons)
- **Activate phase**: Cleans up old cache versions and claims all clients
- **Fetch phase**: Intelligent routing based on request type (navigation, static asset, API)
- **Cache versioning**: `omniroute-pwa-v2` — bump this to force a fresh cache on update
- **Cache versioning**: `omniroute-pwa-v3` — bump this to force a fresh cache on update
### Layout Metadata (`src/app/layout.tsx`)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -15,14 +15,14 @@ lastUpdated: 2026-09-03
| Metric | Tokens / month | Meaning |
| ------------------------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Documented recurring grant (steady)** | **~1.62B** | Free-tier **pools** (per-model catalog), each shared pool counted **once**. The live source behind `/api/free-tier/summary` and the dashboard's Free-Tier Budget page. **Use this number.** |
| **+ first month with signup credits** | **~2.25B** | Steady + one-time signup credits (Together $25, Z.AI 20M, DeepSeek 5M, …), deduped per account. **First month only** — does not recur. |
| **Documented recurring grant (steady)** | **~1.47B** | Free-tier **pools** (per-model catalog), each shared pool counted **once**. The live source behind `/api/free-tier/summary` and the dashboard's Free-Tier Budget page. **Use this number.** |
| **+ first month with signup credits** | **~2.10B** | Steady + one-time signup credits (Together $25, Z.AI 20M, DeepSeek 5M, …), deduped per account. **First month only** — does not recur. |
| **+ permanently free, no published cap** | _un-quantifiable_ | `siliconflow`, `glm-cn` (GLM-4-Flash), `tencent`, `baidu`, `kilo-gateway`, `opencode-zen`, `gemini`, `ollama-cloud` — real recurring access, rate/concurrency-limited, **no token cap to count**. Listed, never summed (counting them at `RPM×24/7` is the inflation we reject). |
| **+ deposit-unlock boost** | **+~24M** | A one-time **$10** OpenRouter top-up raises its free pool from 50 → 1000 req/day. Reported separately so it never inflates the steady number. |
| **+ behind a regional identity check** | **+~6M** | `modelscope` (Alibaba Cloud binding + mainland-China real-name verification). Real recurring quota, exposed as `gatedRecurringTokens` / `gatedProviders` and on the dashboard. Never summed into the headline: +~6M behind regional identity verification. |
| Theoretical ceiling (all rate limits, 24/7) | ~10B | Sum of every provider rate limit extrapolated to non-stop use. **Not a guarantee** — do not headline this. |
**Honest headline:** _OmniRoute aggregates **~1.62B documented free tokens per month** (up to ~2.25B in your first month with signup credits) across 35 free-tier pools — plus a long tail of permanently-free, no-cap providers — and RTK + Caveman compression (1595% token savings) stretches that further._
**Honest headline:** _OmniRoute aggregates **~1.47B documented free tokens per month** (up to ~2.10B in your first month with signup credits) across 34 free-tier pools — plus a long tail of permanently-free, no-cap providers — and RTK + Caveman compression (1595% token savings) stretches that further._
> **Why this dropped from the previous ~1.94B.** The 2026-06-17 refresh is an honesty correction, not a loss: `gemini` is now pool-deduped (was inflated by counting each Flash variant separately, 462M → 60M), `cloudflare-ai` corrected to its real 10k-Neurons/day (122M → 30M), `doubao` reclassified as a one-time signup credit (not recurring), and shut-down tiers removed (`chutes`/`phind`/`kluster` discontinued). Partly offset by `llm7` (correct 5M/day → 150M) and new free providers (Kilo, OpenCode Zen, Z.AI GLM-Flash).
>
@@ -30,13 +30,11 @@ lastUpdated: 2026-09-03
>
> **Updated on 2026-08-26 after retiring Felo Web:** Felo Web is excluded while its GPL-derived provenance/licensing remains on HOLD; the source reported 38 pool keys at the time. The pool count is live and CI-gated (`check:docs-counts` fails the build if the numbers above drift from `computeFreeModelTotals()`).
>
> **Re-audited on 2026-09-02 against the providers' own pages** (sources: the `// evidence:` comments next to each re-audited entry in `open-sse/config/freeModelCatalog.data.ts`): `gemini` and `ollama-cloud` no longer publish a token figure (Google removed the per-model free table on 2025-12-23; Ollama's Free plan is "starter usage credits") and are now listed as **uncapped**, never summed (80M); `groq` is five **per-model** 200K-TPD caps (6M each, +15M) with three retired IDs dropped; `nara` is one 7M/day bucket (+60M, 210M). `mistral`'s 1B is visible only in the account console — see _Evidence classes_ under Methodology.
> **Re-audited on 2026-09-02 against the providers' own pages** (sources: the `// evidence:` comments next to each re-audited entry in `open-sse/config/freeModelCatalog.data.ts`): `gemini` and `ollama-cloud` no longer publish a token figure (Google removed the per-model free table on 2025-12-23; Ollama's Free plan is "starter usage credits") and are now listed as **uncapped**, never summed (80M); `groq` is five **per-model** 200K-TPD caps (6M each, +15M) with three retired IDs dropped; `nara` is one 7M/day bucket (+60M, 210M). `mistral`'s 1B is visible only in the account console — see _Evidence classes_ under Methodology. The source reported 35 such keys at that point (3: `gemini` and `ollama-cloud` moved to the uncapped list, and Groq's per-model caps are not a shared pool).
>
> **Corrected on 2026-09-03 (#11773):** `cerebras` was reclassified from a 30M/mo recurring grant (old no-card 1M tokens/day trial) to a one-time $5 signup credit that requires a payment method. Same honesty rule as LongCat.
>
> **Plus xKiro (2026-09-03):** the new `xkiro-free` pool (150M/mo) adds a 35th recurring pool key. Felo Web stays excluded while its GPL-derived provenance/licensing remains on HOLD. The source now reports **35 recurring pool keys** and **~1.62B steady** — the live, CI-gated number (`check:docs-counts` fails the build if this drifts from `computeFreeModelTotals()`).
> **Corrected to ~1.47B on 2026-09-03 (#11773):** `cerebras` was reclassified from a 30M/mo recurring grant (old no-card 1M tokens/day trial) to a one-time $5 signup credit that requires a payment method. Same honesty rule as LongCat. The source now reports 34 recurring pool keys and ~1.47B steady.
Biggest **documented** contributors: `mistral` 1.00B, `nara` 210M, `llm7` 150M, `xkiro` 150M, `groq` 30M (five per-model caps), `cloudflare-ai` 30M, `api-airforce` 24M. (`longcat` is excluded — its 10M LongCat-2.0 grant is a one-time, KYC-gated signup credit, not a recurring monthly budget.)
Biggest **documented** contributors: `mistral` 1.00B, `nara` 210M, `llm7` 150M, `groq` 30M (five per-model caps), `cloudflare-ai` 30M, `api-airforce` 24M. (`longcat` is excluded — its 10M LongCat-2.0 grant is a one-time, KYC-gated signup credit, not a recurring monthly budget.)
> ⚠️ The theoretical ceiling (~10B) is inflated by rate-limit-only providers with **no published token cap** (`tencent`, `siliconflow`, `nvidia`, `baidu`, `glm-cn`, `sparkdesk`) whose figures would be `RPM/TPM × 24/7 × 30d` — a theoretical maximum no single account will sustain. They are **excluded** from the defensible number (shown in the "permanently free, no cap" row instead). This is the same inflation that makes competitors' multi-billion claims unreliable.
@@ -76,7 +74,7 @@ purpose.
## Methodology & caveats
- Numbers are **upper-bound estimates** from each provider's documented free-tier limits as of **2026-06-17**, gathered by web research. Free tiers change constantly — re-verify before relying on a figure.
- **What an entry actually vouches for.** No entry carries a per-row confidence rating, and the API serves none — treat every figure above as an estimate of the same, unstated quality. Two facts are different, because they are curated by hand rather than inferred: 44 entries carry an independently documented hard stop (39 of them the xKiro rows, which all share one daily allowance), and 13 entries carry a prompt-training disclosure. `hardStopGuaranteed` is set only when the provider's own terms say that exceeding the free allowance refuses the request rather than silently starting to bill you, with the source in a comment next to the entry; it is never defaulted to `true`, and an entry nobody has verified stays unset. So a missing hard-stop flag means "not established", not "known to bill you".
- **What an entry actually vouches for.** No entry carries a per-row confidence rating, and the API serves none — treat every figure above as an estimate of the same, unstated quality. Two facts are different, because they are curated by hand rather than inferred: 5 entries carry an independently documented hard stop, and 13 entries carry a prompt-training disclosure. `hardStopGuaranteed` is set only when the provider's own terms say that exceeding the free allowance refuses the request rather than silently starting to bill you, with the source in a comment next to the entry; it is never defaulted to `true`, and an entry nobody has verified stays unset. So a missing hard-stop flag means "not established", not "known to bill you".
- `estMonthlyFreeTokens` = recurring monthly tokens only. **One-time signup credits do not recur** and count as 0. Discontinued tiers are also 0.
- Daily token cap → `monthly = daily × 30`. Only RPD documented → `RPD × ~800 output tokens × 30`. Only RPM/TPM (no daily cap) → **uncapped** (see below).
- **Permanently free, but no published token cap** (`siliconflow`, `glm-cn`, `tencent`, `baidu`, `kilo-gateway`, `opencode-zen`, `gemini`, `ollama-cloud`): these are real recurring free access, rate/concurrency-limited. We classify them `recurring-uncapped` and **never sum them** — multiplying `RPM × 24/7 × 30d` would produce a fantasy ceiling (the inflation we reject). They are listed so you know they exist.
@@ -191,7 +189,6 @@ Most "free tokens per month" figures in this space are sums of per-model labels.
| `veoaifree-web` | caution | ToS explicitly bans automated bots or scripts running at "inhuman speeds" and prohibits copying the platform to create … |
| `vertex` | caution | Google Cloud Service Terms restrict resale to authorized resellers only (Section 14 requires a Reseller Agreement); a s… |
| `voyage-ai` | caution | ToS grants "personal, non-commercial use" for site content and prohibits credential/account sharing with third parties;… |
| `xkiro` | caution | ToS (2026-07-30) forbids reselling/redistributing the service and violating the upstream providers' terms; personal pro… |
| `360ai` | unknown | ToS for developer API not publicly accessible without registration; access requires application approval which implies … |
| `chutes` | unknown | ToS page exists at chutes.ai/terms but content was not accessible via fetch; no explicit proxy/resale clauses found in … |
| `freemodel-dev` | unknown | The Terms of Service page (freemodel.dev/terms) returned only a header with no readable content via WebFetch; no clause… |
@@ -214,7 +211,6 @@ Most "free tokens per month" figures in this space are sums of per-model labels.
| `mistral` | recurring | ~1.00B | — | caution | 5 |
| `nara` | recurring | ~210M | — | caution | 8 |
| `llm7` | recurring | ~150M | — | caution | 4 |
| `xkiro` | recurring | ~150M | — | caution | 39 |
| `longcat` | one-time | — | 10M | caution | 1 |
| `cerebras` | one-time | — | $5 credit | caution | 2 |
| `cloudflare-ai` | recurring | ~30M | — | caution | 9 |

View File

@@ -1,16 +1,16 @@
---
title: "Provider Reference"
version: 3.8.51
lastUpdated: 2026-09-04
lastUpdated: 2026-09-03
---
# 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-09-04
> **Last generated:** 2026-09-03
Total providers: **357**. See category breakdown below.
Total providers: **356**. See category breakdown below.
## Categories
@@ -118,7 +118,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
| `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) (239)
## API Key Providers (paid / paid-with-free-credits) (238)
| ID | Alias | Name | Tags | Website | Notes |
|----|-------|------|------|---------|-------|
@@ -354,7 +354,6 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
| `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) | — |
| `xkiro` | `xkiro` | xKiro | API key, aggregator | [link](https://xkiro.com) | Create a free account at xkiro.com and paste the key here (Bearer; x-api-key also accepted). |
| `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) | — |

View File

@@ -3,37 +3,36 @@
<rect x="16" y="16" width="868" height="564" rx="13" fill="#161b22" stroke="#30363d"/>
<text x="868" y="572" fill="#484f58" font-size="10.5" text-anchor="end">OmniRoute · /dashboard/free-tiers · preview mockup</text>
<text x="32" y="50" fill="#e6edf3" font-size="18" font-weight="700">Monthly free-token budget</text>
<text x="868" y="50" fill="#7d8590" font-size="13" text-anchor="end">22 free pools · 483 models · one endpoint</text>
<text x="868" y="50" fill="#7d8590" font-size="13" text-anchor="end">21 free pools · 444 models · one endpoint</text>
<text x="32" y="84" fill="#7d8590" font-size="11.5">Steady / month</text>
<text x="32" y="114" fill="#e6edf3" font-size="27" font-weight="800">~1.62B</text>
<text x="32" y="114" fill="#e6edf3" font-size="27" font-weight="800">~1.47B</text>
<text x="330" y="84" fill="#7d8590" font-size="11.5">First month (+ signup credits)</text>
<text x="330" y="114" fill="#3fb950" font-size="27" font-weight="800">~2.25B</text>
<text x="330" y="114" fill="#3fb950" font-size="27" font-weight="800">~2.10B</text>
<text x="700" y="84" fill="#7d8590" font-size="11.5">ToS-flagged (you decide)</text>
<text x="700" y="114" fill="#d29922" font-size="27" font-weight="800">13 providers</text>
<clipPath id="bar"><rect x="32" y="132" width="836" height="16" rx="8"/></clipPath>
<g clip-path="url(#bar)"><rect x="32" y="132" width="836" height="16" fill="#21262d"/>
<rect x="32.0" y="132" width="427.8" height="16" fill="#6c5ce7"/>
<rect x="459.2" y="132" width="95.8" height="16" fill="#00b894"/>
<rect x="554.4" y="132" width="70.6" height="16" fill="#0984e3"/>
<rect x="624.4" y="132" width="70.6" height="16" fill="#e17055"/>
<rect x="694.4" y="132" width="20.2" height="16" fill="#fdcb6e"/>
<rect x="714.0" y="132" width="17.7" height="16" fill="#e84393"/>
<rect x="731.1" y="132" width="10.6" height="16" fill="#00cec9"/>
<rect x="741.1" y="132" width="10.1" height="16" fill="#d63031"/>
<rect x="750.7" y="132" width="10.1" height="16" fill="#a29bfe"/>
<rect x="760.2" y="132" width="10.1" height="16" fill="#55efc4"/>
<rect x="769.7" y="132" width="10.1" height="16" fill="#74b9ff"/>
<rect x="779.2" y="132" width="10.1" height="16" fill="#ffeaa7"/>
<rect x="788.7" y="132" width="10.1" height="16" fill="#fab1a0"/>
<rect x="798.3" y="132" width="9.6" height="16" fill="#81ecec"/>
<rect x="807.3" y="132" width="9.5" height="16" fill="#6c5ce7"/>
<rect x="816.2" y="132" width="9.1" height="16" fill="#00b894"/>
<rect x="824.7" y="132" width="8.1" height="16" fill="#0984e3"/>
<rect x="832.2" y="132" width="7.9" height="16" fill="#e17055"/>
<rect x="839.5" y="132" width="7.8" height="16" fill="#fdcb6e"/>
<rect x="846.7" y="132" width="7.8" height="16" fill="#e84393"/>
<rect x="853.9" y="132" width="7.7" height="16" fill="#00cec9"/>
<rect x="861.0" y="132" width="7.6" height="16" fill="#d63031"/>
<rect x="32.0" y="132" width="475.3" height="16" fill="#6c5ce7"/>
<rect x="506.7" y="132" width="105.8" height="16" fill="#00b894"/>
<rect x="611.9" y="132" width="77.8" height="16" fill="#0984e3"/>
<rect x="689.0" y="132" width="21.6" height="16" fill="#e17055"/>
<rect x="710.1" y="132" width="18.8" height="16" fill="#fdcb6e"/>
<rect x="728.3" y="132" width="11.0" height="16" fill="#e84393"/>
<rect x="738.7" y="132" width="10.4" height="16" fill="#00cec9"/>
<rect x="748.5" y="132" width="10.4" height="16" fill="#d63031"/>
<rect x="758.3" y="132" width="10.4" height="16" fill="#a29bfe"/>
<rect x="768.1" y="132" width="10.4" height="16" fill="#55efc4"/>
<rect x="777.9" y="132" width="10.4" height="16" fill="#74b9ff"/>
<rect x="787.7" y="132" width="10.4" height="16" fill="#ffeaa7"/>
<rect x="797.5" y="132" width="9.8" height="16" fill="#fab1a0"/>
<rect x="806.8" y="132" width="9.7" height="16" fill="#81ecec"/>
<rect x="815.9" y="132" width="9.3" height="16" fill="#6c5ce7"/>
<rect x="824.5" y="132" width="8.2" height="16" fill="#00b894"/>
<rect x="832.1" y="132" width="8.0" height="16" fill="#0984e3"/>
<rect x="839.5" y="132" width="7.8" height="16" fill="#e17055"/>
<rect x="846.7" y="132" width="7.8" height="16" fill="#fdcb6e"/>
<rect x="853.9" y="132" width="7.7" height="16" fill="#e84393"/>
<rect x="861.0" y="132" width="7.6" height="16" fill="#00cec9"/>
</g>
<text x="32" y="172" fill="#7d8590" font-size="12">Each segment = one free pool · widths floored so every provider shows · honest numbers in the grid.</text>
<circle cx="37" cy="196" r="5" fill="#6c5ce7"/>
@@ -43,43 +42,41 @@
<circle cx="463" cy="196" r="5" fill="#0984e3"/>
<text x="474" y="200" fill="#c9d1d9" font-size="12.5">GPT-4o mini <tspan fill="#7d8590">150M</tspan></text>
<circle cx="676" cy="196" r="5" fill="#e17055"/>
<text x="687" y="200" fill="#c9d1d9" font-size="12.5">Qwen3 VL Plus <tspan fill="#7d8590">150M</tspan></text>
<text x="687" y="200" fill="#c9d1d9" font-size="12.5">Llama 3.3 70B <tspan fill="#7d8590">30M</tspan></text>
<circle cx="37" cy="226" r="5" fill="#fdcb6e"/>
<text x="48" y="230" fill="#c9d1d9" font-size="12.5">Llama 3.3 70B <tspan fill="#7d8590">30M</tspan></text>
<text x="48" y="230" fill="#c9d1d9" font-size="12.5">Grok-3 <tspan fill="#7d8590">24M</tspan></text>
<circle cx="250" cy="226" r="5" fill="#e84393"/>
<text x="261" y="230" fill="#c9d1d9" font-size="12.5">Grok-3 <tspan fill="#7d8590">24M</tspan></text>
<text x="261" y="230" fill="#c9d1d9" font-size="12.5">GPT-4o <tspan fill="#7d8590">7M</tspan></text>
<circle cx="463" cy="226" r="5" fill="#00cec9"/>
<text x="474" y="230" fill="#c9d1d9" font-size="12.5">GPT-4o <tspan fill="#7d8590">7M</tspan></text>
<text x="474" y="230" fill="#c9d1d9" font-size="12.5">GPT-OSS 120B <tspan fill="#7d8590">6M</tspan></text>
<circle cx="676" cy="226" r="5" fill="#d63031"/>
<text x="687" y="230" fill="#c9d1d9" font-size="12.5">GPT-OSS 120B <tspan fill="#7d8590">6M</tspan></text>
<text x="687" y="230" fill="#c9d1d9" font-size="12.5">GPT-OSS 20B <tspan fill="#7d8590">6M</tspan></text>
<circle cx="37" cy="256" r="5" fill="#a29bfe"/>
<text x="48" y="260" fill="#c9d1d9" font-size="12.5">GPT-OSS 20B <tspan fill="#7d8590">6M</tspan></text>
<text x="48" y="260" fill="#c9d1d9" font-size="12.5">GPT-OSS Safeguard 20B <tspan fill="#7d8590">6M</tspan></text>
<circle cx="250" cy="256" r="5" fill="#55efc4"/>
<text x="261" y="260" fill="#c9d1d9" font-size="12.5">GPT-OSS Safeguard 20B <tspan fill="#7d8590">6M</tspan></text>
<text x="261" y="260" fill="#c9d1d9" font-size="12.5">Qwen3.6 27B <tspan fill="#7d8590">6M</tspan></text>
<circle cx="463" cy="256" r="5" fill="#74b9ff"/>
<text x="474" y="260" fill="#c9d1d9" font-size="12.5">Qwen3.6 27B <tspan fill="#7d8590">6M</tspan></text>
<text x="474" y="260" fill="#c9d1d9" font-size="12.5">Qwen3.8 27B <tspan fill="#7d8590">6M</tspan></text>
<circle cx="676" cy="256" r="5" fill="#ffeaa7"/>
<text x="687" y="260" fill="#c9d1d9" font-size="12.5">Qwen3.8 27B <tspan fill="#7d8590">6M</tspan></text>
<text x="687" y="260" fill="#c9d1d9" font-size="12.5">MiniMax-M2.7 <tspan fill="#7d8590">6M</tspan></text>
<circle cx="37" cy="286" r="5" fill="#fab1a0"/>
<text x="48" y="290" fill="#c9d1d9" font-size="12.5">MiniMax-M2.7 <tspan fill="#7d8590">6M</tspan></text>
<text x="48" y="290" fill="#c9d1d9" font-size="12.5">Arcee Trinity Large Prev <tspan fill="#7d8590">5M</tspan></text>
<circle cx="250" cy="286" r="5" fill="#81ecec"/>
<text x="261" y="290" fill="#c9d1d9" font-size="12.5">Arcee Trinity Large Prev <tspan fill="#7d8590">5M</tspan></text>
<text x="261" y="290" fill="#c9d1d9" font-size="12.5">NavyAI free pool <tspan fill="#7d8590">5M</tspan></text>
<circle cx="463" cy="286" r="5" fill="#6c5ce7"/>
<text x="474" y="290" fill="#c9d1d9" font-size="12.5">NavyAI free pool <tspan fill="#7d8590">5M</tspan></text>
<text x="474" y="290" fill="#c9d1d9" font-size="12.5">Auto Free <tspan fill="#7d8590">4M</tspan></text>
<circle cx="676" cy="286" r="5" fill="#00b894"/>
<text x="687" y="290" fill="#c9d1d9" font-size="12.5">Auto Free <tspan fill="#7d8590">4M</tspan></text>
<text x="687" y="290" fill="#c9d1d9" font-size="12.5">Auto <tspan fill="#7d8590">1M</tspan></text>
<circle cx="37" cy="316" r="5" fill="#0984e3"/>
<text x="48" y="320" fill="#c9d1d9" font-size="12.5">Auto <tspan fill="#7d8590">1M</tspan></text>
<text x="48" y="320" fill="#c9d1d9" font-size="12.5">Command A Reasoning <tspan fill="#7d8590">800K</tspan></text>
<circle cx="250" cy="316" r="5" fill="#e17055"/>
<text x="261" y="320" fill="#c9d1d9" font-size="12.5">Command A Reasoning <tspan fill="#7d8590">800K</tspan></text>
<text x="261" y="320" fill="#c9d1d9" font-size="12.5">ERNIE 4.5 VL 424B A47B B <tspan fill="#7d8590">500K</tspan></text>
<circle cx="463" cy="316" r="5" fill="#fdcb6e"/>
<text x="474" y="320" fill="#c9d1d9" font-size="12.5">ERNIE 4.5 VL 424B A47B B <tspan fill="#7d8590">500K</tspan></text>
<text x="474" y="320" fill="#c9d1d9" font-size="12.5">morph-v3-large <tspan fill="#7d8590">400K</tspan></text>
<circle cx="676" cy="316" r="5" fill="#e84393"/>
<text x="687" y="320" fill="#c9d1d9" font-size="12.5">morph-v3-large <tspan fill="#7d8590">400K</tspan></text>
<text x="687" y="320" fill="#c9d1d9" font-size="12.5">Llama 3.1 8B <tspan fill="#7d8590">200K</tspan></text>
<circle cx="37" cy="346" r="5" fill="#00cec9"/>
<text x="48" y="350" fill="#c9d1d9" font-size="12.5">Llama 3.1 8B <tspan fill="#7d8590">200K</tspan></text>
<circle cx="250" cy="346" r="5" fill="#d63031"/>
<text x="261" y="350" fill="#c9d1d9" font-size="12.5">Claude Sonnet 4.5 <tspan fill="#7d8590">25K</tspan></text>
<text x="48" y="350" fill="#c9d1d9" font-size="12.5">Claude Sonnet 4.5 <tspan fill="#7d8590">25K</tspan></text>
<line x1="32" y1="386" x2="868" y2="386" stroke="#30363d"/>
<text x="32" y="412" fill="#3fb950" font-size="13" font-weight="700">+ First month: one-time signup credits (~626M)</text>
<rect x="32" y="421" width="90" height="22" rx="11" fill="#13311f" stroke="#238636"/>

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@@ -1,6 +1,6 @@
# OmniRoute
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -277,7 +277,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **357 AI providers** with automatic format translation
- **356 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -506,52 +506,4 @@ export const FREE_MODEL_BUDGETS: FreeModelBudget[] = [
{ provider: "nara", modelId: "mistral-medium-3-5", displayName: "Mistral Medium 3.5", monthlyTokens: 210000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "nara-free", tos: "caution" },
{ provider: "nara", modelId: "qwen3.8-27b", displayName: "Qwen3.8 27B", monthlyTokens: 210000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "nara-free", tos: "caution" },
{ provider: "nara", modelId: "stepfun-3.7-flash", displayName: "StepFun 3.7 Flash", monthlyTokens: 210000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "nara-free", tos: "caution" },
// evidence: public-page https://xkiro.com/ (2026-09-02) — Free plan: "$0 / month · Free forever · Free tokens
// 5M / day · Access to 40+ free models · No credit card"; https://docs.xkiro.com/models/tiers/ — "free — any
// account — Callable on every plan, including the free one, within a daily token allowance". One allowance per
// account ⇒ single pool: 5M × 30 = 150M. Rows = the 40 `access_tier: "free"` models on the public
// GET https://api.xkiro.com/v1/models minus openai/gpt-5.3-codex-spark (undeclared provenance).
// hardStopGuaranteed: https://docs.xkiro.com/api/rate-limits/ — "Past it, free-model requests are blocked with a
// 429 until the daily reset" + "No credit card" (page header) / "no card required" (Free plan card).
// tos: caution — ToS (2026-07-30) forbids reselling the service and violating the upstream providers' terms;
// personal proxy use is not addressed.
{ provider: "xkiro", modelId: "qwen/qwen3-vl-plus:free", displayName: "Qwen3 VL Plus (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "sensenova/sensenova-6.8-flash-lite", displayName: "SenseNova 6.8 Flash-Lite (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "minimax/minimax-m2.7:free", displayName: "MiniMax M2.7 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "minimax/minimax-m2.7-highspeed:free", displayName: "MiniMax M2.7 Highspeed (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "minimax/minimax-m2.5:free", displayName: "MiniMax M2.5 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "minimax/minimax-m3:free", displayName: "MiniMax M3 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "minimax/minimax-m2.1-highspeed:free", displayName: "MiniMax M2.1 Highspeed (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "minimax/minimax-m2.5-highspeed:free", displayName: "MiniMax M2.5 Highspeed (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "minimax/minimax-m2.1:free", displayName: "MiniMax M2.1 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "minimax/minimax-m2:free", displayName: "MiniMax M2 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "qwen/qwen3.5-flash:free", displayName: "Qwen3.5 Flash (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "qwen/qwen3.6-plus:free", displayName: "Qwen3.6 Plus (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "qwen/qwen3.5-397b-a17b:free", displayName: "Qwen3.5 397B A17B (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "qwen/qwen3.5-omni-flash:free", displayName: "Qwen3.5 Omni Flash (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "mistralai/mistral-large-2512", displayName: "Mistral Large 3 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "mistralai/mistral-medium-3.5", displayName: "Mistral Medium 3.5 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "mistralai/mistral-small-2603", displayName: "Mistral Small 4 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "mistralai/codestral-2508", displayName: "Codestral (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "mistralai/devstral-medium", displayName: "Devstral 2 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "mistralai/ministral-8b", displayName: "Ministral 3 8B (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "mistralai/ministral-3b", displayName: "Ministral 3 3B (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "deepseek/deepseek-v4-pro", displayName: "DeepSeek V4 Pro (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "deepseek/deepseek-v3.2", displayName: "DeepSeek V3.2 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "deepseek/deepseek-chat-v3.1", displayName: "DeepSeek V3.1 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "qwen/qwen3.7-plus:free", displayName: "Qwen3.7 Plus (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "mistralai/ministral-14b", displayName: "Ministral 3 14B (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "qwen/qwen3.6-max-preview:free", displayName: "Qwen3.6 Max Preview (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "qwen/qwen3.5-plus:free", displayName: "Qwen3.5 Plus (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "qwen/qwen3.5-omni-plus:free", displayName: "Qwen3.5 Omni Plus (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "deepseek/deepseek-v4-flash", displayName: "DeepSeek V4 Flash (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "sensenova/sensenova-6.7-flash-lite", displayName: "SenseNova 6.7 Flash-Lite (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "qwen/qwen3.8-max:free", displayName: "Qwen3.8 Max (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "qwen/qwen3.7-max:free", displayName: "Qwen3.7 Max (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "qwen/qwen3.6-27b:free", displayName: "Qwen3.6 27B (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "qwen/qwen3.6-35b-a3b:free", displayName: "Qwen3.6 35B A3B (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "qwen/qwen3-max:free", displayName: "Qwen3 Max (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "qwen/qwen3-coder-plus:free", displayName: "Qwen3 Coder Plus (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "qwen/qwen-plus-2025-07-28:free", displayName: "Qwen Plus 0728 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
{ provider: "xkiro", modelId: "qwen/qwen3-omni-flash:free", displayName: "Qwen3 Omni Flash (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
];

View File

@@ -230,7 +230,6 @@ import { x5labProvider } from "./registry/x5lab/index.ts";
import { kenariProvider } from "./registry/kenari/index.ts";
import { navyProvider } from "./registry/navy/index.ts";
import { naraProvider } from "./registry/nara/index.ts";
import { xkiroProvider } from "./registry/xkiro/index.ts";
import { opperProvider } from "./registry/opper/index.ts";
import { requestyProvider } from "./registry/requesty/index.ts";
import { sealionProvider } from "./registry/sealion/index.ts";
@@ -505,7 +504,6 @@ export const REGISTRY: Record<string, RegistryEntry> = {
kenari: kenariProvider,
navy: navyProvider,
nara: naraProvider,
xkiro: xkiroProvider,
opper: opperProvider,
requesty: requestyProvider,
sealion: sealionProvider,

View File

@@ -25,5 +25,5 @@ export const agyProvider: RegistryEntry = {
},
models: [...AGY_PUBLIC_MODELS],
passthroughModels: true,
liveCatalogAuthoritative: false,
liveCatalogAuthoritative: true,
};

View File

@@ -25,5 +25,5 @@ export const antigravityProvider: RegistryEntry = {
},
models: [...ANTIGRAVITY_PUBLIC_MODELS],
passthroughModels: true,
liveCatalogAuthoritative: false,
liveCatalogAuthoritative: true,
};

View File

@@ -1,271 +0,0 @@
import type { RegistryEntry } from "../../shared.ts";
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
/**
* xKiro — OpenAI-compatible gateway (xkiro.com). Free plan: 5M tokens/day per
* account across the models tagged `access_tier: "free"` on the public
* `GET https://api.xkiro.com/v1/models` (2026-09-02: 40 rows; context lengths and
* capabilities below come from that endpoint). `openai/gpt-5.3-codex-spark` is
* listed free upstream but its provenance is undeclared, so it is not pinned.
* Bearer auth; `GET /v1/usage` answers 401 without a valid key.
*/
export const xkiroProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
id: "xkiro",
baseUrl: "https://api.xkiro.com/v1/chat/completions",
models: [
{
id: "qwen/qwen3-vl-plus:free",
name: "Qwen3 VL Plus",
contextLength: 262144,
toolCalling: true,
supportsVision: true,
},
{
id: "sensenova/sensenova-6.8-flash-lite",
name: "SenseNova 6.8 Flash-Lite",
contextLength: 262144,
toolCalling: true,
supportsVision: true,
},
{
id: "minimax/minimax-m2.7:free",
name: "MiniMax M2.7",
contextLength: 204800,
toolCalling: true,
},
{
id: "minimax/minimax-m2.7-highspeed:free",
name: "MiniMax M2.7 Highspeed",
contextLength: 204800,
toolCalling: true,
},
{
id: "minimax/minimax-m2.5:free",
name: "MiniMax M2.5",
contextLength: 204800,
toolCalling: true,
},
{
id: "minimax/minimax-m3:free",
name: "MiniMax M3",
contextLength: 1000000,
toolCalling: true,
supportsVision: true,
},
{
id: "minimax/minimax-m2.1-highspeed:free",
name: "MiniMax M2.1 Highspeed",
contextLength: 204800,
toolCalling: true,
},
{
id: "minimax/minimax-m2.5-highspeed:free",
name: "MiniMax M2.5 Highspeed",
contextLength: 204800,
toolCalling: true,
},
{
id: "minimax/minimax-m2.1:free",
name: "MiniMax M2.1",
contextLength: 204800,
toolCalling: true,
},
{ id: "minimax/minimax-m2:free", name: "MiniMax M2", contextLength: 204800, toolCalling: true },
{
id: "qwen/qwen3.5-flash:free",
name: "Qwen3.5 Flash",
contextLength: 1000000,
toolCalling: true,
supportsVision: true,
},
{
id: "qwen/qwen3.6-plus:free",
name: "Qwen3.6 Plus",
contextLength: 1000000,
toolCalling: true,
supportsVision: true,
},
{
id: "qwen/qwen3.5-397b-a17b:free",
name: "Qwen3.5 397B A17B",
contextLength: 262144,
toolCalling: true,
supportsVision: true,
},
{
id: "qwen/qwen3.5-omni-flash:free",
name: "Qwen3.5 Omni Flash",
contextLength: 262144,
toolCalling: true,
supportsVision: true,
},
{
id: "mistralai/mistral-large-2512",
name: "Mistral Large 3",
contextLength: 256000,
toolCalling: true,
supportsVision: true,
supportsReasoning: false,
},
{
id: "mistralai/mistral-medium-3.5",
name: "Mistral Medium 3.5",
contextLength: 256000,
toolCalling: true,
supportsVision: true,
},
{
id: "mistralai/mistral-small-2603",
name: "Mistral Small 4",
contextLength: 256000,
toolCalling: true,
supportsVision: true,
},
{
id: "mistralai/codestral-2508",
name: "Codestral",
contextLength: 256000,
toolCalling: true,
supportsReasoning: false,
},
{
id: "mistralai/devstral-medium",
name: "Devstral 2",
contextLength: 256000,
toolCalling: true,
supportsReasoning: false,
},
{
id: "mistralai/ministral-8b",
name: "Ministral 3 8B",
contextLength: 256000,
toolCalling: true,
supportsVision: true,
supportsReasoning: false,
},
{
id: "mistralai/ministral-3b",
name: "Ministral 3 3B",
contextLength: 128000,
toolCalling: true,
supportsVision: true,
supportsReasoning: false,
},
{
id: "deepseek/deepseek-v4-pro",
name: "DeepSeek V4 Pro",
contextLength: 1048576,
toolCalling: true,
},
{
id: "deepseek/deepseek-v3.2",
name: "DeepSeek V3.2",
contextLength: 131072,
toolCalling: true,
},
{
id: "deepseek/deepseek-chat-v3.1",
name: "DeepSeek V3.1",
contextLength: 163840,
toolCalling: true,
},
{
id: "qwen/qwen3.7-plus:free",
name: "Qwen3.7 Plus",
contextLength: 1000000,
toolCalling: true,
supportsVision: true,
},
{
id: "mistralai/ministral-14b",
name: "Ministral 3 14B",
contextLength: 256000,
toolCalling: true,
supportsVision: true,
supportsReasoning: false,
},
{
id: "qwen/qwen3.6-max-preview:free",
name: "Qwen3.6 Max Preview",
contextLength: 262144,
toolCalling: true,
},
{
id: "qwen/qwen3.5-plus:free",
name: "Qwen3.5 Plus",
contextLength: 1000000,
toolCalling: true,
supportsVision: true,
},
{
id: "qwen/qwen3.5-omni-plus:free",
name: "Qwen3.5 Omni Plus",
contextLength: 262144,
toolCalling: true,
supportsVision: true,
},
{
id: "deepseek/deepseek-v4-flash",
name: "DeepSeek V4 Flash",
contextLength: 1048576,
toolCalling: true,
},
{
id: "sensenova/sensenova-6.7-flash-lite",
name: "SenseNova 6.7 Flash-Lite",
contextLength: 262144,
toolCalling: true,
supportsVision: true,
},
{
id: "qwen/qwen3.8-max:free",
name: "Qwen3.8 Max",
contextLength: 1000000,
toolCalling: true,
supportsVision: true,
},
{ id: "qwen/qwen3.7-max:free", name: "Qwen3.7 Max", contextLength: 1000000, toolCalling: true },
{
id: "qwen/qwen3.6-27b:free",
name: "Qwen3.6 27B",
contextLength: 262144,
toolCalling: true,
supportsVision: true,
},
{
id: "qwen/qwen3.6-35b-a3b:free",
name: "Qwen3.6 35B A3B",
contextLength: 262144,
toolCalling: true,
supportsVision: true,
},
{
id: "qwen/qwen3-max:free",
name: "Qwen3 Max",
contextLength: 262144,
toolCalling: true,
supportsVision: true,
},
{
id: "qwen/qwen3-coder-plus:free",
name: "Qwen3 Coder Plus",
contextLength: 1048576,
toolCalling: true,
supportsVision: true,
},
{
id: "qwen/qwen-plus-2025-07-28:free",
name: "Qwen Plus 0728",
contextLength: 131072,
toolCalling: true,
supportsVision: true,
},
{
id: "qwen/qwen3-omni-flash:free",
name: "Qwen3 Omni Flash",
contextLength: 262144,
toolCalling: true,
supportsVision: true,
},
],
});

View File

@@ -223,8 +223,8 @@ export function translateSseResponse(
suppressThinkClose: boolean = false
): Response {
if (!response.body) return response;
// GLM is a high-throughput provider — use a larger stream buffer (64KB) to
// keep provider → client pacing ahead of the model's token emission rate.
// Helper has 15 parameters; a 16th positional (65536) was a TS2554 and
// never reached TransformStream. highWaterMark stays at the helper default.
const transform = createSSETransformStreamWithLogger(
FORMATS.CLAUDE,
FORMATS.OPENAI,
@@ -238,10 +238,7 @@ export function translateSseResponse(
null,
null,
false,
suppressThinkClose,
undefined,
undefined,
65536
suppressThinkClose
);
const headers = cloneHeaders(response.headers);
headers.set("content-type", "text/event-stream");

File diff suppressed because it is too large Load Diff

View File

@@ -23,6 +23,36 @@ export function projectFailureUsageErrorCode(opts: {
return errorBody.error.code || String(opts.statusCode);
}
export interface FailureUsageAggregate {
prompt_tokens?: number;
completion_tokens?: number;
cache_read_input_tokens?: number;
cache_creation_input_tokens?: number;
reasoning_tokens?: number;
}
export function toFailureUsageAggregate(
usage:
| {
prompt_tokens?: number;
completion_tokens?: number;
cache_read_input_tokens?: number;
cache_creation_input_tokens?: number;
reasoning_tokens?: number;
}
| null
| undefined
): FailureUsageAggregate | undefined {
if (!usage) return undefined;
return {
prompt_tokens: usage.prompt_tokens,
completion_tokens: usage.completion_tokens,
cache_read_input_tokens: usage.cache_read_input_tokens,
cache_creation_input_tokens: usage.cache_creation_input_tokens,
reasoning_tokens: usage.reasoning_tokens,
};
}
export function buildFailureUsageRecord(opts: {
provider: string | null | undefined;
model: string | null | undefined;
@@ -35,11 +65,18 @@ export function buildFailureUsageRecord(opts: {
errorCode: string | null | undefined;
latencyMs: number;
endpoint?: string | null | undefined;
aggregate?: FailureUsageAggregate | null;
}) {
return {
provider: opts.provider || "unknown",
model: opts.model || "unknown",
tokens: { input: 0, output: 0, cacheRead: 0, cacheCreation: 0, reasoning: 0 },
tokens: {
input: opts.aggregate?.prompt_tokens ?? 0,
output: opts.aggregate?.completion_tokens ?? 0,
cacheRead: opts.aggregate?.cache_read_input_tokens ?? 0,
cacheCreation: opts.aggregate?.cache_creation_input_tokens ?? 0,
reasoning: opts.aggregate?.reasoning_tokens ?? 0,
},
status: String(opts.statusCode),
success: false,
latencyMs: opts.latencyMs,

View File

@@ -5,7 +5,7 @@ import {
toMemoryRetrievalConfig,
} from "@/lib/memory/settings";
import { injectMemory, shouldInjectMemory } from "@/lib/memory/injection";
import { injectSkills } from "@/lib/skills/injection";
import { injectSkillsWithMetadata } from "@/lib/skills/injection";
import { buildMemoryToolsForProvider } from "@/lib/skills/memoryBuiltins";
import { skillRegistry } from "@/lib/skills/registry";
import { FORMATS } from "../../translator/formats.ts";
@@ -13,6 +13,13 @@ import { detectCachingContext } from "../../services/compression/cachingAware.ts
type MemorySkillsLogger = { debug?: (...args: unknown[]) => void } | null | undefined;
export interface MemorySkillsInjectionResult {
body: Record<string, unknown>;
memorySettings: { enabled: boolean; skillsEnabled: boolean; maxTokens: number } | null;
builtinToolNames: string[];
injectedCustomSkillNames: string[];
}
function getToolName(tool: unknown): string {
if (!tool || typeof tool !== "object") return "";
const r = tool as Record<string, unknown>;
@@ -60,11 +67,14 @@ export async function injectMemoryAndSkills({
targetFormat: string;
backgroundReason: string | null;
log: MemorySkillsLogger;
}) {
}): Promise<MemorySkillsInjectionResult> {
const memorySettings = memoryOwnerId
? await getMemorySettings().catch(() => DEFAULT_MEMORY_SETTINGS)
: null;
const builtinOwnerSet: string[] = [];
const injectedCustomSkillNames: string[] = [];
if (
memoryOwnerId &&
memorySettings &&
@@ -178,34 +188,48 @@ export async function injectMemoryAndSkills({
return [];
})
);
const memoryTools = buildMemoryToolsForProvider(
const newMemoryTools = buildMemoryToolsForProvider(
getSkillsProviderForFormat(sourceFormat)
).filter((tool) => {
const record = tool as Record<string, unknown>;
const name = (record.function as Record<string, unknown> | undefined)?.name ?? record.name;
return typeof name === "string" && !existingToolNames.has(name);
});
if (memoryTools.length > 0) {
if (newMemoryTools.length > 0) {
body = {
...body,
tools: [...existingTools, ...memoryTools],
tools: [...existingTools, ...newMemoryTools],
};
// Track the names of newly injected memory tools for the owner set.
builtinOwnerSet.push(
...newMemoryTools
.map((tool) => {
const record = tool as Record<string, unknown>;
const name =
(record.function as Record<string, unknown> | undefined)?.name ?? record.name;
return typeof name === "string" ? name : "";
})
.filter(Boolean)
);
log?.debug?.(
"MEMORY",
`Injected ${memoryTools.length} memory tool(s) for key=${memoryOwnerId}`
`Injected ${newMemoryTools.length} memory tool(s) for key=${memoryOwnerId}`
);
}
}
if (memoryOwnerId && memorySettings?.skillsEnabled) {
if (memoryOwnerId && memorySettings?.skillsEnabled && body.stream !== true) {
// Ensure the registry cache is warm before listing: on a cold/fresh
// process skills that exist only in the DB would be missed (false
// negative -> silent skip). loadFromDatabase() is a no-op when the cache
// is already warm (TTL = 60 s), so repeated calls are cheap. Mirrors the
// pattern in src/lib/skills/interception.ts (#2815).
// Memory builtins and registered Skills are only executed by the
// non-streaming server-owned tool loop; stream clients execute tools
// client-side, so we skip injection for stream requests.
await skillRegistry.loadFromDatabase(memoryOwnerId);
const existingTools = Array.isArray(body.tools) ? body.tools : [];
const mergedTools = injectSkills({
const { tools: mergedTools, injectedNames } = injectSkillsWithMetadata({
provider: getSkillsProviderForFormat(sourceFormat),
existingTools,
apiKeyId: memoryOwnerId,
@@ -225,6 +249,7 @@ export async function injectMemoryAndSkills({
...body,
tools: mergedTools,
};
injectedCustomSkillNames.push(...injectedNames);
log?.debug?.("SKILLS", `Injected ${mergedTools.length - existingTools.length} skills`);
}
}
@@ -236,5 +261,45 @@ export async function injectMemoryAndSkills({
};
}
return { body, memorySettings };
return { body, memorySettings, builtinToolNames: builtinOwnerSet, injectedCustomSkillNames };
}
interface FallbackPlan {
enabled: boolean;
toolName: string | null;
convertedToolCount: number;
}
/**
* Pure helper: merge web-search/web-fetch fallback tool names into the
* builtin owner set. Adds a name only when plan.enabled===true,
* plan.convertedToolCount>0, plan.toolName is non-null, and that name
* did not already exist in the pre-conversion client tools (builtinToolNames)
* OR in the original client tool names captured before fallback injection.
* Does not mutate its input; returns a new result.
*/
export function mergeInjectedFallbackOwnerNames(
injectionResult: { builtinToolNames: string[] },
plans: FallbackPlan[],
preConversionClientToolNames?: string[]
): { builtinToolNames: string[] } {
const existing = new Set(injectionResult.builtinToolNames);
if (preConversionClientToolNames) {
for (const name of preConversionClientToolNames) {
existing.add(name);
}
}
const extraNames: string[] = [];
for (const plan of plans) {
if (
plan.enabled &&
plan.convertedToolCount > 0 &&
plan.toolName &&
!existing.has(plan.toolName)
) {
extraNames.push(plan.toolName);
existing.add(plan.toolName);
}
}
return { builtinToolNames: [...injectionResult.builtinToolNames, ...extraNames] };
}

View File

@@ -0,0 +1,159 @@
/**
* Client translation for non-streaming responses.
* Extracted from chatCore.ts (lines ~5098-5195) by symbol boundaries.
*
* Handles: translate, tool-name restore, finish-reason normalization, sanitize,
* reasoning replay capture, and client usage buffer application.
*
* Phase distinction:
* - "final": applies applyClientUsageBuffer (normalizes visible usage fields)
* - "intermediate": skips usage buffer (raw usage preserved for aggregation)
*/
import type {
NonStreamingClientTranslateInput,
NonStreamingClientTranslateResult,
} from "@/lib/skills/toolLoopTypes.ts";
import { needsTranslation } from "../../translator/index.ts";
import { FORMATS } from "../../translator/formats.ts";
import { translateNonStreamingResponse } from "../responseTranslator.ts";
import { extractToolSchemaMap } from "../../translator/response/openai-responses/toolSchemas.ts";
import { stripMarkdownCodeFence } from "../../utils/aiSdkCompat.ts";
import { normalizeOpenAIToolFinishReasons } from "./passthroughToolNames.ts";
import {
cacheReasoningFromAssistantMessage,
requiresReasoningReplay,
} from "../../services/reasoningCache.ts";
import {
sanitizeOpenAIResponse,
sanitizeResponsesApiResponse,
shouldParseTextualReasoningTags,
} from "../responseSanitizer.ts";
import { isStripReasoningRequested } from "./headers.ts";
import { applyClientUsageBuffer } from "./clientUsageBuffer.ts";
export type { NonStreamingClientTranslateInput, NonStreamingClientTranslateResult };
/**
* Translate a non-streaming provider response to the client's expected format.
*
* All of: translate, tool-name identity restore, finish-reason normalization,
* and sanitize are applied every round (both intermediate and final).
* `applyClientUsageBuffer` is applied only for `phase === "final"`.
* Reasoning replay capture runs every round.
*/
export function translateNonStreamingClientResponse(
input: NonStreamingClientTranslateInput
): NonStreamingClientTranslateResult {
const {
responseBody,
responsePayloadFormat,
clientResponseFormat,
sourceFormat,
provider,
model,
requestBody,
responseToolNameMap,
requestToolIdentityMap,
reasoningCacheScope,
clientHeaders,
isClaudeCodeCompatible,
phase,
} = input;
// ── Extract tool schemas for schema-aware translation ──────────────────────
const finalBody = requestBody as Record<string, unknown> | null;
const responseToolSchemas = extractToolSchemaMap(finalBody || responseBody);
// ── Translate response to client's expected format ─────────────────────────
let translatedResponse = needsTranslation(responsePayloadFormat, clientResponseFormat)
? translateNonStreamingResponse(
responseBody,
responsePayloadFormat,
clientResponseFormat,
responseToolNameMap,
responseToolSchemas
)
: responseBody;
const responseForMemoryExtraction = translatedResponse;
// ── T26: Strip markdown code blocks if provider format is Claude ───────────
if (sourceFormat === "claude") {
if (typeof translatedResponse?.choices?.[0]?.message?.content === "string") {
translatedResponse.choices[0].message.content = stripMarkdownCodeFence(
translatedResponse.choices[0].message.content
) as string;
}
}
// ── T18: Normalize finish_reason to 'tool_calls' if tool calls present ─────
normalizeOpenAIToolFinishReasons(translatedResponse);
// ── Reasoning Replay Cache (#1628) ────────────────────────────────────────
// Capture reasoning_content from non-streaming responses with tool_calls
// so it can be replayed on subsequent turns.
try {
const cacheResponse = translatedResponse?.choices?.[0]
? translatedResponse
: needsTranslation(responsePayloadFormat, FORMATS.OPENAI)
? translateNonStreamingResponse(
responseBody,
responsePayloadFormat,
FORMATS.OPENAI,
responseToolNameMap,
responseToolSchemas
)
: responseBody;
const firstChoice = cacheResponse?.choices?.[0];
const msg = firstChoice?.message;
// Prefer explicit historyMessages (parent: translatedBody.messages). Do not
// overload requestBody — Responses-shaped finalBody has `input`, not `messages`.
const historyMessages = Array.isArray(input.historyMessages)
? input.historyMessages
: (finalBody as { messages?: unknown[] } | null | undefined)?.messages;
if (requiresReasoningReplay({ provider, model })) {
cacheReasoningFromAssistantMessage(msg, provider, model, {
scope: reasoningCacheScope,
historyMessages: Array.isArray(historyMessages) ? historyMessages : [],
});
}
} catch {
// Cache capture is non-critical — never block the response
}
// ── Sanitize response for SDK compatibility ────────────────────────────────
if (clientResponseFormat === FORMATS.OPENAI_RESPONSES) {
translatedResponse = sanitizeResponsesApiResponse(translatedResponse);
// Restore {namespace, name} on function_call items for round-trip closure (#7936)
const responseOutput = translatedResponse?.output;
if (requestToolIdentityMap && Array.isArray(responseOutput)) {
for (const item of responseOutput) {
if (item?.type !== "function_call") continue;
const identity = requestToolIdentityMap.get(item.name);
if (identity) {
item.namespace = identity.namespace;
item.name = identity.name;
}
}
}
} else if (clientResponseFormat === FORMATS.OPENAI) {
const stripReasoning = isStripReasoningRequested(clientHeaders ?? null);
translatedResponse = sanitizeOpenAIResponse(translatedResponse, {
stripReasoning,
parseTextualReasoningTags: shouldParseTextualReasoningTags(provider, model),
});
}
// ── Client usage buffer (#8331) ───────────────────────────────────────────
// Only apply for final phase; intermediate preserves raw usage for aggregation.
if (phase === "final") {
applyClientUsageBuffer(translatedResponse, finalBody || responseBody, clientResponseFormat, {
preserveContextBudgetInVisibleUsage: isClaudeCodeCompatible,
});
}
return {
response: translatedResponse,
responseForMemoryExtraction,
};
}

View File

@@ -0,0 +1,133 @@
/**
* Request-level finalization for non-streaming chat.
* Success and failure each write usage/cost/quota/attempt/pending once.
*/
import type {
ChatCoreErrorResult,
ProviderLegUsage,
ServerOwnedToolLoopResult,
} from "@/lib/skills/toolLoopTypes.ts";
import type { PersistAttemptLogsArgs } from "./attemptLogging.ts";
import { type FailureUsageAggregate, toFailureUsageAggregate } from "./failureUsage.ts";
export type NonStreamingFinalizationPlan =
| {
kind: "success";
usage: ProviderLegUsage | null;
totalCostUsd: number;
receiptCount: number;
}
| {
kind: "failure";
error: ChatCoreErrorResult;
usage: ProviderLegUsage | null;
totalCostUsd: number;
receiptCount: number;
};
export interface NonStreamingFinalizationDeps {
writeUsage: (plan: NonStreamingFinalizationPlan) => void | Promise<void>;
writeCost: (totalCostUsd: number) => void;
scheduleQuota: (plan: NonStreamingFinalizationPlan) => void | Promise<void>;
writeAttempt: (plan: NonStreamingFinalizationPlan) => void;
finalizePending: (plan: NonStreamingFinalizationPlan) => void;
}
function missingError(): ChatCoreErrorResult {
return {
success: false,
status: 500,
response: new Response(null, { status: 500 }),
error: "Missing tool-loop error result",
errorCode: "internal_error",
};
}
export function buildNonStreamingFinalizationPlan(
loop: ServerOwnedToolLoopResult
): NonStreamingFinalizationPlan {
const usage = loop.cumulativeUsage;
const totalCostUsd = loop.totalCostUsd;
const receiptCount = loop.receipts.length;
if (loop.kind === "error") {
return {
kind: "failure",
error: loop.errorResult ?? missingError(),
usage,
totalCostUsd,
receiptCount,
};
}
return {
kind: "success",
usage,
totalCostUsd,
receiptCount,
};
}
export async function finalizeNonStreamingRequest(
plan: NonStreamingFinalizationPlan,
deps: NonStreamingFinalizationDeps
): Promise<void> {
await deps.writeUsage(plan);
deps.writeCost(plan.totalCostUsd);
if (plan.kind === "success") {
await deps.scheduleQuota(plan);
}
deps.writeAttempt(plan);
deps.finalizePending(plan);
}
export async function finalizeToolLoopError(input: {
loop: ServerOwnedToolLoopResult;
model: string;
provider: string;
connectionId?: string;
providerRequest?: Record<string, unknown>;
persistFailureUsage: (
status: number,
errorCode: string,
usage?: FailureUsageAggregate | null
) => void;
persistAttemptLogs: (params: PersistAttemptLogsArgs) => void;
trackPendingRequest: (
model: string,
provider: string,
connectionId?: string,
isPending?: boolean
) => void;
}): Promise<ChatCoreErrorResult> {
const plan = buildNonStreamingFinalizationPlan(input.loop);
const err = plan.kind === "failure" ? plan.error : missingError();
await finalizeNonStreamingRequest(plan, {
writeUsage: () => {
input.persistFailureUsage(
err.status,
err.errorCode || `upstream_${err.status}`,
toFailureUsageAggregate(plan.usage)
);
},
writeCost: () => {},
scheduleQuota: () => {},
writeAttempt: () => {
input.persistAttemptLogs({
status: err.status,
error: err.error || "Provider request failed",
providerRequest: input.providerRequest,
clientResponse: {
error: {
message: err.error || "Provider request failed",
type: err.errorType || "api_error",
},
},
cacheSource: "upstream",
});
},
finalizePending: () => {
input.trackPendingRequest(input.model, input.provider, input.connectionId, false);
},
});
return err;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,449 @@
import type { ChatCoreErrorResult, ProviderLegUsage } from "@/lib/skills/toolLoopTypes.ts";
import type { getProviderCredentials } from "@/sse/services/auth.ts";
import type { updateFromHeaders, updateFromResponseBody } from "../../services/rateLimitManager.ts";
import type { writeTerminalStatus } from "@/shared/utils/terminalStatus.ts";
import type { updateProviderConnection } from "@/lib/db/providers.ts";
import type { lockModel, recordCoreOwnedAntigravityQuotaState } from "../../services/accountFallback.ts";
import { createErrorResult } from "../../utils/error.ts";
import { applyStatusRestatement } from "../../config/upstreamStatusRestatement.ts";
import { recoverAnthropicThinkingSignature } from "./thinkingSignatureRecovery.ts";
import { isModelUnavailableError, getNextFamilyFallback as defaultGetNextFamilyFallback } from "../../services/modelFamilyFallback.ts";
import { COOLDOWN_MS } from "../../config/errorConfig.ts";
import { normalizeHeaders } from "../../utils/headers.ts";
export interface ChatCoreExecutorResult {
response: Response;
url: string;
headers: Record<string, string>;
transformedBody: unknown;
transport?: string;
_executionCredentials?: Record<string, unknown>;
_accountSemaphoreRelease?: () => void;
}
export interface ProviderExecutionPolicy {
allowAccountRotation: boolean;
allowModelFallback: boolean;
expectedConnectionId?: string;
}
export type ProviderExecutionOutcome =
| {
kind: "response";
response: Response;
url: string;
headers: Record<string, string>;
transformedBody: unknown;
model: string;
connectionId: string;
}
| {
kind: "error";
result: ChatCoreErrorResult;
providerUsage: ProviderLegUsage | null;
model: string;
connectionId: string;
};
export interface PipelineTargetContext {
provider: string;
requestedModel: string;
sourceFormat: string;
targetFormat: string;
stream: boolean;
}
export interface PipelineConnectionContext {
initialConnectionId: string;
getCurrentConnectionId: () => string | undefined;
getCredentials: () => Record<string, unknown>;
replaceCredentials: (next: Record<string, unknown>) => void;
onCredentialsRefreshed: (next: Record<string, unknown>) => void | Promise<void>;
assertManagedLeaseFence: (connectionId: string) => void;
getProviderCredentials: typeof getProviderCredentials;
refreshCredentials?: (
credentials: Record<string, unknown>
) => Promise<Record<string, unknown> | null>;
}
export interface PipelineWireState {
body: Record<string, unknown>;
currentModel: string;
triedModels: Set<string>;
setBodyAndModel: (body: Record<string, unknown>, model: string) => void;
}
export interface PipelineStateHooks {
updatePendingStage: (stage: string, data?: Record<string, unknown>) => void;
recordRateLimitHeaders: typeof updateFromHeaders;
recordRateLimitBody: typeof updateFromResponseBody;
writeTerminalStatus: typeof writeTerminalStatus;
persistConnectionPatch: typeof updateProviderConnection;
setConnectionRateLimitedUntil: (
connectionId: string,
untilMs: number | null
) => void | Promise<void>;
lockModel: typeof lockModel;
recordAntigravityQuotaState: typeof recordCoreOwnedAntigravityQuotaState;
markAccountSemaphoreBlocked: (connectionId: string) => void;
isolateProbeFailures: () => boolean | Promise<boolean>;
onCodexScopeRateLimited?: (params: {
failedConnectionId: string;
model: string | null;
rateLimitedUntil: string;
credentials?: Record<string, unknown> | null;
}) => void | Promise<void>;
onClearSessionAffinity?: (params: { failedConnectionId: string }) => void | Promise<void>;
onAuditAccountRotation?: (params: {
action: "codex.account_rotation";
failedConnectionId: string;
newConnectionId: string;
attempt: number;
retryAfterMs: number | null;
}) => void | Promise<void>;
}
export interface ProviderExecutionPipelineInput {
policy: Readonly<ProviderExecutionPolicy>;
target: PipelineTargetContext;
connection: PipelineConnectionContext;
wire: PipelineWireState;
state: PipelineStateHooks;
sendProviderAttempt: (model: string, allowDedup: boolean) => Promise<ChatCoreExecutorResult>;
getNextFamilyFallback?: (
currentModel: string,
triedModels: Set<string>,
providerHint?: string | null
) => string | null;
}
const LEASE_MISMATCH_STATUS = 409;
const LEASE_MISMATCH_CODE = "LEASE_CONNECTION_MISMATCH";
function currentConnectionId(connection: PipelineConnectionContext): string {
return connection.getCurrentConnectionId() ?? connection.initialConnectionId;
}
function retryAfterMsFrom(attempt: ChatCoreExecutorResult): number | null {
// attempt.headers is the outbound request bag (BaseExecutor finalHeaders).
// Retry-After lives on the upstream Response — same source as the parent
// chatCore rotate path. normalizeHeaders lower-cases keys, so "Retry-After"
// is looked up as "retry-after"; it does not drop the field.
const raw = normalizeHeaders(attempt.response?.headers)["retry-after"];
if (raw == null || raw === "") return null;
const parsed = Number.parseFloat(String(raw));
if (!Number.isFinite(parsed) || parsed < 0) return null;
return parsed * 1000;
}
function leaseMismatch(model: string, connectionId: string): ProviderExecutionOutcome {
const result = createErrorResult(
LEASE_MISMATCH_STATUS,
"Managed lease connection mismatch",
null,
LEASE_MISMATCH_CODE,
"lease_error"
);
return {
kind: "error",
result: {
success: false,
status: result.status,
response: result.response,
error: result.error,
errorCode: LEASE_MISMATCH_CODE,
errorType: "lease_error",
},
providerUsage: null,
model,
connectionId,
};
}
async function toOutcome(
attempt: ChatCoreExecutorResult,
model: string,
connectionId: string,
provider: string
): Promise<ProviderExecutionOutcome> {
const status = attempt.response.status;
if (status >= 200 && status < 300) {
return {
kind: "response",
response: attempt.response,
url: attempt.url,
headers: attempt.headers,
transformedBody: attempt.transformedBody,
model,
connectionId,
};
}
let message = attempt.response.statusText || "upstream error";
let body: unknown = attempt.transformedBody;
try {
// clone() is the drain. sendProviderAttempt must not cancel() a streaming
// non-2xx body before we get here (BYOP 422 / Codex 429 Retry-After).
body = JSON.parse(await attempt.response.clone().text());
const err = (body as { error?: { message?: unknown } } | null)?.error;
if (err && typeof err.message === "string" && err.message) message = err.message;
} catch {
// keep statusText
}
const restatement = applyStatusRestatement({
provider,
status,
message,
body,
retryAfterMs: null,
});
const result = createErrorResult(
restatement.status,
message,
restatement.retryAfterMs
);
return {
kind: "error",
result: {
success: false,
status: result.status,
response: attempt.response,
error: result.error,
errorCode: result.errorCode,
errorType: result.errorType,
},
providerUsage: null,
model,
connectionId,
};
}
function assertLease(
policy: Readonly<ProviderExecutionPolicy>,
connection: PipelineConnectionContext,
model: string
): ProviderExecutionOutcome | null {
const expected = policy.expectedConnectionId;
if (!expected) return null;
const current = connection.getCurrentConnectionId();
if (current && current !== expected) {
return leaseMismatch(model, current);
}
return null;
}
function maxAttemptsFor(provider: string): number {
return provider === "codex" ? 3 : 1;
}
/**
* Shared first-send + provider recovery. Does not read a successful body.
* Account/model retries live here; sendProviderAttempt is one wire send.
*/
export async function runProviderExecutionPipeline(
input: ProviderExecutionPipelineInput
): Promise<ProviderExecutionOutcome> {
const { policy, target, connection, wire, state, sendProviderAttempt } = input;
const maxAttempts = maxAttemptsFor(target.provider);
const excludedIds: string[] = [];
let attempts = 0;
let lastAttempt: ChatCoreExecutorResult | null = null;
let antigravityByopRotationPending = false;
let authRefreshPending = false;
let authRefreshed = false;
let modelFallbackPending = false;
const resolveFamilyFallback = input.getNextFamilyFallback ?? defaultGetNextFamilyFallback;
while (
attempts < maxAttempts ||
antigravityByopRotationPending ||
authRefreshPending ||
modelFallbackPending
) {
antigravityByopRotationPending = false;
authRefreshPending = false;
modelFallbackPending = false;
const before = assertLease(policy, connection, wire.currentModel);
if (before) return before;
const attempt = await sendProviderAttempt(wire.currentModel, attempts === 0);
lastAttempt = attempt;
const after = assertLease(policy, connection, wire.currentModel);
if (after) return after;
const status = attempt.response.status;
if (status >= 200 && status < 300) {
return toOutcome(attempt, wire.currentModel, currentConnectionId(connection), target.provider);
}
const isolateProbe = await state.isolateProbeFailures();
const canRotateAccount = policy.allowAccountRotation && !isolateProbe;
if (
canRotateAccount &&
target.provider === "codex" &&
status === 429 &&
attempts < maxAttempts - 1
) {
const failedId = currentConnectionId(connection);
const retryAfterMs = retryAfterMsFrom(attempt);
if (failedId && !excludedIds.includes(failedId)) excludedIds.push(failedId);
if (failedId) {
await state.onCodexScopeRateLimited?.({
failedConnectionId: failedId,
model: wire.currentModel || target.requestedModel || null,
rateLimitedUntil: new Date(Date.now() + (retryAfterMs || 60_000)).toISOString(),
credentials: connection.getCredentials(),
});
await state.onClearSessionAffinity?.({ failedConnectionId: failedId });
}
const nextCreds = await connection
.getProviderCredentials("codex", null, null, wire.currentModel, {
excludeConnectionIds: [...excludedIds],
})
.catch(() => null);
if (nextCreds && !nextCreds.allRateLimited && nextCreds.connectionId) {
await state.onAuditAccountRotation?.({
action: "codex.account_rotation",
failedConnectionId: failedId,
newConnectionId: String(nextCreds.connectionId),
attempt: attempts + 1,
retryAfterMs,
});
connection.replaceCredentials(nextCreds as Record<string, unknown>);
attempts += 1;
continue;
}
}
if (canRotateAccount && target.provider === "antigravity" && status === 422) {
// Same drain as toOutcome: clone the Response. A prior body.cancel()
// makes this throw "Body has already been consumed" and skips rotate.
const byopBody = await attempt.response
.clone()
.text()
.catch(() => "");
if (byopBody.includes("gcp_project_required")) {
const failedId = currentConnectionId(connection);
if (failedId && !excludedIds.includes(failedId)) excludedIds.push(failedId);
if (failedId) {
await state.setConnectionRateLimitedUntil(
failedId,
Date.now() + (COOLDOWN_MS.gcpProjectRequired ?? 24 * 60 * 60 * 1000)
);
}
const nextCreds = await connection
.getProviderCredentials("antigravity", null, null, wire.currentModel, {
excludeConnectionIds: [...excludedIds],
})
.catch(() => null);
if (nextCreds && !nextCreds.allRateLimited && nextCreds.connectionId) {
connection.replaceCredentials(nextCreds as Record<string, unknown>);
antigravityByopRotationPending = true;
continue;
}
}
}
if (
!authRefreshed &&
(status === 401 || status === 403) &&
typeof connection.refreshCredentials === "function"
) {
const refreshed = await connection.refreshCredentials(connection.getCredentials());
if (refreshed && (refreshed.accessToken || refreshed.copilotToken)) {
connection.replaceCredentials({ ...connection.getCredentials(), ...refreshed });
await connection.onCredentialsRefreshed(refreshed);
authRefreshed = true;
authRefreshPending = true;
continue;
}
}
{
let signatureMessage = attempt.response.statusText || "upstream error";
try {
const parsed = JSON.parse(await attempt.response.clone().text()) as {
error?: { message?: unknown };
};
if (typeof parsed?.error?.message === "string" && parsed.error.message) {
signatureMessage = parsed.error.message;
}
} catch {
// keep statusText
}
const signatureRecovery = await recoverAnthropicThinkingSignature({
provider: target.provider,
statusCode: status,
message: signatureMessage,
body: wire.body,
execute: async (recoveryBody) => {
if (recoveryBody && typeof recoveryBody === "object" && !Array.isArray(recoveryBody)) {
wire.setBodyAndModel(recoveryBody as Record<string, unknown>, wire.currentModel);
}
return sendProviderAttempt(wire.currentModel, false);
},
parseError: async (response) => {
let message = response.statusText || "upstream error";
let responseBody: unknown = null;
try {
responseBody = JSON.parse(await response.clone().text());
const err = (responseBody as { error?: { message?: unknown } } | null)?.error;
if (typeof err?.message === "string" && err.message) message = err.message;
} catch {
// keep statusText
}
return {
statusCode: response.status,
message,
retryAfterMs: null,
responseBody,
};
},
});
if (signatureRecovery.attempted && signatureRecovery.succeeded && signatureRecovery.execution) {
lastAttempt = {
response: signatureRecovery.execution.response,
url: signatureRecovery.execution.url ?? attempt.url,
headers: (signatureRecovery.execution.headers as Record<string, string>) ?? attempt.headers,
transformedBody: signatureRecovery.execution.transformedBody ?? attempt.transformedBody,
};
return toOutcome(
lastAttempt,
wire.currentModel,
currentConnectionId(connection),
target.provider
);
}
}
if (policy.allowModelFallback) {
let fallbackMessage = attempt.response.statusText || "upstream error";
try {
const parsed = JSON.parse(await attempt.response.clone().text()) as {
error?: { message?: unknown };
};
if (typeof parsed?.error?.message === "string" && parsed.error.message) {
fallbackMessage = parsed.error.message;
}
} catch {
// keep statusText
}
if (isModelUnavailableError(status, fallbackMessage, target.provider)) {
const nextModel = resolveFamilyFallback(wire.currentModel, wire.triedModels, target.provider);
if (nextModel) {
wire.setBodyAndModel({ ...wire.body, model: nextModel }, nextModel);
modelFallbackPending = true;
continue;
}
}
}
return toOutcome(attempt, wire.currentModel, currentConnectionId(connection), target.provider);
}
if (lastAttempt) {
return toOutcome(lastAttempt, wire.currentModel, currentConnectionId(connection), target.provider);
}
return leaseMismatch(wire.currentModel, currentConnectionId(connection));
}

View File

@@ -0,0 +1,15 @@
import { FORMATS } from "../../translator/formats.ts";
export function shouldRunServerOwnedToolLoop(input: {
enabled: boolean;
stream: boolean;
isResponsesEndpoint: boolean;
sourceFormat: string;
}): boolean {
if (!input.enabled) return false;
if (input.stream) return false;
if (input.isResponsesEndpoint) return false;
if (input.sourceFormat === FORMATS.OPENAI) return true;
if (input.sourceFormat === FORMATS.CLAUDE) return true;
return false;
}

View File

@@ -0,0 +1,175 @@
import type {
ExecutionContext,
NonStreamingProviderLegResult,
ProviderLegUsage,
ServerOwnedToolLoopResult,
ToolCall,
} from "@/lib/skills/toolLoopTypes.ts";
import { executeServerOwned } from "@/lib/skills/interception";
import { runServerOwnedToolLoop, LOOP_BUDGET_MS } from "@/lib/skills/serverOwnedToolLoop.ts";
import { deriveToolRequestIdentity } from "@/lib/skills/stableJson.ts";
import { getIdempotencyKey } from "@/lib/idempotencyLayer";
import { runNonStreamingProviderLeg } from "./nonStreamingProviderLeg.ts";
import type { ProviderLegInput } from "./nonStreamingProviderLeg.ts";
import { shouldRunServerOwnedToolLoop } from "./serverOwnedToolLoopGate.ts";
import { FORMATS } from "../../translator/formats.ts";
export function derivePostInjectionRequestIdentity(input: {
apiKeyId: string;
headers: unknown;
skillRequestId: string;
postInjectionBody: Record<string, unknown>;
}): string {
const stableClientRequestId = getIdempotencyKey(input.headers as never);
return deriveToolRequestIdentity({
apiKeyId: input.apiKeyId,
stableClientRequestId,
skillRequestId: input.skillRequestId,
postInjectionBody: input.postInjectionBody,
});
}
export async function continueServerOwnedToolLoop(input: {
initialLeg: NonStreamingProviderLegResult & { kind: "ok" };
sourceBody: Record<string, unknown>;
sourceFormat: "openai" | "claude";
skillsModelId: string;
executionContext: ExecutionContext;
abortSignal?: AbortSignal;
deadlineAtMs: number;
expectedConnectionId?: string;
followUpLeg: (nextSourceBody: Record<string, unknown>) => Promise<NonStreamingProviderLegResult>;
executeServerOwned?: (
calls: ToolCall[],
context: ExecutionContext
) => Promise<import("@/lib/skills/toolLoopTypes.ts").ExecutedToolResult[]>;
}): Promise<ServerOwnedToolLoopResult> {
const runOwned = input.executeServerOwned ?? executeServerOwned;
return runServerOwnedToolLoop({
initialLeg: input.initialLeg,
sourceBody: input.sourceBody,
sourceFormat: input.sourceFormat,
skillsModelId: input.skillsModelId,
executionContext: input.executionContext,
abortSignal: input.abortSignal,
deadlineAtMs: input.deadlineAtMs,
executeServerOwned: (calls: ToolCall[], context: ExecutionContext) => runOwned(calls, context),
resumeUpstream: async (nextSourceBody, expectedConnectionId) => {
if (
expectedConnectionId &&
input.expectedConnectionId &&
expectedConnectionId !== input.expectedConnectionId
) {
return {
kind: "error",
result: {
success: false,
status: 409,
response: new Response(null, { status: 409 }),
error: "Follow-up connection mismatch",
errorCode: "LEASE_CONNECTION_MISMATCH",
},
receipt: input.initialLeg.receipt,
usage: null,
};
}
return input.followUpLeg(nextSourceBody);
},
});
}
export function followUpLegInput(
base: Omit<
ProviderLegInput,
"phase" | "allowAccountRotation" | "allowModelFallback" | "sourceBody"
>,
nextSourceBody: Record<string, unknown>,
expectedConnectionId?: string
): ProviderLegInput {
return {
...base,
phase: "follow-up",
sourceBody: nextSourceBody,
expectedConnectionId,
allowAccountRotation: false,
allowModelFallback: false,
};
}
export function mergeLoopIntoOkLeg(
leg: NonStreamingProviderLegResult & { kind: "ok" },
loop: ServerOwnedToolLoopResult
): NonStreamingProviderLegResult & { kind: "ok" } {
return {
...leg,
response: loop.response ?? leg.response,
responseForMemoryExtraction:
loop.responseForMemoryExtraction ?? leg.responseForMemoryExtraction,
providerBody: loop.finalProviderBody ?? leg.providerBody,
providerRequest: loop.finalProviderRequest ?? leg.providerRequest,
usage: loop.cumulativeUsage,
};
}
export type ToolLoopApplyResult =
| { kind: "skip" }
| {
kind: "ok";
leg: NonStreamingProviderLegResult & { kind: "ok" };
usage: ProviderLegUsage | null;
loop: ServerOwnedToolLoopResult;
}
| { kind: "error"; loop: ServerOwnedToolLoopResult };
export async function applyServerOwnedToolLoopIfNeeded(input: {
enabled: boolean;
stream: boolean;
isResponsesEndpoint: boolean;
sourceFormat: string;
initialLeg: NonStreamingProviderLegResult;
sourceBody: Record<string, unknown>;
skillsModelId: string;
executionContext: ExecutionContext;
abortSignal?: AbortSignal;
expectedConnectionId?: string;
followUpLeg: (nextSourceBody: Record<string, unknown>) => Promise<NonStreamingProviderLegResult>;
logReceipt: (receipt: ServerOwnedToolLoopResult["receipts"][number]) => void;
executeServerOwned?: (
calls: ToolCall[],
context: ExecutionContext
) => Promise<import("@/lib/skills/toolLoopTypes.ts").ExecutedToolResult[]>;
}): Promise<ToolLoopApplyResult> {
if (
input.initialLeg.kind !== "ok" ||
!shouldRunServerOwnedToolLoop({
enabled: input.enabled,
stream: input.stream,
isResponsesEndpoint: input.isResponsesEndpoint,
sourceFormat: input.sourceFormat,
})
) {
return { kind: "skip" };
}
const loop = await continueServerOwnedToolLoop({
initialLeg: input.initialLeg,
sourceBody: input.sourceBody,
sourceFormat: input.sourceFormat === FORMATS.CLAUDE ? "claude" : "openai",
skillsModelId: input.skillsModelId,
executionContext: input.executionContext,
abortSignal: input.abortSignal,
deadlineAtMs: Date.now() + LOOP_BUDGET_MS,
expectedConnectionId: input.expectedConnectionId,
followUpLeg: input.followUpLeg,
executeServerOwned: input.executeServerOwned,
});
for (const receipt of loop.receipts) input.logReceipt(receipt);
if (loop.kind === "error") return { kind: "error", loop };
return {
kind: "ok",
leg: mergeLoopIntoOkLeg(input.initialLeg, loop),
usage: loop.cumulativeUsage,
loop,
};
}
export { LOOP_BUDGET_MS, runNonStreamingProviderLeg };

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,7 @@ import {
} from "./promptCacheAffinity.ts";
import {
orderTargetsByHeadroom,
orderTargetsByQuotaWeighted,
orderTargetsByResetAwareQuota,
orderTargetsByResetWindow,
} from "./quotaStrategies.ts";
@@ -18,17 +19,19 @@ import {
sortTargetsByCost,
sortTargetsByUsage,
} from "./targetSorters.ts";
import { decrementInflight } from "./quotaShareInflight.ts";
import type { ComboLike, ComboLogger, ResolvedComboTarget } from "./types.ts";
/**
* Result of {@link applyStrategyOrdering}.
*
* `quotaShareRelease` carries the idempotent release for the in-flight slot that
* quota-share ordering reserves for its winner (#11371). It is non-null only when
* the `quota-share` strategy ran; every other strategy leaves it null. The caller
* MUST invoke it exactly once when the request settles — selection reserves the
* slot, so dropping the callback leaks the counter monotonically upward and
* degenerates P2C into "fewest lifetime dispatches".
* quota-share and quota-weighted reserve for their winner. quota-share reserves
* inside selectQuotaShareTarget; quota-weighted reserves inside the orderer so
* two in-process draws cannot both see inflight=0. Stickiness may then move [0];
* resolveComboTargetPipeline transfers the slot for both strategies. The caller
* MUST invoke the callback exactly once when the request settles — dropping it
* leaks the counter and degenerates later draws toward whoever looks idle.
*/
export interface ApplyStrategyOrderingResult {
orderedTargets: ResolvedComboTarget[];
@@ -239,6 +242,27 @@ export async function applyStrategyOrdering(
"COMBO",
`Headroom ordering: ${orderedTargets[0]?.modelStr}${orderedTargets[0]?.connectionId ? ` (${orderedTargets[0].connectionId})` : ""} has most free capacity`
);
} else if (strategy === "quota-weighted") {
orderedTargets = await orderTargetsByQuotaWeighted(
orderedTargets,
combo.name,
config,
log,
apiKeyAllowedConnections
);
const winnerId = orderedTargets[0]?.connectionId ?? "";
if (winnerId) {
let released = false;
quotaShareRelease = () => {
if (released) return;
released = true;
decrementInflight(winnerId);
};
}
log.info(
"COMBO",
`Quota-weighted ordering: ${orderedTargets[0]?.modelStr}${orderedTargets[0]?.connectionId ? ` (${orderedTargets[0].connectionId})` : ""} first`
);
} else if (strategy === "quota-share") {
// Internal quota-share combos (qtSd/): delegate to the dedicated module (DRR +
// P2C in-flight + per-model bucket gating + per-connection concurrency gating).

View File

@@ -0,0 +1,117 @@
/**
* Shared types for the handleComboChat attempt-loop split (ROADMAP 3.8.52).
*
* Mutable loop state lives on AttemptLoopState. Read-only dependencies live on
* AttemptLoopDeps. Do not merge the two into ComboContext.
*
* @internal — not part of the public combo.ts barrel.
*/
import type { PerTargetAdmissionHook } from "../admission/types.ts";
import type { ResilienceSettings } from "../../../src/lib/resilience/settings";
import type { ContextRelayConfig, UniversalHandoffConfig } from "../contextHandoff.ts";
import type { ComboErrorEntry } from "./comboErrorAggregation.ts";
import type { ResetWindowConfig } from "./quotaScoring.ts";
import type { ResponseValidationConfig } from "./responseValidation.ts";
import type { ApplyStickinessResult } from "./sessionStickiness.ts";
import type {
ComboLike,
ComboLogger,
ComboRetryAfter,
HandleSingleModel,
IsModelAvailable,
ResolvedComboTarget,
} from "./types.ts";
export type ExecuteTargetResult = { ok: boolean; response?: Response } | null;
export type AttemptLoopState = {
orderedTargets: ResolvedComboTarget[];
fallbackCount: number;
recordedAttempts: number;
comboErrors: ComboErrorEntry[];
lastError: string | null;
lastStatus: number | null;
earliestRetryAfter: ComboRetryAfter | null;
comboExpired: boolean;
exhaustedProviders: Set<string>;
exhaustedConnections: Set<string>;
transientRateLimitedProviders: Set<string>;
abortControllers: Map<number, AbortController>;
dispatchedTargets: Set<string>;
targetFailureTrust: Map<string, { observedFailure: boolean; allObservedFailuresQuota: boolean }>;
comboAttemptOrder: Array<{ provider: string; model: string }>;
skippedForCircuitOpen: boolean;
earliestCircuitOpenRetryMs: number;
/** Mutable attempt budget shared with dispatchWithCooldownRetry (Task 4). */
globalAttempts: number;
/** Quota-trust accumulators; persist across set retries and cooldown re-dispatch. */
observedFailure: boolean;
allObservedFailuresQuota: boolean;
observeFailure(quotaExhausted: boolean, targetExecutionKey?: string): void;
};
export type AttemptLoopDeps = {
strategy: string;
combo: ComboLike;
config: Record<string, unknown> & {
zeroLatencyOptimizationsEnabled?: boolean;
responseValidation?: ResponseValidationConfig | null;
failoverBeforeRetryExplicit?: boolean;
failoverBeforeRetry?: boolean;
predictiveTtftMs?: number;
fallbackCompressionMode?: string;
fallbackCompressionThreshold?: number;
retryDelayMs?: number;
fallbackDelayMs?: number;
maxGlobalAttempts?: unknown;
hedging?: boolean;
hedgeDelayMs?: unknown;
};
log: ComboLogger;
settings: Record<string, unknown> | null;
resilienceSettings: ResilienceSettings;
sticky: ApplyStickinessResult;
effectiveSessionId: string | null;
preScreenMap: Map<string, { profile?: unknown }>;
quotaCutoffResetWindowConfig: ResetWindowConfig;
maxRetries: number;
traceInvocationId: string;
clientRequestedStream: boolean;
handleSingleModelWithTimeout: HandleSingleModel;
isModelAvailable?: IsModelAvailable;
perTargetAdmission?: PerTargetAdmissionHook | null;
signal?: AbortSignal | null;
body: Record<string, unknown>;
startTime: number;
releaseStickyPinOnFailure: (
messageHash: string | null | undefined,
failedConnectionId: string | null | undefined
) => void;
clearStaleLKGP: (
comboName: string,
executionKey: string | undefined,
comboId: string | undefined,
log: ComboLogger,
tag: string
) => void;
/**
* Closed-over setup values from handleComboChatInner. Optional so Task 2
* gate tests keep compiling; attempt uses defaults when absent.
*/
clientManagedResponsesContext?: boolean;
reasoningTokenBufferEnabled?: boolean;
stickyWeightedLimit?: number;
getWeightedStepKeyForTarget?: (target: ResolvedComboTarget) => string | null;
universalHandoffConfig?: UniversalHandoffConfig;
relayOptions?: { sessionId?: string | null } | null;
relayConfig?: ContextRelayConfig | null;
};
export type GateDecision =
| { kind: "skip"; result: ExecuteTargetResult }
| {
kind: "proceed";
targetForAttempt: ResolvedComboTarget;
profile: unknown;
protectedPriorityTarget: boolean;
};

View File

@@ -0,0 +1,575 @@
/**
* Set-try + speculative dispatch loop for handleComboChatInner.
* Extracted from combo.ts dispatchWithCooldownRetry (#11804 finally lives here).
*
* @internal — not part of the public combo.ts barrel.
*/
import { formatRetryAfter, getModelLockoutInfo } from "../accountFallback.ts";
import {
errorResponse,
errorResponseWithComboDiagnostics,
unavailableResponse,
} from "../../utils/error.ts";
import type { ComboDiagnostics } from "../../utils/error.ts";
import { COMBO_FAILURE_THRESHOLD, recordComboFailure } from "./failureTracker.ts";
import { buildNoUpstreamResponseDiagnostics, buildRecoveryHint } from "./pinRecovery.ts";
import { formatExhaustedConnectionKey } from "./comboDiagFormat.ts";
import { recordComboRequest } from "../comboMetrics.ts";
import { notifyWebhookEvent } from "../../../src/lib/webhookDispatcher.ts";
import { parseModel } from "../model.ts";
import {
formatComboOutcomes,
buildRedactedSummary,
resolveComboTerminalStatus,
} from "./comboErrorAggregation.ts";
import {
resolveComboCooldownWaitDecision,
resolveCircuitOpenWaitDecision,
type ResolveComboCooldownDecisionResult,
} from "./comboCooldownRetry.ts";
import {
computeClosestRetryAfter,
waitForCooldownAwareRetry,
} from "../../../src/sse/services/cooldownAwareRetry.ts";
import { toRetryAfterDisplayValue } from "./validateQuality.ts";
import { finalizeComboTrace, finishComboTrace } from "./decisionTrace.ts";
import { isRetryAfterEligibleStatus } from "./unavailableRetryGate.ts";
import { withQuotaExhaustionClassification } from "./quotaExhaustion.ts";
import {
COMBO_LOOP_SAFETY_TIMEOUT_MS,
COMBO_SAFETY_DRAIN_MS,
resolveDelayMs,
} from "./comboPredicates.ts";
import { evaluateExecuteTargetGates } from "./executeTargetGates.ts";
import { executeTargetAttempt } from "./executeTargetAttempt.ts";
import type { AttemptLoopDeps, AttemptLoopState, ExecuteTargetResult } from "./attemptLoopTypes.ts";
export type DispatchWithCooldownRetryExtra = {
maxSetRetries: number;
setRetryDelayMs: number;
comboTimeoutMs: number;
comboStartTime: number;
comboCooldownWaitEnabled: boolean;
comboCooldownAttempt: { current: number };
comboCooldownBudgetLeftMs: { current: number };
evaluateGates: typeof evaluateExecuteTargetGates;
executeAttempt: typeof executeTargetAttempt;
};
export async function dispatchWithCooldownRetry(opts: {
state: AttemptLoopState;
deps: AttemptLoopDeps;
extra: DispatchWithCooldownRetryExtra;
}): Promise<Response> {
const { state, deps, extra } = opts;
// #7360: persist lastStatus/earliestRetryAfter across set retries; reset
// only on a fresh dispatch (including cooldown-aware re-dispatch).
state.lastError = null;
state.earliestRetryAfter = null;
state.lastStatus = null;
state.skippedForCircuitOpen = false;
state.earliestCircuitOpenRetryMs = 0;
// #11804: the loop-safety timer is armed per setTry iteration but must be
// cleared on EVERY exit path, not just the happy one. Hoisted to function
// scope so the `finally` at the end of this function always reaches it.
let activeLoopSafetyTimer: ReturnType<typeof setTimeout> | null = null;
try {
for (let setTry = 0; setTry <= extra.maxSetRetries; setTry++) {
// #1731: Per-set-iteration set of providers whose quota is fully exhausted.
// Reset each retry so providers excluded in a previous attempt get another chance.
state.exhaustedProviders = new Set<string>();
state.exhaustedConnections = new Set<string>();
state.transientRateLimitedProviders = new Set<string>();
state.skippedForCircuitOpen = false;
state.earliestCircuitOpenRetryMs = 0;
if (setTry > 0) {
deps.log.info(
"COMBO",
`All targets failed — retrying set (${setTry}/${extra.maxSetRetries})`
);
await new Promise((resolve) => {
const timer = setTimeout(resolve, extra.setRetryDelayMs);
deps.signal?.addEventListener(
"abort",
() => {
clearTimeout(timer);
resolve(undefined);
},
{ once: true }
);
});
if (deps.signal?.aborted) {
deps.log.info("COMBO", "Client disconnected during set retry delay — aborting");
return errorResponse(499, "Client disconnected");
}
}
deps.startTime = Date.now();
state.fallbackCount = 0;
state.recordedAttempts = 0;
state.comboErrors = [];
// QA P0: assemble a sanitized diagnostic trace from the state already in scope
// (pool size + this set-try's exhausted providers/connections + attempt order +
// a terminal-reason code). Never touches keys/tokens — provider/model ids only.
// Silent-stop fix: include a `recovery` hint (action verb + human next-step) so the
// OC plugin + non-header-aware clients can render an actionable error instead of an
// opaque 5xx. The optional `retryAfterSeconds` carries the upstream Retry-After hint.
const buildComboDiag = (
terminalReason: string,
retryAfterSeconds?: number
): ComboDiagnostics => ({
poolSize: state.orderedTargets.length,
attempted: state.recordedAttempts,
excluded: [
...[...state.exhaustedProviders].map((p) => ({ provider: p, reason: "exhausted" })),
...[...state.exhaustedConnections].map((c) => formatExhaustedConnectionKey(String(c))),
],
attemptOrder: state.comboAttemptOrder,
terminalReason,
recovery: buildRecoveryHint(terminalReason, retryAfterSeconds),
});
let globalResolve: ((res: Response) => void) | null = null;
const globalPromise = new Promise<Response>((res) => {
globalResolve = res;
});
// G1 (silent-stop fix): the speculative loop's `Promise.race` waits on
// `globalPromise`, which is ONLY resolved from inside a task (success or
// fatal error). If a target hangs — e.g. the operator disabled the per-model
// timeout (`targetTimeoutMs: 0`) and the upstream never settles — the race
// never resolves and the request hangs forever with no response. This safety
// promise force-resolves after the combo budget (extra.comboTimeoutMs when set,
// otherwise a hard ceiling) so the request ALWAYS terminates with an
// actionable 504 instead of dying silently. `state.comboExpired` is flipped so the
// target loop stops launching new work; the existing state.comboExpired branch
// returns the aggregated 504.
const loopSafetyMs =
extra.comboTimeoutMs > 0 ? extra.comboTimeoutMs : COMBO_LOOP_SAFETY_TIMEOUT_MS;
let loopSafetyFired = false;
let loopSafetyTimer: ReturnType<typeof setTimeout> | null = null;
const loopSafetyPromise = new Promise<Response>((resolve) => {
loopSafetyTimer = setTimeout(() => {
loopSafetyFired = true;
deps.log.warn(
"COMBO",
`Combo loop safety timeout (${loopSafetyMs}ms) reached without a terminal response — force-terminating`
);
resolve(
errorResponseWithComboDiagnostics(
504,
`Combo global timeout (${loopSafetyMs}ms) without a terminal response`,
buildComboDiag("combo_timeout"),
{ code: "COMBO_TIMEOUT", type: "server_error" }
)
);
}, loopSafetyMs);
loopSafetyTimer.unref?.();
activeLoopSafetyTimer = loopSafetyTimer;
});
const runningTasks = new Set<Promise<void>>();
let anySuccess = false;
// #10681: steps already recorded as dispatched (so per-target retries do not
// duplicate the decision).
state.dispatchedTargets = new Set<string>();
// G1: flip state.comboExpired as soon as the safety timer fires so the next loop
// iteration breaks instead of launching more targets after the budget, and
// abort every in-flight target so a hung upstream actually gets cancelled
// (not just "response stops").
const markLoopExpiredIfSafetyFired = () => {
if (loopSafetyFired) {
state.comboExpired = true;
for (const [, ac] of state.abortControllers.entries()) ac.abort();
}
};
state.abortControllers = new Map<number, AbortController>();
const zeroLatencyOptimizationsEnabled = deps.config.zeroLatencyOptimizationsEnabled === true;
const hasProtectedPriorityTarget =
deps.strategy === "priority" &&
state.orderedTargets.some((target) => target.fallbackOnlyOnQuotaExhaustion === true);
const executeTarget = async (i: number): Promise<ExecuteTargetResult> => {
const gate = await extra.evaluateGates({ index: i, state, deps });
if (gate.kind === "skip") return gate.result;
return extra.executeAttempt({
index: i,
state,
deps,
targetForAttempt: gate.targetForAttempt,
profile: gate.profile,
protectedPriorityTarget: gate.protectedPriorityTarget,
});
};
for (let i = 0; i < state.orderedTargets.length; i++) {
if (anySuccess || state.comboExpired) break;
const abortController = new AbortController();
state.abortControllers.set(i, abortController);
const onClientAbort = () => abortController.abort();
deps.signal?.addEventListener("abort", onClientAbort);
const task = (async () => {
try {
const res = await executeTarget(i);
if (res && !anySuccess) {
if (res.ok) {
anySuccess = true;
globalResolve!(res.response!);
for (const [idx, ac] of state.abortControllers.entries()) {
if (idx !== i) ac.abort();
}
} else if (res.response) {
// Fatal error, abort combo
anySuccess = true;
globalResolve!(res.response);
}
}
} finally {
deps.signal?.removeEventListener("abort", onClientAbort);
}
})().catch((err) => {
const logError = deps.log.error ?? deps.log.warn;
logError("COMBO", `Speculative task error for target ${i}`, err);
// G2 (silent-stop fix): never leave the speculative loop waiting on an
// unresolved globalPromise. If a task throws unexpectedly (outside
// executeTarget's error handling) and no other task succeeds, the post-loop
// `Promise.race([globalPromise, ...])` would hang forever. Resolve with a
// 502 so the request terminates with an actionable error.
if (!anySuccess && globalResolve) {
anySuccess = true;
globalResolve(errorResponse(502, `Combo target ${i} failed with an unexpected error`));
}
});
runningTasks.add(task);
task.finally(() => runningTasks.delete(task));
if (
zeroLatencyOptimizationsEnabled &&
deps.config.hedging &&
!hasProtectedPriorityTarget &&
i + 1 < state.orderedTargets.length
) {
const hedgeDelay = resolveDelayMs(deps.config.hedgeDelayMs, 500);
const timeoutPromise = new Promise<void>((r) => {
setTimeout(r, hedgeDelay);
});
await Promise.race([task, globalPromise, timeoutPromise, loopSafetyPromise]);
} else {
await Promise.race([task, globalPromise, loopSafetyPromise]);
}
markLoopExpiredIfSafetyFired();
// Global combo timeout check: after each target completes, stop trying
// further targets if the total elapsed time exceeds extra.comboTimeoutMs.
if (
!anySuccess &&
extra.comboTimeoutMs > 0 &&
Date.now() - extra.comboStartTime >= extra.comboTimeoutMs
) {
state.comboExpired = true;
deps.log.info(
"COMBO",
`Combo global timeout (${extra.comboTimeoutMs}ms) reached after ` +
`${i + 1}/${state.orderedTargets.length} targets (${state.recordedAttempts} attempted) — stopping`
);
}
}
if (!anySuccess && runningTasks.size > 0) {
// G1: include loopSafetyPromise so a hung last task (per-model timeout
// disabled) cannot freeze this post-loop race forever.
await Promise.race([globalPromise, Promise.all([...runningTasks]), loopSafetyPromise]);
markLoopExpiredIfSafetyFired();
}
// G1: if the safety timer won the race (request would otherwise hang), give
// in-flight tasks a short drain window to land their per-model errors into
// state.comboErrors so the 504 carries the same "tried: a (500)" summary the
// regular state.comboExpired branch produces — then return the safety 504.
if (loopSafetyFired && !anySuccess) {
if (runningTasks.size > 0) {
await Promise.race([
Promise.allSettled([...runningTasks]),
new Promise((resolve) => setTimeout(resolve, COMBO_SAFETY_DRAIN_MS)),
]);
}
const summary = state.comboErrors
.slice(0, 5)
.map((e) => `${e.model} (${e.status})`)
.join(", ");
const msg =
`Combo global timeout (${loopSafetyMs}ms) after ${state.recordedAttempts}/${state.orderedTargets.length} targets` +
(state.comboErrors.length > 0
? ` | tried: ${summary}${state.comboErrors.length > 5 ? `... (+${state.comboErrors.length - 5})` : ""}`
: "") +
" without a terminal response";
return errorResponseWithComboDiagnostics(504, msg, buildComboDiag("combo_timeout"), {
code: "COMBO_TIMEOUT",
type: "server_error",
});
}
// #10681: finalize the decision trace (success).
finalizeComboTrace(deps.traceInvocationId, state.orderedTargets);
finishComboTrace(deps.traceInvocationId, { status: 200 });
if (anySuccess) {
// G1: clear the safety timer on the happy path so a successful combo does
// not leave a 10-minute timer alive per request.
if (loopSafetyTimer) {
clearTimeout(loopSafetyTimer);
loopSafetyTimer = null;
}
return await globalPromise;
}
// #10681: finalize the decision trace (global timeout).
finalizeComboTrace(deps.traceInvocationId, state.orderedTargets);
finishComboTrace(deps.traceInvocationId, { status: 504 });
// Global combo timeout: return aggregated error immediately, skipping set retries.
if (state.comboExpired) {
const summary = buildRedactedSummary(state.comboErrors);
const msg =
`Combo global timeout (${extra.comboTimeoutMs}ms) after ${state.recordedAttempts}/${state.orderedTargets.length} targets` +
(state.comboErrors.length > 0 ? ` | tried: ${summary}` : "");
const latencyMs = Date.now() - deps.startTime;
if (state.recordedAttempts === 0) {
recordComboRequest(deps.combo.name, null, {
success: false,
latencyMs,
fallbackCount: state.fallbackCount,
strategy: deps.strategy,
});
}
notifyWebhookEvent("request.failed", {
combo: deps.combo.name,
reason: "COMBO_TIMEOUT",
latencyMs,
fallbackCount: state.fallbackCount,
});
return errorResponseWithComboDiagnostics(504, msg, buildComboDiag("combo_timeout"), {
code: "COMBO_TIMEOUT",
type: "server_error",
});
}
// All models failed in this set try
const latencyMs = Date.now() - deps.startTime;
if (state.recordedAttempts === 0) {
recordComboRequest(deps.combo.name, null, {
success: false,
latencyMs,
fallbackCount: state.fallbackCount,
strategy: deps.strategy,
});
}
// Retry the entire set if more attempts remain
if (setTry < extra.maxSetRetries) continue;
if (!state.lastStatus && state.recordedAttempts === 0 && extra.comboCooldownWaitEnabled) {
const circuitOpenWait = resolveCircuitOpenWaitDecision({
skippedForCircuitOpen: state.skippedForCircuitOpen,
retryAfterMs: state.earliestCircuitOpenRetryMs,
attempt: extra.comboCooldownAttempt.current,
budgetLeftMs: extra.comboCooldownBudgetLeftMs.current,
settings: deps.resilienceSettings.comboCooldownWait,
});
if (circuitOpenWait.wait) {
deps.log.info(
"COMBO",
`${deps.strategy} circuit-open wait: waiting ${Math.ceil(circuitOpenWait.waitMs / 1000)}s (reason=${circuitOpenWait.reason ?? "circuit_open"}) then retrying (attempt ${extra.comboCooldownAttempt.current + 1}/${deps.resilienceSettings.comboCooldownWait.maxAttempts})`
);
const completed = await waitForCooldownAwareRetry(circuitOpenWait.waitMs, deps.signal);
if (!completed) {
return errorResponse(499, "Request aborted");
}
extra.comboCooldownAttempt.current += 1;
extra.comboCooldownBudgetLeftMs.current = Math.max(
0,
extra.comboCooldownBudgetLeftMs.current - circuitOpenWait.waitMs
);
return dispatchWithCooldownRetry({ state, deps, extra });
}
}
// All set retries exhausted — return the final error
// #10681: finalize the decision trace (all targets failed or skipped).
finalizeComboTrace(deps.traceInvocationId, state.orderedTargets);
finishComboTrace(deps.traceInvocationId, { status: 503 });
if (!state.lastStatus) {
if (state.recordedAttempts === 0) {
notifyWebhookEvent("request.failed", {
combo: deps.combo.name,
reason: "ALL_TARGETS_SKIPPED",
latencyMs,
fallbackCount: state.fallbackCount,
});
return withQuotaExhaustionClassification(
errorResponseWithComboDiagnostics(
503,
"Service temporarily unavailable: all targets were skipped by pre-dispatch filters",
buildComboDiag("all_targets_skipped"),
{ code: "ALL_TARGETS_SKIPPED", type: "service_unavailable" }
),
state.observedFailure ? state.allObservedFailuresQuota : null
);
}
notifyWebhookEvent("request.failed", {
combo: deps.combo.name,
reason: "ALL_ACCOUNTS_INACTIVE",
latencyMs,
fallbackCount: state.fallbackCount,
});
recordComboFailure(deps.effectiveSessionId, deps.combo.name);
return errorResponseWithComboDiagnostics(
503,
"Service temporarily unavailable: all upstream accounts are inactive",
buildComboDiag("all_accounts_inactive"),
{ code: "ALL_ACCOUNTS_INACTIVE", type: "service_unavailable" }
);
}
// #10501: derive the terminal HTTP status from the structured per-target
// outcomes instead of `state.lastStatus` (whichever target happened to fail
// LAST). A 4xx is preserved only when the request itself is genuinely
// invalid across every eligible target; a heterogeneous mix of failure
// classes (e.g. a quality failure + a sibling's 401) normalizes to a
// 5xx-class status reflecting an infra/provider problem, not a client
// error. See comboErrorAggregation.ts::resolveComboTerminalStatus.
const status = resolveComboTerminalStatus(state.comboErrors, state.lastStatus);
// #10314: build the terminal message from the structured per-target
// outcomes (each distinct class+reason listed separately) instead of
// mashing a single state.lastError with raw `[model (status)]` markers. Connection
// identifiers are redacted. Falls back to state.lastError when no target recorded
// a structured outcome.
const msg =
formatComboOutcomes(state.comboErrors) || state.lastError || "All combo models unavailable";
// Cooldown-aware retry: instead of crystallizing a transient failure, wait
// out a SHORT cooldown and re-run the whole set loop. Guarded by the helper
// (quota_exhausted/auth/not-found excluded, ceiling, attempts, budget).
// MAX_GLOBAL_ATTEMPTS still bounds total dispatches. Available to ALL combo
// strategies when enabled — entry is driven by earliestRetryAfter + the
// real model-lockout reason, NOT by whichever target last overwrote
// `status` (a later 403 must not skip the allow-list check for an earlier
// 429's retry-after hint). SECURITY (see comboCooldownRetry.ts header): the
// allow-list is the PRIMARY barrier and `maxWaitMs` only the SECOND one.
// Hardcoding reason:"rate_limit" would drop the primary barrier and leave
// only the ceiling — which does NOT cover a quota_exhausted lock carrying a
// SHORT upstream retry-after. Model lockouts are recorded for all strategies,
// so the real reason is always available.
if (extra.comboCooldownWaitEnabled && state.earliestRetryAfter) {
const decision: ResolveComboCooldownDecisionResult = resolveComboCooldownWaitDecision({
targets: state.orderedTargets,
earliestRetryAfter: state.earliestRetryAfter,
attempt: extra.comboCooldownAttempt.current,
budgetLeftMs: extra.comboCooldownBudgetLeftMs.current,
settings: deps.resilienceSettings.comboCooldownWait,
// Key each lookup on the TARGET's own model: quota-share combos are
// single-model/multi-account (so this is identical to the previous
// state.orderedTargets[0] behavior), but heterogeneous combos carry a
// different model per target.
lookupLock: (provider, connectionId, target) => {
const rawModel = parseModel(target?.modelStr ?? "").model || "";
if (!rawModel) return null;
return getModelLockoutInfo(provider, connectionId, rawModel);
},
computeWaitMs: (retryAfter) => computeClosestRetryAfter(retryAfter).waitMs,
});
if (decision.wait) {
deps.log.info(
"COMBO",
`${deps.strategy} cooldown wait: ${msg} — waiting ${Math.ceil(
decision.waitMs / 1000
)}s (reason=${decision.reason ?? "?"}) then retrying (attempt ${
extra.comboCooldownAttempt.current + 1
}/${deps.resilienceSettings.comboCooldownWait.maxAttempts})`
);
const completed = await waitForCooldownAwareRetry(decision.waitMs, deps.signal);
if (!completed) {
deps.log.info("COMBO", `${deps.strategy} cooldown wait aborted by client disconnect`);
return errorResponse(499, "Request aborted");
}
extra.comboCooldownAttempt.current += 1;
extra.comboCooldownBudgetLeftMs.current = Math.max(
0,
extra.comboCooldownBudgetLeftMs.current - decision.waitMs
);
return dispatchWithCooldownRetry({ state, deps, extra });
}
}
// #10681: finalize the decision trace with the aggregated terminal status.
finalizeComboTrace(deps.traceInvocationId, state.orderedTargets);
finishComboTrace(deps.traceInvocationId, { status });
// Retry-after decoration is separate from the wait decision above: only
// rate-limit-class final statuses may carry a `(reset after ...)` suffix
// (see unavailableRetryGate.ts — do not stitch a peer target's window onto
// a config-class status like 403/422).
if (state.earliestRetryAfter && isRetryAfterEligibleStatus(status)) {
const retryHuman = formatRetryAfter(toRetryAfterDisplayValue(state.earliestRetryAfter));
deps.log.warn("COMBO", `All models failed | ${msg} (${retryHuman})`);
return withQuotaExhaustionClassification(
unavailableResponse(status, msg, state.earliestRetryAfter, retryHuman),
state.observedFailure ? state.allObservedFailuresQuota : null
);
}
// Silent-stop fix: bump the failure counter (pin clears on 3rd consecutive) and emit
// `try-auto` recovery action via buildRecoveryHint so the OC plugin can show "→ Try
// model: auto" instead of an opaque 5xx. We pass the upstream retry-after seconds to
// the hint so the client can render a precise "wait Ns and retry" message.
deps.log.warn("COMBO", `All models failed | ${msg}`);
const { pinClearedNow } = recordComboFailure(deps.effectiveSessionId, deps.combo.name);
if (pinClearedNow) {
deps.log.info(
"COMBO",
`Auto-cleared session_model_history pin for combo "${deps.combo.name}" after ${COMBO_FAILURE_THRESHOLD} consecutive failures to break the silent-stop loop`
);
}
const retryAfterSeconds = undefined;
// #10966: when every observed failure was independently classified as quota/
// balance exhaustion (isQuotaExhaustionResponse, tracked via state.observeFailure's
// state.allObservedFailuresQuota accumulator), stamp a stable `quota_exhausted`
// terminalReason instead of forwarding the raw upstream error string. The raw
// string falls through buildRecoveryHint's default branch ("retry" / "failed
// transiently"), which is actively misleading for a durable wallet/quota
// exhaustion — retrying the same combo will never refill it.
const terminalReason =
state.observedFailure && state.allObservedFailuresQuota
? "quota_exhausted"
: (state.lastError ?? "all_models_failed");
return withQuotaExhaustionClassification(
errorResponseWithComboDiagnostics(
status,
msg,
buildComboDiag(terminalReason, retryAfterSeconds)
),
state.observedFailure ? state.allObservedFailuresQuota : null
);
}
// Final fallback — when the dispatch returned without crystallizing a status (rare).
// Surface the recovery hint with a generic retry recommendation so the client at least
// gets a non-opaque message instead of "Combo routing completed without an upstream response".
recordComboFailure(deps.effectiveSessionId, deps.combo.name);
return errorResponseWithComboDiagnostics(
503,
"Combo routing completed without an upstream response",
buildNoUpstreamResponseDiagnostics(state.orderedTargets.length)
);
} finally {
// #11804: always release the loop-safety timer. Covering every exit path by
// construction here means a future `return` added to this function cannot
// silently reintroduce the leak.
if (activeLoopSafetyTimer) {
clearTimeout(activeLoopSafetyTimer);
activeLoopSafetyTimer = null;
}
}
}

View File

@@ -15,7 +15,11 @@
import { getModelContextLimit } from "../../../src/lib/modelCapabilities";
import { getHiddenModelsByProvider } from "../../../src/lib/db/models";
import { getComboModelString, normalizeComboStep } from "../../../src/lib/combos/steps.ts";
import {
getComboModelString,
implicitPinAllowlist,
normalizeComboStep,
} from "../../../src/lib/combos/steps.ts";
import { getProviderByAlias, getProviderById } from "../../../src/shared/constants/providers.ts";
import { estimateTokens } from "../contextManager.ts";
import { containsMediaKind } from "../../utils/mediaParts.ts";
@@ -121,6 +125,9 @@ function normalizeRuntimeStep(
const modelStr = getComboModelString(step);
if (!modelStr) return null;
const connectionId = toTrimmedString(step.connectionId);
const allowedConnectionIds = implicitPinAllowlist(connectionId, step.allowedConnectionIds);
return {
kind: "model",
stepId: step.id,
@@ -128,13 +135,13 @@ function normalizeRuntimeStep(
modelStr,
provider: getTargetProvider(modelStr, step.providerId),
providerId: step.providerId || null,
connectionId: step.connectionId || null,
connectionId,
// #3266: a per-step account allowlist scopes round-robin/weighted selection
// to a subset of the provider's connections. This is the second writer of
// `allowedConnectionIds` (tag routing is the first); both feed the existing
// credential-selection filter in auth.ts.
...(Array.isArray(step.allowedConnectionIds) && step.allowedConnectionIds.length > 0
? { allowedConnectionIds: step.allowedConnectionIds }
...(allowedConnectionIds && allowedConnectionIds.length > 0
? { allowedConnectionIds }
: {}),
weight,
label,

View File

@@ -23,6 +23,56 @@
*/
import { getResolvedModelContextOverride } from "../../../src/lib/modelCapabilities";
import { parseModel } from "../model.ts";
/**
* Longest-first so `-xhigh` is not eaten by `-high`. Mirrors
* `stripKnownEffortSuffix` in modelCapabilities.ts, but that helper's array
* order still matches `-high` first (`"…-xhigh".endsWith("-high")`).
*/
const EFFORT_SUFFIXES_LONGEST_FIRST = [
"minimal",
"medium",
"xhigh",
"none",
"high",
"max",
"low",
] as const;
function stripTrailingEffortSuffix(modelId: string): string | null {
const normalized = String(modelId || "").trim();
if (!normalized) return null;
const lowered = normalized.toLowerCase();
for (const suffix of EFFORT_SUFFIXES_LONGEST_FIRST) {
const token = `-${suffix}`;
if (lowered.length > token.length && lowered.endsWith(token)) {
return normalized.slice(0, -token.length);
}
}
return null;
}
/**
* Exact override first; if missing, inherit the base id after stripping a
* trailing effort tier (#12475). Combo members are stored as
* `provider/GLM-5.3-high` while `model_context_overrides` is keyed on
* `GLM-5.3`. Dispatcher already strips the suffix; the compat filter did not.
*/
function lookupOverrideWithEffortInheritance(modelStr: string): number | null {
const exact = getResolvedModelContextOverride(modelStr);
if (exact != null) return exact;
const parsed = parseModel(modelStr);
const modelId = typeof parsed.model === "string" ? parsed.model.trim() : "";
const base = stripTrailingEffortSuffix(modelId);
if (!base || base === modelId) return null;
if (parsed.provider) {
return getResolvedModelContextOverride({ provider: parsed.provider, model: base });
}
return getResolvedModelContextOverride(base);
}
/**
* Resolve the context-fit verdict from a persisted per-model override, if one
@@ -35,7 +85,7 @@ function resolveContextOverrideVerdict(
requiredContextTokens: number
): boolean | undefined {
if (!modelStr) return undefined;
const override = getResolvedModelContextOverride(modelStr);
const override = lookupOverrideWithEffortInheritance(modelStr);
if (override == null) return undefined;
return override >= requiredContextTokens;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,54 @@
/**
* Pure classify helpers for executeTarget's retry loop.
* Lift-as-is from combo.ts #8375 / #2101 / #4279. No I/O.
*
* @internal — not part of the public combo.ts barrel.
*/
import {
isContextOverflow400,
isInputBoundRequestFailure,
isModelScoped400,
isParamValidation400,
} from "./comboPredicates.ts";
export function remainderIsHomogeneous(
orderedTargets: { modelStr: string }[],
index: number,
modelStr: string
): boolean {
return orderedTargets.slice(index + 1).every((nextInPool) => nextInPool.modelStr === modelStr);
}
export function shouldAbortOnInputBoundFailure(opts: {
structuredError: unknown;
remainderIsHomogeneous: boolean;
}): boolean {
const structured = opts.structuredError as
{ code?: string | null; type?: string | null } | undefined;
return isInputBoundRequestFailure(structured) && opts.remainderIsHomogeneous;
}
/**
* #2101 / #4279: body-specific 400 must surface via {ok,response}, not null.
* Same predicate chain as combo.ts (overflow / param / model-scoped excluded).
*/
export function shouldSurfaceBodySpecific400(opts: {
status: number;
errorText: string;
shouldFallback: boolean;
}): boolean {
const errorText = opts.errorText;
return (
opts.status === 400 &&
opts.shouldFallback &&
!isContextOverflow400(errorText) &&
!isParamValidation400(errorText) &&
!isModelScoped400(errorText) &&
(errorText.toLowerCase().includes("context") ||
errorText.toLowerCase().includes("prompt") ||
errorText.toLowerCase().includes("token") ||
errorText.toLowerCase().includes("malformed") ||
errorText.toLowerCase().includes("invalid") ||
errorText.toLowerCase().includes("bad request"))
);
}

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