Files
OmniRoute/tests/unit/combo-routing-engine.test.ts
Diego Rodrigues de Sa e Souza f59f8daa94 Release v3.8.6 (#2804)
* fix(gemini): preserve structured tool calls for antigravity

* fix(gemini): parse prefixed textual tool calls

* fix(antigravity): preserve textual SSE tool calls

* fix(stream): normalize textual passthrough tool calls

* fix(stream): normalize split textual tool calls

* fix(stream): suppress malformed textual tool calls

* fix(stream): suppress compact malformed tool calls

* fix(stream): emit structured textual tool calls

* fix(stream): suppress unknown textual tool calls

* fix(stream): normalize responses textual tool calls

* chore: ignore .claude/settings.local.json (per-user Claude Code permissions)

* fix(opencode-go): route qwen3.x via claude messages + repair fixMissingToolResponses for Claude-shape upstreams (#2791)

Integrated into release/v3.8.6

* fix: resolve npm install warnings — remove dead deps, relax engine constraint (#2792)

Integrated into release/v3.8.6

* fix: register missing web-cookie validators (claude-web, gemini-web, copilot-web, t3-web) (#2793)

Integrated into release/v3.8.6

* fix: Error: Unable to inspect existing database #2771 (#2795)

Integrated into release/v3.8.6

* fix(oauth): repair Google loopback callback flow (#2796)

Integrated into release/v3.8.6

* feat(logs): add clean history button (#2799)

Integrated into release/v3.8.6

* [codex] home: restore settings-driven home layout and quota auto-refresh (#2800)

Integrated into release/v3.8.6

* fix(gemini): emit signaturelessToolCallMode:text for GEMINI format models (#2801)

Integrated into release/v3.8.6

* feat(modelSpecs): align opencode-go family with upstream provider limits (#2802)

Integrated into release/v3.8.6

* chore: apply unit test fixes, polyfills, and environment precedence fixes

* docs(agents): atualiza fluxos de release e triagem

Expande os workflows de release para incluir auditoria de segurança,
CHANGELOG completo por commits, quality gate obrigatório, homologação em
VPS local, publicação oficial, deploy em Akamai e validação de artefatos.

Reorganiza a triagem de features com arquivos permanentes por bucket,
suporte a itens em andamento, regra de reclaim após 15 dias e novo
tratamento para ideias viáveis catalogadas.

Corrige a orientação de revisão de discussões para usar a ordem
cronológica real dos comentários e respostas ao identificar a última
atividade.

* fix(lockout): classify Gemini Antigravity resource exhaustion as quota_exhausted

* fix(reasoning): gate replay by interleaved field

* docs(rule-16): permit human Co-authored-by, restrict only AI/bot trailers

Rule #16 previously banned all `Co-Authored-By` trailers absolutely.
That blocked the upstream-port workflows (`/port-upstream-features` and
`/port-upstream-issues`), which must credit human upstream PR authors
and issue reporters in OmniRoute commits.

Refine the rule to ban only AI/bot-attributed trailers (Claude, GPT,
Copilot, Bot; anthropic.com / openai.com / bot-owned noreply.github.com
emails) while allowing standard human `Co-authored-by: Name <email>`
attribution.

Sync the rule across the source CLAUDE.md, the E2E shakedown doc note,
and 41 i18n translations.

* fix(gitlawb): add specialty validators for connection test — bypass /models probe

GitLawB OpenGateway API (xiaomi-mimo compatible) does not expose a /models
endpoint, causing validateOpenAILikeProvider to 404 on the initial probe
and report 'Provider validation endpoint not supported'.

Add specialty validators for both gitlawb and gitlawb-gmi that follow the
same pattern as the existing xiaomi-mimo validator: skip GET /models,
validate directly via POST /chat/completions with a minimal test message.
Any 401/403 response means an invalid key; all other responses mean auth
is OK.

Fixes test-connection returning 404 for GitLawB providers.

* test(gitlawb): add 12 unit tests for gitlawb and gitlawb-gmi specialty validators

Covers success, auth failure (401/403), non-auth acceptance (400/422/429),
network errors, and custom baseUrl overrides for both providers.

* feat(gitlawb): serve models from static registry without API-unavailable warning

GitLawB's OpenGateway API does not expose a /models endpoint per
provider-path. Previously the models route fell through to the generic
fallback which returned static catalog models with the misleading
'API unavailable — using local catalog' warning.

Now gitlawb and gitlawb-gmi are handled as static model providers
(same pattern as reka and qwen OAuth) — models are served from the
provider registry without any warning, since all registered models
are functional via POST /chat/completions.

* refactor(gitlawb): extract shared opengateway validator factory, fix docs path in test

- Extract gitlawb/gitlawb-gmi validators into buildOpengatewayValidator factory
- Fix dockerignore-docs-coverage test: update stale docs/AUTO-COMBO.md -> docs/routing/AUTO-COMBO.md

* fix(reasoning): guard interleaved capability lookup

* feat(gitlawb): dynamic model fetch with gmi-cloud fallback

Hybrid approach:
- gitlawb (xiaomi-mimo): dynamic /models endpoint → 356 models
- gitlawb-gmi (gmi-cloud): 404 fallback → local catalog gracefully
Mimics Gitlawb/openclaude's model-routing pattern

* i18n(pt-BR): complete missing translations and sync with en.json

* feat(build): nix multi-OS package manager install (#2806)

Integrated into release/v3.8.6

* fix(i18n): translate 144 new __MISSING__ pt-BR strings (#2816)

Integrated into release/v3.8.6

* chore(docs): set coverage gate to 40/40/40/40 in CLAUDE.md

Aligns the documented coverage gate with the v3.8.6 release decision
(lowered from 75/75/75/70). Matches the threshold already set in
package.json by the large feature PRs (planos 11-22).

* fix(cli): respect PORT env var in serve command (#2845)

Integrated into release/v3.8.6.

* fix(deepseek-web): return 400 when client sends tools[] - chat.deepseek.com has no tool support (#2854)

Integrated into release/v3.8.6.

* fix(qoder): reject invalid/expired PATs returning Cosy 500 error (#2860)

Integrated into release/v3.8.6.

* fix(cli): register openclaw in tool-detector (#2833) (#2850)

Integrated into release/v3.8.6.

* fix(api): include noAuth providers in /v1/models catalog (#2798) (#2814)

Integrated into release/v3.8.6.

* fix(combo): resolve custom provider targets via combo name (#2778) (#2812)

Integrated into release/v3.8.6.

* fix(translator): strip safety_identifier in openai-responses cleanup (#2770) (#2809)

Integrated into release/v3.8.6.

* fix(quota): honor explicit per-connection preflight opt-out (#2831) (#2844)

Integrated into release/v3.8.6.

* fix(usage): un-invert GitHub Copilot Free/limited quota — limited_user_quotas is remaining (#2876) (#2881)

Integrated into release/v3.8.6.

* fix(nous-research): correct baseUrl to include /chat/completions (#2826) (#2835)

Integrated into release/v3.8.6.

* fix(opencode): qwen3.x max/plus models lack vision support (#2822) (#2836)

Integrated into release/v3.8.6.

* fix(translator): pass-through tool_search built-in tool type (#2766) (#2811)

Integrated into release/v3.8.6.

* fix(github): route claude-opus-4.6 via chat completions (#2821)

Integrated into release/v3.8.6.

* docs(oauth): add Windsurf login fix design (Phase 1 hotfix + Phase 2 Firebase OAuth)

Two-phase plan to fix the broken Windsurf OAuth flow:
- Phase 1: drop the dead app.devin.ai/editor/signin PKCE path, promote
  import-token from windsurf.com/show-auth-token as the primary path
- Phase 2: port Firebase OAuth + RegisterUser flow from
  fendoushaonian/WindSurf-gRPC-API for full browser-based automation

Spec only - no code changes yet.

* docs(plan): Phase 1 windsurf login hotfix implementation plan

10 tasks covering:
- TDD assertions for flowType + 410 Gone responses
- Provider switch to import_token
- Route handler retiring authorize/start-callback-server/poll-callback
- OAuthModal UI override
- i18n sync
- Verification + PR steps

* fix(cli): replace cli-table3 with hand-rolled formatter (#2752) (#2813)

Integrated into release/v3.8.6.

* fix(skills): skip interception for unregistered client-native tools (#2815) (#2817)

Integrated into release/v3.8.6.

* feat(sse): add RTK filters for kubectl, docker-build, composer, gh (#2824)

Integrated into release/v3.8.6.

* fix(geminiHelper): support rec.image content shape + warn on dropped remote URLs (refs #2807) (#2855)

Integrated into release/v3.8.6.

* fix(cli): allow nullable/optional apiKey in cliMitmStartSchema (#2857)

Integrated into release/v3.8.6.

* fix(combo): preserve system messages during context handoff summary generation (#2865)

Integrated into release/v3.8.6.

* fix: wire CLIProxyAPI fallback settings into chatCore routing engine (#2866)

Integrated into release/v3.8.6.

* fix(usage): add opencode quota fetcher (#2852) (#2867)

Integrated into release/v3.8.6.

* feat(claude): default xhigh support for newer Opus models (#2874)

Integrated into release/v3.8.6.

* fix(cli): restore omniroute logs command stream (#2756) (#2810)

Integrated into release/v3.8.6.

* fix(combo): normalize upstream Headers for Node 24 undici interop (#2751) (#2823)

Integrated into release/v3.8.6.

* Rename proxy log Public IP to Client IP (#2880)

Integrated into release/v3.8.6.

* fix(claude): preserve max effort for supported models (#2875)

Integrated into release/v3.8.6.

* fix(oauth): switch windsurf provider to import_token flow

The PKCE auth URL targeting app.devin.ai/editor/signin returns 404
post-rebrand. Until Phase 2 ports Firebase OAuth + RegisterUser, the
only supported path is import-token via windsurf.com/show-auth-token.

- windsurf.ts: drop buildAuthUrl, set flowType=import_token
- generateAuthData returns supported:false + helpful error for windsurf/devin-cli
- tests: assert flowType + disabled stub

* fix(oauth): return 410 Gone for retired windsurf/devin-cli PKCE actions

start-callback-server, authorize, and poll-callback (GET + POST) now
return 410 Gone with a pointer to /import-token. The 410 short-circuit
runs before auth so the response is honest about the action being
permanently gone, not gated. Codex PKCE flow unchanged.

Tests: 5 new assertions cover GET + POST 410 paths and a Codex
regression check.

* refactor(oauth): annotate retired PKCE fields in WINDSURF_CONFIG

No behaviour change - comment-only update documenting that authorizeUrl,
codeChallengeMethod, callbackPort, callbackPath, apiServerUrl, and
exchangePath are no longer consumed. Active fields (inferenceUrl,
showAuthTokenUrl, firebaseApiKey, ideName) called out separately.

* fix(cli,docs): use requireCliToolsAuth in logs route + document OPENCODE quota env

Post-merge contract fixes for v3.8.6:
- src/app/api/cli-tools/logs/route.ts (#2810) now uses the shared
  requireCliToolsAuth guard (param renamed req->request) to satisfy the
  cli-tools-auth-hardening contract test.
- Document OMNIROUTE_OPENCODE_QUOTA_URL (#2867) in docs/reference/ENVIRONMENT.md
  to satisfy the env/docs sync contract.

* fix(dashboard): force import-token panel for windsurf/devin-cli

Phase 1 hotfix: hide the 'Browser Login' tab and start in Paste API Key
mode. Removes windsurf/devin-cli from PKCE_CALLBACK_SERVER_PROVIDERS so
no callback server is started for them. Codex still uses the PKCE flow.

The 'Get token' link continues to point at windsurf.com/show-auth-token
via the existing supportsTokenPaste form copy.

* fix(oauth): windsurf import-token mapTokens signature mismatch

The route at `src/app/api/oauth/[provider]/[action]/route.ts` invokes
`providerData.mapTokens({ accessToken: token })` (object), matching the
cursor/kiro signature. The windsurf provider was declared with
`mapTokens(token: string)` instead, so the entire object was stored as
`accessToken`. When the connection record reached the SQLite layer it
crashed with:

  SQLite3 can only bind numbers, strings, bigints, buffers, and null

Fix by aligning windsurf's `mapTokens` signature with the route caller
and the cursor/kiro convention. Also dedupe a copy-pasted second
`if (action === "import-token")` block in the route handler — the
second block was unreachable but identical to the first.

Adds two regression tests asserting that
`provider.mapTokens({ accessToken })` returns a string `accessToken` for
both windsurf and devin-cli, so a future signature drift trips the gate
instead of the SQLite bind error in production.

* feat(compression): expand pt-BR pack with troglodita rules (15 → 49) (#2818)

Integrated into release/v3.8.6

* fix(sse): repair RTK engine defaults so dedup and direct calls work (#2825)

Integrated into release/v3.8.6

* fix(mcp): redirect console.log/warn to stderr in --mcp stdio mode (#2840)

Integrated into release/v3.8.6

* fix(gemini-cli): prefer real project IDs over default-project (#2841)

Integrated into release/v3.8.6

* fix(opencode-go): add provider limits quota fetcher (#2861)

Integrated into release/v3.8.6

* Audit & add web cookie providers: fix 4 missing registry entries + DuckDuckGo (#2862)

Integrated into release/v3.8.6

* fix(antigravity): harden signatureless tool history (#2878)

Integrated into release/v3.8.6

* fix: provider model sync pruning and dynamic antigravity MITM proxy mappings (#2886)

Integrated into release/v3.8.6

* feat(usage): per-API-key token limits scoped to model/provider/global (#2888)

Integrated into release/v3.8.6

* fix(audio): build multipart body manually to preserve Content-Type (#2842)

Integrated into release/v3.8.6

* refactor: remove agent skill documentation files and streamline maintenance workflows

* test(stabilization): resolve unit test failures in blackbox-web, schema-coercion, translator-helper-branches, usage-service-hardening, and audio-transcription

* fix(security): mitigate Socket.dev supply-chain findings + secrets opt-in + minimal build profile (#2863) (#2871)

Two real security gaps closed and four cosmetic Socket.dev fingerprints removed.
See docs/security/SOCKET_DEV_FINDINGS.md for the per-finding maintainer
attestation.

Real bugs fixed:
- cloudSync: HMAC verification of `X-Cloud-Sig` + opt-in
  `OMNIROUTE_CLOUD_SYNC_SECRETS=true` before overwriting `accessToken` /
  `refreshToken` / `providerSpecificData` from a remote response. Closes the
  silent-credential-swap surface (a misconfigured or hostile CLOUD_URL could
  previously replace local tokens unverified).
- Zed import: split into 2-step `/discover` + `/import` flow. `/import` now
  requires `confirmedAccounts: [{ service, account, fingerprint }]` and
  re-reads the keychain server-side to filter by fingerprint, so a tampered
  discover response cannot trick the endpoint into saving an unrelated token.

Cosmetic Socket.dev mitigations:
- runElevatedPowerShell writes the elevated payload to a per-call temp `.ps1`
  file (mode 0o600) and references it via `-File`. Removes the textbook
  `-EncodedCommand <base64utf16le>` pattern flagged as malware by Socket's AI
  classifier.
- Maintainer attestation `SECURITY-AUDITOR-NOTE:` blocks added at every
  flagged call site pointing to `docs/security/SOCKET_DEV_FINDINGS.md`.

Build-time hardening:
- `OMNIROUTE_BUILD_PROFILE=minimal` (`npm run build:secure`) physically
  removes the four sensitive modules from the standalone bundle via webpack
  `NormalModuleReplacementPlugin`. Stubs throw `FeatureDisabledError` at
  runtime. Intended for the `omniroute-secure` artifact.

Tests:
- 24 new unit tests in `tests/unit/security/` covering the wrapper builder,
  HMAC verification (4 cases), credential fingerprint determinism (5 cases),
  confirmedAccounts validation + fingerprint filtering (6 cases), and the
  minimal-build stubs (5 cases).

Docs:
- New `docs/security/SOCKET_DEV_FINDINGS.md` — per-finding attestation.
- New `socket.yml` — Socket.dev v2 config pointing at the attestation.
- Updated `SECURITY.md` — supply-chain scanner section.
- Updated `.env.example` — three new env vars documented.

Backwards compatibility:
- Cloud sync token overwrite is OFF by default. Users who relied on
  it must set `OMNIROUTE_CLOUD_SYNC_SECRETS=true`. Breaking change documented
  in CHANGELOG.
- Zed import 2-step is the new default; legacy 1-step preserved behind
  `OMNIROUTE_ZED_IMPORT_LEGACY_ONE_STEP=true` and will be removed in v3.9.

Closes #2863

* fix(security): redact public Firebase Web key from windsurf spec; doc SHA-256 cache-key rationale (#2894)

Two security-scanning findings on release/v3.8.6:

- Secret-scanning alert 7 (google_api_key): the windsurf login-fix design spec
  embedded the literal public Firebase Web API key on two lines. Firebase Web
  API keys are non-sensitive by design (they identify the project; access is
  gated by Firebase Security Rules + key restrictions), but the literal trips
  secret scanning. Redacted to a placeholder; the embedded default still goes
  through resolvePublicCred per rule #11.

- Code-scanning alert 261 (js/insufficient-password-hash): tokenCacheKey() uses
  SHA-256 to derive an in-memory cache key from the session token, not for
  password-at-rest storage. Added a comment documenting why CWE-916 KDFs do not
  apply (false positive).

* fix(ci): resolve release/v3.8.6 gate failures (docs-sync, any-budget, pack-artifact) (#2895)

* fix(ci): resolve release/v3.8.6 gate failures (docs-sync, any-budget, pack-artifact)

Three CI gates failed on release/v3.8.6 (run 26630300877):

- docs-sync: CHANGELOG had a spurious "## [3.8.6-patch]" section above
  "## [3.8.6]", so the latest release no longer matched package.json (3.8.6)
  and the 41 i18n CHANGELOG mirrors were flagged as missing that section.
  Fold the lone #2752 entry into [3.8.6] and drop the patch heading.
- any-budget:t11: open-sse/handlers/chatCore.ts regressed to 1 explicit `any`
  (budget 0). Type the persist callback arg as Record<string, unknown>, which
  matches runWithOnPersist's RefreshPersistFn contract exactly.
- pack-artifact: open-sse/utils/setupPolyfill.ts ships via package.json "files"
  (bin/omniroute.mjs imports it at startup) but was missing from the pack
  policy allowlist. Allow it and add a regression test.

* fix(security): redact public Firebase Web key from windsurf spec

Redact the literal public Firebase Web API key (secret-scanning #7) to a
placeholder, mirroring the redaction on release/v3.8.6 (PR #2894) and the
windsurf fix branch. Non-sensitive public Web key; trips secret scanning.

* feat(combo): Zero-Latency Combos (Hedging, Proactive Compression, Predictive TTFT) (#2868)

* feat(combo): implement zero-latency combo optimizations (hedging, proactive compression, predictive TTFT)

* fix(combo): fix predictive TTFT skip logic and unhandled promise rejections

---------

Co-authored-by: Automation <automation@omniroute>

* feat: implement automated skill workflows and update system configuration and validation schemas

* test: eliminate dynamic cast warnings in cloud-sync unit test

* test: isolate services-branch-hardening database directory to avoid concurrency issues

* feat(providers): add 7 new web-cookie providers + research catalog + discovery tool

New providers:
- huggingchat: free LLM chat via huggingface.co/chat (no subscription)
- phind: free dev-focused AI chat via phind.com/api/agent
- poe-web: multi-model chat via poe.com GraphQL (p-b cookie)
- venice-web: privacy-focused AI chat via venice.ai (session cookie)
- v0-vercel-web: Vercel v0 code gen via v0.dev (session cookie)
- kimi-web: Moonshot Kimi chat via kimi.moonshot.cn (session cookie)
- doubao-web: ByteDance Doubao chat via doubao.com (session cookie)

Additional:
- Research catalog: docs/research/UNLIMITED_LLM_ACCESS.md
- Discovery tool design + stub: src/lib/discovery/ + migration 073
- Unit tests: 33 tests for all 7 providers
- Shared helpers consolidated in error.ts (slop cleanup)
- All registered in WEB_COOKIE_PROVIDERS + providerRegistry + webSessionCredentials

Closes #2885

* fix(typecheck): resolve typecheck errors in combo spec and compression modules

* feat(api,oauth): add `agy` (Antigravity CLI) standalone provider with CLI token import (#2899)

Add a standalone OAuth provider `agy` (Antigravity CLI) next to gemini-cli/antigravity.
It reuses the antigravity inference backend (identical Google client_id +
daily-cloudcode-pa.googleapis.com endpoint, executor and token-refresh) but ships its own
model catalog — including the Claude models the backend exposes (claude-opus-4-6-thinking,
claude-sonnet-4-6) — its own account pool, and four ways to connect:

- token-file import (paste/upload the agy oauth token JSON)
- auto-detect a local CLI login (~/.gemini/antigravity-cli/antigravity-oauth-token)
- browser OAuth (via the shared OAuthModal Google loopback flow)
- bulk / ZIP import

New routes: POST /api/providers/agy-auth/{import,import-bulk,zip-extract,apply-local}.
Catalog pinned from the live :fetchAvailableModels endpoint. Docs (openapi.yaml,
ENVIRONMENT.md, .env.example, CHANGELOG) updated; new unit tests for registration,
the token parser, and route auth-hardening.

* fix(security): redact public Firebase Web key from windsurf spec (#2896)

Redact the literal public Firebase Web API key (secret-scanning #7) to a
placeholder. Firebase Web API keys are non-sensitive by design but the literal
trips GitHub secret scanning. Mirrors the redaction landed on release/v3.8.6
(PR #2894). Embedded default still flows through resolvePublicCred (rule #11).

* Pr 2871 (#2897)

* fix(security): mitigate Socket.dev supply-chain findings + secrets opt-in + minimal build profile (#2863)

Two real security gaps closed and four cosmetic Socket.dev fingerprints removed.
See docs/security/SOCKET_DEV_FINDINGS.md for the per-finding maintainer
attestation.

Real bugs fixed:
- cloudSync: HMAC verification of `X-Cloud-Sig` + opt-in
  `OMNIROUTE_CLOUD_SYNC_SECRETS=true` before overwriting `accessToken` /
  `refreshToken` / `providerSpecificData` from a remote response. Closes the
  silent-credential-swap surface (a misconfigured or hostile CLOUD_URL could
  previously replace local tokens unverified).
- Zed import: split into 2-step `/discover` + `/import` flow. `/import` now
  requires `confirmedAccounts: [{ service, account, fingerprint }]` and
  re-reads the keychain server-side to filter by fingerprint, so a tampered
  discover response cannot trick the endpoint into saving an unrelated token.

Cosmetic Socket.dev mitigations:
- runElevatedPowerShell writes the elevated payload to a per-call temp `.ps1`
  file (mode 0o600) and references it via `-File`. Removes the textbook
  `-EncodedCommand <base64utf16le>` pattern flagged as malware by Socket's AI
  classifier.
- Maintainer attestation `SECURITY-AUDITOR-NOTE:` blocks added at every
  flagged call site pointing to `docs/security/SOCKET_DEV_FINDINGS.md`.

Build-time hardening:
- `OMNIROUTE_BUILD_PROFILE=minimal` (`npm run build:secure`) physically
  removes the four sensitive modules from the standalone bundle via webpack
  `NormalModuleReplacementPlugin`. Stubs throw `FeatureDisabledError` at
  runtime. Intended for the `omniroute-secure` artifact.

Tests:
- 24 new unit tests in `tests/unit/security/` covering the wrapper builder,
  HMAC verification (4 cases), credential fingerprint determinism (5 cases),
  confirmedAccounts validation + fingerprint filtering (6 cases), and the
  minimal-build stubs (5 cases).

Docs:
- New `docs/security/SOCKET_DEV_FINDINGS.md` — per-finding attestation.
- New `socket.yml` — Socket.dev v2 config pointing at the attestation.
- Updated `SECURITY.md` — supply-chain scanner section.
- Updated `.env.example` — three new env vars documented.

Backwards compatibility:
- Cloud sync token overwrite is OFF by default. Users who relied on
  it must set `OMNIROUTE_CLOUD_SYNC_SECRETS=true`. Breaking change documented
  in CHANGELOG.
- Zed import 2-step is the new default; legacy 1-step preserved behind
  `OMNIROUTE_ZED_IMPORT_LEGACY_ONE_STEP=true` and will be removed in v3.9.

Closes #2863

* feat: implement automated skill workflows and update system configuration and validation schemas

* test: eliminate dynamic cast warnings in cloud-sync unit test

* test: isolate services-branch-hardening database directory to avoid concurrency issues

* chore(docs): refresh generated docs collection index

Update the generated Fumadocs browser collection mapping to keep
documentation imports in sync with the current docs structure.

* docs: update generated browser docs collection manifest

Refresh the generated Fumadocs browser collection mapping so the docs site can resolve the current documentation files correctly.

---------

Co-authored-by: OpenClaw <openclaw@kuzhomesrv.local>
Co-authored-by: Dmitry Kuznetsov <139351986+dmitry@users.noreply.local>
Co-authored-by: KuzyaBot <kuzya@local>
Co-authored-by: JeferssonLemes <jeferssondev@gmail.com>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: Markus Hartung <mail@hartmark.se>
Co-authored-by: akarray <akarray@users.noreply.github.com>
Co-authored-by: Apostol Apostolov <theapoapostolov@gmail.com>
Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com>
Co-authored-by: Dmitry Kuznetsov <dmitry@kuznetsov.me>
Co-authored-by: Nikolay Alafuzov <alafuzov_nn@rusklimat.ru>
Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
Co-authored-by: Ronaldo Davi <alltomatos@users.noreply.github.com>
Co-authored-by: levonk <277861+levonk@users.noreply.github.com>
Co-authored-by: Lenine Júnior <lenine@engrene.com.br>
Co-authored-by: Annas Alghoffar <aag.annas@gmail.com>
Co-authored-by: Tushar Agarwal <76201310+Tushar49@users.noreply.github.com>
Co-authored-by: GreatLiu <eurasiaxz@qq.com>
Co-authored-by: yuna amelia <230527278+yunaamelia@users.noreply.github.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Container <78986709+disonjer@users.noreply.github.com>
Co-authored-by: nickwizard <35692452+nickwizard@users.noreply.github.com>
Co-authored-by: Rajvardhan Patil <rajvardhanpatil7890@gmail.com>
Co-authored-by: Raxxoor <manker_lol@hotmail.com>
Co-authored-by: Muhammad Mugni Hadi <mugnimaestra3@gmail.com>
Co-authored-by: mi <123757457+soyelmismo@users.noreply.github.com>
Co-authored-by: Automation <automation@omniroute>
2026-05-29 12:44:29 -03:00

2622 lines
76 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-combo-routing-"));
const ORIGINAL_DATA_DIR = process.env.DATA_DIR;
process.env.DATA_DIR = TEST_DATA_DIR;
const {
getComboFromData,
getComboModelsFromData,
validateComboDAG,
resolveNestedComboModels,
handleComboChat,
} = await import("../../open-sse/services/combo.ts");
const { normalizeComboStep } = await import("../../src/lib/combos/steps.ts");
const { registerStrategy } = await import("../../open-sse/services/autoCombo/routerStrategy.ts");
const core = await import("../../src/lib/db/core.ts");
const settingsDb = await import("../../src/lib/db/settings.ts");
const evalsDb = await import("../../src/lib/db/evals.ts");
const { saveModelsDevCapabilities, clearModelsDevCapabilities } =
await import("../../src/lib/modelsDevSync.ts");
const { getComboMetrics, recordComboRequest, resetAllComboMetrics } =
await import("../../open-sse/services/comboMetrics.ts");
const { resetEvalRoutingCache } = await import("../../open-sse/services/evalRouting.ts");
const { resetAllCircuitBreakers } = await import("../../src/shared/utils/circuitBreaker.ts");
const { acquire: acquireSemaphore, resetAll: resetAllSemaphores } =
await import("../../open-sse/services/rateLimitSemaphore.ts");
const { _resetAllDecks } = await import("../../src/shared/utils/shuffleDeck.ts");
function createLog() {
const entries: any[] = [];
return {
info: (tag: any, msg: any) => entries.push({ level: "info", tag, msg }),
warn: (tag: any, msg: any) => entries.push({ level: "warn", tag, msg }),
error: (tag: any, msg: any) => entries.push({ level: "error", tag, msg }),
debug: (tag: any, msg: any) => entries.push({ level: "debug", tag, msg }),
entries,
};
}
function okResponse(body: any = { choices: [{ message: { content: "ok" } }] }) {
return new Response(JSON.stringify(body), {
status: 200,
headers: { "content-type": "application/json" },
});
}
function errorResponse(status: number, message: string = `Error ${status}`) {
return new Response(JSON.stringify({ error: { message } }), {
status,
headers: { "content-type": "application/json" },
});
}
function waitForBackgroundWork() {
return new Promise((resolve) => setTimeout(resolve, 25));
}
function providerBreakerOpenResponse() {
return new Response(
JSON.stringify({
error: {
message: "Provider circuit breaker is open",
code: "provider_circuit_open",
},
}),
{
status: 503,
headers: {
"content-type": "application/json",
"x-omniroute-provider-breaker": "open",
},
}
);
}
function streamResponse(chunks: any[]) {
return new Response(chunks.join(""), {
status: 200,
headers: { "content-type": "text/event-stream" },
});
}
function capabilityEntry(limitContext: unknown, overrides: Record<string, unknown> = {}) {
return {
tool_call: true,
reasoning: false,
attachment: false,
structured_output: true,
temperature: true,
modalities_input: JSON.stringify(["text"]),
modalities_output: JSON.stringify(["text"]),
knowledge_cutoff: null,
release_date: null,
last_updated: null,
status: null,
family: null,
open_weights: false,
limit_context: limitContext,
limit_input: limitContext,
limit_output: 4096,
interleaved_field: null,
...overrides,
};
}
function getComboTargetExecutionKey(comboName: string, index: number, stepInput: any) {
const step = normalizeComboStep(stepInput, { comboName, index });
if (!step) throw new Error(`Failed to normalize combo step for ${comboName}#${index}`);
return `combo:${comboName}:${step.id}`;
}
async function cleanupTestDataDir() {
let lastError;
for (let attempt = 0; attempt < 5; attempt += 1) {
try {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
return;
} catch (error: any) {
lastError = error;
await new Promise((resolve) => setTimeout(resolve, 25));
}
}
if (lastError) {
throw lastError;
}
}
async function resetStorage() {
await cleanupTestDataDir();
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
await settingsDb.resetAllPricing();
settingsDb.clearAllLKGP();
clearModelsDevCapabilities();
}
test.beforeEach(async () => {
resetAllComboMetrics();
resetEvalRoutingCache();
resetAllCircuitBreakers();
resetAllSemaphores();
_resetAllDecks();
await resetStorage();
});
test.after(async () => {
resetAllComboMetrics();
resetEvalRoutingCache();
resetAllCircuitBreakers();
resetAllSemaphores();
_resetAllDecks();
clearModelsDevCapabilities();
settingsDb.clearAllLKGP();
if (ORIGINAL_DATA_DIR === undefined) {
delete process.env.DATA_DIR;
} else {
process.env.DATA_DIR = ORIGINAL_DATA_DIR;
}
await cleanupTestDataDir();
});
test("getComboFromData and getComboModelsFromData resolve combos from array and object containers", () => {
const combos = [
{ name: "alpha", models: ["openai/gpt-4o-mini", { model: "claude/sonnet", weight: 2 }] },
];
const fromArray = getComboFromData("alpha", combos);
const fromObject = getComboFromData("alpha", { combos });
const models = getComboModelsFromData("alpha", { combos });
assert.equal(fromArray.name, "alpha");
assert.equal(fromObject.name, "alpha");
assert.deepEqual(models, ["openai/gpt-4o-mini", "claude/sonnet"]);
});
test("validateComboDAG rejects circular references and resolveNestedComboModels expands nested combos", () => {
const combos = [
{ name: "root", models: ["child-a", "openai/gpt-4o-mini"] },
{ name: "child-a", models: ["child-b", "claude/sonnet"] },
{ name: "child-b", models: ["groq/llama-3.3-70b"] },
];
validateComboDAG("root", combos);
assert.deepEqual(resolveNestedComboModels(combos[0], combos), [
"groq/llama-3.3-70b",
"claude/sonnet",
"openai/gpt-4o-mini",
]);
assert.throws(
() =>
validateComboDAG("loop-a", [
{ name: "loop-a", models: ["loop-b"] },
{ name: "loop-b", models: ["loop-a"] },
]),
/Circular combo reference detected/
);
});
test("resolveNestedComboModels expands explicit combo-ref steps", () => {
const combos = [
{
name: "root",
models: [
{ id: "root-ref-child", kind: "combo-ref", comboName: "child", weight: 0 },
{ id: "root-model", kind: "model", providerId: "openai", model: "openai/gpt-4o-mini" },
],
},
{
name: "child",
models: [
{ id: "child-model", kind: "model", providerId: "anthropic", model: "claude/sonnet" },
],
},
];
validateComboDAG("root", combos);
assert.deepEqual(resolveNestedComboModels(combos[0], combos), [
"claude/sonnet",
"openai/gpt-4o-mini",
]);
});
test("validateComboDAG enforces maximum nesting depth", () => {
const combos = [
{ name: "c1", models: ["c2"] },
{ name: "c2", models: ["c3"] },
{ name: "c3", models: ["c4"] },
{ name: "c4", models: ["c5"] },
{ name: "c5", models: ["openai/gpt-4o-mini"] },
];
assert.throws(() => validateComboDAG("c1", combos), /Max combo nesting depth/);
});
test("handleComboChat priority strategy defaults to first model and records success metrics", async () => {
const calls: any[] = [];
const combo = {
name: "priority-default",
models: ["openai/gpt-4o-mini", "claude/sonnet"],
};
const result = await handleComboChat({
body: {},
combo,
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
allCombos: null,
});
const metrics = getComboMetrics("priority-default");
const firstStep = normalizeComboStep(combo.models[0], {
comboName: combo.name,
index: 0,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["openai/gpt-4o-mini"]);
assert.equal(metrics.totalRequests, 1);
assert.equal(metrics.totalSuccesses, 1);
assert.equal(metrics.byModel["openai/gpt-4o-mini"].requests, 1);
assert.equal(metrics.byTarget[firstStep.id].requests, 1);
assert.equal(metrics.byTarget[firstStep.id].model, "openai/gpt-4o-mini");
assert.equal(metrics.strategy, "priority");
});
test("handleComboChat runs shadow targets without changing the primary response or metrics", async () => {
const calls: any[] = [];
const shadowRequests: any[] = [];
const combo = {
name: "shadow-routing-priority",
models: ["openai/gpt-4o-mini"],
config: {
shadowRouting: {
enabled: true,
targets: [
{
kind: "model",
id: "shadow-anthropic",
providerId: "anthropic",
model: "anthropic/claude-3-haiku",
label: "Dark launch target",
},
],
sampleRate: 1,
maxTargets: 1,
},
},
};
const result = await handleComboChat({
body: { stream: true },
combo,
handleSingleModel: async (body: any, modelStr: any, target?: any) => {
calls.push({
modelStr,
trafficType: target?.trafficType ?? "production",
stream: body.stream,
});
if (target?.trafficType === "shadow") {
shadowRequests.push({
executionKey: target.executionKey,
hasBodyMarker: body._omnirouteShadowRouting === true,
});
}
if (target?.trafficType === "shadow") return errorResponse(503, "shadow failed");
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
allCombos: null,
});
await waitForBackgroundWork();
const metrics = getComboMetrics("shadow-routing-priority");
assert.equal(result.ok, true);
calls.sort((a, b) => a.trafficType.localeCompare(b.trafficType));
assert.deepEqual(calls, [
{ modelStr: "openai/gpt-4o-mini", trafficType: "production", stream: true },
{ modelStr: "anthropic/claude-3-haiku", trafficType: "shadow", stream: false },
]);
assert.deepEqual(shadowRequests, [
{ executionKey: "shadow>shadow-anthropic", hasBodyMarker: false },
]);
assert.equal(metrics.totalRequests, 1);
assert.equal(metrics.totalSuccesses, 1);
assert.equal(metrics.byModel["openai/gpt-4o-mini"].requests, 1);
assert.equal(metrics.byModel["anthropic/claude-3-haiku"], undefined);
assert.equal(metrics.shadow.totalRequests, 1);
assert.equal(metrics.shadow.totalFailures, 1);
assert.equal(metrics.shadow.byModel["anthropic/claude-3-haiku"].requests, 1);
});
test("handleComboChat isolates nested shadow request bodies", async () => {
type MutableChatBody = { messages: Array<{ role: string; content: string }>; stream: boolean };
const shadowMessages: string[] = [];
const body = { messages: [{ role: "user", content: "original" }], stream: true };
const combo = {
name: "shadow-routing-isolation",
models: ["openai/gpt-4o-mini"],
config: {
shadowRouting: {
enabled: true,
targets: ["anthropic/claude-3-haiku"],
sampleRate: 1,
},
},
};
const result = await handleComboChat({
body,
combo,
handleSingleModel: async (
requestBody: MutableChatBody,
_modelStr: unknown,
target?: unknown
) => {
const trafficType =
target && typeof target === "object" && "trafficType" in target
? (target as { trafficType?: unknown }).trafficType
: null;
if (trafficType === "shadow") {
shadowMessages.push(requestBody.messages[0].content);
return okResponse();
}
requestBody.messages[0].content = "mutated by production";
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
allCombos: null,
});
await waitForBackgroundWork();
assert.equal(result.ok, true);
assert.deepEqual(shadowMessages, ["original"]);
});
test("handleComboChat honors shadow sampleRate zero", async () => {
const calls: any[] = [];
const combo = {
name: "shadow-routing-sampled-out",
models: ["openai/gpt-4o-mini"],
config: {
shadowRouting: {
enabled: true,
targets: ["anthropic/claude-3-haiku"],
sampleRate: 0,
},
},
};
const result = await handleComboChat({
body: {},
combo,
handleSingleModel: async (_body: any, modelStr: any, target?: any) => {
calls.push({ modelStr, trafficType: target?.trafficType ?? "production" });
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
allCombos: null,
});
await waitForBackgroundWork();
const metrics = getComboMetrics("shadow-routing-sampled-out");
assert.equal(result.ok, true);
assert.deepEqual(calls, [{ modelStr: "openai/gpt-4o-mini", trafficType: "production" }]);
assert.equal(metrics.totalRequests, 1);
assert.equal(metrics.shadow.totalRequests, 0);
});
test("handleComboChat priority strategy honors composite tier order before fallback", async () => {
const calls: any[] = [];
const combo = {
name: "priority-composite-tiers",
strategy: "priority",
models: [
{
kind: "model",
id: "step-primary",
providerId: "openai",
model: "openai/gpt-4o-mini",
},
{
kind: "model",
id: "step-backup",
providerId: "anthropic",
model: "claude/sonnet",
},
{
kind: "model",
id: "step-last",
providerId: "google",
model: "gemini/gemini-2.5-flash",
},
],
config: {
maxRetries: 0,
compositeTiers: {
defaultTier: "backup",
tiers: {
backup: {
stepId: "step-backup",
fallbackTier: "primary",
},
primary: {
stepId: "step-primary",
},
},
},
},
};
const result = await handleComboChat({
body: {},
combo,
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
if (modelStr === "claude/sonnet") {
return errorResponse(503, "backup failed");
}
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["claude/sonnet", "openai/gpt-4o-mini"]);
});
test("handleComboChat weighted strategy selects by weight and falls back in descending weight order", async () => {
const originalRandom = Math.random;
const calls: any[] = [];
Math.random = () => 0.95;
try {
const result = await handleComboChat({
body: {},
combo: {
name: "weighted-selection",
strategy: "weighted",
models: [
{ model: "openai/gpt-4o-mini", weight: 1 },
{ model: "claude/sonnet", weight: 9 },
],
config: { maxRetries: 0 },
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
if (modelStr === "claude/sonnet") return errorResponse(500, "temporary");
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["claude/sonnet", "openai/gpt-4o-mini"]);
} finally {
Math.random = originalRandom;
}
});
test("handleComboChat weighted strategy falls back to uniform random when all weights are zero", async () => {
const originalRandom = Math.random;
const calls: any[] = [];
Math.random = () => 0.75;
try {
const result = await handleComboChat({
body: {},
combo: {
name: "weighted-zero-fallback",
strategy: "weighted",
models: [
{ model: "model-a", weight: 0 },
{ model: "model-b", weight: 0 },
],
config: { maxRetries: 0 },
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["model-b"]);
} finally {
Math.random = originalRandom;
}
});
test("handleComboChat random strategy uses shuffled model order", async () => {
const originalRandom = Math.random;
const calls: any[] = [];
const sequence = [0.99, 0.0];
let idx = 0;
Math.random = () => sequence[idx++] ?? 0;
try {
await handleComboChat({
body: {},
combo: {
name: "random-order",
strategy: "random",
models: ["model-a", "model-b", "model-c"],
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(calls.length, 1);
assert.notEqual(calls[0], "model-a");
} finally {
Math.random = originalRandom;
}
});
test("handleComboChat fill-first explicitly preserves priority order", async () => {
const calls: any[] = [];
await handleComboChat({
body: {},
combo: {
name: "fill-first-order",
strategy: "fill-first",
models: ["model-a", "model-b"],
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.deepEqual(calls, ["model-a"]);
});
test("handleComboChat p2c selects the better of two random choices by metrics", async () => {
const originalRandom = Math.random;
const calls: any[] = [];
const sequence = [0.0, 0.0];
let idx = 0;
recordComboRequest("p2c-combo", "model-a", {
success: true,
latencyMs: 2000,
strategy: "p2c",
});
recordComboRequest("p2c-combo", "model-b", {
success: true,
latencyMs: 20,
strategy: "p2c",
});
Math.random = () => sequence[idx++] ?? 0;
try {
await handleComboChat({
body: {},
combo: {
name: "p2c-combo",
strategy: "p2c",
models: ["model-a", "model-b", "model-c"],
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.deepEqual(calls, ["model-b"]);
} finally {
Math.random = originalRandom;
}
});
test("handleComboChat least-used strategy prefers the model with fewer recorded requests", async () => {
recordComboRequest("least-used-combo", "model-a", {
success: true,
latencyMs: 100,
strategy: "least-used",
});
recordComboRequest("least-used-combo", "model-a", {
success: true,
latencyMs: 100,
strategy: "least-used",
});
recordComboRequest("least-used-combo", "model-b", {
success: true,
latencyMs: 100,
strategy: "least-used",
});
const calls: any[] = [];
await handleComboChat({
body: {},
combo: {
name: "least-used-combo",
strategy: "least-used",
models: ["model-a", "model-b", "model-c"],
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(calls[0], "model-c");
});
test("handleComboChat skips unavailable models and falls through to the next active target", async () => {
const calls: any[] = [];
const result = await handleComboChat({
body: {},
combo: {
name: "availability-skip",
strategy: "priority",
models: ["model-a", "model-b"],
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async (modelStr) => modelStr !== "model-a",
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["model-b"]);
});
test("handleComboChat falls through empty successful responses and records failure metrics before succeeding", async () => {
const calls: any[] = [];
const result = await handleComboChat({
body: {},
combo: {
name: "quality-fallback",
strategy: "priority",
models: ["model-a", "model-b"],
config: { maxRetries: 0 },
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
if (modelStr === "model-a") {
return okResponse({ choices: [{ message: { content: "" } }] });
}
return okResponse({ choices: [{ message: { content: "fallback ok" } }] });
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
const metrics = getComboMetrics("quality-fallback");
assert.equal(result.ok, true);
assert.deepEqual(calls, ["model-a", "model-b"]);
assert.equal(metrics.totalRequests, 2);
assert.equal(metrics.totalFailures, 1);
assert.equal(metrics.totalSuccesses, 1);
assert.equal(metrics.byModel["model-a"].lastStatus, "error");
assert.equal(metrics.byModel["model-b"].lastStatus, "ok");
});
test("handleComboChat records per-target metrics separately when the same model repeats with different accounts", async () => {
const calls: any[] = [];
const combo = {
name: "per-target-repeat",
strategy: "priority",
models: [
{
kind: "model",
providerId: "openai",
model: "openai/gpt-4o-mini",
connectionId: "conn-openai-a",
label: "Account A",
},
{
kind: "model",
providerId: "openai",
model: "openai/gpt-4o-mini",
connectionId: "conn-openai-b",
label: "Account B",
},
],
config: { maxRetries: 0 },
};
const result = await handleComboChat({
body: {},
combo,
handleSingleModel: async (_body: any, modelStr: any, target: any) => {
calls.push(`${modelStr}:${target?.connectionId || "none"}`);
if (target?.connectionId === "conn-openai-a") {
return errorResponse(503, "account-a down");
}
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
const firstStep = normalizeComboStep(combo.models[0], {
comboName: combo.name,
index: 0,
});
const secondStep = normalizeComboStep(combo.models[1], {
comboName: combo.name,
index: 1,
});
const metrics = getComboMetrics(combo.name);
assert.equal(result.ok, true);
assert.deepEqual(calls, ["openai/gpt-4o-mini:conn-openai-a", "openai/gpt-4o-mini:conn-openai-b"]);
assert.equal(metrics.byModel["openai/gpt-4o-mini"].requests, 2);
assert.equal(metrics.byTarget[firstStep.id].failures, 1);
assert.equal(metrics.byTarget[firstStep.id].connectionId, "conn-openai-a");
assert.equal(metrics.byTarget[secondStep.id].successes, 1);
assert.equal(metrics.byTarget[secondStep.id].connectionId, "conn-openai-b");
});
test("handleComboChat preserves the first failure status but surfaces the last error message", async () => {
const result = await handleComboChat({
body: {},
combo: {
name: "all-fail",
strategy: "priority",
models: ["model-a", "model-b"],
config: { maxRetries: 0 },
},
handleSingleModel: async (_body: any, modelStr: any) => {
return errorResponse(modelStr === "model-a" ? 500 : 429, `fail:${modelStr}`);
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
const payload = (await result.json()) as any;
assert.equal(result.status, 500);
assert.equal(payload.error.message, "fail:model-b");
});
test("handleComboChat round-robin rotates sequentially across requests", async () => {
const calls: any[] = [];
const combo = {
name: "rr-sequence",
strategy: "round-robin",
models: ["model-a", "model-b"],
config: { maxRetries: 0, concurrencyPerModel: 1, queueTimeoutMs: 1000 },
};
for (let i = 0; i < 3; i++) {
const result = await handleComboChat({
body: {},
combo,
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
}
assert.deepEqual(calls, ["model-a", "model-b", "model-a"]);
});
test("handleComboChat round-robin starts from composite tier default ordering", async () => {
const calls: any[] = [];
const combo = {
name: "rr-composite-order",
strategy: "round-robin",
models: [
{
kind: "model",
id: "step-primary",
providerId: "openai",
model: "openai/gpt-4o-mini",
},
{
kind: "model",
id: "step-backup",
providerId: "anthropic",
model: "claude/sonnet",
},
],
config: {
maxRetries: 0,
concurrencyPerModel: 1,
queueTimeoutMs: 1000,
compositeTiers: {
defaultTier: "backup",
tiers: {
backup: {
stepId: "step-backup",
fallbackTier: "primary",
},
primary: {
stepId: "step-primary",
},
},
},
},
};
for (let i = 0; i < 2; i++) {
const result = await handleComboChat({
body: {},
combo,
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
}
assert.deepEqual(calls, ["claude/sonnet", "openai/gpt-4o-mini"]);
});
test("combo helpers short-circuit safely for missing combos, cycles, and excessive depth", () => {
assert.equal(getComboFromData("missing", null), null);
assert.equal(getComboModelsFromData("missing", { combos: [] }), null);
assert.doesNotThrow(() =>
validateComboDAG("ghost", {
combos: [{ name: "alpha", models: ["openai/gpt-4o-mini"] }],
})
);
assert.doesNotThrow(() => validateComboDAG("empty", [{ name: "empty" }]));
assert.deepEqual(
resolveNestedComboModels(
{ name: "loop", models: ["model-a", "model-b"] },
[],
new Set(["loop"])
),
[]
);
assert.deepEqual(
resolveNestedComboModels(
{ name: "deep", models: ["model-a", { model: "model-b", weight: 2 }] },
[],
new Set(),
99
),
["model-a", "model-b"]
);
});
test("handleComboChat accepts binary and Responses-style 200 bodies but falls through malformed success payloads", async () => {
const binaryResult = await handleComboChat({
body: {},
combo: {
name: "quality-binary",
strategy: "priority",
models: ["model-a"],
config: { maxRetries: 0 },
},
handleSingleModel: async () =>
new Response("binary-payload", {
status: 200,
headers: { "content-type": "application/octet-stream" },
}),
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(binaryResult.ok, true);
assert.equal(await binaryResult.text(), "binary-payload");
const responsesResult = await handleComboChat({
body: {},
combo: {
name: "quality-responses",
strategy: "priority",
models: ["model-a"],
config: { maxRetries: 0 },
},
handleSingleModel: async () =>
okResponse({
output: [{ type: "output_text", text: "done" }],
}),
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(responsesResult.ok, true);
const calls: any[] = [];
const malformedResult = await handleComboChat({
body: {},
combo: {
name: "quality-malformed",
strategy: "priority",
models: ["model-a", "model-b", "model-c"],
config: { maxRetries: 0 },
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
if (modelStr === "model-a") {
return new Response("", {
status: 200,
headers: { "content-type": "application/json" },
});
}
if (modelStr === "model-b") {
return okResponse({ choices: [{}] });
}
return okResponse({ choices: [{ message: { content: "recovered" } }] });
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(malformedResult.ok, true);
assert.deepEqual(calls, ["model-a", "model-b", "model-c"]);
});
test("handleComboChat accepts text-mode SSE payloads as valid non-streaming passthrough responses", async () => {
const result = await handleComboChat({
body: {},
combo: {
name: "quality-sse-data",
strategy: "priority",
models: ["model-a"],
config: { maxRetries: 0 },
},
handleSingleModel: async () =>
new Response('data: {"choices":[{"delta":{"content":"hello"}}]}\n\n', {
status: 200,
headers: { "content-type": "text/plain" },
}),
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.match(await result.text(), /^data:/);
});
test("handleComboChat falls through invalid JSON and embedded 200 error bodies before succeeding", async () => {
const calls: any[] = [];
const result = await handleComboChat({
body: {},
combo: {
name: "quality-invalid-json-and-error",
strategy: "priority",
models: ["model-a", "model-b", "model-c"],
config: { maxRetries: 0 },
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
if (modelStr === "model-a") {
return new Response("{bad-json", {
status: 200,
headers: { "content-type": "text/plain" },
});
}
if (modelStr === "model-b") {
return new Response(JSON.stringify({ error: { message: "embedded upstream failure" } }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
return okResponse({ choices: [{ delta: { content: "recovered" } }] });
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["model-a", "model-b", "model-c"]);
});
test("handleComboChat returns the earliest retry-after when all priority targets are rate-limited", async () => {
const soon = new Date(Date.now() + 1_000).toISOString();
const later = new Date(Date.now() + 5_000).toISOString();
const result = await handleComboChat({
body: {},
combo: {
name: "priority-retry-after",
strategy: "priority",
models: ["model-a", "model-b"],
},
handleSingleModel: async (_body: any, modelStr: any) =>
new Response(
JSON.stringify({
error: { message: `limited:${modelStr}` },
retryAfter: modelStr === "model-a" ? later : soon,
}),
{
status: 429,
headers: { "content-type": "application/json" },
}
),
isModelAvailable: async () => true,
log: createLog(),
settings: {
comboDefaults: { maxRetries: 0, retryDelayMs: 1 },
},
relayOptions: null as any,
allCombos: null,
});
const payload = (await result.json()) as any;
assert.equal(result.status, 429);
assert.match(payload.error.message, /limited:model-b/);
assert.ok(Number(result.headers.get("Retry-After")) >= 1);
});
test("handleComboChat returns 404 model_not_found when a combo has no executable targets", async () => {
const result = await handleComboChat({
body: {},
combo: {
name: "empty-priority",
strategy: "priority",
models: [],
},
handleSingleModel: async () => {
throw new Error("handleSingleModel should not run for empty combos");
},
isModelAvailable: async () => true,
log: createLog(),
settings: {
comboDefaults: {
maxRetries: 0,
retryDelayMs: 1,
},
},
relayOptions: null as any,
allCombos: null,
});
const payload = (await result.json()) as any;
assert.equal(result.status, 404);
assert.equal(payload.error.code, "model_not_found");
assert.match(payload.error.message, /Combo has no executable targets/);
});
test("handleComboChat round-robin returns 404 when no models are configured", async () => {
const result = await handleComboChat({
body: {},
combo: {
name: "rr-empty",
strategy: "round-robin",
models: [],
},
handleSingleModel: async () => {
throw new Error("handleSingleModel should not run for empty round-robin combos");
},
isModelAvailable: async () => true,
log: createLog(),
settings: {
comboDefaults: {
concurrencyPerModel: 1,
queueTimeoutMs: 5,
maxRetries: 0,
retryDelayMs: 1,
},
},
relayOptions: null as any,
allCombos: null,
});
const payload = (await result.json()) as any;
assert.equal(result.status, 404);
assert.equal(payload.error.code, "model_not_found");
assert.match(payload.error.message, /Round-robin combo has no executable targets/);
});
test("handleComboChat round-robin falls through semaphore timeouts and malformed success payloads", async () => {
const release = await acquireSemaphore(
getComboTargetExecutionKey("rr-timeout-fallback", 0, "model-a"),
{
maxConcurrency: 1,
timeoutMs: 100,
}
);
const calls: any[] = [];
try {
const result = await handleComboChat({
body: {},
combo: {
name: "rr-timeout-fallback",
strategy: "round-robin",
models: ["model-a", "model-b", "model-c"],
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
if (modelStr === "model-b") {
return okResponse({ choices: [{}] });
}
return okResponse({ choices: [{ message: { content: "rr ok" } }] });
},
isModelAvailable: async () => true,
log: createLog(),
settings: {
comboDefaults: {
concurrencyPerModel: 1,
queueTimeoutMs: 5,
maxRetries: 0,
retryDelayMs: 1,
},
},
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["model-b", "model-c"]);
} finally {
release();
}
});
test("handleComboChat round-robin surfaces retry-after metadata after exhausting all models", async () => {
const sooner = new Date(Date.now() + 1_500).toISOString();
const later = new Date(Date.now() + 7_000).toISOString();
const result = await handleComboChat({
body: {},
combo: {
name: "rr-retry-after",
strategy: "round-robin",
models: ["model-a", "model-b"],
},
handleSingleModel: async (_body: any, modelStr: any) =>
new Response(
JSON.stringify({
error: { message: `rr-limited:${modelStr}` },
retryAfter: modelStr === "model-a" ? later : sooner,
}),
{
status: 429,
headers: { "content-type": "application/json" },
}
),
isModelAvailable: async () => true,
log: createLog(),
settings: {
comboDefaults: {
concurrencyPerModel: 1,
queueTimeoutMs: 5,
maxRetries: 0,
retryDelayMs: 1,
},
},
relayOptions: null as any,
allCombos: null,
});
const payload = (await result.json()) as any;
assert.equal(result.status, 429);
assert.match(payload.error.message, /rr-limited:model-b/);
assert.ok(Number(result.headers.get("Retry-After")) >= 1);
});
test("handleComboChat falls through generic 400s when a later priority target succeeds", async () => {
const calls: any[] = [];
const result = await handleComboChat({
body: {},
combo: {
name: "priority-generic-400-recover",
strategy: "priority",
models: ["provider-a/model-a", "provider-b/model-b"],
config: { maxRetries: 0, retryDelayMs: 1 },
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
if (modelStr === "provider-a/model-a") {
return new Response(JSON.stringify({ error: { message: "Instructions are required" } }), {
status: 400,
headers: { "content-type": "application/json" },
});
}
return okResponse({ choices: [{ message: { content: "recovered" } }] });
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
const payload = (await result.json()) as any;
assert.equal(result.status, 200);
assert.equal(payload.choices[0].message.content, "recovered");
assert.deepEqual(calls, ["provider-a/model-a", "provider-b/model-b"]);
});
test("handleComboChat round-robin falls through generic 400s when a later model succeeds", async () => {
const calls: any[] = [];
const result = await handleComboChat({
body: {},
combo: {
name: "rr-generic-400-recover",
strategy: "round-robin",
models: ["model-a", "model-b"],
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
if (modelStr === "model-a") {
return new Response(JSON.stringify({ error: { message: "generic bad request" } }), {
status: 400,
headers: { "content-type": "application/json" },
});
}
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: {
comboDefaults: {
concurrencyPerModel: 1,
queueTimeoutMs: 5,
maxRetries: 0,
retryDelayMs: 1,
},
},
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.status, 200);
assert.deepEqual(calls, ["model-a", "model-b"]);
});
test("handleComboChat round-robin falls through 400s and returns the final error payload when no target recovers", async () => {
const calls: any[] = [];
const result = await handleComboChat({
body: {},
combo: {
name: "rr-provider-scoped-400-fallback",
strategy: "round-robin",
models: ["model-a", "model-b"],
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
if (modelStr === "model-a") {
return new Response(
JSON.stringify({ error: { message: "unsupported message role for this provider" } }),
{
status: 400,
headers: { "content-type": "application/json" },
}
);
}
return errorResponse(500, "rr-final-fail");
},
isModelAvailable: async () => true,
log: createLog(),
settings: {
comboDefaults: {
concurrencyPerModel: 1,
queueTimeoutMs: 5,
maxRetries: 0,
retryDelayMs: 1,
},
},
relayOptions: null as any,
allCombos: null,
});
const payload = (await result.json()) as any;
assert.equal(result.status, 400);
assert.equal(payload.error.message, "rr-final-fail");
assert.deepEqual(calls, ["model-a", "model-b"]);
});
test("handleComboChat strict-random uses the shared deck without repeating within a cycle", async () => {
const calls: any[] = [];
const combo = {
name: "strict-random-deck",
strategy: "strict-random",
models: ["model-a", "model-b", "model-c"],
};
for (let i = 0; i < 3; i++) {
const result = await handleComboChat({
body: {},
combo,
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
}
assert.equal(new Set(calls).size, 3);
});
test("handleComboChat cost-optimized orders models by the cheapest configured input price", async () => {
await settingsDb.updatePricing({
openai: {
"gpt-4o-mini": { input: 5, output: 10 },
"gpt-4o": { input: 1, output: 2 },
"gpt-4o-nano": { input: 0.1, output: 0.2 },
},
});
const calls: any[] = [];
const result = await handleComboChat({
body: {},
combo: {
name: "cost-optimized-combo",
strategy: "cost-optimized",
models: ["openai/gpt-4o-mini", "openai/gpt-4o", "openai/gpt-4o-nano"],
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.equal(calls[0], "openai/gpt-4o-nano");
});
test("handleComboChat weighted strategy resolves nested combos before falling back to the next weighted target", async () => {
const originalRandom = Math.random;
const calls: any[] = [];
Math.random = () => 0.01;
try {
const result = await handleComboChat({
body: {},
combo: {
name: "weighted-nested-selection",
strategy: "weighted",
models: [
{ model: "nested-priority", weight: 9 },
{ model: "model-c", weight: 1 },
],
config: { maxRetries: 0 },
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
if (modelStr === "model-a") return errorResponse(500, "nested-first-fail");
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: [
{
name: "weighted-nested-selection",
models: [
{ model: "nested-priority", weight: 9 },
{ model: "model-c", weight: 1 },
],
},
{ name: "nested-priority", models: ["model-a", "model-b"] },
],
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["model-a", "model-b"]);
} finally {
Math.random = originalRandom;
}
});
test("handleComboChat context-optimized orders models by the largest synced context window", async () => {
saveModelsDevCapabilities({
openai: {
"gpt-4o-mini": capabilityEntry(128000),
"gpt-4o": capabilityEntry(64000),
"gpt-4o-max": capabilityEntry(256000),
},
});
const calls: any[] = [];
const result = await handleComboChat({
body: {},
combo: {
name: "context-optimized-combo",
strategy: "context-optimized",
models: ["openai/gpt-4o-mini", "openai/gpt-4o", "openai/gpt-4o-max"],
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.equal(calls[0], "openai/gpt-4o-max");
});
test("handleComboChat context-optimized preserves order when all context limits are unknown", async () => {
const calls: any[] = [];
const result = await handleComboChat({
body: {},
combo: {
name: "context-optimized-unknown",
strategy: "context-optimized",
models: ["unknown/model-a", "unknown/model-b"],
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["unknown/model-a"]);
});
test("handleComboChat skips fallback targets with too small context windows", async () => {
saveModelsDevCapabilities({
openai: {
"tiny-context": capabilityEntry(32),
"large-context": capabilityEntry(4096),
},
});
const calls: any[] = [];
const result = await handleComboChat({
body: {
messages: [{ role: "user", content: "x".repeat(800) }],
},
combo: {
name: "context-aware-fallback-context",
strategy: "priority",
models: ["openai/tiny-context", "openai/large-context"],
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["openai/large-context"]);
});
test("handleComboChat skips tool, vision, and structured-output incompatible fallbacks", async () => {
saveModelsDevCapabilities({
openai: {
"no-tools": capabilityEntry(128000, {
tool_call: false,
attachment: true,
structured_output: true,
modalities_input: JSON.stringify(["text", "image"]),
}),
"no-vision": capabilityEntry(128000, {
tool_call: true,
attachment: false,
structured_output: true,
modalities_input: JSON.stringify(["text"]),
}),
"no-json": capabilityEntry(128000, {
tool_call: true,
attachment: true,
structured_output: false,
modalities_input: JSON.stringify(["text", "image"]),
}),
compatible: capabilityEntry(128000, {
tool_call: true,
attachment: true,
structured_output: true,
modalities_input: JSON.stringify(["text", "image"]),
}),
},
});
const calls: any[] = [];
const result = await handleComboChat({
body: {
messages: [
{
role: "user",
content: [
{ type: "text", text: "Describe this image and return JSON." },
{ type: "image_url", image_url: { url: "data:image/png;base64,AAAA" } },
],
},
],
tools: [{ type: "function", function: { name: "save", parameters: {} } }],
response_format: { type: "json_schema", json_schema: { name: "result", schema: {} } },
},
combo: {
name: "context-aware-fallback-capabilities",
strategy: "priority",
models: ["openai/no-tools", "openai/no-vision", "openai/no-json", "openai/compatible"],
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["openai/compatible"]);
});
test("handleComboChat preserves strategy order when context-aware filtering rejects all targets", async () => {
saveModelsDevCapabilities({
openai: {
"no-tools-a": capabilityEntry(128000, { tool_call: false }),
"no-tools-b": capabilityEntry(128000, { tool_call: false }),
},
});
const calls: any[] = [];
const result = await handleComboChat({
body: {
messages: [{ role: "user", content: "Use a tool." }],
tools: [{ type: "function", function: { name: "lookup", parameters: {} } }],
},
combo: {
name: "context-aware-fallback-full-filter",
strategy: "priority",
models: ["openai/no-tools-a", "openai/no-tools-b"],
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["openai/no-tools-a"]);
});
test("handleComboChat eval-driven routing prioritizes higher scoring evaluated targets", async () => {
evalsDb.saveEvalRun({
suiteId: "routing-quality",
suiteName: "Routing Quality",
target: { type: "model", id: "openai/eval-low", label: "Model: openai/eval-low" },
summary: { total: 10, passed: 4, failed: 6, passRate: 40 },
avgLatencyMs: 100,
results: [],
createdAt: new Date().toISOString(),
});
evalsDb.saveEvalRun({
suiteId: "routing-quality",
suiteName: "Routing Quality",
target: { type: "model", id: "openai/eval-high", label: "Model: openai/eval-high" },
summary: { total: 10, passed: 9, failed: 1, passRate: 90 },
avgLatencyMs: 120,
results: [],
createdAt: new Date().toISOString(),
});
const calls: any[] = [];
const result = await handleComboChat({
body: {},
combo: {
name: "eval-driven-priority",
strategy: "priority",
models: ["openai/eval-low", "openai/eval-high"],
config: {
evalRouting: {
enabled: true,
suiteIds: ["routing-quality"],
qualityWeight: 1,
latencyWeight: 0,
},
},
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["openai/eval-high"]);
});
test("handleComboChat eval-driven routing ignores stale and undersized eval runs", async () => {
evalsDb.saveEvalRun({
suiteId: "routing-quality",
suiteName: "Routing Quality",
target: { type: "model", id: "openai/stale-good", label: "Model: openai/stale-good" },
summary: { total: 10, passed: 10, failed: 0, passRate: 100 },
avgLatencyMs: 50,
results: [],
createdAt: "2020-01-01T00:00:00.000Z",
});
evalsDb.saveEvalRun({
suiteId: "routing-quality",
suiteName: "Routing Quality",
target: { type: "model", id: "openai/small-good", label: "Model: openai/small-good" },
summary: { total: 1, passed: 1, failed: 0, passRate: 100 },
avgLatencyMs: 50,
results: [],
createdAt: new Date().toISOString(),
});
const calls: any[] = [];
const result = await handleComboChat({
body: {},
combo: {
name: "eval-driven-ignored-runs",
strategy: "priority",
models: ["openai/stale-good", "openai/small-good", "openai/no-evals"],
config: {
evalRouting: {
enabled: true,
suiteIds: ["routing-quality"],
maxAgeHours: 24,
minCases: 5,
qualityWeight: 1,
latencyWeight: 0,
},
},
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["openai/stale-good"]);
});
test("handleComboChat eval-driven routing can match bare model eval target ids", async () => {
evalsDb.saveEvalRun({
suiteId: "routing-quality",
suiteName: "Routing Quality",
target: { type: "model", id: "bare-high", label: "Model: bare-high" },
summary: { total: 8, passed: 8, failed: 0, passRate: 100 },
avgLatencyMs: 200,
results: [],
createdAt: new Date().toISOString(),
});
const calls: any[] = [];
const result = await handleComboChat({
body: {},
combo: {
name: "eval-driven-bare-model-id",
strategy: "priority",
models: ["openai/bare-low", "openai/bare-high"],
config: {
evalRouting: {
enabled: true,
suiteIds: ["routing-quality"],
qualityWeight: 1,
latencyWeight: 0,
},
},
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["openai/bare-high"]);
});
test("handleComboChat normalizes legacy strategy names at runtime", async () => {
const usageCalls: any[] = [];
recordComboRequest("legacy-usage-combo", "model-a", {
success: true,
latencyMs: 100,
strategy: "least-used",
});
await handleComboChat({
body: {},
combo: {
name: "legacy-usage-combo",
strategy: "usage",
models: ["model-a", "model-b"],
},
handleSingleModel: async (_body: any, modelStr: any) => {
usageCalls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.deepEqual(usageCalls, ["model-b"]);
});
test("handleComboChat returns a 503 when every model is unavailable before execution", async () => {
const result = await handleComboChat({
body: {},
combo: {
name: "inactive-accounts",
strategy: "priority",
models: ["openai/model-a", "openai/model-b"],
},
handleSingleModel: async () => {
throw new Error("handleSingleModel should not run when all models are inactive");
},
isModelAvailable: async () => false,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
const payload = (await result.json()) as any;
assert.equal(result.status, 503);
assert.equal(payload.error.code, "ALL_ACCOUNTS_INACTIVE");
});
test("handleComboChat treats provider circuit breaker responses as ordinary target failures", async () => {
const calls = [];
const result = await handleComboChat({
body: {},
combo: {
name: "provider-breaker-open",
strategy: "priority",
models: ["openai/model-a", "openai/model-b"],
config: { maxRetries: 0 },
},
handleSingleModel: async (_body, modelStr) => {
calls.push(modelStr);
if (modelStr === "openai/model-a") {
return providerBreakerOpenResponse();
}
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["openai/model-a", "openai/model-b"]);
});
test("handleComboChat auto strategy honors LKGP after filtering to tool-capable models", async () => {
await settingsDb.setLKGP("auto-lkgp", "auto-lkgp", "claude");
const calls: any[] = [];
const result = await handleComboChat({
body: {
messages: [{ role: "user", content: "Write code using a tool" }],
tools: [{ type: "function", function: { name: "lookup_weather" } }],
},
combo: {
id: "auto-lkgp",
name: "auto-lkgp",
strategy: "auto",
models: ["openai/gpt-oss-120b", "openai/gpt-4o-mini", "claude/claude-sonnet-4-6"],
autoConfig: { routerStrategy: "lkgp" },
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.equal(calls[0], "claude/claude-sonnet-4-6");
});
test("handleComboChat standalone lkgp strategy prioritizes the last known good provider", async () => {
await settingsDb.setLKGP("standalone-lkgp", "standalone-lkgp", "anthropic");
const calls: any[] = [];
const result = await handleComboChat({
body: {},
combo: {
id: "standalone-lkgp",
name: "standalone-lkgp",
strategy: "lkgp",
models: ["openai/gpt-4o-mini", "anthropic/claude-sonnet-4-6"],
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.equal(calls[0], "anthropic/claude-sonnet-4-6");
});
test("handleComboChat standalone lkgp strategy falls back to original order when no state exists", async () => {
const calls: any[] = [];
const result = await handleComboChat({
body: {},
combo: {
id: "standalone-lkgp-no-state",
name: "standalone-lkgp-no-state",
strategy: "lkgp",
models: ["openai/gpt-4o-mini", "anthropic/claude-sonnet-4-6"],
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.equal(calls[0], "openai/gpt-4o-mini");
});
test("handleComboChat standalone lkgp strategy updates LKGP after a successful call", async () => {
const result = await handleComboChat({
body: {},
combo: {
id: "standalone-lkgp-save",
name: "standalone-lkgp-save",
strategy: "lkgp",
models: ["openai/gpt-4o-mini"],
},
handleSingleModel: async () => okResponse(),
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
// Give the async fire-and-forget LKGP update a chance to execute
let persistedProvider: any = null;
for (let i = 0; i < 20; i++) {
persistedProvider = await settingsDb.getLKGP(
"standalone-lkgp-save",
"standalone-lkgp-save"
);
if (persistedProvider?.provider === "openai") {
break;
}
await new Promise((resolve) => setTimeout(resolve, 10));
}
assert.equal(result.ok, true);
// getLKGP now returns LKGPRecord | null — source: src/lib/db/settings.ts getLKGP()
assert.equal(persistedProvider?.provider, "openai");
});
test("handleComboChat auto strategy falls back to the full pool when tool filtering empties candidates", async () => {
await settingsDb.updatePricing({
openai: {
"gpt-oss-120b": { input: 5, output: 10 },
},
deepseek: {
reasoner: { input: 0.1, output: 0.2 },
},
});
const calls: any[] = [];
const result = await handleComboChat({
body: {
input: [{ role: "user", text: "Summarize this request" }],
tools: [{ type: "function", function: { name: "unsupported_tool" } }],
},
combo: {
name: "auto-cost-fallback",
strategy: "auto",
models: ["openai/gpt-oss-120b", "deepseek/reasoner"],
autoConfig: { routerStrategy: "cost" },
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: {
intentSimpleMaxWords: 5,
intentExtraSimpleKeywords: "summarize, brief",
},
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.equal(calls[0], "deepseek/reasoner");
});
test("handleComboChat auto strategy falls back to rules when a custom router strategy throws", async () => {
registerStrategy("throwing-test", {
name: "throwing-test",
description: "test strategy that always throws",
select() {
throw new Error("synthetic router failure");
},
});
const log = createLog();
const calls: any[] = [];
const result = await handleComboChat({
body: { prompt: "Hello there" },
combo: {
name: "auto-throwing-strategy",
strategy: "auto",
models: ["openai/gpt-4o-mini"],
autoConfig: { routerStrategy: "throwing-test" },
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log,
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["openai/gpt-4o-mini"]);
assert.ok(
log.entries.some(
(entry) => entry.level === "warn" && /falling back to rules/i.test(String(entry.msg))
)
);
});
test("handleComboChat auto strategy reads strategyName from combo.config.auto and can prefer latency", async () => {
const calls: any[] = [];
const result = await handleComboChat({
body: { prompt: "Just answer briefly" },
combo: {
name: "auto-latency-strategy-name",
strategy: "auto",
models: ["openai/gpt-4o-mini", "gemini/gemini-2.5-flash"],
config: {
auto: {
strategyName: "latency",
},
},
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.equal(calls[0], "gemini/gemini-2.5-flash");
});
test("handleComboChat auto strategy can route by SLA targets", async () => {
const calls: any[] = [];
const log = createLog();
const result = await handleComboChat({
body: { prompt: "Keep this response fast and reliable" },
combo: {
name: "auto-sla-aware",
strategy: "auto",
models: ["openai/gpt-4o-mini", "gemini/gemini-2.5-flash", "claude/claude-sonnet-4-6"],
autoConfig: {
routerStrategy: "sla-aware",
slaTargetP95Ms: 1500,
slaMaxErrorRate: 0.05,
},
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log,
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.equal(calls[0], "gemini/gemini-2.5-flash");
assert.ok(
log.entries.some(
(entry) => entry.level === "info" && /strategy=sla-aware/i.test(String(entry.msg))
)
);
});
test("handleComboChat context cache protection pins the model and tags tool-call responses", async () => {
const calls: any[] = [];
const result = await handleComboChat({
body: {
model: "openai/gpt-4o-mini",
messages: [
{
role: "assistant",
content: "cached\n<omniModel>claude/claude-sonnet-4-6</omniModel>",
},
],
},
combo: {
name: "context-cache-pinned",
strategy: "priority",
models: ["openai/gpt-4o-mini", "claude/claude-sonnet-4-6"],
context_cache_protection: true,
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
return okResponse({
choices: [
{
message: {
role: "assistant",
tool_calls: [
{
id: "call_1",
type: "function",
function: { name: "lookup_weather", arguments: "{}" },
},
],
},
},
],
});
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
const payload = (await result.json()) as any;
assert.equal(result.ok, true);
assert.deepEqual(calls, ["claude/claude-sonnet-4-6"]);
assert.match(
payload.choices[0].message.content,
/<omniModel>claude\/claude-sonnet-4-6<\/omniModel>/
);
});
test("handleComboChat context cache protection preserves omniModel tag in streamed output for round-trip pinning", async () => {
const result = await handleComboChat({
body: { stream: true, messages: [{ role: "user", content: "stream it" }] },
combo: {
name: "context-cache-stream",
strategy: "priority",
models: ["openai/gpt-4o-mini"],
context_cache_protection: true,
},
handleSingleModel: async () =>
streamResponse([
'data: {"choices":[{"index":0,"delta":{"content":"hello world"},"finish_reason":null}]}\n\n',
'data: {"choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}\n\n',
"data: [DONE]\n\n",
]),
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
const text = await result.text();
assert.equal(result.ok, true);
assert.equal(result.headers.get("X-OmniRoute-Model"), "openai/gpt-4o-mini");
assert.match(text, /hello world/);
assert.match(text, /<omniModel>openai\/gpt-4o-mini<\/omniModel>/);
});
test("handleComboChat context cache protection injects a hidden tag for tool-call-only streams", async () => {
const result = await handleComboChat({
body: { stream: true, messages: [{ role: "user", content: "tool only" }] },
combo: {
name: "context-cache-tool-stream",
strategy: "priority",
models: ["openai/gpt-4o-mini"],
context_cache_protection: true,
},
handleSingleModel: async () =>
streamResponse([
'data: {"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_1","type":"function","function":{"name":"lookup","arguments":"{}"}}]},"finish_reason":null}]}\n\n',
'data: {"choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]}\n\n',
"data: [DONE]\n\n",
]),
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
const text = await result.text();
assert.equal(result.ok, true);
assert.match(text, /"finish_reason":"tool_calls"/);
assert.match(text, /<omniModel>openai\/gpt-4o-mini<\/omniModel>/);
});
test("handleComboChat context cache protection flushes cleanly when a stream ends without content", async () => {
const result = await handleComboChat({
body: { stream: true, messages: [{ role: "user", content: "empty stream" }] },
combo: {
name: "context-cache-empty-stream",
strategy: "priority",
models: ["openai/gpt-4o-mini"],
context_cache_protection: true,
},
handleSingleModel: async () => streamResponse(["data: [DONE]\n\n"]),
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
const text = await result.text();
assert.equal(result.ok, true);
assert.equal(result.headers.get("X-OmniRoute-Model"), "openai/gpt-4o-mini");
assert.match(text, /data: \[DONE\]/);
assert.match(text, /"content":"<omniModel>openai\/gpt-4o-mini<\/omniModel>"/);
});
test("handleComboChat round-robin resolves nested combos and returns inactive when every target is skipped", async () => {
const result = await handleComboChat({
body: {},
combo: {
name: "rr-nested-inactive",
strategy: "round-robin",
models: ["nested-combo"],
},
handleSingleModel: async () => {
throw new Error("round-robin should not execute when all nested targets are inactive");
},
isModelAvailable: async () => false,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: [
{ name: "rr-nested-inactive", models: ["nested-combo"] },
{ name: "nested-combo", models: ["openai/model-a"] },
],
});
const payload = (await result.json()) as any;
assert.equal(result.status, 503);
assert.equal(payload.error.code, "ALL_ACCOUNTS_INACTIVE");
});
test("handleComboChat round-robin treats provider circuit breaker responses as ordinary target failures", async () => {
const calls = [];
const result = await handleComboChat({
body: {},
combo: {
name: "rr-provider-breaker-open",
strategy: "round-robin",
models: ["openai/model-a", "openai/model-b"],
config: { maxRetries: 0 },
},
handleSingleModel: async (_body, modelStr) => {
calls.push(modelStr);
if (modelStr === "openai/model-a") {
return providerBreakerOpenResponse();
}
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["openai/model-a", "openai/model-b"]);
});
test("handleComboChat round-robin retries a transient failure on the same model before succeeding", async () => {
const calls: any[] = [];
let attempts = 0;
const result = await handleComboChat({
body: {},
combo: {
name: "rr-same-model-retry",
strategy: "round-robin",
models: ["model-a"],
config: { maxRetries: 1, retryDelayMs: 1, concurrencyPerModel: 1, queueTimeoutMs: 5 },
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
attempts += 1;
if (attempts === 1) return errorResponse(503, "try again");
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["model-a", "model-a"]);
});
test("handleComboChat round-robin recovers from 400s when a later model succeeds", async () => {
const calls: any[] = [];
const result = await handleComboChat({
body: {},
combo: {
name: "rr-provider-400-recover",
strategy: "round-robin",
models: ["model-a", "model-b"],
config: { maxRetries: 0, retryDelayMs: 1, concurrencyPerModel: 1, queueTimeoutMs: 5 },
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
if (modelStr === "model-a") {
return new Response(
JSON.stringify({ error: { message: "unsupported message role for this provider" } }),
{
status: 400,
headers: { "content-type": "application/json" },
}
);
}
return okResponse({ choices: [{ message: { content: "recovered" } }] });
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["model-a", "model-b"]);
});
test("handleComboChat single-target quality failure returns explicit quality error instead of ALL_ACCOUNTS_INACTIVE", async () => {
const result = await handleComboChat({
body: {},
combo: {
name: "single-target-quality-failure",
strategy: "priority",
models: ["openai/model-a"],
config: { maxRetries: 0 },
},
handleSingleModel: async () =>
new Response('{"choices":[{"message":{"content":"unterminated"}}', {
status: 200,
headers: { "content-type": "application/json" },
}),
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
const payload = (await result.json()) as any;
assert.equal(result.status, 502);
assert.match(payload.error.message, /quality validation/i);
assert.notEqual(payload.error.code, "ALL_ACCOUNTS_INACTIVE");
});
test("handleComboChat round-robin single-target quality failure returns explicit quality error instead of ALL_ACCOUNTS_INACTIVE", async () => {
const result = await handleComboChat({
body: {},
combo: {
name: "rr-single-target-quality-failure",
strategy: "round-robin",
models: ["openai/model-a"],
config: { maxRetries: 0, retryDelayMs: 1, concurrencyPerModel: 1, queueTimeoutMs: 5 },
},
handleSingleModel: async () =>
new Response('{"choices":[{"message":{"content":"unterminated"}}', {
status: 200,
headers: { "content-type": "application/json" },
}),
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
const payload = (await result.json()) as any;
assert.equal(result.status, 502);
assert.match(payload.error.message, /quality validation/i);
assert.notEqual(payload.error.code, "ALL_ACCOUNTS_INACTIVE");
});
test("handleComboChat falls back to next model when first model returns all-accounts-rate-limited 503", async () => {
const calls: any[] = [];
const result = await handleComboChat({
body: {},
combo: {
name: "all-accounts-rate-limited-fallback",
strategy: "priority",
models: ["model-a", "model-b"],
config: { maxRetries: 0 },
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
if (modelStr === "model-b") {
return okResponse({ choices: [{ message: { content: "ok" } }] });
}
// Simulate handleNoCredentials returning a 503 with "unavailable" message
// This is the signal emitted when getProviderCredentialsWithQuotaPreflight exhausts all accounts
return new Response(
JSON.stringify({ error: { message: `[provider/model] Service temporarily unavailable` } }),
{
status: 503,
headers: { "content-type": "application/json" },
}
);
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
const payload = (await result.json()) as any;
// First model returns 503 with "unavailable" → combo should try model-b next
// If the fix is not applied, combo would abort here and return 503 immediately
assert.equal(result.ok, true);
assert.deepEqual(calls, ["model-a", "model-b"]);
assert.equal(payload.choices[0].message.content, "ok");
});
test("handleComboChat round-robin falls back when all-accounts-rate-limited 503 is returned", async () => {
const calls: any[] = [];
// Use distinct provider prefixes so #1731 exhaustedProviders does not block model-b
// (getTargetProvider("openai/model-a") → "openai"; getTargetProvider("anthropic/model-b") → "anthropic")
const result = await handleComboChat({
body: {},
combo: {
name: "rr-all-accounts-rate-limited",
strategy: "round-robin",
models: ["openai/model-a", "anthropic/model-b"],
config: { maxRetries: 0, retryDelayMs: 1, concurrencyPerModel: 1, queueTimeoutMs: 5 },
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
if (modelStr === "anthropic/model-b") {
return okResponse({ choices: [{ message: { content: "ok" } }] });
}
// Simulate all accounts rate-limited — handleNoCredentials signal
return new Response(
JSON.stringify({ error: { message: `[provider/model] Service temporarily unavailable` } }),
{
status: 503,
headers: { "content-type": "application/json" },
}
);
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
const payload = (await result.json()) as any;
assert.equal(result.ok, true);
assert.deepEqual(calls, ["openai/model-a", "anthropic/model-b"]);
assert.equal(payload.choices[0].message.content, "ok");
});
test("handleComboChat aborts combo when 503 response does NOT contain the unavailable signal", async () => {
const calls: any[] = [];
const result = await handleComboChat({
body: {},
combo: {
name: "503-no-signal-abort",
strategy: "priority",
models: ["model-a", "model-b"],
config: { maxRetries: 0 },
},
handleSingleModel: async (_body: any, modelStr: any) => {
calls.push(modelStr);
// A generic 503 that is NOT an all-accounts-rate-limited signal
// (missing "unavailable" in message or wrong content-type)
return new Response(JSON.stringify({ error: { message: "Server error" } }), {
status: 503,
headers: { "content-type": "text/html" },
});
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
const payload = (await result.json()) as any;
// Without the fix, combo would abort (still 503). With the fix, it's still 503 because
// the signal check filters out non-JSON or non-"unavailable" responses.
assert.equal(result.status, 503);
// Model-a was tried, it failed with 503, so it fell back to model-b, which also returned 503.
assert.deepEqual(calls, ["model-a", "model-b"]);
assert.ok(
payload.error?.message?.includes("Server error") ||
payload.error?.message?.includes("unavailable") ||
result.status === 503
);
});