blackriver-gateway 3.8.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +2070 -0
- package/@omniroute/opencode-plugin/LICENSE +21 -0
- package/@omniroute/opencode-plugin/README.md +296 -0
- package/@omniroute/opencode-plugin/package-lock.json +1930 -0
- package/@omniroute/opencode-plugin/package.json +73 -0
- package/@omniroute/opencode-plugin/src/index.ts +4685 -0
- package/@omniroute/opencode-plugin/src/logger.ts +74 -0
- package/@omniroute/opencode-plugin/src/naming.ts +301 -0
- package/@omniroute/opencode-plugin/tsconfig.json +20 -0
- package/@omniroute/opencode-plugin/tsup.config.ts +20 -0
- package/@omniroute/opencode-provider/LICENSE +21 -0
- package/@omniroute/opencode-provider/README.md +156 -0
- package/@omniroute/opencode-provider/package-lock.json +1514 -0
- package/@omniroute/opencode-provider/package.json +63 -0
- package/@omniroute/opencode-provider/src/index.ts +908 -0
- package/@omniroute/opencode-provider/tsconfig.json +20 -0
- package/@omniroute/opencode-provider/tsup.config.ts +18 -0
- package/LICENSE +22 -0
- package/README.md +1265 -0
- package/bin/_ops-common.sh +70 -0
- package/bin/cli/CONVENTIONS.md +224 -0
- package/bin/cli/README.md +146 -0
- package/bin/cli/api-commands/agent-skills.mjs +70 -0
- package/bin/cli/api-commands/agentbridge.mjs +155 -0
- package/bin/cli/api-commands/api-keys.mjs +42 -0
- package/bin/cli/api-commands/audio.mjs +40 -0
- package/bin/cli/api-commands/chat.mjs +58 -0
- package/bin/cli/api-commands/cli-tools.mjs +361 -0
- package/bin/cli/api-commands/cloud.mjs +81 -0
- package/bin/cli/api-commands/combos.mjs +69 -0
- package/bin/cli/api-commands/compression.mjs +128 -0
- package/bin/cli/api-commands/embedded-services.mjs +202 -0
- package/bin/cli/api-commands/embeddings.mjs +42 -0
- package/bin/cli/api-commands/fallback.mjs +49 -0
- package/bin/cli/api-commands/images.mjs +42 -0
- package/bin/cli/api-commands/memory.mjs +251 -0
- package/bin/cli/api-commands/messages.mjs +40 -0
- package/bin/cli/api-commands/models.mjs +89 -0
- package/bin/cli/api-commands/moderations.mjs +24 -0
- package/bin/cli/api-commands/oauth.mjs +114 -0
- package/bin/cli/api-commands/playground.mjs +83 -0
- package/bin/cli/api-commands/pricing.mjs +44 -0
- package/bin/cli/api-commands/provider-nodes.mjs +62 -0
- package/bin/cli/api-commands/providers.mjs +148 -0
- package/bin/cli/api-commands/quota.mjs +154 -0
- package/bin/cli/api-commands/registry.mjs +75 -0
- package/bin/cli/api-commands/rerank.mjs +24 -0
- package/bin/cli/api-commands/responses.mjs +24 -0
- package/bin/cli/api-commands/settings.mjs +228 -0
- package/bin/cli/api-commands/system.mjs +320 -0
- package/bin/cli/api-commands/telemetry.mjs +26 -0
- package/bin/cli/api-commands/traffic-inspector.mjs +307 -0
- package/bin/cli/api-commands/translator.mjs +65 -0
- package/bin/cli/api-commands/usage.mjs +117 -0
- package/bin/cli/api.mjs +269 -0
- package/bin/cli/commands/a2a.mjs +323 -0
- package/bin/cli/commands/audit.mjs +203 -0
- package/bin/cli/commands/autostart.mjs +57 -0
- package/bin/cli/commands/backup.mjs +469 -0
- package/bin/cli/commands/batches.mjs +235 -0
- package/bin/cli/commands/cache.mjs +102 -0
- package/bin/cli/commands/chat.mjs +162 -0
- package/bin/cli/commands/cloud.mjs +233 -0
- package/bin/cli/commands/combo.mjs +361 -0
- package/bin/cli/commands/completion.mjs +346 -0
- package/bin/cli/commands/compression.mjs +247 -0
- package/bin/cli/commands/config.mjs +351 -0
- package/bin/cli/commands/configure.mjs +180 -0
- package/bin/cli/commands/connect.mjs +132 -0
- package/bin/cli/commands/context-eng.mjs +182 -0
- package/bin/cli/commands/contexts.mjs +271 -0
- package/bin/cli/commands/cost.mjs +144 -0
- package/bin/cli/commands/dashboard.mjs +65 -0
- package/bin/cli/commands/doctor.mjs +527 -0
- package/bin/cli/commands/env.mjs +100 -0
- package/bin/cli/commands/eval.mjs +278 -0
- package/bin/cli/commands/files.mjs +144 -0
- package/bin/cli/commands/health.mjs +123 -0
- package/bin/cli/commands/keys.mjs +599 -0
- package/bin/cli/commands/launch-codex.mjs +201 -0
- package/bin/cli/commands/launch.mjs +155 -0
- package/bin/cli/commands/login.mjs +192 -0
- package/bin/cli/commands/logs.mjs +183 -0
- package/bin/cli/commands/mcp.mjs +273 -0
- package/bin/cli/commands/memory.mjs +244 -0
- package/bin/cli/commands/models.mjs +92 -0
- package/bin/cli/commands/nodes.mjs +177 -0
- package/bin/cli/commands/oauth.mjs +258 -0
- package/bin/cli/commands/oneproxy.mjs +120 -0
- package/bin/cli/commands/open.mjs +75 -0
- package/bin/cli/commands/openapi.mjs +168 -0
- package/bin/cli/commands/plugin.mjs +219 -0
- package/bin/cli/commands/policy.mjs +216 -0
- package/bin/cli/commands/pricing.mjs +123 -0
- package/bin/cli/commands/provider-cmd.mjs +18 -0
- package/bin/cli/commands/providers.mjs +706 -0
- package/bin/cli/commands/quota.mjs +100 -0
- package/bin/cli/commands/redis.mjs +294 -0
- package/bin/cli/commands/registry.mjs +162 -0
- package/bin/cli/commands/repl.mjs +19 -0
- package/bin/cli/commands/reset-encrypted-columns.mjs +88 -0
- package/bin/cli/commands/resilience.mjs +208 -0
- package/bin/cli/commands/restart.mjs +25 -0
- package/bin/cli/commands/runtime.mjs +98 -0
- package/bin/cli/commands/serve.mjs +426 -0
- package/bin/cli/commands/sessions.mjs +108 -0
- package/bin/cli/commands/setup-aider.mjs +146 -0
- package/bin/cli/commands/setup-claude.mjs +219 -0
- package/bin/cli/commands/setup-cline.mjs +160 -0
- package/bin/cli/commands/setup-codex.mjs +387 -0
- package/bin/cli/commands/setup-continue.mjs +173 -0
- package/bin/cli/commands/setup-crush.mjs +148 -0
- package/bin/cli/commands/setup-cursor.mjs +108 -0
- package/bin/cli/commands/setup-goose.mjs +150 -0
- package/bin/cli/commands/setup-kilo.mjs +178 -0
- package/bin/cli/commands/setup-open-code.mjs +398 -0
- package/bin/cli/commands/setup-opencode.mjs +129 -0
- package/bin/cli/commands/setup-qwen.mjs +156 -0
- package/bin/cli/commands/setup-roo.mjs +172 -0
- package/bin/cli/commands/setup.mjs +211 -0
- package/bin/cli/commands/simulate.mjs +125 -0
- package/bin/cli/commands/skills.mjs +373 -0
- package/bin/cli/commands/status.mjs +95 -0
- package/bin/cli/commands/stop.mjs +96 -0
- package/bin/cli/commands/stream.mjs +166 -0
- package/bin/cli/commands/sync.mjs +230 -0
- package/bin/cli/commands/tags.mjs +116 -0
- package/bin/cli/commands/telemetry.mjs +74 -0
- package/bin/cli/commands/test-provider.mjs +242 -0
- package/bin/cli/commands/tokens.mjs +118 -0
- package/bin/cli/commands/translator.mjs +131 -0
- package/bin/cli/commands/tray.mjs +36 -0
- package/bin/cli/commands/tunnel.mjs +347 -0
- package/bin/cli/commands/update.mjs +194 -0
- package/bin/cli/commands/usage.mjs +331 -0
- package/bin/cli/commands/webhooks.mjs +196 -0
- package/bin/cli/contexts.mjs +60 -0
- package/bin/cli/data-dir.mjs +59 -0
- package/bin/cli/encryption.mjs +67 -0
- package/bin/cli/i18n.mjs +106 -0
- package/bin/cli/io.mjs +83 -0
- package/bin/cli/locales/ar.json +32 -0
- package/bin/cli/locales/az.json +32 -0
- package/bin/cli/locales/bg.json +32 -0
- package/bin/cli/locales/bn.json +5 -0
- package/bin/cli/locales/cs.json +32 -0
- package/bin/cli/locales/da.json +32 -0
- package/bin/cli/locales/de.json +32 -0
- package/bin/cli/locales/en.json +1289 -0
- package/bin/cli/locales/es.json +32 -0
- package/bin/cli/locales/fa.json +32 -0
- package/bin/cli/locales/fi.json +32 -0
- package/bin/cli/locales/fr.json +32 -0
- package/bin/cli/locales/gu.json +5 -0
- package/bin/cli/locales/he.json +5 -0
- package/bin/cli/locales/hi.json +32 -0
- package/bin/cli/locales/hu.json +32 -0
- package/bin/cli/locales/id.json +32 -0
- package/bin/cli/locales/in.json +5 -0
- package/bin/cli/locales/it.json +32 -0
- package/bin/cli/locales/ja.json +32 -0
- package/bin/cli/locales/ko.json +32 -0
- package/bin/cli/locales/mr.json +5 -0
- package/bin/cli/locales/ms.json +5 -0
- package/bin/cli/locales/nl.json +32 -0
- package/bin/cli/locales/no.json +32 -0
- package/bin/cli/locales/phi.json +5 -0
- package/bin/cli/locales/pl.json +32 -0
- package/bin/cli/locales/pt-BR.json +1286 -0
- package/bin/cli/locales/pt.json +32 -0
- package/bin/cli/locales/ro.json +32 -0
- package/bin/cli/locales/ru.json +32 -0
- package/bin/cli/locales/sk.json +32 -0
- package/bin/cli/locales/sv.json +32 -0
- package/bin/cli/locales/sw.json +5 -0
- package/bin/cli/locales/ta.json +5 -0
- package/bin/cli/locales/te.json +5 -0
- package/bin/cli/locales/th.json +32 -0
- package/bin/cli/locales/tr.json +32 -0
- package/bin/cli/locales/uk-UA.json +32 -0
- package/bin/cli/locales/ur.json +5 -0
- package/bin/cli/locales/vi.json +32 -0
- package/bin/cli/locales/zh-CN.json +1262 -0
- package/bin/cli/output.mjs +200 -0
- package/bin/cli/plugins.mjs +90 -0
- package/bin/cli/program.mjs +40 -0
- package/bin/cli/provider-catalog.mjs +175 -0
- package/bin/cli/provider-store.mjs +312 -0
- package/bin/cli/provider-test.mjs +181 -0
- package/bin/cli/runtime/index.mjs +19 -0
- package/bin/cli/runtime/magicBytes.mjs +47 -0
- package/bin/cli/runtime/nativeDeps.mjs +137 -0
- package/bin/cli/runtime/processSupervisor.mjs +136 -0
- package/bin/cli/runtime/sqliteRuntime.mjs +129 -0
- package/bin/cli/runtime/supervisorPolicy.mjs +56 -0
- package/bin/cli/runtime/trayRuntime.ts +94 -0
- package/bin/cli/runtime.mjs +61 -0
- package/bin/cli/schemas/output-schemas.mjs +56 -0
- package/bin/cli/scripts/generate-locales.mjs +911 -0
- package/bin/cli/settings-store.mjs +45 -0
- package/bin/cli/spinner.mjs +30 -0
- package/bin/cli/sqlite.mjs +158 -0
- package/bin/cli/tray/autostart.mjs +410 -0
- package/bin/cli/tray/autostart.ts +18 -0
- package/bin/cli/tray/icon.png +0 -0
- package/bin/cli/tray/index.mjs +28 -0
- package/bin/cli/tray/tray.ps1 +99 -0
- package/bin/cli/tray/tray.ts +186 -0
- package/bin/cli/tray/traySystray.mjs +135 -0
- package/bin/cli/tray/trayWin.ts +91 -0
- package/bin/cli/tray/trayWindows.mjs +75 -0
- package/bin/cli/tui/Dashboard.jsx +70 -0
- package/bin/cli/tui/EvalWatch.jsx +171 -0
- package/bin/cli/tui/InterfaceMenu.jsx +55 -0
- package/bin/cli/tui/OAuthFlow.jsx +193 -0
- package/bin/cli/tui/ProvidersTestAll.jsx +190 -0
- package/bin/cli/tui/Repl.jsx +392 -0
- package/bin/cli/tui/session.mjs +54 -0
- package/bin/cli/tui/tabs/Combos.jsx +47 -0
- package/bin/cli/tui/tabs/Cost.jsx +52 -0
- package/bin/cli/tui/tabs/Health.jsx +53 -0
- package/bin/cli/tui/tabs/Keys.jsx +48 -0
- package/bin/cli/tui/tabs/Logs.jsx +60 -0
- package/bin/cli/tui/tabs/Overview.jsx +80 -0
- package/bin/cli/tui/tabs/Providers.jsx +48 -0
- package/bin/cli/tui-components/CodeBlock.jsx +15 -0
- package/bin/cli/tui-components/ConfirmDialog.jsx +40 -0
- package/bin/cli/tui-components/DataTable.jsx +50 -0
- package/bin/cli/tui-components/HeaderSwr.jsx +25 -0
- package/bin/cli/tui-components/KeyMaskedDisplay.jsx +12 -0
- package/bin/cli/tui-components/MarkdownView.jsx +13 -0
- package/bin/cli/tui-components/MenuSelect.jsx +38 -0
- package/bin/cli/tui-components/MultilineInput.jsx +18 -0
- package/bin/cli/tui-components/ProgressBar.jsx +15 -0
- package/bin/cli/tui-components/Sparkline.jsx +15 -0
- package/bin/cli/tui-components/StatusBadge.jsx +17 -0
- package/bin/cli/tui-components/TokenCounter.jsx +18 -0
- package/bin/cli/tui-components/theme.jsx +11 -0
- package/bin/cli/utils/cliToken.mjs +22 -0
- package/bin/cli/utils/clipboard.mjs +35 -0
- package/bin/cli/utils/environment.mjs +50 -0
- package/bin/cli/utils/pid.mjs +110 -0
- package/bin/cli/utils/storageKeyProvision.mjs +31 -0
- package/bin/cold-start-bench.sh +82 -0
- package/bin/mcp-server.mjs +71 -0
- package/bin/nodeRuntimeSupport.mjs +84 -0
- package/bin/omniroute.mjs +237 -0
- package/bin/reset-password.mjs +137 -0
- package/bin/restore-data.sh +64 -0
- package/bin/restore-policies.sh +77 -0
- package/bin/rollback.sh +102 -0
- package/bin/snapshot-data.sh +61 -0
- package/open-sse/config/agyModels.ts +157 -0
- package/open-sse/config/anthropicHeaders.ts +127 -0
- package/open-sse/config/antigravityModelAliases.ts +309 -0
- package/open-sse/config/antigravityUpstream.ts +20 -0
- package/open-sse/config/audioRegistry.ts +547 -0
- package/open-sse/config/azureAi.ts +49 -0
- package/open-sse/config/bedrock.ts +196 -0
- package/open-sse/config/cliFingerprints.ts +405 -0
- package/open-sse/config/codexClient.ts +49 -0
- package/open-sse/config/codexIdentity.ts +87 -0
- package/open-sse/config/codexInstructions.ts +122 -0
- package/open-sse/config/codexQuotaScopes.ts +87 -0
- package/open-sse/config/constants.ts +281 -0
- package/open-sse/config/contextEditing.ts +156 -0
- package/open-sse/config/credentialLoader.ts +123 -0
- package/open-sse/config/datarobot.ts +69 -0
- package/open-sse/config/defaultThinkingSignature.ts +8 -0
- package/open-sse/config/embeddingRegistry.ts +434 -0
- package/open-sse/config/errorConfig.ts +201 -0
- package/open-sse/config/freeModelCatalog.data.ts +467 -0
- package/open-sse/config/freeModelCatalog.ts +142 -0
- package/open-sse/config/freeTierCatalog.ts +101 -0
- package/open-sse/config/geminiRateLimits.json +34 -0
- package/open-sse/config/glmProvider.ts +488 -0
- package/open-sse/config/imageRegistry.ts +752 -0
- package/open-sse/config/maritalk.ts +18 -0
- package/open-sse/config/mediaServiceKinds.ts +70 -0
- package/open-sse/config/moderationRegistry.ts +89 -0
- package/open-sse/config/musicRegistry.ts +115 -0
- package/open-sse/config/oci.ts +44 -0
- package/open-sse/config/ocrRegistry.ts +82 -0
- package/open-sse/config/ollamaModels.ts +28 -0
- package/open-sse/config/providerErrorRules.ts +218 -0
- package/open-sse/config/providerFieldStrips.ts +42 -0
- package/open-sse/config/providerHeaderProfiles.ts +188 -0
- package/open-sse/config/providerModels.ts +199 -0
- package/open-sse/config/providerPluginManifest.ts +186 -0
- package/open-sse/config/providerPluginManifestRegistry.ts +31 -0
- package/open-sse/config/providerPluginManifestUrl.ts +26 -0
- package/open-sse/config/providerRegistry.ts +257 -0
- package/open-sse/config/providers/index.ts +377 -0
- package/open-sse/config/providers/registry/adapta-web/index.ts +20 -0
- package/open-sse/config/providers/registry/agentrouter/index.ts +24 -0
- package/open-sse/config/providers/registry/agy/index.ts +28 -0
- package/open-sse/config/providers/registry/ai21/index.ts +13 -0
- package/open-sse/config/providers/registry/aimlapi/index.ts +21 -0
- package/open-sse/config/providers/registry/alibaba/cn/index.ts +15 -0
- package/open-sse/config/providers/registry/alibaba/index.ts +15 -0
- package/open-sse/config/providers/registry/anthropic/index.ts +52 -0
- package/open-sse/config/providers/registry/antigravity/index.ts +28 -0
- package/open-sse/config/providers/registry/api-airforce/index.ts +57 -0
- package/open-sse/config/providers/registry/auggie/index.ts +38 -0
- package/open-sse/config/providers/registry/bai/index.ts +14 -0
- package/open-sse/config/providers/registry/baichuan/index.ts +20 -0
- package/open-sse/config/providers/registry/baidu/index.ts +31 -0
- package/open-sse/config/providers/registry/bailian-coding-plan/index.ts +27 -0
- package/open-sse/config/providers/registry/baseten/index.ts +13 -0
- package/open-sse/config/providers/registry/bazaarlink/index.ts +48 -0
- package/open-sse/config/providers/registry/bedrock/index.ts +49 -0
- package/open-sse/config/providers/registry/blackbox/index.ts +25 -0
- package/open-sse/config/providers/registry/blackbox/web/index.ts +19 -0
- package/open-sse/config/providers/registry/bluesminds/index.ts +39 -0
- package/open-sse/config/providers/registry/byteplus/index.ts +21 -0
- package/open-sse/config/providers/registry/bytez/index.ts +17 -0
- package/open-sse/config/providers/registry/cerebras/index.ts +16 -0
- package/open-sse/config/providers/registry/charm-hyper/index.ts +14 -0
- package/open-sse/config/providers/registry/chatgpt-web/index.ts +23 -0
- package/open-sse/config/providers/registry/chipotle/index.ts +14 -0
- package/open-sse/config/providers/registry/chutes/index.ts +12 -0
- package/open-sse/config/providers/registry/claude/index.ts +102 -0
- package/open-sse/config/providers/registry/claude/web/index.ts +16 -0
- package/open-sse/config/providers/registry/cline/index.ts +45 -0
- package/open-sse/config/providers/registry/clinepass/index.ts +47 -0
- package/open-sse/config/providers/registry/cloudflare-ai/index.ts +33 -0
- package/open-sse/config/providers/registry/codebuddy-cn/index.ts +151 -0
- package/open-sse/config/providers/registry/codestral/index.ts +13 -0
- package/open-sse/config/providers/registry/codex/index.ts +115 -0
- package/open-sse/config/providers/registry/cohere/index.ts +28 -0
- package/open-sse/config/providers/registry/command-code/index.ts +143 -0
- package/open-sse/config/providers/registry/copilot-m365-web/index.ts +12 -0
- package/open-sse/config/providers/registry/copilot-web/index.ts +16 -0
- package/open-sse/config/providers/registry/coze/index.ts +12 -0
- package/open-sse/config/providers/registry/crof/index.ts +38 -0
- package/open-sse/config/providers/registry/cursor/index.ts +111 -0
- package/open-sse/config/providers/registry/databricks/index.ts +13 -0
- package/open-sse/config/providers/registry/deepinfra/index.ts +13 -0
- package/open-sse/config/providers/registry/deepseek/index.ts +15 -0
- package/open-sse/config/providers/registry/deepseek/web/index.ts +35 -0
- package/open-sse/config/providers/registry/devin-cli/index.ts +68 -0
- package/open-sse/config/providers/registry/dgrid/index.ts +15 -0
- package/open-sse/config/providers/registry/dify/index.ts +12 -0
- package/open-sse/config/providers/registry/digitalocean/index.ts +11 -0
- package/open-sse/config/providers/registry/dit/index.ts +41 -0
- package/open-sse/config/providers/registry/doubao/index.ts +28 -0
- package/open-sse/config/providers/registry/doubao/web/index.ts +15 -0
- package/open-sse/config/providers/registry/duckduckgo-web/index.ts +19 -0
- package/open-sse/config/providers/registry/factory/index.ts +33 -0
- package/open-sse/config/providers/registry/featherless-ai/index.ts +13 -0
- package/open-sse/config/providers/registry/fireworks/index.ts +35 -0
- package/open-sse/config/providers/registry/freeaiapikey/index.ts +38 -0
- package/open-sse/config/providers/registry/freemodel-dev/index.ts +19 -0
- package/open-sse/config/providers/registry/friendliai/index.ts +16 -0
- package/open-sse/config/providers/registry/galadriel/index.ts +13 -0
- package/open-sse/config/providers/registry/gemini/index.ts +63 -0
- package/open-sse/config/providers/registry/gemini/web/index.ts +16 -0
- package/open-sse/config/providers/registry/gigachat/index.ts +13 -0
- package/open-sse/config/providers/registry/github/index.ts +161 -0
- package/open-sse/config/providers/registry/github/models/index.ts +38 -0
- package/open-sse/config/providers/registry/gitlab-duo/index.ts +29 -0
- package/open-sse/config/providers/registry/gitlawb/gmi/index.ts +19 -0
- package/open-sse/config/providers/registry/gitlawb/index.ts +18 -0
- package/open-sse/config/providers/registry/glhf/index.ts +12 -0
- package/open-sse/config/providers/registry/glm/cn/index.ts +17 -0
- package/open-sse/config/providers/registry/glm/index.ts +16 -0
- package/open-sse/config/providers/registry/glm/t/index.ts +16 -0
- package/open-sse/config/providers/registry/grok-cli/index.ts +32 -0
- package/open-sse/config/providers/registry/grok-web/index.ts +18 -0
- package/open-sse/config/providers/registry/groq/index.ts +25 -0
- package/open-sse/config/providers/registry/hackclub/index.ts +19 -0
- package/open-sse/config/providers/registry/haiper/index.ts +15 -0
- package/open-sse/config/providers/registry/hcnsec/index.ts +11 -0
- package/open-sse/config/providers/registry/heroku/index.ts +13 -0
- package/open-sse/config/providers/registry/huggingchat/index.ts +143 -0
- package/open-sse/config/providers/registry/huggingface/index.ts +20 -0
- package/open-sse/config/providers/registry/hyperbolic/index.ts +21 -0
- package/open-sse/config/providers/registry/ideogram/index.ts +15 -0
- package/open-sse/config/providers/registry/iflytek/index.ts +21 -0
- package/open-sse/config/providers/registry/inclusionai/index.ts +12 -0
- package/open-sse/config/providers/registry/inference-net/index.ts +13 -0
- package/open-sse/config/providers/registry/inner-ai/index.ts +43 -0
- package/open-sse/config/providers/registry/kenari/index.ts +14 -0
- package/open-sse/config/providers/registry/kie/index.ts +27 -0
- package/open-sse/config/providers/registry/kilo-gateway/index.ts +21 -0
- package/open-sse/config/providers/registry/kilocode/index.ts +43 -0
- package/open-sse/config/providers/registry/kimi/coding/index.ts +17 -0
- package/open-sse/config/providers/registry/kimi/coding-apikey/index.ts +9 -0
- package/open-sse/config/providers/registry/kimi/index.ts +17 -0
- package/open-sse/config/providers/registry/kimi/web/index.ts +27 -0
- package/open-sse/config/providers/registry/kiro/index.ts +50 -0
- package/open-sse/config/providers/registry/kluster/index.ts +12 -0
- package/open-sse/config/providers/registry/lambda-ai/index.ts +13 -0
- package/open-sse/config/providers/registry/leonardo/index.ts +15 -0
- package/open-sse/config/providers/registry/liquid/index.ts +12 -0
- package/open-sse/config/providers/registry/llamagate/index.ts +13 -0
- package/open-sse/config/providers/registry/llm7/index.ts +27 -0
- package/open-sse/config/providers/registry/longcat/index.ts +24 -0
- package/open-sse/config/providers/registry/maritalk/index.ts +13 -0
- package/open-sse/config/providers/registry/meta-llama/index.ts +13 -0
- package/open-sse/config/providers/registry/mimocode/index.ts +16 -0
- package/open-sse/config/providers/registry/minimax/cn/index.ts +24 -0
- package/open-sse/config/providers/registry/minimax/index.ts +24 -0
- package/open-sse/config/providers/registry/mistral/index.ts +18 -0
- package/open-sse/config/providers/registry/modal/index.ts +12 -0
- package/open-sse/config/providers/registry/modelscope/index.ts +24 -0
- package/open-sse/config/providers/registry/monsterapi/index.ts +17 -0
- package/open-sse/config/providers/registry/moonshot/index.ts +13 -0
- package/open-sse/config/providers/registry/morph/index.ts +13 -0
- package/open-sse/config/providers/registry/muse-spark-web/index.ts +24 -0
- package/open-sse/config/providers/registry/nanogpt/index.ts +13 -0
- package/open-sse/config/providers/registry/nebius/index.ts +9 -0
- package/open-sse/config/providers/registry/nlpcloud/index.ts +19 -0
- package/open-sse/config/providers/registry/nous-research/index.ts +15 -0
- package/open-sse/config/providers/registry/novita/index.ts +15 -0
- package/open-sse/config/providers/registry/nscale/index.ts +13 -0
- package/open-sse/config/providers/registry/nube/index.ts +17 -0
- package/open-sse/config/providers/registry/nvidia/index.ts +36 -0
- package/open-sse/config/providers/registry/ollama-cloud/index.ts +27 -0
- package/open-sse/config/providers/registry/openadapter/index.ts +25 -0
- package/open-sse/config/providers/registry/openai/index.ts +43 -0
- package/open-sse/config/providers/registry/opencode/go/index.ts +61 -0
- package/open-sse/config/providers/registry/opencode/index.ts +51 -0
- package/open-sse/config/providers/registry/opencode/zen/index.ts +98 -0
- package/open-sse/config/providers/registry/openrouter/index.ts +17 -0
- package/open-sse/config/providers/registry/orcarouter/index.ts +87 -0
- package/open-sse/config/providers/registry/ovhcloud/index.ts +13 -0
- package/open-sse/config/providers/registry/perplexity/index.ts +22 -0
- package/open-sse/config/providers/registry/perplexity/web/index.ts +23 -0
- package/open-sse/config/providers/registry/pioneer/index.ts +41 -0
- package/open-sse/config/providers/registry/pollinations/index.ts +51 -0
- package/open-sse/config/providers/registry/predibase/index.ts +13 -0
- package/open-sse/config/providers/registry/publicai/index.ts +13 -0
- package/open-sse/config/providers/registry/puter/index.ts +70 -0
- package/open-sse/config/providers/registry/qianfan/index.ts +18 -0
- package/open-sse/config/providers/registry/qiniu/index.ts +15 -0
- package/open-sse/config/providers/registry/qoder/index.ts +37 -0
- package/open-sse/config/providers/registry/qwen/index.ts +25 -0
- package/open-sse/config/providers/registry/qwen/web/index.ts +24 -0
- package/open-sse/config/providers/registry/reka/index.ts +18 -0
- package/open-sse/config/providers/registry/requesty/index.ts +11 -0
- package/open-sse/config/providers/registry/sambanova/index.ts +13 -0
- package/open-sse/config/providers/registry/scaleway/index.ts +20 -0
- package/open-sse/config/providers/registry/sensenova/index.ts +27 -0
- package/open-sse/config/providers/registry/siliconflow/index.ts +84 -0
- package/open-sse/config/providers/registry/snowflake/index.ts +13 -0
- package/open-sse/config/providers/registry/sparkdesk/index.ts +20 -0
- package/open-sse/config/providers/registry/stepfun/index.ts +20 -0
- package/open-sse/config/providers/registry/sumopod/index.ts +15 -0
- package/open-sse/config/providers/registry/suno/index.ts +18 -0
- package/open-sse/config/providers/registry/synthetic/index.ts +21 -0
- package/open-sse/config/providers/registry/t3-web/index.ts +45 -0
- package/open-sse/config/providers/registry/tencent/index.ts +25 -0
- package/open-sse/config/providers/registry/theoldllm/index.ts +51 -0
- package/open-sse/config/providers/registry/together/index.ts +20 -0
- package/open-sse/config/providers/registry/tokenrouter/index.ts +44 -0
- package/open-sse/config/providers/registry/trae/index.ts +24 -0
- package/open-sse/config/providers/registry/udio/index.ts +12 -0
- package/open-sse/config/providers/registry/uncloseai/index.ts +19 -0
- package/open-sse/config/providers/registry/upstage/index.ts +13 -0
- package/open-sse/config/providers/registry/v0-vercel/index.ts +13 -0
- package/open-sse/config/providers/registry/venice/index.ts +13 -0
- package/open-sse/config/providers/registry/veoaifree-web/index.ts +15 -0
- package/open-sse/config/providers/registry/vercel-ai-gateway/index.ts +13 -0
- package/open-sse/config/providers/registry/vertex/index.ts +33 -0
- package/open-sse/config/providers/registry/vertex/partner/index.ts +22 -0
- package/open-sse/config/providers/registry/volcengine/index.ts +13 -0
- package/open-sse/config/providers/registry/wafer/index.ts +19 -0
- package/open-sse/config/providers/registry/wandb/index.ts +13 -0
- package/open-sse/config/providers/registry/windsurf/index.ts +119 -0
- package/open-sse/config/providers/registry/x5lab/index.ts +15 -0
- package/open-sse/config/providers/registry/xai/index.ts +18 -0
- package/open-sse/config/providers/registry/xiaomi-mimo/index.ts +13 -0
- package/open-sse/config/providers/registry/yi/index.ts +12 -0
- package/open-sse/config/providers/registry/yuanbao-web/index.ts +37 -0
- package/open-sse/config/providers/registry/zai/index.ts +28 -0
- package/open-sse/config/providers/registry/zed-hosted/index.ts +35 -0
- package/open-sse/config/providers/registry/zenmux/index.ts +87 -0
- package/open-sse/config/providers/registry/zenmux-free/index.ts +40 -0
- package/open-sse/config/providers/shared.ts +766 -0
- package/open-sse/config/registryUtils.ts +134 -0
- package/open-sse/config/rerankRegistry.ts +196 -0
- package/open-sse/config/runway.ts +39 -0
- package/open-sse/config/sap.ts +58 -0
- package/open-sse/config/searchRegistry.ts +317 -0
- package/open-sse/config/toolCloaking.ts +253 -0
- package/open-sse/config/videoRegistry.ts +229 -0
- package/open-sse/config/watsonx.ts +43 -0
- package/open-sse/executors/adapta-web.ts +564 -0
- package/open-sse/executors/antigravity/sseCollect.ts +148 -0
- package/open-sse/executors/antigravity.ts +1716 -0
- package/open-sse/executors/antigravityUpstreamError.ts +25 -0
- package/open-sse/executors/auggie.ts +573 -0
- package/open-sse/executors/azure-openai.ts +48 -0
- package/open-sse/executors/base/headers.ts +93 -0
- package/open-sse/executors/base/reasoningEffort.ts +160 -0
- package/open-sse/executors/base.ts +1315 -0
- package/open-sse/executors/bedrock.ts +714 -0
- package/open-sse/executors/blackbox-web.ts +708 -0
- package/open-sse/executors/chatgpt-web/models.ts +133 -0
- package/open-sse/executors/chatgpt-web.ts +3107 -0
- package/open-sse/executors/chatgptWebErrors.ts +18 -0
- package/open-sse/executors/chatgptWebTools.ts +127 -0
- package/open-sse/executors/chipotle.ts +432 -0
- package/open-sse/executors/claude-web/payload.ts +230 -0
- package/open-sse/executors/claude-web-with-auto-refresh.ts +117 -0
- package/open-sse/executors/claude-web.ts +833 -0
- package/open-sse/executors/claudeIdentity.ts +445 -0
- package/open-sse/executors/cliproxyapi.ts +466 -0
- package/open-sse/executors/cloudflare-ai.ts +97 -0
- package/open-sse/executors/codebuddy-cn.ts +53 -0
- package/open-sse/executors/codex/quota.ts +114 -0
- package/open-sse/executors/codex/tools.ts +165 -0
- package/open-sse/executors/codex.ts +1270 -0
- package/open-sse/executors/commandCode.ts +716 -0
- package/open-sse/executors/copilot-m365-connection.ts +271 -0
- package/open-sse/executors/copilot-m365-frames.ts +279 -0
- package/open-sse/executors/copilot-m365-web.ts +341 -0
- package/open-sse/executors/copilot-web.ts +722 -0
- package/open-sse/executors/cursor/composer.ts +53 -0
- package/open-sse/executors/cursor/prompt.ts +75 -0
- package/open-sse/executors/cursor.ts +1465 -0
- package/open-sse/executors/deepseek-web/stream-format.ts +44 -0
- package/open-sse/executors/deepseek-web-with-auto-refresh.ts +184 -0
- package/open-sse/executors/deepseek-web.ts +1123 -0
- package/open-sse/executors/default/urlNormalizers.ts +64 -0
- package/open-sse/executors/default.ts +873 -0
- package/open-sse/executors/devin-cli.ts +470 -0
- package/open-sse/executors/doubao-web.ts +665 -0
- package/open-sse/executors/duckduckgo-web/challenge.ts +151 -0
- package/open-sse/executors/duckduckgo-web.ts +782 -0
- package/open-sse/executors/firecrawl-fetch.ts +177 -0
- package/open-sse/executors/forceResponsesUpstream.ts +60 -0
- package/open-sse/executors/gemini-business.ts +446 -0
- package/open-sse/executors/gemini-web.ts +413 -0
- package/open-sse/executors/github.ts +376 -0
- package/open-sse/executors/gitlab.ts +793 -0
- package/open-sse/executors/gitlabResponses.ts +191 -0
- package/open-sse/executors/glm.ts +520 -0
- package/open-sse/executors/grok-cli.ts +253 -0
- package/open-sse/executors/grok-web/native-tools.ts +182 -0
- package/open-sse/executors/grok-web/text-cleanup.ts +137 -0
- package/open-sse/executors/grok-web/tool-bridge.ts +666 -0
- package/open-sse/executors/grok-web/types.ts +47 -0
- package/open-sse/executors/grok-web.ts +883 -0
- package/open-sse/executors/huggingchat/jsonlStream.ts +221 -0
- package/open-sse/executors/huggingchat.ts +593 -0
- package/open-sse/executors/index.ts +232 -0
- package/open-sse/executors/inner-ai.ts +764 -0
- package/open-sse/executors/jina-reader-fetch.ts +119 -0
- package/open-sse/executors/kie.ts +109 -0
- package/open-sse/executors/kimi-web.ts +455 -0
- package/open-sse/executors/kimi.ts +133 -0
- package/open-sse/executors/kiro/eventstream.ts +192 -0
- package/open-sse/executors/kiro.ts +821 -0
- package/open-sse/executors/kiroThinking.ts +116 -0
- package/open-sse/executors/lmarena.ts +511 -0
- package/open-sse/executors/mimocode.ts +560 -0
- package/open-sse/executors/muse-spark-web/response-parser.ts +383 -0
- package/open-sse/executors/muse-spark-web.ts +928 -0
- package/open-sse/executors/ninerouter.ts +212 -0
- package/open-sse/executors/nlpcloud.ts +546 -0
- package/open-sse/executors/opencode.ts +336 -0
- package/open-sse/executors/perplexity-web/protocol.ts +503 -0
- package/open-sse/executors/perplexity-web.ts +545 -0
- package/open-sse/executors/poe-web.ts +158 -0
- package/open-sse/executors/pollinations.ts +119 -0
- package/open-sse/executors/puter.ts +59 -0
- package/open-sse/executors/qoder.ts +416 -0
- package/open-sse/executors/qwen-web.ts +474 -0
- package/open-sse/executors/t3-chat-web.ts +582 -0
- package/open-sse/executors/tavily-fetch.ts +103 -0
- package/open-sse/executors/theoldllm.ts +354 -0
- package/open-sse/executors/tinyfish-fetch.ts +131 -0
- package/open-sse/executors/trae.ts +481 -0
- package/open-sse/executors/v0-vercel-web.ts +164 -0
- package/open-sse/executors/venice-web.ts +165 -0
- package/open-sse/executors/veoaifree-web.ts +397 -0
- package/open-sse/executors/vertex.ts +208 -0
- package/open-sse/executors/vertexMedia.ts +341 -0
- package/open-sse/executors/windsurf.ts +706 -0
- package/open-sse/executors/xai.ts +94 -0
- package/open-sse/executors/yuanbao-web.ts +504 -0
- package/open-sse/executors/zed-hosted.ts +364 -0
- package/open-sse/executors/zenmux-free.ts +283 -0
- package/open-sse/handlers/audioSpeech.ts +1060 -0
- package/open-sse/handlers/audioTranscription.ts +554 -0
- package/open-sse/handlers/audioTranslation.ts +135 -0
- package/open-sse/handlers/chatCore/attemptLogging.ts +210 -0
- package/open-sse/handlers/chatCore/backgroundRedirect.ts +41 -0
- package/open-sse/handlers/chatCore/cacheUsageMeta.ts +65 -0
- package/open-sse/handlers/chatCore/cavemanOutputAnalytics.ts +47 -0
- package/open-sse/handlers/chatCore/claudeClassifierCompat.ts +99 -0
- package/open-sse/handlers/chatCore/claudeEffortVariant.ts +62 -0
- package/open-sse/handlers/chatCore/claudeMessageTypes.ts +15 -0
- package/open-sse/handlers/chatCore/claudeSystemRole.ts +48 -0
- package/open-sse/handlers/chatCore/claudeToolDefaults.ts +27 -0
- package/open-sse/handlers/chatCore/claudeUpstreamMessages.ts +143 -0
- package/open-sse/handlers/chatCore/clientUsageBuffer.ts +54 -0
- package/open-sse/handlers/chatCore/clineResponseEnvelope.ts +25 -0
- package/open-sse/handlers/chatCore/codexFailover.ts +41 -0
- package/open-sse/handlers/chatCore/codexQuota.ts +85 -0
- package/open-sse/handlers/chatCore/comboContextCache.ts +63 -0
- package/open-sse/handlers/chatCore/compressionAnalyticsWrite.ts +149 -0
- package/open-sse/handlers/chatCore/compressionCacheStats.ts +53 -0
- package/open-sse/handlers/chatCore/compressionComboPredicates.ts +41 -0
- package/open-sse/handlers/chatCore/compressionSettings.ts +35 -0
- package/open-sse/handlers/chatCore/compressionUsageReceipt.ts +27 -0
- package/open-sse/handlers/chatCore/contextEditingTelemetry.ts +40 -0
- package/open-sse/handlers/chatCore/cooldownClassification.ts +26 -0
- package/open-sse/handlers/chatCore/executionCredentials.ts +72 -0
- package/open-sse/handlers/chatCore/executorClientHeaders.ts +36 -0
- package/open-sse/handlers/chatCore/executorHelpers.ts +151 -0
- package/open-sse/handlers/chatCore/executorProxy.ts +117 -0
- package/open-sse/handlers/chatCore/failureUsage.ts +41 -0
- package/open-sse/handlers/chatCore/gamificationEvent.ts +28 -0
- package/open-sse/handlers/chatCore/headers.ts +65 -0
- package/open-sse/handlers/chatCore/idempotency.ts +65 -0
- package/open-sse/handlers/chatCore/jsonBodyToSse.ts +161 -0
- package/open-sse/handlers/chatCore/keyHealth.ts +113 -0
- package/open-sse/handlers/chatCore/logTruncation.ts +93 -0
- package/open-sse/handlers/chatCore/memoryExtraction.ts +131 -0
- package/open-sse/handlers/chatCore/memorySkillsInjection.ts +165 -0
- package/open-sse/handlers/chatCore/nonStreamingJsonResponse.ts +16 -0
- package/open-sse/handlers/chatCore/nonStreamingResponseBody.ts +155 -0
- package/open-sse/handlers/chatCore/nonStreamingResponseHeaders.ts +46 -0
- package/open-sse/handlers/chatCore/nonStreamingResponseParse.ts +135 -0
- package/open-sse/handlers/chatCore/nonStreamingSse.ts +171 -0
- package/open-sse/handlers/chatCore/nonStreamingUsageStats.ts +90 -0
- package/open-sse/handlers/chatCore/outputStyleTelemetry.ts +53 -0
- package/open-sse/handlers/chatCore/passthroughHelpers.ts +83 -0
- package/open-sse/handlers/chatCore/passthroughToolNames.ts +65 -0
- package/open-sse/handlers/chatCore/pluginOnRequest.ts +65 -0
- package/open-sse/handlers/chatCore/pluginOnResponse.ts +34 -0
- package/open-sse/handlers/chatCore/postCallGuardrailContext.ts +47 -0
- package/open-sse/handlers/chatCore/quotaShareConsumption.ts +41 -0
- package/open-sse/handlers/chatCore/requestFormat.ts +111 -0
- package/open-sse/handlers/chatCore/requestSetup.ts +51 -0
- package/open-sse/handlers/chatCore/responseHeaders.ts +138 -0
- package/open-sse/handlers/chatCore/sanitization.ts +63 -0
- package/open-sse/handlers/chatCore/semanticCache.ts +98 -0
- package/open-sse/handlers/chatCore/semanticCacheStore.ts +73 -0
- package/open-sse/handlers/chatCore/serviceTier.ts +70 -0
- package/open-sse/handlers/chatCore/skillsFormat.ts +33 -0
- package/open-sse/handlers/chatCore/stageTrace.ts +30 -0
- package/open-sse/handlers/chatCore/streamErrorResult.ts +58 -0
- package/open-sse/handlers/chatCore/streamFinalize.ts +48 -0
- package/open-sse/handlers/chatCore/streamingCost.ts +37 -0
- package/open-sse/handlers/chatCore/streamingPipeline.ts +99 -0
- package/open-sse/handlers/chatCore/streamingQuotaShare.ts +54 -0
- package/open-sse/handlers/chatCore/streamingResponseHeaders.ts +39 -0
- package/open-sse/handlers/chatCore/streamingSemanticCacheStore.ts +95 -0
- package/open-sse/handlers/chatCore/streamingUsageStats.ts +79 -0
- package/open-sse/handlers/chatCore/targetFormat.ts +34 -0
- package/open-sse/handlers/chatCore/telemetryHelpers.ts +78 -0
- package/open-sse/handlers/chatCore/upstreamBody.ts +168 -0
- package/open-sse/handlers/chatCore/upstreamExecuteHeaders.ts +71 -0
- package/open-sse/handlers/chatCore/upstreamTimeouts.ts +158 -0
- package/open-sse/handlers/chatCore.ts +4452 -0
- package/open-sse/handlers/embeddings.ts +378 -0
- package/open-sse/handlers/imageGeneration/providers/chatgptWeb.ts +187 -0
- package/open-sse/handlers/imageGeneration/providers/comfyUI.ts +116 -0
- package/open-sse/handlers/imageGeneration/providers/haiper.ts +120 -0
- package/open-sse/handlers/imageGeneration/providers/huggingface.ts +90 -0
- package/open-sse/handlers/imageGeneration/providers/hyperbolic.ts +100 -0
- package/open-sse/handlers/imageGeneration/providers/ideogram.ts +96 -0
- package/open-sse/handlers/imageGeneration/providers/imagen3.ts +136 -0
- package/open-sse/handlers/imageGeneration/providers/leonardo.ts +140 -0
- package/open-sse/handlers/imageGeneration/providers/nvidiaNim.ts +286 -0
- package/open-sse/handlers/imageGeneration/providers/sdWebUI.ts +94 -0
- package/open-sse/handlers/imageGeneration.ts +2880 -0
- package/open-sse/handlers/moderations.ts +82 -0
- package/open-sse/handlers/musicGeneration.ts +640 -0
- package/open-sse/handlers/ocr.ts +79 -0
- package/open-sse/handlers/rerank.ts +210 -0
- package/open-sse/handlers/responseSanitizer/reasoning.ts +149 -0
- package/open-sse/handlers/responseSanitizer.ts +1086 -0
- package/open-sse/handlers/responseTranslator.ts +640 -0
- package/open-sse/handlers/responsesHandler.ts +92 -0
- package/open-sse/handlers/search.ts +1545 -0
- package/open-sse/handlers/sseParser.ts +825 -0
- package/open-sse/handlers/usageExtractor.ts +93 -0
- package/open-sse/handlers/videoGeneration/googleFlow.ts +219 -0
- package/open-sse/handlers/videoGeneration/googleFlowHandler.ts +145 -0
- package/open-sse/handlers/videoGeneration.ts +1264 -0
- package/open-sse/handlers/webFetch.ts +130 -0
- package/open-sse/index.ts +170 -0
- package/open-sse/lib/deepseek-pow-solver.cjs +3 -0
- package/open-sse/lib/deepseek-pow.ts +189 -0
- package/open-sse/lib/sha3_wasm_bg.wasm +0 -0
- package/open-sse/mcp-server/README.md +611 -0
- package/open-sse/mcp-server/audit.ts +478 -0
- package/open-sse/mcp-server/catalog.ts +290 -0
- package/open-sse/mcp-server/descriptionCompressor.ts +243 -0
- package/open-sse/mcp-server/httpAuthContext.ts +42 -0
- package/open-sse/mcp-server/httpTransport.ts +306 -0
- package/open-sse/mcp-server/index.ts +19 -0
- package/open-sse/mcp-server/mcpCallerIdentity.ts +53 -0
- package/open-sse/mcp-server/runtimeHeartbeat.ts +162 -0
- package/open-sse/mcp-server/schemas/a2a.ts +203 -0
- package/open-sse/mcp-server/schemas/audit.ts +121 -0
- package/open-sse/mcp-server/schemas/index.ts +116 -0
- package/open-sse/mcp-server/schemas/pickFastestModel.ts +110 -0
- package/open-sse/mcp-server/schemas/toolDefinition.ts +31 -0
- package/open-sse/mcp-server/schemas/toolSearch.ts +37 -0
- package/open-sse/mcp-server/schemas/tools.ts +1481 -0
- package/open-sse/mcp-server/scopeEnforcement.ts +135 -0
- package/open-sse/mcp-server/server.ts +1370 -0
- package/open-sse/mcp-server/toolCardinality.ts +254 -0
- package/open-sse/mcp-server/toolSearch/catalog.ts +98 -0
- package/open-sse/mcp-server/toolSearch/handler.ts +18 -0
- package/open-sse/mcp-server/toolSearch/index.ts +5 -0
- package/open-sse/mcp-server/toolSearch/register.ts +36 -0
- package/open-sse/mcp-server/toolSearch/search.ts +96 -0
- package/open-sse/mcp-server/toolSearch/signature.ts +92 -0
- package/open-sse/mcp-server/tools/advancedTools.ts +1119 -0
- package/open-sse/mcp-server/tools/agentSkillTools.ts +83 -0
- package/open-sse/mcp-server/tools/compressionTools.ts +451 -0
- package/open-sse/mcp-server/tools/gamificationTools.ts +148 -0
- package/open-sse/mcp-server/tools/githubSkillTools.ts +141 -0
- package/open-sse/mcp-server/tools/memoryTools.ts +137 -0
- package/open-sse/mcp-server/tools/notionTools.ts +106 -0
- package/open-sse/mcp-server/tools/obsidianTools.ts +327 -0
- package/open-sse/mcp-server/tools/pickFastestModel.ts +293 -0
- package/open-sse/mcp-server/tools/pluginTools.ts +216 -0
- package/open-sse/mcp-server/tools/poolTools.ts +205 -0
- package/open-sse/mcp-server/tools/skillTools.ts +119 -0
- package/open-sse/package.json +17 -0
- package/open-sse/services/AGENTS.md +78 -0
- package/open-sse/services/accountFallback.ts +1863 -0
- package/open-sse/services/accountSelector.ts +80 -0
- package/open-sse/services/accountSemaphore.ts +367 -0
- package/open-sse/services/antigravity429Engine.ts +178 -0
- package/open-sse/services/antigravityClientProfile.ts +167 -0
- package/open-sse/services/antigravityCredits.ts +68 -0
- package/open-sse/services/antigravityHeaderScrub.ts +75 -0
- package/open-sse/services/antigravityHeaders.ts +121 -0
- package/open-sse/services/antigravityIdentity.ts +127 -0
- package/open-sse/services/antigravityObfuscation.ts +64 -0
- package/open-sse/services/antigravityProjectBootstrap.ts +145 -0
- package/open-sse/services/antigravityQuotaFamily.ts +56 -0
- package/open-sse/services/antigravityVersion.ts +163 -0
- package/open-sse/services/apiKeyRotator.ts +385 -0
- package/open-sse/services/autoCombo/autoPrefix.ts +61 -0
- package/open-sse/services/autoCombo/builtinCatalog.ts +126 -0
- package/open-sse/services/autoCombo/complexityRouter.ts +111 -0
- package/open-sse/services/autoCombo/engine.ts +314 -0
- package/open-sse/services/autoCombo/index.ts +18 -0
- package/open-sse/services/autoCombo/modePacks.ts +105 -0
- package/open-sse/services/autoCombo/modelFamily.ts +95 -0
- package/open-sse/services/autoCombo/paidModelFilter.ts +44 -0
- package/open-sse/services/autoCombo/persistence.ts +124 -0
- package/open-sse/services/autoCombo/pipelineRouter.ts +418 -0
- package/open-sse/services/autoCombo/providerDiversity.ts +170 -0
- package/open-sse/services/autoCombo/providerRegistryAccessor.ts +9 -0
- package/open-sse/services/autoCombo/requestControls.ts +80 -0
- package/open-sse/services/autoCombo/routerStrategy.ts +375 -0
- package/open-sse/services/autoCombo/scoring.ts +242 -0
- package/open-sse/services/autoCombo/selfHealing.ts +167 -0
- package/open-sse/services/autoCombo/speedRanking.ts +327 -0
- package/open-sse/services/autoCombo/suffixComposition.ts +147 -0
- package/open-sse/services/autoCombo/taskFitness.ts +407 -0
- package/open-sse/services/autoCombo/virtualFactory.ts +446 -0
- package/open-sse/services/autoRefreshDaemon.ts +206 -0
- package/open-sse/services/backgroundTaskDetector.ts +263 -0
- package/open-sse/services/bailianQuotaFetcher.ts +333 -0
- package/open-sse/services/batchProcessor.ts +914 -0
- package/open-sse/services/bedrock.ts +160 -0
- package/open-sse/services/browserBackedChat.ts +849 -0
- package/open-sse/services/browserPool.ts +501 -0
- package/open-sse/services/ccBridgeTransforms.ts +581 -0
- package/open-sse/services/ccWireImageBuiltins.ts +26 -0
- package/open-sse/services/chatgptImageCache.ts +143 -0
- package/open-sse/services/chatgptTlsClient.ts +628 -0
- package/open-sse/services/claudeAdaptiveThinking.ts +52 -0
- package/open-sse/services/claudeCodeCCH.ts +49 -0
- package/open-sse/services/claudeCodeCompatible.ts +1198 -0
- package/open-sse/services/claudeCodeCompatibleBeta.ts +23 -0
- package/open-sse/services/claudeCodeCompatibleThinkingDisplay.ts +34 -0
- package/open-sse/services/claudeCodeConstraints.ts +144 -0
- package/open-sse/services/claudeCodeExtraRemap.ts +18 -0
- package/open-sse/services/claudeCodeFingerprint.ts +46 -0
- package/open-sse/services/claudeCodeObfuscation.ts +116 -0
- package/open-sse/services/claudeCodeToolRemapper.ts +339 -0
- package/open-sse/services/claudeHaikuConstraints.ts +88 -0
- package/open-sse/services/claudeTlsClient.ts +590 -0
- package/open-sse/services/claudeTurnstileSolver.ts +206 -0
- package/open-sse/services/claudeUsageCooldown.ts +51 -0
- package/open-sse/services/claudeWebAutoRefresh.ts +224 -0
- package/open-sse/services/clinepassModels.ts +76 -0
- package/open-sse/services/cloudCodeHeaders.ts +41 -0
- package/open-sse/services/cloudCodeThinking.ts +71 -0
- package/open-sse/services/codeAssistSubscription.ts +83 -0
- package/open-sse/services/codexQuotaFetcher.ts +523 -0
- package/open-sse/services/codexUsageQuotas.ts +270 -0
- package/open-sse/services/codexVerbosity.ts +59 -0
- package/open-sse/services/combo/applyStrategyOrdering.ts +226 -0
- package/open-sse/services/combo/autoConfig.ts +62 -0
- package/open-sse/services/combo/autoStrategy.ts +475 -0
- package/open-sse/services/combo/comboCompatFallback.ts +82 -0
- package/open-sse/services/combo/comboCooldownRetry.ts +237 -0
- package/open-sse/services/combo/comboData.ts +24 -0
- package/open-sse/services/combo/comboPredicates.ts +211 -0
- package/open-sse/services/combo/comboSetup.ts +135 -0
- package/open-sse/services/combo/comboStructure.ts +766 -0
- package/open-sse/services/combo/concurrencyCaps.ts +76 -0
- package/open-sse/services/combo/context.ts +46 -0
- package/open-sse/services/combo/fingerprintExpansion.ts +105 -0
- package/open-sse/services/combo/headroomRanking.ts +91 -0
- package/open-sse/services/combo/providerWildcard.ts +253 -0
- package/open-sse/services/combo/quotaExhaustionCutoff.ts +140 -0
- package/open-sse/services/combo/quotaScoring.ts +311 -0
- package/open-sse/services/combo/quotaShareConcurrency.ts +74 -0
- package/open-sse/services/combo/quotaShareInflight.ts +133 -0
- package/open-sse/services/combo/quotaShareStrategy.ts +349 -0
- package/open-sse/services/combo/quotaStrategies.ts +647 -0
- package/open-sse/services/combo/resolveAutoStrategy.ts +334 -0
- package/open-sse/services/combo/responseValidation.ts +206 -0
- package/open-sse/services/combo/rrState.ts +98 -0
- package/open-sse/services/combo/runtimeUnits.ts +269 -0
- package/open-sse/services/combo/sessionStickiness.ts +301 -0
- package/open-sse/services/combo/shadowRouting.ts +179 -0
- package/open-sse/services/combo/targetExhaustion.ts +160 -0
- package/open-sse/services/combo/targetSorters.ts +174 -0
- package/open-sse/services/combo/targetTimeoutRunner.ts +91 -0
- package/open-sse/services/combo/types.ts +175 -0
- package/open-sse/services/combo/validateQuality.ts +445 -0
- package/open-sse/services/combo.ts +3085 -0
- package/open-sse/services/comboAgentMiddleware.ts +214 -0
- package/open-sse/services/comboConfig.ts +248 -0
- package/open-sse/services/comboManifestMetrics.ts +13 -0
- package/open-sse/services/comboMetrics.ts +480 -0
- package/open-sse/services/compression/adaptiveCompression/computeTarget.ts +31 -0
- package/open-sse/services/compression/adaptiveCompression/ladder.ts +59 -0
- package/open-sse/services/compression/adaptiveCompression/resolveAdaptivePlan.ts +104 -0
- package/open-sse/services/compression/adaptiveCompression/types.ts +61 -0
- package/open-sse/services/compression/aggressive.ts +204 -0
- package/open-sse/services/compression/bodyAdapter.ts +354 -0
- package/open-sse/services/compression/cacheAwareConfig.ts +60 -0
- package/open-sse/services/compression/cachingAware.ts +130 -0
- package/open-sse/services/compression/caveman.ts +602 -0
- package/open-sse/services/compression/cavemanRules.ts +432 -0
- package/open-sse/services/compression/deriveDefaultPlan.ts +59 -0
- package/open-sse/services/compression/diffHelper.ts +219 -0
- package/open-sse/services/compression/engineBreakdown.ts +29 -0
- package/open-sse/services/compression/engineCatalog.ts +91 -0
- package/open-sse/services/compression/engines/cavemanAdapter.ts +454 -0
- package/open-sse/services/compression/engines/ccr/ccrQuery.ts +110 -0
- package/open-sse/services/compression/engines/ccr/index.ts +460 -0
- package/open-sse/services/compression/engines/headroom/encoderComparison.ts +72 -0
- package/open-sse/services/compression/engines/headroom/gcf/decode_generic.ts +711 -0
- package/open-sse/services/compression/engines/headroom/gcf/generic.ts +332 -0
- package/open-sse/services/compression/engines/headroom/gcf/index.ts +9 -0
- package/open-sse/services/compression/engines/headroom/gcf/scalar.ts +336 -0
- package/open-sse/services/compression/engines/headroom/index.ts +286 -0
- package/open-sse/services/compression/engines/headroom/smartcrusher.ts +255 -0
- package/open-sse/services/compression/engines/headroom/tabular.ts +263 -0
- package/open-sse/services/compression/engines/headroom/toon.ts +43 -0
- package/open-sse/services/compression/engines/index.ts +42 -0
- package/open-sse/services/compression/engines/ionizer/index.ts +124 -0
- package/open-sse/services/compression/engines/ionizer/sample.ts +200 -0
- package/open-sse/services/compression/engines/llm/index.ts +346 -0
- package/open-sse/services/compression/engines/llmlingua/constants.ts +55 -0
- package/open-sse/services/compression/engines/llmlingua/index.ts +474 -0
- package/open-sse/services/compression/engines/llmlingua/modelStore.ts +67 -0
- package/open-sse/services/compression/engines/llmlingua/onnxWorker.ts +117 -0
- package/open-sse/services/compression/engines/llmlingua/ultraEntry.ts +109 -0
- package/open-sse/services/compression/engines/llmlingua/worker.ts +373 -0
- package/open-sse/services/compression/engines/mcpAccessibility/collapseRepeated.ts +121 -0
- package/open-sse/services/compression/engines/mcpAccessibility/constants.ts +63 -0
- package/open-sse/services/compression/engines/mcpAccessibility/index.ts +52 -0
- package/open-sse/services/compression/engines/readLifecycle/index.ts +299 -0
- package/open-sse/services/compression/engines/registry.ts +87 -0
- package/open-sse/services/compression/engines/relevance/configSchema.ts +63 -0
- package/open-sse/services/compression/engines/relevance/index.ts +185 -0
- package/open-sse/services/compression/engines/relevance/scorer.ts +85 -0
- package/open-sse/services/compression/engines/rtk/codeStripper.ts +145 -0
- package/open-sse/services/compression/engines/rtk/commandDetector.ts +482 -0
- package/open-sse/services/compression/engines/rtk/configSchema.ts +117 -0
- package/open-sse/services/compression/engines/rtk/deduplicator.ts +37 -0
- package/open-sse/services/compression/engines/rtk/discover.ts +150 -0
- package/open-sse/services/compression/engines/rtk/filterLoader.ts +245 -0
- package/open-sse/services/compression/engines/rtk/filterSchema.ts +207 -0
- package/open-sse/services/compression/engines/rtk/filters/aws.json +34 -0
- package/open-sse/services/compression/engines/rtk/filters/biome.json +35 -0
- package/open-sse/services/compression/engines/rtk/filters/build-eslint.json +37 -0
- package/open-sse/services/compression/engines/rtk/filters/build-typescript.json +33 -0
- package/open-sse/services/compression/engines/rtk/filters/build-vite.json +41 -0
- package/open-sse/services/compression/engines/rtk/filters/build-webpack.json +34 -0
- package/open-sse/services/compression/engines/rtk/filters/bundle-install.json +35 -0
- package/open-sse/services/compression/engines/rtk/filters/composer.json +81 -0
- package/open-sse/services/compression/engines/rtk/filters/curl.json +34 -0
- package/open-sse/services/compression/engines/rtk/filters/df.json +34 -0
- package/open-sse/services/compression/engines/rtk/filters/docker-build.json +78 -0
- package/open-sse/services/compression/engines/rtk/filters/docker-logs.json +42 -0
- package/open-sse/services/compression/engines/rtk/filters/docker-ps.json +33 -0
- package/open-sse/services/compression/engines/rtk/filters/dotnet.json +47 -0
- package/open-sse/services/compression/engines/rtk/filters/du.json +33 -0
- package/open-sse/services/compression/engines/rtk/filters/error-stacktrace.json +41 -0
- package/open-sse/services/compression/engines/rtk/filters/gcloud.json +34 -0
- package/open-sse/services/compression/engines/rtk/filters/generic-output.json +32 -0
- package/open-sse/services/compression/engines/rtk/filters/gh.json +98 -0
- package/open-sse/services/compression/engines/rtk/filters/git-branch.json +45 -0
- package/open-sse/services/compression/engines/rtk/filters/git-diff.json +33 -0
- package/open-sse/services/compression/engines/rtk/filters/git-log.json +33 -0
- package/open-sse/services/compression/engines/rtk/filters/git-status.json +40 -0
- package/open-sse/services/compression/engines/rtk/filters/golangci-lint.json +34 -0
- package/open-sse/services/compression/engines/rtk/filters/gradle.json +47 -0
- package/open-sse/services/compression/engines/rtk/filters/json-output.json +40 -0
- package/open-sse/services/compression/engines/rtk/filters/kubectl.json +99 -0
- package/open-sse/services/compression/engines/rtk/filters/make.json +38 -0
- package/open-sse/services/compression/engines/rtk/filters/mypy.json +34 -0
- package/open-sse/services/compression/engines/rtk/filters/npm-audit.json +43 -0
- package/open-sse/services/compression/engines/rtk/filters/npm-install.json +41 -0
- package/open-sse/services/compression/engines/rtk/filters/nx.json +34 -0
- package/open-sse/services/compression/engines/rtk/filters/pip.json +41 -0
- package/open-sse/services/compression/engines/rtk/filters/playwright.json +42 -0
- package/open-sse/services/compression/engines/rtk/filters/poetry-install.json +35 -0
- package/open-sse/services/compression/engines/rtk/filters/prettier.json +40 -0
- package/open-sse/services/compression/engines/rtk/filters/ps.json +34 -0
- package/open-sse/services/compression/engines/rtk/filters/rsync.json +33 -0
- package/open-sse/services/compression/engines/rtk/filters/rubocop.json +34 -0
- package/open-sse/services/compression/engines/rtk/filters/ruff.json +40 -0
- package/open-sse/services/compression/engines/rtk/filters/shell-find.json +33 -0
- package/open-sse/services/compression/engines/rtk/filters/shell-grep.json +37 -0
- package/open-sse/services/compression/engines/rtk/filters/shell-ls.json +33 -0
- package/open-sse/services/compression/engines/rtk/filters/ssh.json +43 -0
- package/open-sse/services/compression/engines/rtk/filters/systemctl-status.json +43 -0
- package/open-sse/services/compression/engines/rtk/filters/terraform-plan.json +34 -0
- package/open-sse/services/compression/engines/rtk/filters/test-cargo.json +42 -0
- package/open-sse/services/compression/engines/rtk/filters/test-go.json +41 -0
- package/open-sse/services/compression/engines/rtk/filters/test-jest.json +43 -0
- package/open-sse/services/compression/engines/rtk/filters/test-pytest.json +41 -0
- package/open-sse/services/compression/engines/rtk/filters/test-vitest.json +43 -0
- package/open-sse/services/compression/engines/rtk/filters/tofu-plan.json +34 -0
- package/open-sse/services/compression/engines/rtk/filters/turbo.json +34 -0
- package/open-sse/services/compression/engines/rtk/filters/uv-sync.json +35 -0
- package/open-sse/services/compression/engines/rtk/filters/wget.json +34 -0
- package/open-sse/services/compression/engines/rtk/grouper.ts +99 -0
- package/open-sse/services/compression/engines/rtk/index.ts +706 -0
- package/open-sse/services/compression/engines/rtk/learn.ts +290 -0
- package/open-sse/services/compression/engines/rtk/lineFilter.ts +178 -0
- package/open-sse/services/compression/engines/rtk/rawOutput.ts +208 -0
- package/open-sse/services/compression/engines/rtk/renderers/gitDiff.ts +39 -0
- package/open-sse/services/compression/engines/rtk/renderers/index.ts +50 -0
- package/open-sse/services/compression/engines/rtk/renderers/structuredTable.ts +96 -0
- package/open-sse/services/compression/engines/rtk/renderers/terraformPlan.ts +40 -0
- package/open-sse/services/compression/engines/rtk/renderers/testGreen.ts +70 -0
- package/open-sse/services/compression/engines/rtk/renderers/types.ts +13 -0
- package/open-sse/services/compression/engines/rtk/smartTruncate.ts +67 -0
- package/open-sse/services/compression/engines/rtk/splitCompositeCommand.ts +111 -0
- package/open-sse/services/compression/engines/rtk/verify.ts +107 -0
- package/open-sse/services/compression/engines/session-dedup/fuzzy.ts +153 -0
- package/open-sse/services/compression/engines/session-dedup/index.ts +435 -0
- package/open-sse/services/compression/engines/types.ts +77 -0
- package/open-sse/services/compression/entrypointWrap.ts +43 -0
- package/open-sse/services/compression/eval/aggregate.ts +62 -0
- package/open-sse/services/compression/eval/corpus.ts +45 -0
- package/open-sse/services/compression/eval/costMeter.ts +31 -0
- package/open-sse/services/compression/eval/executorModelClient.ts +44 -0
- package/open-sse/services/compression/eval/fidelityCheck.ts +61 -0
- package/open-sse/services/compression/eval/grader.ts +27 -0
- package/open-sse/services/compression/eval/judge.ts +68 -0
- package/open-sse/services/compression/eval/report.ts +44 -0
- package/open-sse/services/compression/eval/runner.ts +135 -0
- package/open-sse/services/compression/eval/savings.ts +30 -0
- package/open-sse/services/compression/eval/seedCorpus.ts +60 -0
- package/open-sse/services/compression/eval/types.ts +65 -0
- package/open-sse/services/compression/fidelityGate.ts +124 -0
- package/open-sse/services/compression/fidelityGateStep.ts +44 -0
- package/open-sse/services/compression/hardBudget.ts +196 -0
- package/open-sse/services/compression/harness/benchmark.ts +318 -0
- package/open-sse/services/compression/harness/budgetGate.ts +62 -0
- package/open-sse/services/compression/harness/index.ts +52 -0
- package/open-sse/services/compression/harness/measure.ts +98 -0
- package/open-sse/services/compression/harness/replay.ts +72 -0
- package/open-sse/services/compression/harness/runner.ts +58 -0
- package/open-sse/services/compression/index.ts +165 -0
- package/open-sse/services/compression/languageDetector.ts +51 -0
- package/open-sse/services/compression/lite.ts +241 -0
- package/open-sse/services/compression/messageContent.ts +89 -0
- package/open-sse/services/compression/outputMode.ts +175 -0
- package/open-sse/services/compression/outputStyles/apply.ts +135 -0
- package/open-sse/services/compression/outputStyles/backCompat.ts +29 -0
- package/open-sse/services/compression/outputStyles/catalog.ts +76 -0
- package/open-sse/services/compression/outputStyles/telemetry.ts +50 -0
- package/open-sse/services/compression/pipelineEngineBreaker.ts +139 -0
- package/open-sse/services/compression/pipelineGuards.ts +45 -0
- package/open-sse/services/compression/planResolution.ts +128 -0
- package/open-sse/services/compression/prefixFreeze.ts +124 -0
- package/open-sse/services/compression/preservation.ts +0 -0
- package/open-sse/services/compression/preserveSystemPromptMode.ts +72 -0
- package/open-sse/services/compression/progressiveAging.ts +145 -0
- package/open-sse/services/compression/quantumLock/index.ts +17 -0
- package/open-sse/services/compression/quantumLock/quantumLock.ts +58 -0
- package/open-sse/services/compression/quantumLock/quantumLockStep.ts +72 -0
- package/open-sse/services/compression/quantumLock/quantumPatterns.ts +80 -0
- package/open-sse/services/compression/quantumLock/strategyWrap.ts +72 -0
- package/open-sse/services/compression/resolveCompressionPlan.ts +28 -0
- package/open-sse/services/compression/resultMemo.ts +83 -0
- package/open-sse/services/compression/riskGate/index.ts +3 -0
- package/open-sse/services/compression/riskGate/riskGate.ts +136 -0
- package/open-sse/services/compression/riskGate/riskGateStep.ts +115 -0
- package/open-sse/services/compression/riskGate/riskPatterns.ts +61 -0
- package/open-sse/services/compression/riskGate/strategyWrap.ts +44 -0
- package/open-sse/services/compression/ruleLoader.ts +273 -0
- package/open-sse/services/compression/rules/_schema.json +31 -0
- package/open-sse/services/compression/rules/de/context.json +38 -0
- package/open-sse/services/compression/rules/de/dedup.json +38 -0
- package/open-sse/services/compression/rules/de/filler.json +70 -0
- package/open-sse/services/compression/rules/de/structural.json +30 -0
- package/open-sse/services/compression/rules/de/ultra.json +107 -0
- package/open-sse/services/compression/rules/en/context.json +88 -0
- package/open-sse/services/compression/rules/en/dedup.json +38 -0
- package/open-sse/services/compression/rules/en/filler.json +129 -0
- package/open-sse/services/compression/rules/en/structural.json +102 -0
- package/open-sse/services/compression/rules/en/ultra.json +107 -0
- package/open-sse/services/compression/rules/es/context.json +87 -0
- package/open-sse/services/compression/rules/es/dedup.json +38 -0
- package/open-sse/services/compression/rules/es/filler.json +110 -0
- package/open-sse/services/compression/rules/es/structural.json +105 -0
- package/open-sse/services/compression/rules/es/ultra.json +107 -0
- package/open-sse/services/compression/rules/fr/context.json +38 -0
- package/open-sse/services/compression/rules/fr/dedup.json +38 -0
- package/open-sse/services/compression/rules/fr/filler.json +70 -0
- package/open-sse/services/compression/rules/fr/structural.json +30 -0
- package/open-sse/services/compression/rules/fr/ultra.json +107 -0
- package/open-sse/services/compression/rules/id/context.json +113 -0
- package/open-sse/services/compression/rules/id/dedup.json +38 -0
- package/open-sse/services/compression/rules/id/filler.json +138 -0
- package/open-sse/services/compression/rules/id/structural.json +111 -0
- package/open-sse/services/compression/rules/id/ultra.json +252 -0
- package/open-sse/services/compression/rules/ja/context.json +38 -0
- package/open-sse/services/compression/rules/ja/dedup.json +38 -0
- package/open-sse/services/compression/rules/ja/filler.json +70 -0
- package/open-sse/services/compression/rules/ja/structural.json +30 -0
- package/open-sse/services/compression/rules/ja/ultra.json +86 -0
- package/open-sse/services/compression/rules/pt-BR/context.json +69 -0
- package/open-sse/services/compression/rules/pt-BR/dedup.json +51 -0
- package/open-sse/services/compression/rules/pt-BR/filler.json +151 -0
- package/open-sse/services/compression/rules/pt-BR/structural.json +78 -0
- package/open-sse/services/compression/rules/pt-BR/ultra.json +192 -0
- package/open-sse/services/compression/rules/zh/dedup.json +38 -0
- package/open-sse/services/compression/rules/zh/filler.json +54 -0
- package/open-sse/services/compression/rules/zh/ultra.json +55 -0
- package/open-sse/services/compression/stackedStepCore.ts +87 -0
- package/open-sse/services/compression/stats.ts +59 -0
- package/open-sse/services/compression/strategySelector.ts +1007 -0
- package/open-sse/services/compression/summarizer.ts +244 -0
- package/open-sse/services/compression/toolResultCompressor.ts +283 -0
- package/open-sse/services/compression/types.ts +479 -0
- package/open-sse/services/compression/ultra.ts +321 -0
- package/open-sse/services/compression/ultraHeuristic.ts +157 -0
- package/open-sse/services/compression/validation.ts +432 -0
- package/open-sse/services/contextHandoff.ts +770 -0
- package/open-sse/services/contextManager.ts +617 -0
- package/open-sse/services/credentialGate.ts +84 -0
- package/open-sse/services/crofUsageFetcher.ts +182 -0
- package/open-sse/services/cursorSessionManager.ts +208 -0
- package/open-sse/services/deepseekQuotaFetcher.ts +244 -0
- package/open-sse/services/deviceTracker.ts +307 -0
- package/open-sse/services/emergencyFallback.ts +162 -0
- package/open-sse/services/errorClassifier.ts +198 -0
- package/open-sse/services/evalRouting.ts +276 -0
- package/open-sse/services/freeWebSearch.ts +154 -0
- package/open-sse/services/fusion.ts +337 -0
- package/open-sse/services/geminiRateLimitTracker.ts +145 -0
- package/open-sse/services/geminiThoughtSignatureStore.ts +329 -0
- package/open-sse/services/genericQuotaFetcher.ts +215 -0
- package/open-sse/services/gigachatAuth.ts +113 -0
- package/open-sse/services/githubCopilotModels.ts +162 -0
- package/open-sse/services/gpt5SamplingGuard.ts +81 -0
- package/open-sse/services/grokTlsClient.ts +607 -0
- package/open-sse/services/hfModelSuggestions.ts +63 -0
- package/open-sse/services/inAppLoginService.ts +256 -0
- package/open-sse/services/intentClassifier.ts +665 -0
- package/open-sse/services/ipFilter.ts +340 -0
- package/open-sse/services/keyGroupAuth.ts +84 -0
- package/open-sse/services/kiroModels.ts +195 -0
- package/open-sse/services/manifestAdapter.ts +134 -0
- package/open-sse/services/mimoThinking.ts +54 -0
- package/open-sse/services/model.ts +738 -0
- package/open-sse/services/modelCapabilities.ts +5 -0
- package/open-sse/services/modelDeprecation.ts +173 -0
- package/open-sse/services/modelFamilyFallback.ts +240 -0
- package/open-sse/services/modelStrip.ts +60 -0
- package/open-sse/services/modelscopePolicy.ts +97 -0
- package/open-sse/services/opencodeOllamaUsage.ts +562 -0
- package/open-sse/services/opencodeQuotaFetcher.ts +351 -0
- package/open-sse/services/payloadRules.ts +474 -0
- package/open-sse/services/perplexityTlsClient.ts +593 -0
- package/open-sse/services/pipeline.ts +189 -0
- package/open-sse/services/provider.ts +460 -0
- package/open-sse/services/providerCooldownTracker.ts +198 -0
- package/open-sse/services/providerCostData.ts +52 -0
- package/open-sse/services/providerDefaultRateLimit.ts +94 -0
- package/open-sse/services/providerRequestDefaults.ts +88 -0
- package/open-sse/services/proxyAutoSelector.ts +58 -0
- package/open-sse/services/qoderCli.ts +987 -0
- package/open-sse/services/qoderCliResolve.ts +111 -0
- package/open-sse/services/quotaFetchThrottle.ts +118 -0
- package/open-sse/services/quotaMonitor.ts +377 -0
- package/open-sse/services/quotaPreflight.ts +349 -0
- package/open-sse/services/qwenThinking.ts +44 -0
- package/open-sse/services/rateLimitManager/headers.ts +94 -0
- package/open-sse/services/rateLimitManager.ts +945 -0
- package/open-sse/services/rateLimitSemaphore.ts +231 -0
- package/open-sse/services/reasoningCache.ts +514 -0
- package/open-sse/services/reasoningTokenBuffer.ts +50 -0
- package/open-sse/services/refreshSerializer.ts +131 -0
- package/open-sse/services/requestDedup.ts +127 -0
- package/open-sse/services/responseModelEcho.ts +79 -0
- package/open-sse/services/responsesInputSanitizer.ts +145 -0
- package/open-sse/services/retryAfterJson.ts +44 -0
- package/open-sse/services/roleNormalizer.ts +283 -0
- package/open-sse/services/searchCache.ts +150 -0
- package/open-sse/services/sessionManager.ts +380 -0
- package/open-sse/services/sessionPool/fingerprintRotator.ts +126 -0
- package/open-sse/services/sessionPool/index.ts +28 -0
- package/open-sse/services/sessionPool/poolRegistry.ts +93 -0
- package/open-sse/services/sessionPool/session.ts +119 -0
- package/open-sse/services/sessionPool/sessionFactory.ts +57 -0
- package/open-sse/services/sessionPool/sessionPool.ts +326 -0
- package/open-sse/services/sessionPool/types.ts +100 -0
- package/open-sse/services/sessionPool/webExecutorWrapper.ts +132 -0
- package/open-sse/services/signatureCache.ts +189 -0
- package/open-sse/services/slidingWindowLimiter.ts +84 -0
- package/open-sse/services/specificityDetector.ts +89 -0
- package/open-sse/services/specificityRules.ts +257 -0
- package/open-sse/services/specificityTypes.ts +43 -0
- package/open-sse/services/streamRecovery.ts +545 -0
- package/open-sse/services/systemPrompt.ts +187 -0
- package/open-sse/services/systemTransforms.ts +532 -0
- package/open-sse/services/taskAwareRouter.ts +326 -0
- package/open-sse/services/taskAwareRouting.ts +553 -0
- package/open-sse/services/thinkingBudget.ts +391 -0
- package/open-sse/services/tierConfig.ts +124 -0
- package/open-sse/services/tierDefaults.json +26 -0
- package/open-sse/services/tierResolver.ts +144 -0
- package/open-sse/services/tierTypes.ts +40 -0
- package/open-sse/services/tlsClientProxy.ts +30 -0
- package/open-sse/services/tokenExtractionConfig.ts +398 -0
- package/open-sse/services/tokenLimitCounter.ts +342 -0
- package/open-sse/services/tokenRefresh.ts +2180 -0
- package/open-sse/services/toolLatencyTracker.ts +89 -0
- package/open-sse/services/toolLimitDetector.ts +87 -0
- package/open-sse/services/toolSchemaSanitizer.ts +165 -0
- package/open-sse/services/usage/antigravity.ts +794 -0
- package/open-sse/services/usage/claude.ts +216 -0
- package/open-sse/services/usage/codebuddy-cn.ts +199 -0
- package/open-sse/services/usage/codex.ts +75 -0
- package/open-sse/services/usage/cursor.ts +161 -0
- package/open-sse/services/usage/glm.ts +220 -0
- package/open-sse/services/usage/kimi.ts +209 -0
- package/open-sse/services/usage/kiro.ts +243 -0
- package/open-sse/services/usage/minimax.ts +346 -0
- package/open-sse/services/usage/quota.ts +85 -0
- package/open-sse/services/usage/scalars.ts +75 -0
- package/open-sse/services/usage.ts +1058 -0
- package/open-sse/services/volumeDetector.ts +224 -0
- package/open-sse/services/webSearchFallback.ts +246 -0
- package/open-sse/services/webSearchRouting.ts +68 -0
- package/open-sse/services/webSessionPoolHealth.ts +287 -0
- package/open-sse/services/wildcardRouter.ts +136 -0
- package/open-sse/services/workflowFSM.ts +340 -0
- package/open-sse/shared/zedAuth.ts +535 -0
- package/open-sse/transformer/responsesTransformer.ts +667 -0
- package/open-sse/translator/bootstrap.ts +27 -0
- package/open-sse/translator/deepseekWebTools.ts +486 -0
- package/open-sse/translator/formats.ts +12 -0
- package/open-sse/translator/helpers/claudeHelper.ts +563 -0
- package/open-sse/translator/helpers/geminiHelper.ts +657 -0
- package/open-sse/translator/helpers/geminiToolsSanitizer.ts +255 -0
- package/open-sse/translator/helpers/jsonUtil.ts +18 -0
- package/open-sse/translator/helpers/maxTokensHelper.ts +21 -0
- package/open-sse/translator/helpers/openaiHelper.ts +244 -0
- package/open-sse/translator/helpers/responsesApiHelper.ts +9 -0
- package/open-sse/translator/helpers/schemaCoercion.ts +408 -0
- package/open-sse/translator/helpers/toolCallHelper.ts +224 -0
- package/open-sse/translator/helpers/toolCallShim.ts +113 -0
- package/open-sse/translator/image/sizeMapper.ts +22 -0
- package/open-sse/translator/index.ts +614 -0
- package/open-sse/translator/paramSupport.ts +70 -0
- package/open-sse/translator/registry.ts +41 -0
- package/open-sse/translator/request/antigravity-to-openai.ts +357 -0
- package/open-sse/translator/request/claude-to-gemini.ts +236 -0
- package/open-sse/translator/request/claude-to-openai.ts +534 -0
- package/open-sse/translator/request/gemini-to-openai.ts +193 -0
- package/open-sse/translator/request/openai-responses/helpers.ts +69 -0
- package/open-sse/translator/request/openai-responses/toResponses.ts +357 -0
- package/open-sse/translator/request/openai-responses.ts +533 -0
- package/open-sse/translator/request/openai-to-claude/thinkingBudget.ts +89 -0
- package/open-sse/translator/request/openai-to-claude/toolResultAdjacency.ts +106 -0
- package/open-sse/translator/request/openai-to-claude.ts +747 -0
- package/open-sse/translator/request/openai-to-cursor.ts +226 -0
- package/open-sse/translator/request/openai-to-gemini/helpers.ts +142 -0
- package/open-sse/translator/request/openai-to-gemini.ts +753 -0
- package/open-sse/translator/request/openai-to-kiro/messageHelpers.ts +109 -0
- package/open-sse/translator/request/openai-to-kiro.ts +889 -0
- package/open-sse/translator/response/claude-to-openai.ts +327 -0
- package/open-sse/translator/response/cursor-to-openai.ts +30 -0
- package/open-sse/translator/response/gemini-to-claude.ts +199 -0
- package/open-sse/translator/response/gemini-to-openai.ts +765 -0
- package/open-sse/translator/response/kiro-to-openai.ts +211 -0
- package/open-sse/translator/response/openai-responses/pureHelpers.ts +92 -0
- package/open-sse/translator/response/openai-responses.ts +1011 -0
- package/open-sse/translator/response/openai-to-antigravity.ts +145 -0
- package/open-sse/translator/response/openai-to-claude.ts +289 -0
- package/open-sse/translator/response/openai-to-gemini-sse.ts +431 -0
- package/open-sse/translator/webTools.ts +531 -0
- package/open-sse/tsconfig.json +25 -0
- package/open-sse/types.d.ts +139 -0
- package/open-sse/utils/agentGoalPolicy.ts +112 -0
- package/open-sse/utils/aiSdkCompat.ts +179 -0
- package/open-sse/utils/anthropicHost.ts +25 -0
- package/open-sse/utils/awsSigV4.ts +139 -0
- package/open-sse/utils/bypassHandler.ts +300 -0
- package/open-sse/utils/cacheControlPolicy.ts +374 -0
- package/open-sse/utils/claudeCodeMetaRequests.ts +87 -0
- package/open-sse/utils/clinepassEnvelope.ts +59 -0
- package/open-sse/utils/comfyuiClient.ts +126 -0
- package/open-sse/utils/composerToolCalls.ts +307 -0
- package/open-sse/utils/cors.ts +13 -0
- package/open-sse/utils/cursorAgentProtobuf/wire.ts +143 -0
- package/open-sse/utils/cursorAgentProtobuf.ts +1400 -0
- package/open-sse/utils/cursorChecksum.ts +139 -0
- package/open-sse/utils/cursorImages.ts +354 -0
- package/open-sse/utils/cursorVersionDetector.ts +74 -0
- package/open-sse/utils/diagnostics.ts +288 -0
- package/open-sse/utils/earlyStreamKeepalive.ts +228 -0
- package/open-sse/utils/error.ts +515 -0
- package/open-sse/utils/estimateSize.ts +35 -0
- package/open-sse/utils/finishReason.ts +36 -0
- package/open-sse/utils/flattenToolHistory.ts +116 -0
- package/open-sse/utils/headers.ts +61 -0
- package/open-sse/utils/heapPressure.ts +81 -0
- package/open-sse/utils/jsonToSse.ts +127 -0
- package/open-sse/utils/keepaliveThreshold.ts +62 -0
- package/open-sse/utils/kieTask.ts +127 -0
- package/open-sse/utils/kiroSanitizer.ts +127 -0
- package/open-sse/utils/logger.ts +190 -0
- package/open-sse/utils/networkProxy.ts +75 -0
- package/open-sse/utils/noThinkingAlias.ts +153 -0
- package/open-sse/utils/number.ts +4 -0
- package/open-sse/utils/ollamaTransform.ts +122 -0
- package/open-sse/utils/opencodeHeaders.ts +98 -0
- package/open-sse/utils/passthroughTailProcessor.ts +299 -0
- package/open-sse/utils/progressTracker.ts +112 -0
- package/open-sse/utils/providerRequestLogging.ts +272 -0
- package/open-sse/utils/proxyDispatcher.ts +507 -0
- package/open-sse/utils/proxyDispatcherCache.ts +124 -0
- package/open-sse/utils/proxyFallback.ts +418 -0
- package/open-sse/utils/proxyFamily.ts +24 -0
- package/open-sse/utils/proxyFamilyResolve.ts +39 -0
- package/open-sse/utils/proxyFetch.ts +666 -0
- package/open-sse/utils/publicCreds.ts +215 -0
- package/open-sse/utils/reasoningContentInjector.ts +73 -0
- package/open-sse/utils/reasoningFields.ts +72 -0
- package/open-sse/utils/requestLogger.ts +386 -0
- package/open-sse/utils/responsesInputNormalization.ts +94 -0
- package/open-sse/utils/responsesStatePolicy.ts +75 -0
- package/open-sse/utils/responsesStreamHelpers.ts +166 -0
- package/open-sse/utils/setupPolyfill.ts +33 -0
- package/open-sse/utils/sha3-512.ts +164 -0
- package/open-sse/utils/sleep.ts +9 -0
- package/open-sse/utils/socksConnectorWithFamily.ts +89 -0
- package/open-sse/utils/sseHeartbeat.ts +119 -0
- package/open-sse/utils/stream.ts +2791 -0
- package/open-sse/utils/streamFailureFinalization.ts +174 -0
- package/open-sse/utils/streamHandler.ts +680 -0
- package/open-sse/utils/streamHelpers.ts +460 -0
- package/open-sse/utils/streamPayloadCollector.ts +700 -0
- package/open-sse/utils/streamReadiness.ts +385 -0
- package/open-sse/utils/streamReadinessPolicy.ts +132 -0
- package/open-sse/utils/textualToolCall.ts +112 -0
- package/open-sse/utils/thinkCloseMarker.ts +77 -0
- package/open-sse/utils/thinkTagParser.ts +146 -0
- package/open-sse/utils/tlsClient.ts +243 -0
- package/open-sse/utils/toolCallArguments.ts +32 -0
- package/open-sse/utils/toolSources.ts +69 -0
- package/open-sse/utils/upstreamResponseHeaders.ts +47 -0
- package/open-sse/utils/urlSanitize.ts +24 -0
- package/open-sse/utils/usageTracking.ts +596 -0
- package/package.json +393 -0
- package/scripts/build/build-next-isolated.mjs +322 -0
- package/scripts/build/colocateOptionals.mjs +144 -0
- package/scripts/build/native-binary-compat.mjs +167 -0
- package/scripts/build/postinstall.mjs +357 -0
- package/scripts/build/postinstallSupport.mjs +45 -0
- package/scripts/build/runtime-env.mjs +140 -0
- package/scripts/build/sync-env.mjs +3 -0
- package/scripts/check/check-supported-node-runtime.ts +20 -0
- package/scripts/dev/responses-ws-proxy.mjs +907 -0
- package/scripts/dev/sync-env.mjs +362 -0
- package/scripts/dev/tls-options.mjs +92 -0
- package/scripts/postinstall.mjs +27 -0
- package/src/domain/assessment/assessor.ts +206 -0
- package/src/domain/assessment/categorizer.ts +118 -0
- package/src/domain/assessment/index.ts +24 -0
- package/src/domain/assessment/migration.ts +111 -0
- package/src/domain/assessment/selfHealer.ts +261 -0
- package/src/domain/assessment/types.ts +361 -0
- package/src/domain/comboResolver.ts +106 -0
- package/src/domain/configAudit.ts +285 -0
- package/src/domain/connectionModelRules.ts +82 -0
- package/src/domain/costRules.ts +631 -0
- package/src/domain/degradation.ts +253 -0
- package/src/domain/fallbackPolicy.ts +160 -0
- package/src/domain/lockoutPolicy.ts +208 -0
- package/src/domain/modelAvailability.ts +41 -0
- package/src/domain/omnirouteResponseMeta.ts +202 -0
- package/src/domain/pipeline.ts +307 -0
- package/src/domain/policyEngine.ts +195 -0
- package/src/domain/prompts.ts +103 -0
- package/src/domain/providerExpiration.ts +255 -0
- package/src/domain/quotaCache.ts +553 -0
- package/src/domain/responses.ts +139 -0
- package/src/domain/tagRouter.ts +64 -0
- package/src/domain/types.ts +123 -0
- package/src/lib/a2a/README.md +748 -0
- package/src/lib/a2a/routingLogger.ts +67 -0
- package/src/lib/a2a/skills/costAnalysis.ts +162 -0
- package/src/lib/a2a/skills/healthReport.ts +155 -0
- package/src/lib/a2a/skills/listCapabilities.ts +72 -0
- package/src/lib/a2a/skills/providerDiscovery.ts +202 -0
- package/src/lib/a2a/skills/quotaManagement.ts +121 -0
- package/src/lib/a2a/skills/smartRouting.ts +89 -0
- package/src/lib/a2a/streaming.ts +149 -0
- package/src/lib/a2a/taskExecution.ts +64 -0
- package/src/lib/a2a/taskManager.ts +240 -0
- package/src/lib/accessTokens/scopes.ts +51 -0
- package/src/lib/acp/index.ts +11 -0
- package/src/lib/acp/manager.ts +202 -0
- package/src/lib/acp/registry.ts +384 -0
- package/src/lib/agentSkills/catalog.ts +256 -0
- package/src/lib/agentSkills/cliRegistryParser.ts +242 -0
- package/src/lib/agentSkills/generator.ts +367 -0
- package/src/lib/agentSkills/openapiParser.ts +201 -0
- package/src/lib/agentSkills/schemas.ts +36 -0
- package/src/lib/agentSkills/types.ts +101 -0
- package/src/lib/api/comboErrorResponse.ts +80 -0
- package/src/lib/api/errorResponse.ts +54 -0
- package/src/lib/api/modelTestRunner.ts +390 -0
- package/src/lib/api/proxyRegistryRouteHandlers.ts +142 -0
- package/src/lib/api/requireCliToolsAuth.ts +5 -0
- package/src/lib/api/requireManagementAuth.ts +118 -0
- package/src/lib/api/serverErrorMessage.ts +36 -0
- package/src/lib/apiBridgeServer.ts +233 -0
- package/src/lib/apiKeyExposure.ts +19 -0
- package/src/lib/arenaEloSync.ts +564 -0
- package/src/lib/audit/activityIcons.ts +69 -0
- package/src/lib/audit/highLevelActions.ts +57 -0
- package/src/lib/audit/timeline.ts +132 -0
- package/src/lib/auth/managementPassword.ts +117 -0
- package/src/lib/batches/costEstimator.ts +130 -0
- package/src/lib/batches/csvToJsonl.ts +248 -0
- package/src/lib/batches/dispatch.ts +53 -0
- package/src/lib/batches/retryFailed.ts +77 -0
- package/src/lib/batches/schemas.ts +38 -0
- package/src/lib/batches/types.ts +78 -0
- package/src/lib/batches/validateJsonl.ts +154 -0
- package/src/lib/benchmarks.ts +33 -0
- package/src/lib/build-profile/featureDisabled.ts +23 -0
- package/src/lib/cacheControlSettings.ts +25 -0
- package/src/lib/cacheLayer.ts +220 -0
- package/src/lib/catalog/openrouterCatalog.ts +177 -0
- package/src/lib/cli-helper/claudeProfileAutoSync.ts +92 -0
- package/src/lib/cli-helper/codexProfileAutoSync.ts +80 -0
- package/src/lib/cli-helper/config-generator/claude.ts +25 -0
- package/src/lib/cli-helper/config-generator/cline.ts +23 -0
- package/src/lib/cli-helper/config-generator/codex.ts +34 -0
- package/src/lib/cli-helper/config-generator/continue.ts +37 -0
- package/src/lib/cli-helper/config-generator/hermes-agent.ts +210 -0
- package/src/lib/cli-helper/config-generator/hermes.ts +47 -0
- package/src/lib/cli-helper/config-generator/hermesHome.ts +40 -0
- package/src/lib/cli-helper/config-generator/index.ts +124 -0
- package/src/lib/cli-helper/config-generator/kilocode.ts +23 -0
- package/src/lib/cli-helper/config-generator/opencode.ts +443 -0
- package/src/lib/cli-helper/doctor/checks.ts +41 -0
- package/src/lib/cli-helper/log-streamer.ts +78 -0
- package/src/lib/cli-helper/tool-detector.ts +165 -0
- package/src/lib/cliTools/batchStatusCache.ts +47 -0
- package/src/lib/cliTools/checkToolConfigStatus.ts +112 -0
- package/src/lib/cloudAgent/agents/codex.ts +148 -0
- package/src/lib/cloudAgent/agents/cursor.ts +205 -0
- package/src/lib/cloudAgent/agents/devin.ts +137 -0
- package/src/lib/cloudAgent/agents/jules.ts +354 -0
- package/src/lib/cloudAgent/api.ts +86 -0
- package/src/lib/cloudAgent/baseAgent.ts +98 -0
- package/src/lib/cloudAgent/credentials.ts +89 -0
- package/src/lib/cloudAgent/db.ts +145 -0
- package/src/lib/cloudAgent/index.ts +8 -0
- package/src/lib/cloudAgent/julesApi.ts +7 -0
- package/src/lib/cloudAgent/registry.ts +29 -0
- package/src/lib/cloudAgent/types.ts +90 -0
- package/src/lib/cloudSync.stub.ts +31 -0
- package/src/lib/cloudSync.ts +210 -0
- package/src/lib/cloudflaredTunnel.ts +933 -0
- package/src/lib/combos/autoPromote.ts +92 -0
- package/src/lib/combos/builderDraft.ts +242 -0
- package/src/lib/combos/builderOptions.ts +681 -0
- package/src/lib/combos/compositeTiers.ts +200 -0
- package/src/lib/combos/controlCenter.ts +271 -0
- package/src/lib/combos/intelligentRouting.ts +192 -0
- package/src/lib/combos/steps.ts +322 -0
- package/src/lib/combos/testHealth.ts +190 -0
- package/src/lib/compliance/index.ts +637 -0
- package/src/lib/compliance/noLog.ts +86 -0
- package/src/lib/compliance/providerAudit.ts +98 -0
- package/src/lib/compression/judgeModelClient.ts +43 -0
- package/src/lib/config/hotReload.ts +115 -0
- package/src/lib/config/runtimeSettings.ts +551 -0
- package/src/lib/consoleInterceptor.ts +136 -0
- package/src/lib/container.ts +122 -0
- package/src/lib/contextWindowResolver.ts +147 -0
- package/src/lib/copilot/codegraphKnowledge.ts +229 -0
- package/src/lib/copilot/engine.ts +372 -0
- package/src/lib/copilot/systemPrompt.ts +189 -0
- package/src/lib/copilot/tools.ts +415 -0
- package/src/lib/credentialHealth/cache.ts +206 -0
- package/src/lib/credentialHealth/scheduler.ts +328 -0
- package/src/lib/dataPaths.ts +122 -0
- package/src/lib/db/AGENTS.md +70 -0
- package/src/lib/db/_rowTypes.ts +45 -0
- package/src/lib/db/accessTokens.ts +183 -0
- package/src/lib/db/adapters/betterSqliteAdapter.ts +58 -0
- package/src/lib/db/adapters/driverFactory.ts +98 -0
- package/src/lib/db/adapters/nodeSqliteAdapter.ts +60 -0
- package/src/lib/db/adapters/nodeSqliteShared.ts +148 -0
- package/src/lib/db/adapters/sqljsAdapter.ts +232 -0
- package/src/lib/db/adapters/types.ts +34 -0
- package/src/lib/db/agentBridgeBypass.ts +81 -0
- package/src/lib/db/agentBridgeMappings.ts +47 -0
- package/src/lib/db/agentBridgeState.ts +115 -0
- package/src/lib/db/apiKeyColumnFallbacks.ts +47 -0
- package/src/lib/db/apiKeyContextSources.ts +107 -0
- package/src/lib/db/apiKeyGroups.ts +318 -0
- package/src/lib/db/apiKeyUsageLimitFields.ts +65 -0
- package/src/lib/db/apiKeys/modelPermissions.ts +122 -0
- package/src/lib/db/apiKeys/rowParsers.ts +161 -0
- package/src/lib/db/apiKeys/types.ts +21 -0
- package/src/lib/db/apiKeys.ts +1492 -0
- package/src/lib/db/backup.ts +708 -0
- package/src/lib/db/batches.ts +450 -0
- package/src/lib/db/callLogStats.ts +235 -0
- package/src/lib/db/caseMapping.ts +80 -0
- package/src/lib/db/cleanup.ts +584 -0
- package/src/lib/db/cliToolState.ts +159 -0
- package/src/lib/db/comboForecast.ts +119 -0
- package/src/lib/db/combos.ts +339 -0
- package/src/lib/db/commandCodeAuth.ts +209 -0
- package/src/lib/db/compression.ts +780 -0
- package/src/lib/db/compressionAnalytics.ts +661 -0
- package/src/lib/db/compressionCacheStats.ts +112 -0
- package/src/lib/db/compressionCombos.ts +491 -0
- package/src/lib/db/compressionRunTelemetry.ts +126 -0
- package/src/lib/db/contextHandoffs.ts +233 -0
- package/src/lib/db/core.ts +1520 -0
- package/src/lib/db/creditBalance.ts +92 -0
- package/src/lib/db/databaseSettings.ts +309 -0
- package/src/lib/db/detailedLogs.ts +170 -0
- package/src/lib/db/discovery.ts +97 -0
- package/src/lib/db/discoveryResults.ts +176 -0
- package/src/lib/db/domainState.ts +630 -0
- package/src/lib/db/encryption.ts +327 -0
- package/src/lib/db/evals.ts +786 -0
- package/src/lib/db/featureFlags.ts +82 -0
- package/src/lib/db/files.ts +170 -0
- package/src/lib/db/freeProxies.ts +360 -0
- package/src/lib/db/gamification.ts +613 -0
- package/src/lib/db/healthCheck.ts +565 -0
- package/src/lib/db/inspectorCustomHosts.ts +88 -0
- package/src/lib/db/inspectorSessions.ts +161 -0
- package/src/lib/db/jsonMigration.ts +321 -0
- package/src/lib/db/memoryVec.ts +138 -0
- package/src/lib/db/middleware.ts +239 -0
- package/src/lib/db/migrationRunner/constants.ts +118 -0
- package/src/lib/db/migrationRunner.ts +1069 -0
- package/src/lib/db/migrations/001_initial_schema.sql +226 -0
- package/src/lib/db/migrations/002_mcp_a2a_tables.sql +95 -0
- package/src/lib/db/migrations/003_provider_node_custom_paths.sql +5 -0
- package/src/lib/db/migrations/004_proxy_registry.sql +31 -0
- package/src/lib/db/migrations/005_combo_agent_fields.sql +19 -0
- package/src/lib/db/migrations/006_detailed_request_logs.sql +42 -0
- package/src/lib/db/migrations/007_search_request_type.sql +4 -0
- package/src/lib/db/migrations/008_registered_keys.sql +65 -0
- package/src/lib/db/migrations/009_requested_model.sql +9 -0
- package/src/lib/db/migrations/010_model_combo_mappings.sql +19 -0
- package/src/lib/db/migrations/011_webhooks.sql +15 -0
- package/src/lib/db/migrations/012_fix_token_input_cache_tokens.sql +15 -0
- package/src/lib/db/migrations/013_quota_snapshots.sql +19 -0
- package/src/lib/db/migrations/014_unified_log_artifacts.sql +15 -0
- package/src/lib/db/migrations/015_create_memories.sql +22 -0
- package/src/lib/db/migrations/016_create_skills.sql +37 -0
- package/src/lib/db/migrations/017_version_manager_upstream_proxy.sql +56 -0
- package/src/lib/db/migrations/018_call_logs_detailed_tokens.sql +6 -0
- package/src/lib/db/migrations/019_context_handoffs.sql +28 -0
- package/src/lib/db/migrations/020_combo_sort_order.sql +16 -0
- package/src/lib/db/migrations/021_combo_call_log_targets.sql +5 -0
- package/src/lib/db/migrations/022_add_memory_fts5.sql +33 -0
- package/src/lib/db/migrations/023_fix_memory_fts_uuid.sql +55 -0
- package/src/lib/db/migrations/024_create_sync_tokens.sql +15 -0
- package/src/lib/db/migrations/025_call_logs_summary_storage.sql +145 -0
- package/src/lib/db/migrations/027_skill_mode_and_metadata.sql +10 -0
- package/src/lib/db/migrations/028_create_files_and_batches.sql +51 -0
- package/src/lib/db/migrations/029_provider_connection_max_concurrent.sql +10 -0
- package/src/lib/db/migrations/030_create_eval_runs.sql +28 -0
- package/src/lib/db/migrations/031_create_eval_suites.sql +27 -0
- package/src/lib/db/migrations/032_apikey_lifecycle.sql +24 -0
- package/src/lib/db/migrations/033_create_reasoning_cache.sql +27 -0
- package/src/lib/db/migrations/034_compression_settings.sql +10 -0
- package/src/lib/db/migrations/035_standard_compression_config.sql +9 -0
- package/src/lib/db/migrations/036_aggressive_compression.sql +4 -0
- package/src/lib/db/migrations/037_ultra_compression.sql +6 -0
- package/src/lib/db/migrations/038_compression_analytics.sql +13 -0
- package/src/lib/db/migrations/039_compression_cache_stats.sql +15 -0
- package/src/lib/db/migrations/040_oneproxy_proxy_fields.sql +24 -0
- package/src/lib/db/migrations/041_compression_receipts.sql +18 -0
- package/src/lib/db/migrations/042_compression_combos.sql +43 -0
- package/src/lib/db/migrations/043_default_compression_combo_pipeline.sql +12 -0
- package/src/lib/db/migrations/044_usage_history_api_key_backfill.sql +27 -0
- package/src/lib/db/migrations/045_compression_tokens.sql +4 -0
- package/src/lib/db/migrations/046_database_settings.sql +44 -0
- package/src/lib/db/migrations/047_aggregation_tables.sql +44 -0
- package/src/lib/db/migrations/048_summary_indexes.sql +29 -0
- package/src/lib/db/migrations/049_compression_analytics_indexes.sql +10 -0
- package/src/lib/db/migrations/050_session_account_affinity.sql +11 -0
- package/src/lib/db/migrations/051_hot_path_db_indexes.sql +10 -0
- package/src/lib/db/migrations/052_api_key_combo_throttle.sql +4 -0
- package/src/lib/db/migrations/053_remove_status_from_files.sql +2 -0
- package/src/lib/db/migrations/054_usage_history_service_tier.sql +4 -0
- package/src/lib/db/migrations/056_mcp_accessibility_compression.sql +8 -0
- package/src/lib/db/migrations/057_provider_connection_quota_window_thresholds.sql +14 -0
- package/src/lib/db/migrations/058_command_code_auth_sessions.sql +19 -0
- package/src/lib/db/migrations/059_manifest_routing.sql +21 -0
- package/src/lib/db/migrations/060_create_gamification.sql +94 -0
- package/src/lib/db/migrations/061_cloud_agent_credentials.sql +9 -0
- package/src/lib/db/migrations/062_usage_history_combo_strategy.sql +4 -0
- package/src/lib/db/migrations/063_add_last_model_to_handoffs.sql +7 -0
- package/src/lib/db/migrations/064_create_session_model_history.sql +19 -0
- package/src/lib/db/migrations/065_middleware_hooks.sql +45 -0
- package/src/lib/db/migrations/066_api_key_groups.sql +47 -0
- package/src/lib/db/migrations/067_relay_proxies.sql +56 -0
- package/src/lib/db/migrations/068_free_proxies.sql +52 -0
- package/src/lib/db/migrations/069_webhook_deliveries.sql +15 -0
- package/src/lib/db/migrations/070_webhooks_kind_metadata.sql +3 -0
- package/src/lib/db/migrations/071_services.sql +19 -0
- package/src/lib/db/migrations/072_free_proxies_fk.sql +27 -0
- package/src/lib/db/migrations/073_per_model_token_limits.sql +61 -0
- package/src/lib/db/migrations/074_discovery_results.sql +20 -0
- package/src/lib/db/migrations/075_api_key_self_service_usage_scopes.sql +51 -0
- package/src/lib/db/migrations/076_create_plugins.sql +31 -0
- package/src/lib/db/migrations/077_api_key_stream_default_mode.sql +3 -0
- package/src/lib/db/migrations/078_quota_consumption.sql +24 -0
- package/src/lib/db/migrations/079_provider_plans.sql +19 -0
- package/src/lib/db/migrations/080_agent_bridge.sql +24 -0
- package/src/lib/db/migrations/081_inspector_custom_hosts.sql +11 -0
- package/src/lib/db/migrations/082_inspector_sessions.sql +18 -0
- package/src/lib/db/migrations/083_memory_vec.sql +26 -0
- package/src/lib/db/migrations/084_playground_presets.sql +15 -0
- package/src/lib/db/migrations/085_quota_pools.sql +39 -0
- package/src/lib/db/migrations/086_api_key_allowed_quotas.sql +8 -0
- package/src/lib/db/migrations/087_quota_pool_connections.sql +21 -0
- package/src/lib/db/migrations/088_quota_groups.sql +28 -0
- package/src/lib/db/migrations/089_add_disable_non_public_to_api_keys.sql +3 -0
- package/src/lib/db/migrations/090_plugin_metrics.sql +9 -0
- package/src/lib/db/migrations/091_plugin_analytics.sql +23 -0
- package/src/lib/db/migrations/092_api_key_context_sources.sql +13 -0
- package/src/lib/db/migrations/093_proxy_enable_toggles.sql +17 -0
- package/src/lib/db/migrations/094_per_key_proxy_toggles.sql +12 -0
- package/src/lib/db/migrations/095_provider_node_custom_headers.sql +5 -0
- package/src/lib/db/migrations/096_sync_context_cache_protection.sql +9 -0
- package/src/lib/db/migrations/097_model_intelligence.sql +25 -0
- package/src/lib/db/migrations/098_clear_semantic_cache_for_key_isolation.sql +4 -0
- package/src/lib/db/migrations/099_proxy_family.sql +5 -0
- package/src/lib/db/migrations/100_cli_access_tokens.sql +18 -0
- package/src/lib/db/migrations/101_api_key_usage_limits.sql +4 -0
- package/src/lib/db/migrations/102_compression_engines_map.sql +5 -0
- package/src/lib/db/migrations/103_strip_legacy_combo_config_keys.sql +54 -0
- package/src/lib/db/migrations/104_normalize_database_cache_size.sql +10 -0
- package/src/lib/db/migrations/105_usage_history_endpoint.sql +6 -0
- package/src/lib/db/migrations/106_quota_allocation_model_caps.sql +32 -0
- package/src/lib/db/migrations/107_quota_combos_quota_share_strategy.sql +25 -0
- package/src/lib/db/migrations/108_provider_quota_reset_events.sql +26 -0
- package/src/lib/db/migrations/109_call_logs_correlation_id.sql +2 -0
- package/src/lib/db/migrations/110_model_context_overrides.sql +17 -0
- package/src/lib/db/migrations/111_memory_typed_decay.sql +10 -0
- package/src/lib/db/migrations/112_batch_item_checkpoints.sql +18 -0
- package/src/lib/db/migrations/113_provider_node_icon_url.sql +5 -0
- package/src/lib/db/migrations/114_mux_service_seed.sql +12 -0
- package/src/lib/db/migrations/115_bifrost_service.sql +9 -0
- package/src/lib/db/migrations/116_call_logs_reasoning_source.sql +13 -0
- package/src/lib/db/migrations/117_proxy_pool_rotation.sql +53 -0
- package/src/lib/db/modelComboMappings.ts +255 -0
- package/src/lib/db/modelContextOverrides.ts +133 -0
- package/src/lib/db/modelIntelligence.ts +232 -0
- package/src/lib/db/models/aliases.ts +61 -0
- package/src/lib/db/models/compat.ts +249 -0
- package/src/lib/db/models/mitmAlias.ts +32 -0
- package/src/lib/db/models/shared.ts +19 -0
- package/src/lib/db/models.ts +936 -0
- package/src/lib/db/notion.ts +48 -0
- package/src/lib/db/obsidian.ts +283 -0
- package/src/lib/db/oneproxy.ts +293 -0
- package/src/lib/db/optimizationSettings.ts +284 -0
- package/src/lib/db/playgroundPresets.ts +167 -0
- package/src/lib/db/pluginMetrics.ts +88 -0
- package/src/lib/db/plugins.ts +280 -0
- package/src/lib/db/prompts.ts +302 -0
- package/src/lib/db/providerLimits.ts +131 -0
- package/src/lib/db/providerNodeSelect.ts +48 -0
- package/src/lib/db/providerPlans.ts +149 -0
- package/src/lib/db/providerStats.ts +68 -0
- package/src/lib/db/providers/columns.ts +116 -0
- package/src/lib/db/providers/nodes.ts +169 -0
- package/src/lib/db/providers/rateLimit.ts +198 -0
- package/src/lib/db/providers.ts +803 -0
- package/src/lib/db/proxies/mappers.ts +181 -0
- package/src/lib/db/proxies/types.ts +78 -0
- package/src/lib/db/proxies.ts +1176 -0
- package/src/lib/db/proxyLogs.ts +35 -0
- package/src/lib/db/quotaConsumption.ts +274 -0
- package/src/lib/db/quotaGroups.ts +147 -0
- package/src/lib/db/quotaModelCaps.ts +128 -0
- package/src/lib/db/quotaPools.ts +548 -0
- package/src/lib/db/quotaResetEvents.ts +334 -0
- package/src/lib/db/quotaSnapshots.ts +209 -0
- package/src/lib/db/readCache.ts +202 -0
- package/src/lib/db/reasoningCache.ts +276 -0
- package/src/lib/db/recovery.ts +33 -0
- package/src/lib/db/registeredKeys.ts +532 -0
- package/src/lib/db/relayProxies.ts +364 -0
- package/src/lib/db/schemaColumns.ts +226 -0
- package/src/lib/db/secrets.ts +28 -0
- package/src/lib/db/semanticCache.ts +117 -0
- package/src/lib/db/serviceModels.ts +79 -0
- package/src/lib/db/sessionAccountAffinity.ts +212 -0
- package/src/lib/db/settings/cacheMetrics.ts +235 -0
- package/src/lib/db/settings/lkgp.ts +49 -0
- package/src/lib/db/settings/pricing.ts +254 -0
- package/src/lib/db/settings/shared.ts +9 -0
- package/src/lib/db/settings.ts +661 -0
- package/src/lib/db/skills.ts +62 -0
- package/src/lib/db/stateReset.ts +30 -0
- package/src/lib/db/stats.ts +71 -0
- package/src/lib/db/syncTokens.ts +163 -0
- package/src/lib/db/tierConfig.ts +88 -0
- package/src/lib/db/tokenLimits.ts +295 -0
- package/src/lib/db/upstreamProxy.ts +247 -0
- package/src/lib/db/usageAnalytics/sources.ts +208 -0
- package/src/lib/db/usageAnalytics.ts +723 -0
- package/src/lib/db/usageLogs.ts +94 -0
- package/src/lib/db/usageSummary.ts +18 -0
- package/src/lib/db/vacuumScheduler.ts +326 -0
- package/src/lib/db/versionManager.ts +377 -0
- package/src/lib/db/webSessionDedup.ts +57 -0
- package/src/lib/db/webhookDeliveries.ts +77 -0
- package/src/lib/db/webhooks.ts +178 -0
- package/src/lib/discovery/index.ts +137 -0
- package/src/lib/display/names.ts +73 -0
- package/src/lib/display/useProviderNodeMap.ts +68 -0
- package/src/lib/docsI18nPath.ts +33 -0
- package/src/lib/docsSanitizer.ts +42 -0
- package/src/lib/embeddings/familyGuard.ts +24 -0
- package/src/lib/embeddings/service.ts +288 -0
- package/src/lib/env/runtimeEnv.ts +166 -0
- package/src/lib/evals/evalRunner/builtinSuites.ts +676 -0
- package/src/lib/evals/evalRunner.ts +301 -0
- package/src/lib/evals/runtime.ts +311 -0
- package/src/lib/events/eventBus.ts +148 -0
- package/src/lib/events/types.ts +181 -0
- package/src/lib/freeProviderRankings.ts +340 -0
- package/src/lib/freeProxyProviders/index.ts +24 -0
- package/src/lib/freeProxyProviders/iplocate.ts +107 -0
- package/src/lib/freeProxyProviders/oneproxy.ts +127 -0
- package/src/lib/freeProxyProviders/proxifly.ts +143 -0
- package/src/lib/freeProxyProviders/types.ts +33 -0
- package/src/lib/freeProxyProviders/webshare.ts +148 -0
- package/src/lib/gamification/antiCheat.ts +160 -0
- package/src/lib/gamification/badges.ts +542 -0
- package/src/lib/gamification/events.ts +187 -0
- package/src/lib/gamification/index.ts +37 -0
- package/src/lib/gamification/invites.ts +127 -0
- package/src/lib/gamification/leaderboard.ts +64 -0
- package/src/lib/gamification/notifications.ts +118 -0
- package/src/lib/gamification/servers.ts +179 -0
- package/src/lib/gamification/sharing.ts +56 -0
- package/src/lib/gamification/streaks.ts +164 -0
- package/src/lib/gamification/xp.ts +152 -0
- package/src/lib/gracefulShutdown.ts +151 -0
- package/src/lib/guardrails/base.ts +65 -0
- package/src/lib/guardrails/index.ts +4 -0
- package/src/lib/guardrails/piiMasker.ts +208 -0
- package/src/lib/guardrails/promptInjection.ts +285 -0
- package/src/lib/guardrails/registry.ts +282 -0
- package/src/lib/guardrails/visionBridge.ts +237 -0
- package/src/lib/guardrails/visionBridgeHelpers.ts +499 -0
- package/src/lib/guardrails/visionBridgeRouter.ts +271 -0
- package/src/lib/headroom/detect.ts +214 -0
- package/src/lib/headroom/process.ts +177 -0
- package/src/lib/idempotencyLayer.ts +105 -0
- package/src/lib/images/imageRouteModel.ts +168 -0
- package/src/lib/initCloudSync.ts +52 -0
- package/src/lib/inspector/agentBridgeMaintenanceApi.ts +70 -0
- package/src/lib/inspector/captureState.ts +90 -0
- package/src/lib/inspector/configPortability.ts +86 -0
- package/src/lib/inspector/harExport.ts +188 -0
- package/src/lib/inspector/matchesTrafficFilter.ts +37 -0
- package/src/lib/inspector/secretMask.ts +6 -0
- package/src/lib/inspector/tproxyCaptureApi.ts +60 -0
- package/src/lib/ipUtils.ts +94 -0
- package/src/lib/jobs/budgetResetJob.ts +40 -0
- package/src/lib/jobs/reasoningCacheCleanupJob.ts +40 -0
- package/src/lib/localDb.ts +797 -0
- package/src/lib/localHealthCheck.ts +267 -0
- package/src/lib/logEnv.ts +164 -0
- package/src/lib/logPayloads.ts +110 -0
- package/src/lib/logRotation.ts +206 -0
- package/src/lib/machineToken.ts +53 -0
- package/src/lib/memory/cache.ts +76 -0
- package/src/lib/memory/embedding/cache.ts +77 -0
- package/src/lib/memory/embedding/index.ts +281 -0
- package/src/lib/memory/embedding/remote.ts +95 -0
- package/src/lib/memory/embedding/staticPotion.ts +253 -0
- package/src/lib/memory/embedding/transformersLocal.ts +153 -0
- package/src/lib/memory/embedding/types.ts +40 -0
- package/src/lib/memory/extraction.ts +195 -0
- package/src/lib/memory/injection.ts +211 -0
- package/src/lib/memory/qdrant.ts +521 -0
- package/src/lib/memory/reindex.ts +102 -0
- package/src/lib/memory/retrieval/scoring.ts +104 -0
- package/src/lib/memory/retrieval.ts +1072 -0
- package/src/lib/memory/schemas.ts +39 -0
- package/src/lib/memory/settings.ts +184 -0
- package/src/lib/memory/store.ts +621 -0
- package/src/lib/memory/summarization.ts +202 -0
- package/src/lib/memory/typedDecay.ts +245 -0
- package/src/lib/memory/types.ts +45 -0
- package/src/lib/memory/vectorStore.ts +425 -0
- package/src/lib/memory/verify.ts +56 -0
- package/src/lib/middleware/cliTokenAuth.ts +91 -0
- package/src/lib/middleware/registry.ts +432 -0
- package/src/lib/middleware/types.ts +129 -0
- package/src/lib/modelAliasSeed.ts +82 -0
- package/src/lib/modelCapabilities.ts +481 -0
- package/src/lib/modelMetadataRegistry.ts +574 -0
- package/src/lib/modelsDevSync/transform.ts +279 -0
- package/src/lib/modelsDevSync.ts +677 -0
- package/src/lib/monitoring/comboHealthAutopilot.ts +494 -0
- package/src/lib/monitoring/observability.ts +336 -0
- package/src/lib/monitoring/providerHealthAutopilot.ts +743 -0
- package/src/lib/monitoring/providerHealthMatrix.ts +621 -0
- package/src/lib/ngrokTunnel.ts +142 -0
- package/src/lib/notion/api.ts +247 -0
- package/src/lib/oauth/codexDeviceFlow.ts +316 -0
- package/src/lib/oauth/config/index.ts +31 -0
- package/src/lib/oauth/connectionPersistence.ts +114 -0
- package/src/lib/oauth/constants/oauth.ts +494 -0
- package/src/lib/oauth/credentialBlob.ts +105 -0
- package/src/lib/oauth/deviceFlowTickets.ts +110 -0
- package/src/lib/oauth/gitlab.ts +103 -0
- package/src/lib/oauth/pasteCredentials.ts +51 -0
- package/src/lib/oauth/providers/agy.ts +15 -0
- package/src/lib/oauth/providers/antigravity.ts +159 -0
- package/src/lib/oauth/providers/claude.ts +170 -0
- package/src/lib/oauth/providers/cline.ts +91 -0
- package/src/lib/oauth/providers/codebuddy-cn.ts +123 -0
- package/src/lib/oauth/providers/codex.ts +214 -0
- package/src/lib/oauth/providers/cursor.ts +15 -0
- package/src/lib/oauth/providers/github.ts +89 -0
- package/src/lib/oauth/providers/gitlab-duo.ts +124 -0
- package/src/lib/oauth/providers/grok-cli.ts +155 -0
- package/src/lib/oauth/providers/index.ts +59 -0
- package/src/lib/oauth/providers/kilocode.ts +77 -0
- package/src/lib/oauth/providers/kimi-coding.ts +135 -0
- package/src/lib/oauth/providers/kiro.ts +185 -0
- package/src/lib/oauth/providers/qoder.ts +81 -0
- package/src/lib/oauth/providers/qwen.ts +82 -0
- package/src/lib/oauth/providers/trae.ts +78 -0
- package/src/lib/oauth/providers/windsurf.ts +64 -0
- package/src/lib/oauth/providers/zed-hosted.ts +89 -0
- package/src/lib/oauth/providers/zed.ts +35 -0
- package/src/lib/oauth/providers.ts +278 -0
- package/src/lib/oauth/services/codexImport.ts +222 -0
- package/src/lib/oauth/services/cursor.ts +219 -0
- package/src/lib/oauth/services/kiro.ts +441 -0
- package/src/lib/oauth/utils/agyAuthImport.ts +259 -0
- package/src/lib/oauth/utils/banner.ts +62 -0
- package/src/lib/oauth/utils/claudeAuthFile.ts +334 -0
- package/src/lib/oauth/utils/claudeAuthImport.ts +259 -0
- package/src/lib/oauth/utils/claudeAuthZipExtract.ts +5 -0
- package/src/lib/oauth/utils/cliProxyAuthImport.ts +142 -0
- package/src/lib/oauth/utils/codexAuthFile.ts +352 -0
- package/src/lib/oauth/utils/codexAuthImport.ts +294 -0
- package/src/lib/oauth/utils/codexAuthZipExtract.ts +5 -0
- package/src/lib/oauth/utils/jsonZipExtract.ts +89 -0
- package/src/lib/oauth/utils/pkce.ts +37 -0
- package/src/lib/oauth/utils/server.ts +122 -0
- package/src/lib/oauth/utils/ui.ts +47 -0
- package/src/lib/obsidian/api.ts +390 -0
- package/src/lib/obsidianSync.ts +110 -0
- package/src/lib/oneproxyRotator.ts +19 -0
- package/src/lib/oneproxySync.ts +182 -0
- package/src/lib/piiSanitizer.ts +416 -0
- package/src/lib/playground/codeExport.ts +408 -0
- package/src/lib/playground/promptImprover.ts +147 -0
- package/src/lib/playground/streamMetrics.ts +61 -0
- package/src/lib/playground/types.ts +114 -0
- package/src/lib/plugins/devMode.ts +65 -0
- package/src/lib/plugins/doctor.ts +138 -0
- package/src/lib/plugins/errors.ts +38 -0
- package/src/lib/plugins/hooks.ts +322 -0
- package/src/lib/plugins/index.ts +72 -0
- package/src/lib/plugins/loader.ts +326 -0
- package/src/lib/plugins/logger.ts +50 -0
- package/src/lib/plugins/manager.ts +579 -0
- package/src/lib/plugins/manifest.ts +213 -0
- package/src/lib/plugins/marketplace.ts +202 -0
- package/src/lib/plugins/pluginWorker.ts +246 -0
- package/src/lib/plugins/sandbox.ts +29 -0
- package/src/lib/plugins/scanner.ts +110 -0
- package/src/lib/plugins/sdk.ts +88 -0
- package/src/lib/plugins/signing.ts +34 -0
- package/src/lib/plugins/testRunner.ts +95 -0
- package/src/lib/plugins/watcher.ts +90 -0
- package/src/lib/pricingSync.ts +456 -0
- package/src/lib/promptCache/index.ts +1 -0
- package/src/lib/promptCache/prefixAnalyzer.ts +81 -0
- package/src/lib/providerModels/cursorAgent.ts +166 -0
- package/src/lib/providerModels/geminiModelsParser.ts +91 -0
- package/src/lib/providerModels/managedAvailableModels.ts +165 -0
- package/src/lib/providerModels/managedModelImport.ts +349 -0
- package/src/lib/providerModels/modelDiscovery.ts +151 -0
- package/src/lib/providers/catalog.ts +246 -0
- package/src/lib/providers/claudeExtraUsage.ts +160 -0
- package/src/lib/providers/claudeFastMode.ts +75 -0
- package/src/lib/providers/codexConnectionDefaults.ts +85 -0
- package/src/lib/providers/codexFastTier.ts +210 -0
- package/src/lib/providers/imageValidation.ts +160 -0
- package/src/lib/providers/managedAvailableModels.ts +21 -0
- package/src/lib/providers/modelListingCapability.ts +26 -0
- package/src/lib/providers/nvidiaValidationModel.ts +24 -0
- package/src/lib/providers/requestDefaults.ts +318 -0
- package/src/lib/providers/serviceKindIndex.ts +0 -0
- package/src/lib/providers/staticModels.ts +170 -0
- package/src/lib/providers/validation/anthropicFormat.ts +319 -0
- package/src/lib/providers/validation/audioMiscProviders.ts +582 -0
- package/src/lib/providers/validation/cloudProviders.ts +669 -0
- package/src/lib/providers/validation/cozeError.ts +56 -0
- package/src/lib/providers/validation/directChatProbe.ts +47 -0
- package/src/lib/providers/validation/embeddingProviders.ts +143 -0
- package/src/lib/providers/validation/headers.ts +122 -0
- package/src/lib/providers/validation/metaAi.ts +117 -0
- package/src/lib/providers/validation/openaiFormat.ts +500 -0
- package/src/lib/providers/validation/searchProviders.ts +199 -0
- package/src/lib/providers/validation/transport.ts +109 -0
- package/src/lib/providers/validation/urlHelpers.ts +114 -0
- package/src/lib/providers/validation/webProvidersA.ts +808 -0
- package/src/lib/providers/validation/webProvidersB.ts +536 -0
- package/src/lib/providers/validation.ts +772 -0
- package/src/lib/providers/webCookieAuth.ts +157 -0
- package/src/lib/providers/xai/thinking.ts +127 -0
- package/src/lib/providers/xai/translators/claude.ts +368 -0
- package/src/lib/providers/xai/translators/gemini.ts +371 -0
- package/src/lib/providers/xai/translators/openai-chat.ts +310 -0
- package/src/lib/providers/xai/translators/openai-responses.ts +77 -0
- package/src/lib/proxyEgress.ts +388 -0
- package/src/lib/proxyHealth/decision.ts +77 -0
- package/src/lib/proxyHealth/scheduler.ts +207 -0
- package/src/lib/proxyHealth/statusPolicy.ts +41 -0
- package/src/lib/proxyHealth.ts +173 -0
- package/src/lib/proxyLogger.ts +240 -0
- package/src/lib/proxyRelay/cloudflareWorkerScript.ts +107 -0
- package/src/lib/quota/QuotaStore.ts +23 -0
- package/src/lib/quota/accountBuckets.ts +225 -0
- package/src/lib/quota/burnRate.ts +111 -0
- package/src/lib/quota/connectionProvider.ts +32 -0
- package/src/lib/quota/connectionRecovery.ts +303 -0
- package/src/lib/quota/dimensions.ts +61 -0
- package/src/lib/quota/enforce.ts +401 -0
- package/src/lib/quota/fairShare.ts +186 -0
- package/src/lib/quota/planRegistry.ts +107 -0
- package/src/lib/quota/planResolver.ts +78 -0
- package/src/lib/quota/quotaCombos.ts +414 -0
- package/src/lib/quota/quotaKey.ts +202 -0
- package/src/lib/quota/quotaModelNaming.ts +84 -0
- package/src/lib/quota/redisQuotaStore.ts +352 -0
- package/src/lib/quota/saturationSignals.ts +559 -0
- package/src/lib/quota/spendRecorder.ts +131 -0
- package/src/lib/quota/sqliteQuotaStore.ts +282 -0
- package/src/lib/quota/storeFactory.ts +124 -0
- package/src/lib/quota/types.ts +94 -0
- package/src/lib/resilience/modelLockoutSettings.ts +95 -0
- package/src/lib/resilience/settings/normalize.ts +359 -0
- package/src/lib/resilience/settings/types.ts +182 -0
- package/src/lib/resilience/settings.ts +386 -0
- package/src/lib/runtime/ports.ts +32 -0
- package/src/lib/search/executeWebSearch.ts +270 -0
- package/src/lib/security/localEndpoints.ts +82 -0
- package/src/lib/semanticCache.ts +377 -0
- package/src/lib/services/ServiceSupervisor.ts +243 -0
- package/src/lib/services/apiKey.ts +40 -0
- package/src/lib/services/bootstrap.ts +133 -0
- package/src/lib/services/embedPath.ts +19 -0
- package/src/lib/services/embedWsProxy.ts +285 -0
- package/src/lib/services/healthCheck.ts +79 -0
- package/src/lib/services/htmlRewriter.ts +185 -0
- package/src/lib/services/installers/bifrost.ts +145 -0
- package/src/lib/services/installers/cliproxy.ts +116 -0
- package/src/lib/services/installers/mux.ts +178 -0
- package/src/lib/services/installers/ninerouter.stub.ts +17 -0
- package/src/lib/services/installers/ninerouter.ts +157 -0
- package/src/lib/services/installers/utils.ts +167 -0
- package/src/lib/services/modelSync.ts +105 -0
- package/src/lib/services/portProbe.ts +104 -0
- package/src/lib/services/registry.ts +42 -0
- package/src/lib/services/reverseProxy.ts +180 -0
- package/src/lib/services/ringBuffer.ts +91 -0
- package/src/lib/services/types.ts +50 -0
- package/src/lib/settingsCache.ts +62 -0
- package/src/lib/skills/a2a.ts +34 -0
- package/src/lib/skills/builtin/browser.ts +22 -0
- package/src/lib/skills/builtins.ts +493 -0
- package/src/lib/skills/custom.ts +41 -0
- package/src/lib/skills/executor.ts +196 -0
- package/src/lib/skills/githubCollector.ts +426 -0
- package/src/lib/skills/hybrid.ts +67 -0
- package/src/lib/skills/injection.ts +307 -0
- package/src/lib/skills/interception.ts +343 -0
- package/src/lib/skills/providerSettings.ts +14 -0
- package/src/lib/skills/registry.ts +398 -0
- package/src/lib/skills/sandbox.ts +178 -0
- package/src/lib/skills/schemas.ts +43 -0
- package/src/lib/skills/skillssh.ts +73 -0
- package/src/lib/skills/types.ts +61 -0
- package/src/lib/source.ts +7 -0
- package/src/lib/spend/batchWriter.ts +208 -0
- package/src/lib/sseTextTransform.ts +346 -0
- package/src/lib/streamingPiiTransform.ts +350 -0
- package/src/lib/sync/bundle.ts +208 -0
- package/src/lib/sync/tokens.ts +107 -0
- package/src/lib/system/autoUpdate.ts +400 -0
- package/src/lib/system/globalPackagePath.ts +49 -0
- package/src/lib/system/versionCheck.ts +163 -0
- package/src/lib/tailscaleTunnel.ts +1201 -0
- package/src/lib/tokenHealthCheck.ts +829 -0
- package/src/lib/toolPolicy.ts +150 -0
- package/src/lib/translator/streamTransform.ts +27 -0
- package/src/lib/translatorEvents.ts +40 -0
- package/src/lib/usage/aggregateHistory.ts +212 -0
- package/src/lib/usage/apiKeySelfService.ts +420 -0
- package/src/lib/usage/apiKeyUsageLimits.ts +578 -0
- package/src/lib/usage/callLogArtifacts.ts +337 -0
- package/src/lib/usage/callLogs/format.ts +129 -0
- package/src/lib/usage/callLogs.ts +928 -0
- package/src/lib/usage/callLogsBoundedQueries.ts +42 -0
- package/src/lib/usage/codexResetCredits.ts +355 -0
- package/src/lib/usage/comboForecast.ts +441 -0
- package/src/lib/usage/comboHealth.ts +545 -0
- package/src/lib/usage/comboHealthDashboard.ts +109 -0
- package/src/lib/usage/comboScoringInspector.ts +416 -0
- package/src/lib/usage/completedRequestDetails.ts +103 -0
- package/src/lib/usage/costCalculator.ts +279 -0
- package/src/lib/usage/fetcher.ts +483 -0
- package/src/lib/usage/flatRateProviders.ts +59 -0
- package/src/lib/usage/internalUsageCommand.ts +788 -0
- package/src/lib/usage/migrations.ts +471 -0
- package/src/lib/usage/pendingRequestScope.ts +26 -0
- package/src/lib/usage/providerLimits/quotaNormalize.ts +72 -0
- package/src/lib/usage/providerLimits.ts +997 -0
- package/src/lib/usage/providerWindowCosts.ts +571 -0
- package/src/lib/usage/resilienceExplain.ts +468 -0
- package/src/lib/usage/routeExplain.ts +747 -0
- package/src/lib/usage/tokenAccounting.ts +212 -0
- package/src/lib/usage/usageEvents.ts +91 -0
- package/src/lib/usage/usageHistory/helpers.ts +85 -0
- package/src/lib/usage/usageHistory.ts +916 -0
- package/src/lib/usage/usageStats.ts +581 -0
- package/src/lib/usageAnalytics.ts +372 -0
- package/src/lib/usageDb.ts +38 -0
- package/src/lib/versionManager/binaryManager.ts +240 -0
- package/src/lib/versionManager/healthMonitor.ts +71 -0
- package/src/lib/versionManager/index.ts +155 -0
- package/src/lib/versionManager/processManager.ts +155 -0
- package/src/lib/versionManager/releaseChecker.ts +98 -0
- package/src/lib/vscode/familyFirstModelIds.ts +92 -0
- package/src/lib/vscode/modelPresentation.ts +132 -0
- package/src/lib/vscode/reasoningMetadata.ts +183 -0
- package/src/lib/vscode/serviceTierVariants.ts +202 -0
- package/src/lib/vscode/tokenizedRequest.ts +79 -0
- package/src/lib/webhookDispatcher.ts +221 -0
- package/src/lib/webhooks/eventDescriptions.ts +76 -0
- package/src/lib/webhooks/integrations/discord.ts +49 -0
- package/src/lib/webhooks/integrations/slack.ts +45 -0
- package/src/lib/webhooks/integrations/telegram.ts +67 -0
- package/src/lib/ws/handshake.ts +129 -0
- package/src/lib/zed-oauth/confirmedAccounts.ts +38 -0
- package/src/lib/zed-oauth/credentialFingerprint.ts +23 -0
- package/src/lib/zed-oauth/dockerDetect.ts +38 -0
- package/src/lib/zed-oauth/importUtils.ts +46 -0
- package/src/lib/zed-oauth/keychain-reader.stub.ts +28 -0
- package/src/lib/zed-oauth/keychain-reader.ts +231 -0
- package/src/mitm/_internal/bypass.cjs +170 -0
- package/src/mitm/_internal/forwardTarget.cjs +55 -0
- package/src/mitm/_internal/ingest.cjs +82 -0
- package/src/mitm/cert/generate.ts +41 -0
- package/src/mitm/cert/install.stub.ts +23 -0
- package/src/mitm/cert/install.ts +440 -0
- package/src/mitm/dataDir.ts +41 -0
- package/src/mitm/detection/antigravity.ts +33 -0
- package/src/mitm/detection/claudeCode.ts +25 -0
- package/src/mitm/detection/codex.ts +25 -0
- package/src/mitm/detection/copilot.ts +39 -0
- package/src/mitm/detection/cursor.ts +31 -0
- package/src/mitm/detection/index.ts +53 -0
- package/src/mitm/detection/kiro.ts +30 -0
- package/src/mitm/detection/openCode.ts +32 -0
- package/src/mitm/detection/zed.ts +26 -0
- package/src/mitm/dns/dnsConfig.ts +242 -0
- package/src/mitm/dns/provision.ts +92 -0
- package/src/mitm/handlers/antigravity.ts +192 -0
- package/src/mitm/handlers/base.ts +333 -0
- package/src/mitm/handlers/claudeCode.ts +56 -0
- package/src/mitm/handlers/codex.ts +55 -0
- package/src/mitm/handlers/copilot.ts +55 -0
- package/src/mitm/handlers/cursor.ts +55 -0
- package/src/mitm/handlers/kiro.ts +58 -0
- package/src/mitm/handlers/openCode.ts +55 -0
- package/src/mitm/handlers/trae.ts +24 -0
- package/src/mitm/handlers/zed.ts +55 -0
- package/src/mitm/inspector/agentBridgeHook.ts +137 -0
- package/src/mitm/inspector/buffer.ts +201 -0
- package/src/mitm/inspector/contextKey.ts +98 -0
- package/src/mitm/inspector/conversationNormalizer.ts +393 -0
- package/src/mitm/inspector/diagnostics.ts +67 -0
- package/src/mitm/inspector/httpProxyServer.ts +272 -0
- package/src/mitm/inspector/kindDetector.ts +87 -0
- package/src/mitm/inspector/llmMetadataExtractor.ts +183 -0
- package/src/mitm/inspector/pricing.ts +57 -0
- package/src/mitm/inspector/processAttribution.ts +107 -0
- package/src/mitm/inspector/sseMerger.ts +316 -0
- package/src/mitm/inspector/systemProxyConfig.ts +316 -0
- package/src/mitm/inspector/types.ts +112 -0
- package/src/mitm/manager.runtime.ts +5 -0
- package/src/mitm/manager.stub.ts +44 -0
- package/src/mitm/manager.ts +762 -0
- package/src/mitm/maskSecrets.ts +31 -0
- package/src/mitm/passthrough.ts +76 -0
- package/src/mitm/sanitizeHeaders.ts +61 -0
- package/src/mitm/server.cjs +786 -0
- package/src/mitm/socketTimeouts.ts +24 -0
- package/src/mitm/systemCommands.ts +280 -0
- package/src/mitm/targets/antigravity.ts +85 -0
- package/src/mitm/targets/claudeCode.ts +37 -0
- package/src/mitm/targets/codex.ts +30 -0
- package/src/mitm/targets/copilot.ts +32 -0
- package/src/mitm/targets/cursor.ts +33 -0
- package/src/mitm/targets/index.ts +75 -0
- package/src/mitm/targets/kiro.ts +64 -0
- package/src/mitm/targets/openCode.ts +34 -0
- package/src/mitm/targets/trae.ts +32 -0
- package/src/mitm/targets/zed.ts +32 -0
- package/src/mitm/tproxy/caTrust.ts +120 -0
- package/src/mitm/tproxy/captureManager.ts +118 -0
- package/src/mitm/tproxy/captureMode.ts +226 -0
- package/src/mitm/tproxy/commands.ts +122 -0
- package/src/mitm/tproxy/dynamicCert.ts +119 -0
- package/src/mitm/tproxy/native/README.md +55 -0
- package/src/mitm/tproxy/native/binding.gyp +9 -0
- package/src/mitm/tproxy/native/transparent.c +147 -0
- package/src/mitm/tproxy/setup.ts +68 -0
- package/src/mitm/tproxy/tlsCapture.ts +368 -0
- package/src/mitm/tproxy/transparentSocket.ts +131 -0
- package/src/mitm/types.ts +75 -0
- package/src/mitm/upstreamTrust.ts +40 -0
- package/src/models/index.ts +29 -0
- package/src/server/auth/loginGuard.ts +130 -0
- package/src/server/authz/accessScopes.ts +62 -0
- package/src/server/authz/accessTokenAuth.ts +67 -0
- package/src/server/authz/assertAuth.ts +88 -0
- package/src/server/authz/classify.ts +135 -0
- package/src/server/authz/context.ts +44 -0
- package/src/server/authz/csrf.ts +85 -0
- package/src/server/authz/headers.ts +76 -0
- package/src/server/authz/peerStamp.ts +99 -0
- package/src/server/authz/pipeline.ts +370 -0
- package/src/server/authz/policies/clientApi.ts +72 -0
- package/src/server/authz/policies/management.ts +298 -0
- package/src/server/authz/policies/public.ts +9 -0
- package/src/server/authz/routeGuard.ts +233 -0
- package/src/server/authz/types.ts +77 -0
- package/src/server/cors/origins.ts +172 -0
- package/src/server/origin/publicOrigin.ts +270 -0
- package/src/server/ws/liveServer.ts +630 -0
- package/src/server/ws/liveServerAllowList.ts +106 -0
- package/src/server/ws/types.ts +57 -0
- package/src/shared/components/Avatar.tsx +81 -0
- package/src/shared/components/Badge.tsx +68 -0
- package/src/shared/components/Breadcrumbs.tsx +103 -0
- package/src/shared/components/Button.tsx +85 -0
- package/src/shared/components/Card.tsx +141 -0
- package/src/shared/components/Checkbox.tsx +38 -0
- package/src/shared/components/CloudSyncStatus.tsx +104 -0
- package/src/shared/components/Collapsible.tsx +93 -0
- package/src/shared/components/CollapsibleSection.tsx +66 -0
- package/src/shared/components/ColumnToggle.tsx +100 -0
- package/src/shared/components/CommandPalette.tsx +375 -0
- package/src/shared/components/ConsoleLogViewer.tsx +326 -0
- package/src/shared/components/CursorAuthModal.tsx +198 -0
- package/src/shared/components/DataTable.tsx +195 -0
- package/src/shared/components/DegradationBadge.tsx +41 -0
- package/src/shared/components/DistributeProxiesButton.tsx +74 -0
- package/src/shared/components/EmptyState.tsx +121 -0
- package/src/shared/components/ErrorPageScaffold.tsx +91 -0
- package/src/shared/components/FilterBar.tsx +197 -0
- package/src/shared/components/Footer.tsx +169 -0
- package/src/shared/components/Header.tsx +284 -0
- package/src/shared/components/InfoTooltip.tsx +34 -0
- package/src/shared/components/Input.tsx +155 -0
- package/src/shared/components/KiroAuthModal.tsx +425 -0
- package/src/shared/components/KiroOAuthWrapper.tsx +116 -0
- package/src/shared/components/KiroSocialOAuthModal.tsx +202 -0
- package/src/shared/components/LanguageSelector.tsx +106 -0
- package/src/shared/components/LinkifiedText.tsx +30 -0
- package/src/shared/components/Loading.tsx +128 -0
- package/src/shared/components/LocaleAutoDetect.tsx +43 -0
- package/src/shared/components/MaintenanceBanner.tsx +87 -0
- package/src/shared/components/ManualConfigModal.tsx +51 -0
- package/src/shared/components/Modal.tsx +240 -0
- package/src/shared/components/ModelRoutingSection.tsx +323 -0
- package/src/shared/components/ModelSelectModal.tsx +618 -0
- package/src/shared/components/MonacoEditor.tsx +23 -0
- package/src/shared/components/NavigationProgress.tsx +105 -0
- package/src/shared/components/NoAuthAccountCard.tsx +539 -0
- package/src/shared/components/NoAuthProviderCard.tsx +40 -0
- package/src/shared/components/NoAuthProviderToggle.tsx +36 -0
- package/src/shared/components/NotificationToast.tsx +206 -0
- package/src/shared/components/OAuthModal.tsx +992 -0
- package/src/shared/components/OmniRouteLogo.tsx +86 -0
- package/src/shared/components/PresetSlider.tsx +64 -0
- package/src/shared/components/PricingModal.tsx +211 -0
- package/src/shared/components/ProviderIcon.tsx +263 -0
- package/src/shared/components/ProviderTestSlideOver.tsx +560 -0
- package/src/shared/components/ProxyConfigModal.tsx +781 -0
- package/src/shared/components/ProxyLogDetail.tsx +192 -0
- package/src/shared/components/ProxyLogger.tsx +573 -0
- package/src/shared/components/PwaRegister.tsx +22 -0
- package/src/shared/components/RequestLoggerDetail.tsx +797 -0
- package/src/shared/components/RequestLoggerV2.tsx +1628 -0
- package/src/shared/components/RiskNoticeModal.tsx +81 -0
- package/src/shared/components/SegmentedControl.tsx +69 -0
- package/src/shared/components/Select.tsx +110 -0
- package/src/shared/components/Sidebar.tsx +726 -0
- package/src/shared/components/SkillsConceptCard.tsx +84 -0
- package/src/shared/components/SystemMonitor.tsx +179 -0
- package/src/shared/components/Textarea.tsx +29 -0
- package/src/shared/components/ThemeProvider.tsx +14 -0
- package/src/shared/components/ThemeToggle.tsx +52 -0
- package/src/shared/components/Toggle.tsx +102 -0
- package/src/shared/components/TokenHealthBadge.tsx +113 -0
- package/src/shared/components/Tooltip.tsx +193 -0
- package/src/shared/components/TraeAuthModal.tsx +334 -0
- package/src/shared/components/UsageAnalytics.tsx +414 -0
- package/src/shared/components/UsageStats.tsx +693 -0
- package/src/shared/components/analytics/ApiKeyFilterDropdown.tsx +205 -0
- package/src/shared/components/analytics/CustomRangePicker.tsx +195 -0
- package/src/shared/components/analytics/TimeRangeSelector.tsx +49 -0
- package/src/shared/components/analytics/chartColors.ts +12 -0
- package/src/shared/components/analytics/charts.tsx +1023 -0
- package/src/shared/components/analytics/index.tsx +28 -0
- package/src/shared/components/analytics/rechartsCore.tsx +85 -0
- package/src/shared/components/analytics/rechartsDonuts.tsx +195 -0
- package/src/shared/components/analytics/rechartsUsageCharts.tsx +312 -0
- package/src/shared/components/cli/CliComparisonCard.tsx +82 -0
- package/src/shared/components/cli/CliConceptCard.tsx +58 -0
- package/src/shared/components/cli/CliToolCard.tsx +152 -0
- package/src/shared/components/cli/index.ts +8 -0
- package/src/shared/components/compression/CompressionPipelineEditor.tsx +175 -0
- package/src/shared/components/compression/EngineConfigForm.tsx +78 -0
- package/src/shared/components/compression/EngineConfigPage.tsx +411 -0
- package/src/shared/components/compression/compressionPipelineModel.ts +78 -0
- package/src/shared/components/docs/APIReference.stories.tsx +35 -0
- package/src/shared/components/docs/APIReference.tsx +83 -0
- package/src/shared/components/docs/Callout.stories.tsx +40 -0
- package/src/shared/components/docs/Callout.tsx +38 -0
- package/src/shared/components/docs/CodeBlock.stories.tsx +25 -0
- package/src/shared/components/docs/CodeBlock.tsx +52 -0
- package/src/shared/components/docs/DocsBreadcrumbs.stories.tsx +20 -0
- package/src/shared/components/docs/DocsBreadcrumbs.tsx +46 -0
- package/src/shared/components/docs/DocsSidebar.stories.tsx +31 -0
- package/src/shared/components/docs/DocsSidebar.tsx +77 -0
- package/src/shared/components/docs/DocsThemeProvider.tsx +21 -0
- package/src/shared/components/docs/Tabs.stories.tsx +26 -0
- package/src/shared/components/docs/Tabs.tsx +50 -0
- package/src/shared/components/docs/tokens.ts +38 -0
- package/src/shared/components/flow/FlowCanvas.tsx +151 -0
- package/src/shared/components/flow/StatusDot.tsx +36 -0
- package/src/shared/components/flow/edgeStyles.ts +32 -0
- package/src/shared/components/index.tsx +49 -0
- package/src/shared/components/layouts/AuthLayout.tsx +28 -0
- package/src/shared/components/layouts/DashboardLayout.tsx +131 -0
- package/src/shared/components/layouts/index.tsx +3 -0
- package/src/shared/components/lobeProviderIcons.ts +488 -0
- package/src/shared/components/logTableStyles.ts +7 -0
- package/src/shared/components/modelSelectModalHelpers.ts +75 -0
- package/src/shared/components/oauthBlobSubmit.ts +42 -0
- package/src/shared/components/proxyAssignment.ts +40 -0
- package/src/shared/components/requestLoggerPreferences.ts +44 -0
- package/src/shared/components/requestLoggerSignature.ts +56 -0
- package/src/shared/constants/agentSkills.ts +457 -0
- package/src/shared/constants/antigravityClientProfile.ts +29 -0
- package/src/shared/constants/apiKeyPolicyScopes.ts +5 -0
- package/src/shared/constants/appConfig.ts +12 -0
- package/src/shared/constants/batch.ts +1 -0
- package/src/shared/constants/batchEndpoints.ts +11 -0
- package/src/shared/constants/bodySize.ts +39 -0
- package/src/shared/constants/cliCompatProviders.ts +88 -0
- package/src/shared/constants/cliTools.ts +862 -0
- package/src/shared/constants/clientIdentityProfiles.ts +89 -0
- package/src/shared/constants/colors.ts +147 -0
- package/src/shared/constants/comboConfigMode.ts +9 -0
- package/src/shared/constants/config.ts +44 -0
- package/src/shared/constants/dashboardCsrf.ts +1 -0
- package/src/shared/constants/endpointCategories.ts +133 -0
- package/src/shared/constants/errorCodes.ts +245 -0
- package/src/shared/constants/featureFlagDefinitions.ts +495 -0
- package/src/shared/constants/headers.ts +16 -0
- package/src/shared/constants/homeWidgets.ts +5 -0
- package/src/shared/constants/index.ts +4 -0
- package/src/shared/constants/managementScopes.ts +31 -0
- package/src/shared/constants/mcpScopes.ts +78 -0
- package/src/shared/constants/mitmToolHosts.ts +31 -0
- package/src/shared/constants/modal.ts +5 -0
- package/src/shared/constants/modelCompat.ts +9 -0
- package/src/shared/constants/modelSpecs.ts +607 -0
- package/src/shared/constants/models.ts +40 -0
- package/src/shared/constants/openRouterPreset.ts +11 -0
- package/src/shared/constants/pricing/default-pricing.ts +16 -0
- package/src/shared/constants/pricing/frontier-labs.ts +386 -0
- package/src/shared/constants/pricing/inference-hosts.ts +208 -0
- package/src/shared/constants/pricing/oauth-subscriptions.ts +599 -0
- package/src/shared/constants/pricing/regional.ts +168 -0
- package/src/shared/constants/pricing/shared-tiers.ts +162 -0
- package/src/shared/constants/pricing.ts +40 -0
- package/src/shared/constants/providers/apikey/enterprise-cloud.ts +214 -0
- package/src/shared/constants/providers/apikey/frontier-labs.ts +253 -0
- package/src/shared/constants/providers/apikey/gateways.ts +662 -0
- package/src/shared/constants/providers/apikey/index.ts +21 -0
- package/src/shared/constants/providers/apikey/inference-hosts.ts +328 -0
- package/src/shared/constants/providers/apikey/regional.ts +372 -0
- package/src/shared/constants/providers/apikey/specialty-media.ts +266 -0
- package/src/shared/constants/providers/audio.ts +71 -0
- package/src/shared/constants/providers/cloud-agent.ts +36 -0
- package/src/shared/constants/providers/local.ts +162 -0
- package/src/shared/constants/providers/noauth.ts +123 -0
- package/src/shared/constants/providers/oauth.ts +231 -0
- package/src/shared/constants/providers/search.ts +125 -0
- package/src/shared/constants/providers/system.ts +16 -0
- package/src/shared/constants/providers/upstream-proxy.ts +36 -0
- package/src/shared/constants/providers/web-cookie.ts +364 -0
- package/src/shared/constants/providers.ts +449 -0
- package/src/shared/constants/publicApiRoutes.ts +62 -0
- package/src/shared/constants/responsesPreviousResponseId.ts +5 -0
- package/src/shared/constants/routingStrategies.ts +216 -0
- package/src/shared/constants/selfServiceScopes.ts +20 -0
- package/src/shared/constants/serviceKinds.ts +34 -0
- package/src/shared/constants/sidebarGroupVisibility.ts +31 -0
- package/src/shared/constants/sidebarVisibility/sections.ts +769 -0
- package/src/shared/constants/sidebarVisibility/types.ts +161 -0
- package/src/shared/constants/sidebarVisibility.ts +291 -0
- package/src/shared/constants/spawnCapablePrefixes.ts +35 -0
- package/src/shared/constants/statusColors.ts +17 -0
- package/src/shared/constants/upstreamHeaders.ts +42 -0
- package/src/shared/constants/visionBridgeDefaults.ts +88 -0
- package/src/shared/constants/visionModels.ts +68 -0
- package/src/shared/contracts/quota.ts +138 -0
- package/src/shared/hooks/cli/useToolBatchStatuses.ts +54 -0
- package/src/shared/hooks/index.ts +3 -0
- package/src/shared/hooks/useCopyToClipboard.ts +38 -0
- package/src/shared/hooks/useDisplayBaseUrl.ts +49 -0
- package/src/shared/hooks/useElectron.ts +197 -0
- package/src/shared/hooks/useTheme.ts +59 -0
- package/src/shared/http/apiErrorMessage.ts +19 -0
- package/src/shared/lib/persistLocale.ts +19 -0
- package/src/shared/middleware/bodySizeGuard.ts +97 -0
- package/src/shared/middleware/chatBodyAdmission.ts +161 -0
- package/src/shared/middleware/correlationId.ts +43 -0
- package/src/shared/network/outboundUrlGuard.ts +276 -0
- package/src/shared/network/remoteImageFetch.ts +182 -0
- package/src/shared/network/safeOutboundFetch.ts +372 -0
- package/src/shared/providers/webSessionCredentials.ts +308 -0
- package/src/shared/reasoning/effortStandardization.ts +124 -0
- package/src/shared/schemas/agentBridge.ts +37 -0
- package/src/shared/schemas/cliCatalog.ts +66 -0
- package/src/shared/schemas/inspector.ts +47 -0
- package/src/shared/schemas/memory.ts +142 -0
- package/src/shared/schemas/playground.ts +67 -0
- package/src/shared/schemas/qdrant.ts +39 -0
- package/src/shared/schemas/quota.ts +61 -0
- package/src/shared/schemas/searchTools.ts +39 -0
- package/src/shared/schemas/validation.ts +154 -0
- package/src/shared/services/apiKeyResolver.ts +44 -0
- package/src/shared/services/backupService.ts +233 -0
- package/src/shared/services/claudeCliConfig.ts +17 -0
- package/src/shared/services/cliRuntime.ts +1099 -0
- package/src/shared/services/cloudSyncScheduler.ts +135 -0
- package/src/shared/services/droidCustomModels.ts +118 -0
- package/src/shared/services/initializeCloudSync.ts +31 -0
- package/src/shared/services/loginShellPath.ts +87 -0
- package/src/shared/services/modelSyncScheduler.ts +225 -0
- package/src/shared/services/opencodeConfig.ts +140 -0
- package/src/shared/services/providerLimitsSyncScheduler.ts +72 -0
- package/src/shared/types/cliBatchStatus.ts +18 -0
- package/src/shared/types/index.ts +3 -0
- package/src/shared/types/pagination.ts +61 -0
- package/src/shared/types/utilization.ts +405 -0
- package/src/shared/utils/a11yAudit.ts +140 -0
- package/src/shared/utils/api.ts +112 -0
- package/src/shared/utils/apiAuth.ts +354 -0
- package/src/shared/utils/apiKey.ts +92 -0
- package/src/shared/utils/apiKeyPolicy.ts +680 -0
- package/src/shared/utils/apiResponse.ts +79 -0
- package/src/shared/utils/bulkApiKeyParser.ts +113 -0
- package/src/shared/utils/circuitBreaker.ts +611 -0
- package/src/shared/utils/classify429.ts +253 -0
- package/src/shared/utils/cliCompat.ts +5 -0
- package/src/shared/utils/clineAuth.ts +62 -0
- package/src/shared/utils/clipboard.ts +49 -0
- package/src/shared/utils/cloud.ts +41 -0
- package/src/shared/utils/cn.ts +12 -0
- package/src/shared/utils/codexBaseUrl.ts +32 -0
- package/src/shared/utils/codexConfig.ts +30 -0
- package/src/shared/utils/compressionHeaderEcho.ts +59 -0
- package/src/shared/utils/cors.ts +24 -0
- package/src/shared/utils/costEstimator.ts +126 -0
- package/src/shared/utils/dashboardCsrf.ts +196 -0
- package/src/shared/utils/featureFlags.ts +113 -0
- package/src/shared/utils/fetchError.ts +26 -0
- package/src/shared/utils/fetchTimeout.ts +78 -0
- package/src/shared/utils/formatting.ts +183 -0
- package/src/shared/utils/freeModels.ts +112 -0
- package/src/shared/utils/index.ts +39 -0
- package/src/shared/utils/inputSanitizer.ts +339 -0
- package/src/shared/utils/linkify.ts +54 -0
- package/src/shared/utils/logRedaction.ts +108 -0
- package/src/shared/utils/logger.ts +173 -0
- package/src/shared/utils/machine.ts +5 -0
- package/src/shared/utils/machineId.ts +167 -0
- package/src/shared/utils/maskEmail.ts +77 -0
- package/src/shared/utils/modelCatalogSearch.ts +84 -0
- package/src/shared/utils/noAuthProviders.ts +79 -0
- package/src/shared/utils/nodeRuntimeSupport.ts +112 -0
- package/src/shared/utils/parseApiKeys.ts +41 -0
- package/src/shared/utils/providerDisplayLabel.ts +41 -0
- package/src/shared/utils/providerHints.ts +73 -0
- package/src/shared/utils/providerModelAliases.ts +68 -0
- package/src/shared/utils/rateLimiter.ts +243 -0
- package/src/shared/utils/releaseNotes.ts +53 -0
- package/src/shared/utils/requestId.ts +102 -0
- package/src/shared/utils/requestTelemetry.ts +189 -0
- package/src/shared/utils/requestTimeout.ts +64 -0
- package/src/shared/utils/resolveOmniRouteBaseUrl.ts +24 -0
- package/src/shared/utils/runtimeTimeouts.ts +257 -0
- package/src/shared/utils/secretsValidator.ts +135 -0
- package/src/shared/utils/secureRandom.ts +66 -0
- package/src/shared/utils/serviceTierLabels.ts +42 -0
- package/src/shared/utils/shuffleDeck.ts +147 -0
- package/src/shared/utils/sidebarRouteMatch.ts +43 -0
- package/src/shared/utils/streamTracker.ts +187 -0
- package/src/shared/utils/structuredLogger.ts +219 -0
- package/src/shared/utils/tiktokenCounter.ts +21 -0
- package/src/shared/utils/turkishText.ts +66 -0
- package/src/shared/utils/upstreamError.ts +112 -0
- package/src/shared/validation/compressionConfigSchemas.ts +234 -0
- package/src/shared/validation/freeProxySchemas.ts +111 -0
- package/src/shared/validation/helpers.ts +117 -0
- package/src/shared/validation/oneproxySchemas.ts +14 -0
- package/src/shared/validation/providerSchema.ts +51 -0
- package/src/shared/validation/providerSpecificData.ts +354 -0
- package/src/shared/validation/schemas/apiV1.ts +297 -0
- package/src/shared/validation/schemas/auth.ts +200 -0
- package/src/shared/validation/schemas/cli.ts +86 -0
- package/src/shared/validation/schemas/cloud.ts +53 -0
- package/src/shared/validation/schemas/combo.ts +373 -0
- package/src/shared/validation/schemas/evals.ts +86 -0
- package/src/shared/validation/schemas/gemini.ts +59 -0
- package/src/shared/validation/schemas/keys.ts +144 -0
- package/src/shared/validation/schemas/misc.ts +203 -0
- package/src/shared/validation/schemas/payloadRules.ts +64 -0
- package/src/shared/validation/schemas/pricing.ts +40 -0
- package/src/shared/validation/schemas/provider.ts +430 -0
- package/src/shared/validation/schemas/proxy.ts +243 -0
- package/src/shared/validation/schemas/routing.ts +88 -0
- package/src/shared/validation/schemas/settings.ts +267 -0
- package/src/shared/validation/schemas/translator.ts +44 -0
- package/src/shared/validation/schemas.ts +18 -0
- package/src/shared/validation/settingsSchemas.ts +396 -0
- package/src/sse/handlers/chat.ts +1750 -0
- package/src/sse/handlers/chatHelpers.ts +859 -0
- package/src/sse/handlers/requestBody.ts +24 -0
- package/src/sse/handlers/resolveRoutingModel.ts +17 -0
- package/src/sse/services/auth.ts +2446 -0
- package/src/sse/services/cooldownAwareRetry.ts +162 -0
- package/src/sse/services/model.ts +254 -0
- package/src/sse/services/noAuthProviderSettings.ts +18 -0
- package/src/sse/services/noAuthProxyResolution.ts +111 -0
- package/src/sse/services/sessionAffinityPin.ts +247 -0
- package/src/sse/services/streamState.ts +218 -0
- package/src/sse/services/tokenRefresh.ts +275 -0
- package/src/sse/utils/logger.ts +37 -0
- package/src/types/databaseSettings.ts +124 -0
- package/src/types/global.d.ts +122 -0
- package/src/types/index.ts +10 -0
- package/src/types/provider.ts +9 -0
- package/src/types/sqljs.d.ts +32 -0
|
@@ -0,0 +1,3085 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared combo (model combo) handling with fallback support
|
|
3
|
+
* Supports: priority, weighted, round-robin, random, least-used, cost-optimized,
|
|
4
|
+
* reset-aware, reset-window, strict-random, auto, fill-first, p2c, lkgp,
|
|
5
|
+
* context-optimized, context-relay, and fusion strategies
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
checkFallbackError,
|
|
10
|
+
classifyLockoutReason,
|
|
11
|
+
decayModelFailureCount,
|
|
12
|
+
formatRetryAfter,
|
|
13
|
+
getModelLockoutInfo,
|
|
14
|
+
getRuntimeProviderProfile,
|
|
15
|
+
hasPerModelQuota,
|
|
16
|
+
isModelLocked,
|
|
17
|
+
recordModelLockoutFailure,
|
|
18
|
+
recordProviderFailure,
|
|
19
|
+
selectLockoutCooldownMs,
|
|
20
|
+
} from "./accountFallback.ts";
|
|
21
|
+
import { errorResponse, unavailableResponse } from "../utils/error.ts";
|
|
22
|
+
import { buildTargetTimeoutRunner } from "./combo/targetTimeoutRunner.ts";
|
|
23
|
+
import { recordComboRequest, recordComboShadowRequest, getComboMetrics } from "./comboMetrics.ts";
|
|
24
|
+
import {
|
|
25
|
+
resolveComboConfig,
|
|
26
|
+
getDefaultComboConfig,
|
|
27
|
+
resolveComboQueueDepth,
|
|
28
|
+
} from "./comboConfig.ts";
|
|
29
|
+
import {
|
|
30
|
+
maybeGenerateHandoff,
|
|
31
|
+
maybeGenerateUniversalHandoff,
|
|
32
|
+
injectUniversalHandoffBody,
|
|
33
|
+
SKIP_UNIVERSAL_HANDOFF_FLAG,
|
|
34
|
+
type MessageLike,
|
|
35
|
+
} from "./contextHandoff.ts";
|
|
36
|
+
import {
|
|
37
|
+
recordSessionModelUsage,
|
|
38
|
+
getLastSessionModel,
|
|
39
|
+
getHandoff,
|
|
40
|
+
} from "../../src/lib/db/contextHandoffs.ts";
|
|
41
|
+
import { extractSessionAffinityKey } from "@/sse/services/auth";
|
|
42
|
+
import { getHiddenModelsByProvider } from "@/models";
|
|
43
|
+
import { resolveModelLockoutSettings } from "../../src/lib/resilience/modelLockoutSettings";
|
|
44
|
+
import { fetchCodexQuota } from "./codexQuotaFetcher.ts";
|
|
45
|
+
import { evaluateQuotaCutoff, getQuotaFetcher, type QuotaInfo } from "./quotaPreflight.ts";
|
|
46
|
+
import * as semaphore from "./rateLimitSemaphore.ts";
|
|
47
|
+
import { getCircuitBreaker } from "../../src/shared/utils/circuitBreaker";
|
|
48
|
+
import { fisherYatesShuffle, getNextFromDeck } from "../../src/shared/utils/shuffleDeck";
|
|
49
|
+
import { parseModel } from "./model.ts";
|
|
50
|
+
import { createComboContext } from "./combo/context.ts";
|
|
51
|
+
import { phaseComboSetup } from "./combo/comboSetup.ts";
|
|
52
|
+
import { checkCredentialGate, logCredentialSkip } from "./credentialGate.ts";
|
|
53
|
+
import { emit } from "../../src/lib/events/eventBus";
|
|
54
|
+
import { notifyWebhookEvent } from "../../src/lib/webhookDispatcher";
|
|
55
|
+
import { parseAutoPrefix } from "./autoCombo/autoPrefix.ts";
|
|
56
|
+
import { resolveAutoStrategyOrder } from "./combo/resolveAutoStrategy.ts";
|
|
57
|
+
import { applyStrategyOrdering } from "./combo/applyStrategyOrdering.ts";
|
|
58
|
+
import { handlePipelineCombo, buildPipelineResponse } from "./autoCombo/pipelineRouter.ts";
|
|
59
|
+
import { type ProviderCandidate } from "./autoCombo/scoring.ts";
|
|
60
|
+
import { estimateTokens } from "./contextManager.ts";
|
|
61
|
+
import { getSessionConnection } from "./sessionManager.ts";
|
|
62
|
+
import {
|
|
63
|
+
applySessionStickiness,
|
|
64
|
+
recordStickyBinding,
|
|
65
|
+
resolveDisableSessionStickiness,
|
|
66
|
+
} from "./combo/sessionStickiness.ts";
|
|
67
|
+
import { selectQuotaShareTarget } from "./combo/quotaShareStrategy.ts";
|
|
68
|
+
import { makeConnectionConcurrencyResolver, lookupPositiveCap } from "./combo/concurrencyCaps.ts";
|
|
69
|
+
import { acquireQuotaShareConcurrencySlot } from "./combo/quotaShareConcurrency.ts";
|
|
70
|
+
import { orderTargetsByEvalScores } from "./evalRouting.ts";
|
|
71
|
+
import type { CompressionMode } from "./compression/types.ts";
|
|
72
|
+
import { getProviderConnections } from "../../src/lib/db/providers";
|
|
73
|
+
import {
|
|
74
|
+
isProviderInCooldown,
|
|
75
|
+
recordProviderCooldown,
|
|
76
|
+
recordProviderSuccess,
|
|
77
|
+
} from "./providerCooldownTracker.ts";
|
|
78
|
+
import {
|
|
79
|
+
resolveResilienceSettings,
|
|
80
|
+
type ResilienceSettings,
|
|
81
|
+
} from "../../src/lib/resilience/settings";
|
|
82
|
+
import { resolveReasoningBufferedMaxTokens, toPositiveInteger } from "./reasoningTokenBuffer.ts";
|
|
83
|
+
import { RESET_WINDOW_NAMES } from "./combo/types.ts";
|
|
84
|
+
import type {
|
|
85
|
+
ComboLike,
|
|
86
|
+
ComboRetryAfter,
|
|
87
|
+
ComboErrorBody,
|
|
88
|
+
SingleModelTarget,
|
|
89
|
+
HandleComboChatOptions,
|
|
90
|
+
HandleRoundRobinOptions,
|
|
91
|
+
NestedComboMode,
|
|
92
|
+
ResolvedComboTarget,
|
|
93
|
+
ResolvedComboUnit,
|
|
94
|
+
AutoProviderCandidate,
|
|
95
|
+
ComboRuntimeStep,
|
|
96
|
+
HistoricalLatencyStatsEntry,
|
|
97
|
+
} from "./combo/types.ts";
|
|
98
|
+
|
|
99
|
+
import {
|
|
100
|
+
MAX_RR_COUNTERS,
|
|
101
|
+
rrCounters,
|
|
102
|
+
rrStickyTargets,
|
|
103
|
+
weightedStickyTargets,
|
|
104
|
+
clampStickyRoundRobinTargetLimit,
|
|
105
|
+
clampStickyWeightedTargetLimit,
|
|
106
|
+
getStickyRoundRobinStartIndex,
|
|
107
|
+
recordStickyRoundRobinSuccess,
|
|
108
|
+
getStickyWeightedExecutionKey,
|
|
109
|
+
recordStickyWeightedSuccess,
|
|
110
|
+
} from "./combo/rrState.ts";
|
|
111
|
+
import {
|
|
112
|
+
validateResponseQuality,
|
|
113
|
+
releaseQualityClone,
|
|
114
|
+
toRetryAfterDisplayValue,
|
|
115
|
+
} from "./combo/validateQuality.ts";
|
|
116
|
+
import { resolveComboCooldownWaitDecision } from "./combo/comboCooldownRetry.ts";
|
|
117
|
+
import {
|
|
118
|
+
computeClosestRetryAfter,
|
|
119
|
+
waitForCooldownAwareRetry,
|
|
120
|
+
} from "../../src/sse/services/cooldownAwareRetry.ts";
|
|
121
|
+
import { handleFusionChat, type FusionTuning } from "./fusion.ts";
|
|
122
|
+
import { handlePipelineChat, type PipelineStep } from "./pipeline.ts";
|
|
123
|
+
import {
|
|
124
|
+
TRANSIENT_FOR_SEMAPHORE,
|
|
125
|
+
MAX_FALLBACK_WAIT_MS,
|
|
126
|
+
MAX_GLOBAL_ATTEMPTS,
|
|
127
|
+
isAllAccountsRateLimitedResponse,
|
|
128
|
+
clampComboDepth,
|
|
129
|
+
shouldSkipForPredictedTtft,
|
|
130
|
+
shouldRecordProviderBreakerFailure,
|
|
131
|
+
resolveDelayMs,
|
|
132
|
+
comboModelNotFoundResponse,
|
|
133
|
+
isStreamReadinessFailureErrorBody,
|
|
134
|
+
isTokenLimitBreachErrorBody,
|
|
135
|
+
toRecordedTarget,
|
|
136
|
+
getExhaustedTargetSkipReason,
|
|
137
|
+
} from "./combo/comboPredicates.ts";
|
|
138
|
+
import { applyComboTargetExhaustion } from "./combo/targetExhaustion.ts";
|
|
139
|
+
import { executeRuntimeUnitCombo } from "./combo/runtimeUnits.ts";
|
|
140
|
+
import { isRecord } from "./combo/comboData.ts";
|
|
141
|
+
import {
|
|
142
|
+
expandProviderWildcardsInCombo,
|
|
143
|
+
expandProviderWildcardsInCollection,
|
|
144
|
+
} from "./combo/providerWildcard.ts";
|
|
145
|
+
import { resolveShadowTargets, scheduleShadowRouting } from "./combo/shadowRouting.ts";
|
|
146
|
+
import { attemptCompatRejectedFallback } from "./combo/comboCompatFallback.ts";
|
|
147
|
+
import {
|
|
148
|
+
filterTargetsByRequestCompatibility,
|
|
149
|
+
resolveComboRuntimeUnits,
|
|
150
|
+
resolveComboTargets,
|
|
151
|
+
resolveWeightedTargets,
|
|
152
|
+
resolveWeightedStepGroups,
|
|
153
|
+
} from "./combo/comboStructure.ts";
|
|
154
|
+
import {
|
|
155
|
+
QUOTA_SOFT_DEPRIORITIZE_FACTOR,
|
|
156
|
+
setCandidateQuotaSoftPenalty,
|
|
157
|
+
_registerExecutionCandidates,
|
|
158
|
+
_unregisterExecutionCandidates,
|
|
159
|
+
applyRequestTagRouting,
|
|
160
|
+
scoreAutoTargets,
|
|
161
|
+
expandAutoComboCandidatePool,
|
|
162
|
+
} from "./combo/autoStrategy.ts";
|
|
163
|
+
import {
|
|
164
|
+
resolveResetWindowConfig,
|
|
165
|
+
calculateResetWindowAffinity,
|
|
166
|
+
type ResetWindowConfig,
|
|
167
|
+
} from "./combo/quotaScoring.ts";
|
|
168
|
+
import {
|
|
169
|
+
fetchResetAwareQuotaWithCache,
|
|
170
|
+
preScreenTargets,
|
|
171
|
+
type PreScreenResult,
|
|
172
|
+
} from "./combo/quotaStrategies.ts";
|
|
173
|
+
import {
|
|
174
|
+
buildAutoQuotaThresholds,
|
|
175
|
+
resolveQuotaExhaustionCutoffForTarget,
|
|
176
|
+
} from "./combo/quotaExhaustionCutoff.ts";
|
|
177
|
+
import {
|
|
178
|
+
classifyTask,
|
|
179
|
+
getConversationCacheKey,
|
|
180
|
+
isTaskRoutingStrategy,
|
|
181
|
+
reorderByTaskWeight,
|
|
182
|
+
} from "./taskAwareRouting.ts";
|
|
183
|
+
import { expandTargetsByFingerprints } from "./combo/fingerprintExpansion.ts";
|
|
184
|
+
|
|
185
|
+
export { RESET_WINDOW_NAMES };
|
|
186
|
+
export { QUOTA_SOFT_DEPRIORITIZE_FACTOR, setCandidateQuotaSoftPenalty };
|
|
187
|
+
export { scoreAutoTargets, expandAutoComboCandidatePool };
|
|
188
|
+
export type { SingleModelTarget, ResolvedComboTarget };
|
|
189
|
+
export { validateResponseQuality };
|
|
190
|
+
export { clampComboDepth, shouldSkipForPredictedTtft, shouldRecordProviderBreakerFailure };
|
|
191
|
+
export { resolveShadowTargets, scheduleShadowRouting };
|
|
192
|
+
export { preScreenTargets };
|
|
193
|
+
export { resolveComboRuntimeUnits, resolveComboTargets, filterTargetsByRequestCompatibility };
|
|
194
|
+
export {
|
|
195
|
+
getComboFromData,
|
|
196
|
+
getComboModelsFromData,
|
|
197
|
+
resolveNestedComboModels,
|
|
198
|
+
resolveNestedComboTargets,
|
|
199
|
+
validateComboDAG,
|
|
200
|
+
} from "./combo/comboStructure.ts";
|
|
201
|
+
|
|
202
|
+
const DEFAULT_MODEL_P95_MS: Record<string, number> = {
|
|
203
|
+
"grok-4-fast-non-reasoning": 1143,
|
|
204
|
+
"grok-4-1-fast-non-reasoning": 1244,
|
|
205
|
+
"gemini-2.5-flash": 1238,
|
|
206
|
+
"kimi-k2.5": 1646,
|
|
207
|
+
"gpt-4o-mini": 2764,
|
|
208
|
+
"claude-sonnet-4.6": 4000,
|
|
209
|
+
"claude-opus-4.6": 6000,
|
|
210
|
+
"deepseek-chat": 2000,
|
|
211
|
+
};
|
|
212
|
+
const MIN_HISTORY_SAMPLES = 10;
|
|
213
|
+
const OUTPUT_TOKEN_RATIO = 0.4;
|
|
214
|
+
|
|
215
|
+
function normalizeNestedComboMode(value: unknown): NestedComboMode {
|
|
216
|
+
return value === "execute" ? "execute" : "flatten";
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function calculateTargetContextAffinity(
|
|
220
|
+
target: ResolvedComboTarget,
|
|
221
|
+
sessionId: string | null | undefined
|
|
222
|
+
): number {
|
|
223
|
+
const sessionConnectionId = getSessionConnection(sessionId || null);
|
|
224
|
+
if (!sessionConnectionId) return 0.5;
|
|
225
|
+
if (target.connectionId === sessionConnectionId) return 1;
|
|
226
|
+
if (!target.connectionId) return 0.5;
|
|
227
|
+
return 0.1;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function getBootstrapLatencyMs(modelId: string): number {
|
|
231
|
+
const normalized = String(modelId || "").toLowerCase();
|
|
232
|
+
return DEFAULT_MODEL_P95_MS[normalized] ?? 1500;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function clampPercent(value: number): number {
|
|
236
|
+
if (!Number.isFinite(value)) return 100;
|
|
237
|
+
return Math.max(0, Math.min(100, value));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function quotaRemainingPercentFromQuota(quota: unknown): number {
|
|
241
|
+
if (!quota || typeof quota !== "object") return 100;
|
|
242
|
+
const record = quota as Record<string, unknown>;
|
|
243
|
+
if (record.limitReached === true) return 0;
|
|
244
|
+
|
|
245
|
+
const windows = record.windows;
|
|
246
|
+
if (windows && typeof windows === "object" && !Array.isArray(windows)) {
|
|
247
|
+
let minRemaining: number | null = null;
|
|
248
|
+
for (const windowInfo of Object.values(windows as Record<string, unknown>)) {
|
|
249
|
+
if (!windowInfo || typeof windowInfo !== "object") continue;
|
|
250
|
+
const percentUsed = Number((windowInfo as Record<string, unknown>).percentUsed);
|
|
251
|
+
if (!Number.isFinite(percentUsed)) continue;
|
|
252
|
+
const remaining = clampPercent((1 - percentUsed) * 100);
|
|
253
|
+
minRemaining = minRemaining === null ? remaining : Math.min(minRemaining, remaining);
|
|
254
|
+
}
|
|
255
|
+
if (minRemaining !== null) return minRemaining;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const percentUsed = Number(record.percentUsed);
|
|
259
|
+
if (Number.isFinite(percentUsed)) return clampPercent((1 - percentUsed) * 100);
|
|
260
|
+
return 100;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const QUOTA_BLOCKING_CONNECTION_STATUSES = new Set([
|
|
264
|
+
"banned",
|
|
265
|
+
"credits_exhausted",
|
|
266
|
+
"deactivated",
|
|
267
|
+
"expired",
|
|
268
|
+
"rate_limited",
|
|
269
|
+
]);
|
|
270
|
+
|
|
271
|
+
function normalizeConnectionStatus(value: unknown): string {
|
|
272
|
+
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function hasFutureRateLimitUntil(value: unknown): boolean {
|
|
276
|
+
if (value == null || value === "") return false;
|
|
277
|
+
const time = new Date(String(value)).getTime();
|
|
278
|
+
return Number.isFinite(time) && time > Date.now();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function getConnectionStatusQuotaCutoffReason(
|
|
282
|
+
connection: Record<string, unknown> | undefined
|
|
283
|
+
): string | undefined {
|
|
284
|
+
if (!connection) return undefined;
|
|
285
|
+
const status = normalizeConnectionStatus(connection.testStatus);
|
|
286
|
+
if (QUOTA_BLOCKING_CONNECTION_STATUSES.has(status)) return status;
|
|
287
|
+
if (status === "unavailable" && hasFutureRateLimitUntil(connection.rateLimitedUntil)) {
|
|
288
|
+
return "rate_limited";
|
|
289
|
+
}
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export async function buildAutoCandidates(
|
|
294
|
+
targets: ResolvedComboTarget[],
|
|
295
|
+
comboName: string,
|
|
296
|
+
sessionId: string | null | undefined = null,
|
|
297
|
+
resetWindowConfig: ResetWindowConfig = resolveResetWindowConfig(null),
|
|
298
|
+
resilienceSettings: ResilienceSettings | null = null
|
|
299
|
+
): Promise<AutoProviderCandidate[]> {
|
|
300
|
+
const hiddenModelsMap = getHiddenModelsByProvider();
|
|
301
|
+
const metrics = getComboMetrics(comboName);
|
|
302
|
+
// Opt-in hard quota cutoff (default OFF). When disabled, candidates are never
|
|
303
|
+
// dropped for low quota here — the soft quota penalty + connection cooldown still
|
|
304
|
+
// apply, so auto-routing behavior is unchanged.
|
|
305
|
+
const quotaCutoffEnabled =
|
|
306
|
+
(resilienceSettings ?? resolveResilienceSettings(null))?.quotaPreflight?.enabled === true;
|
|
307
|
+
const { getPricingForModel } = await import("../../src/lib/localDb");
|
|
308
|
+
const quotaPromises = new Map<string, Promise<unknown>>();
|
|
309
|
+
let historicalLatencyStats: Record<string, HistoricalLatencyStatsEntry> = {};
|
|
310
|
+
try {
|
|
311
|
+
const { getModelLatencyStats } = await import("../../src/lib/usageDb");
|
|
312
|
+
historicalLatencyStats = await getModelLatencyStats({
|
|
313
|
+
windowHours: 24,
|
|
314
|
+
minSamples: 3,
|
|
315
|
+
maxRows: 10000,
|
|
316
|
+
});
|
|
317
|
+
} catch {
|
|
318
|
+
// keep empty stats — auto-combo will use runtime + bootstrap signals
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const uniqueProviders = Array.from(
|
|
322
|
+
new Set(
|
|
323
|
+
targets.map((target) => target.provider || parseModel(target.modelStr).provider || "unknown")
|
|
324
|
+
)
|
|
325
|
+
);
|
|
326
|
+
const connectionPoolCounts = new Map<string, number>();
|
|
327
|
+
const connectionsByProvider = new Map<string, Array<Record<string, unknown>>>();
|
|
328
|
+
const connectionById = new Map<string, Record<string, unknown>>();
|
|
329
|
+
await Promise.all(
|
|
330
|
+
uniqueProviders.map(async (provider) => {
|
|
331
|
+
try {
|
|
332
|
+
const connections = await getProviderConnections({ provider, isActive: true });
|
|
333
|
+
const active = Array.isArray(connections) ? connections : [];
|
|
334
|
+
connectionPoolCounts.set(provider, active.length);
|
|
335
|
+
connectionsByProvider.set(provider, active);
|
|
336
|
+
for (const connection of active) {
|
|
337
|
+
if (connection && typeof connection === "object" && typeof connection.id === "string") {
|
|
338
|
+
connectionById.set(connection.id, connection as Record<string, unknown>);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
} catch {
|
|
342
|
+
connectionPoolCounts.set(provider, 0);
|
|
343
|
+
connectionsByProvider.set(provider, []);
|
|
344
|
+
}
|
|
345
|
+
})
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
const expandedTargets: ResolvedComboTarget[] = [];
|
|
349
|
+
for (const target of targets) {
|
|
350
|
+
const provider = target.provider || parseModel(target.modelStr).provider || "unknown";
|
|
351
|
+
const providerConnections = connectionsByProvider.get(provider) || [];
|
|
352
|
+
if (target.connectionId) {
|
|
353
|
+
expandedTargets.push(target);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
const connectionIds = providerConnections
|
|
357
|
+
.map((c) => (c && typeof c === "object" && typeof c.id === "string" ? c.id : null))
|
|
358
|
+
.filter((id): id is string => id !== null);
|
|
359
|
+
const allowedConnectionIds = Array.isArray(target.allowedConnectionIds)
|
|
360
|
+
? new Set(
|
|
361
|
+
target.allowedConnectionIds.filter(
|
|
362
|
+
(connectionId): connectionId is string =>
|
|
363
|
+
typeof connectionId === "string" && connectionId.trim().length > 0
|
|
364
|
+
)
|
|
365
|
+
)
|
|
366
|
+
: null;
|
|
367
|
+
const scopedConnectionIds = allowedConnectionIds
|
|
368
|
+
? connectionIds.filter((connectionId) => allowedConnectionIds.has(connectionId))
|
|
369
|
+
: connectionIds;
|
|
370
|
+
if (scopedConnectionIds.length === 0) {
|
|
371
|
+
expandedTargets.push(target);
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
for (const connectionId of scopedConnectionIds) {
|
|
375
|
+
expandedTargets.push({
|
|
376
|
+
...target,
|
|
377
|
+
connectionId,
|
|
378
|
+
executionKey: `${target.executionKey}@${connectionId}`,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// #5521: Expand fingerprint-based providers (mimocode, mcode, opencode) so each
|
|
384
|
+
// fingerprint gets its own combo slot instead of being bundled into one connection.
|
|
385
|
+
const fingerprintExpandedTargets = expandTargetsByFingerprints(
|
|
386
|
+
expandedTargets,
|
|
387
|
+
connectionById,
|
|
388
|
+
(t) => {
|
|
389
|
+
const parsed = parseModel(t.modelStr);
|
|
390
|
+
return t.provider || parsed.provider || parsed.providerAlias || "unknown";
|
|
391
|
+
}
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
const candidates = await Promise.all(
|
|
395
|
+
fingerprintExpandedTargets.map(async (target) => {
|
|
396
|
+
const modelStr = target.modelStr;
|
|
397
|
+
const parsed = parseModel(modelStr);
|
|
398
|
+
const provider = target.provider || parsed.provider || parsed.providerAlias || "unknown";
|
|
399
|
+
const model = parsed.model || modelStr;
|
|
400
|
+
const historicalKey = `${provider}/${model}`;
|
|
401
|
+
const historicalModelMetric = historicalLatencyStats[historicalKey] || null;
|
|
402
|
+
const historicalTotal = Number(historicalModelMetric?.totalRequests);
|
|
403
|
+
const hasHistoricalSignal =
|
|
404
|
+
Number.isFinite(historicalTotal) && historicalTotal >= MIN_HISTORY_SAMPLES;
|
|
405
|
+
|
|
406
|
+
let costPer1MTokens = 1;
|
|
407
|
+
try {
|
|
408
|
+
const pricing = await getPricingForModel(provider, model);
|
|
409
|
+
const inputPrice = Number(pricing?.input);
|
|
410
|
+
const outputPrice = Number(pricing?.output);
|
|
411
|
+
if (Number.isFinite(inputPrice) && inputPrice >= 0) {
|
|
412
|
+
if (Number.isFinite(outputPrice) && outputPrice >= 0) {
|
|
413
|
+
costPer1MTokens =
|
|
414
|
+
inputPrice * (1 - OUTPUT_TOKEN_RATIO) + outputPrice * OUTPUT_TOKEN_RATIO;
|
|
415
|
+
} else {
|
|
416
|
+
costPer1MTokens = inputPrice;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
} catch {
|
|
420
|
+
// keep default cost
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const modelMetric = metrics?.byModel?.[modelStr] || null;
|
|
424
|
+
const avgLatency = Number(modelMetric?.avgLatencyMs);
|
|
425
|
+
const successRate = Number(modelMetric?.successRate);
|
|
426
|
+
const historicalP95Latency = Number(historicalModelMetric?.p95LatencyMs);
|
|
427
|
+
const historicalStdDev = Number(historicalModelMetric?.latencyStdDev);
|
|
428
|
+
const historicalSuccessRate = Number(historicalModelMetric?.successRate); // 0..1
|
|
429
|
+
|
|
430
|
+
const p95LatencyMs = hasHistoricalSignal
|
|
431
|
+
? Number.isFinite(historicalP95Latency) && historicalP95Latency > 0
|
|
432
|
+
? historicalP95Latency
|
|
433
|
+
: getBootstrapLatencyMs(model)
|
|
434
|
+
: Number.isFinite(avgLatency) && avgLatency > 0
|
|
435
|
+
? avgLatency
|
|
436
|
+
: getBootstrapLatencyMs(model);
|
|
437
|
+
|
|
438
|
+
const errorRate = hasHistoricalSignal
|
|
439
|
+
? Number.isFinite(historicalSuccessRate) &&
|
|
440
|
+
historicalSuccessRate >= 0 &&
|
|
441
|
+
historicalSuccessRate <= 1
|
|
442
|
+
? 1 - historicalSuccessRate
|
|
443
|
+
: 0.05
|
|
444
|
+
: Number.isFinite(successRate) && successRate >= 0 && successRate <= 100
|
|
445
|
+
? 1 - successRate / 100
|
|
446
|
+
: 0.05;
|
|
447
|
+
const latencyStdDev =
|
|
448
|
+
hasHistoricalSignal && Number.isFinite(historicalStdDev) && historicalStdDev > 0
|
|
449
|
+
? Math.max(10, historicalStdDev)
|
|
450
|
+
: Math.max(10, p95LatencyMs * 0.1);
|
|
451
|
+
|
|
452
|
+
const breakerStateRaw = getCircuitBreaker(provider)?.getStatus?.()?.state;
|
|
453
|
+
const circuitBreakerState: ProviderCandidate["circuitBreakerState"] =
|
|
454
|
+
breakerStateRaw === "OPEN" || breakerStateRaw === "HALF_OPEN" ? breakerStateRaw : "CLOSED";
|
|
455
|
+
const contextAffinity = calculateTargetContextAffinity(target, sessionId);
|
|
456
|
+
let resetWindowAffinity = 0.5;
|
|
457
|
+
let quotaRemaining = 100;
|
|
458
|
+
let quotaCutoffBlocked = false;
|
|
459
|
+
let quotaCutoffReason: string | undefined;
|
|
460
|
+
const fetcher = getQuotaFetcher(provider);
|
|
461
|
+
const connection = target.connectionId ? connectionById.get(target.connectionId) : undefined;
|
|
462
|
+
// Gate the terminal-status cutoff behind the same opt-in as the quota-percent
|
|
463
|
+
// cutoff (#4483): when quota cutoff is disabled, a connection in a terminal
|
|
464
|
+
// testStatus must still fall through to normal connection-cooldown / model-lockout
|
|
465
|
+
// handling instead of being hard-blocked here (which would surface a misleading
|
|
466
|
+
// "below quota cutoff" 429 when every candidate is transiently unavailable).
|
|
467
|
+
// The connection's terminal/transient status (credits_exhausted / rate_limited /
|
|
468
|
+
// banned / expired / future-dated unavailable) is classified unconditionally.
|
|
469
|
+
const connectionStatusReason = getConnectionStatusQuotaCutoffReason(connection);
|
|
470
|
+
const statusCutoffReason = quotaCutoffEnabled ? connectionStatusReason : undefined;
|
|
471
|
+
// #4540: when the HARD cutoff is OFF (default), a status-flagged connection is NOT
|
|
472
|
+
// hard-blocked (that would surface a misleading "below quota cutoff" 429), but it
|
|
473
|
+
// also must not score identically to a healthy provider. A no-fetcher exhausted
|
|
474
|
+
// connection keeps quotaRemaining=100, so we tag a SOFT penalty applied at scoring
|
|
475
|
+
// time (scoreAutoTargets → STATUS_SOFT_DEPRIORITIZE_FACTOR) instead.
|
|
476
|
+
let statusPenalty = false;
|
|
477
|
+
let statusPenaltyReason: string | undefined;
|
|
478
|
+
if (statusCutoffReason) {
|
|
479
|
+
quotaCutoffBlocked = true;
|
|
480
|
+
quotaCutoffReason = statusCutoffReason;
|
|
481
|
+
quotaRemaining = 0;
|
|
482
|
+
} else if (connectionStatusReason) {
|
|
483
|
+
statusPenalty = true;
|
|
484
|
+
statusPenaltyReason = connectionStatusReason;
|
|
485
|
+
}
|
|
486
|
+
if (fetcher && target.connectionId) {
|
|
487
|
+
const quotaKey = `${provider}:${target.connectionId}`;
|
|
488
|
+
if (!quotaPromises.has(quotaKey)) {
|
|
489
|
+
quotaPromises.set(
|
|
490
|
+
quotaKey,
|
|
491
|
+
fetchResetAwareQuotaWithCache({
|
|
492
|
+
provider,
|
|
493
|
+
connectionId: target.connectionId,
|
|
494
|
+
connection,
|
|
495
|
+
fetcher,
|
|
496
|
+
config: resetWindowConfig,
|
|
497
|
+
log: {},
|
|
498
|
+
comboName,
|
|
499
|
+
})
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
const quota = await quotaPromises.get(quotaKey)!;
|
|
503
|
+
resetWindowAffinity = calculateResetWindowAffinity(quota, resetWindowConfig);
|
|
504
|
+
if (!quotaCutoffBlocked) {
|
|
505
|
+
quotaRemaining = quotaRemainingPercentFromQuota(quota);
|
|
506
|
+
}
|
|
507
|
+
if (!quotaCutoffBlocked && quotaCutoffEnabled) {
|
|
508
|
+
const cutoffDecision = evaluateQuotaCutoff(
|
|
509
|
+
quota as QuotaInfo | null,
|
|
510
|
+
buildAutoQuotaThresholds(provider, connection, resilienceSettings)
|
|
511
|
+
);
|
|
512
|
+
if (!cutoffDecision.proceed) {
|
|
513
|
+
quotaCutoffBlocked = true;
|
|
514
|
+
quotaCutoffReason = cutoffDecision.reason || "quota_exhausted";
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return {
|
|
520
|
+
stepId: target.stepId,
|
|
521
|
+
executionKey: target.executionKey,
|
|
522
|
+
modelStr,
|
|
523
|
+
provider,
|
|
524
|
+
model,
|
|
525
|
+
quotaRemaining,
|
|
526
|
+
quotaTotal: 100,
|
|
527
|
+
circuitBreakerState,
|
|
528
|
+
costPer1MTokens,
|
|
529
|
+
p95LatencyMs,
|
|
530
|
+
latencyStdDev,
|
|
531
|
+
errorRate,
|
|
532
|
+
accountTier: "standard" as const,
|
|
533
|
+
quotaResetIntervalSecs: 86400,
|
|
534
|
+
contextAffinity,
|
|
535
|
+
resetWindowAffinity,
|
|
536
|
+
quotaCutoffBlocked,
|
|
537
|
+
quotaCutoffReason,
|
|
538
|
+
statusPenalty,
|
|
539
|
+
statusPenaltyReason,
|
|
540
|
+
connectionPoolSize: connectionPoolCounts.get(provider) ?? 1,
|
|
541
|
+
connectionId: target.connectionId ?? undefined,
|
|
542
|
+
};
|
|
543
|
+
})
|
|
544
|
+
);
|
|
545
|
+
|
|
546
|
+
// Filter out candidates whose model is hidden by the user in the dashboard
|
|
547
|
+
return candidates.filter((c) => {
|
|
548
|
+
const hiddenModels = hiddenModelsMap.get(c.provider);
|
|
549
|
+
return !hiddenModels?.has(c.model);
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const TERMINAL_PIN_STATUSES = new Set(["credits_exhausted", "banned", "expired"]);
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Pure decision: should a context-cache pin be DROPPED because its provider has
|
|
557
|
+
* DURABLY fallen? A ccp pin keeps the prompt cache warm by bypassing the combo
|
|
558
|
+
* strategy — but if the pinned provider is dead (credits exhausted / banned /
|
|
559
|
+
* expired, circuit-open, repeated failures, or a long rate-limit) honoring the
|
|
560
|
+
* pin pounds a dead account forever with no failover (laila throttle + credits
|
|
561
|
+
* incidents, 2026-06-22). A brief transient cooldown is tolerated (pin kept) so
|
|
562
|
+
* an unstable provider does not churn the pin every turn. Connection-level
|
|
563
|
+
* `backoffLevel` already resets on success, so `backoffLevel >= K` ≈ K
|
|
564
|
+
* consecutive failures — no per-session counter needed.
|
|
565
|
+
*
|
|
566
|
+
* Returns true ⇒ drop the pin and use the strategy. Pure + unit-testable.
|
|
567
|
+
*/
|
|
568
|
+
export function pinIsDurablyUnhealthy(
|
|
569
|
+
circuitState: string | undefined,
|
|
570
|
+
connections: Array<{
|
|
571
|
+
testStatus?: string | null;
|
|
572
|
+
backoffLevel?: number | null;
|
|
573
|
+
rateLimitedUntil?: string | null;
|
|
574
|
+
}>,
|
|
575
|
+
now: number,
|
|
576
|
+
opts: { backoffLevel?: number; graceMs?: number } = {}
|
|
577
|
+
): boolean {
|
|
578
|
+
if (circuitState === "OPEN") return true;
|
|
579
|
+
if (!Array.isArray(connections) || connections.length === 0) return true;
|
|
580
|
+
const backoffThreshold = opts.backoffLevel ?? Number(process.env.PIN_DROP_BACKOFF_LEVEL || "2");
|
|
581
|
+
const graceMs = opts.graceMs ?? Number(process.env.PIN_DROP_GRACE_MS || "20000");
|
|
582
|
+
// The pin survives as long as AT LEAST ONE connection is healthy or only
|
|
583
|
+
// briefly cooling down — failover only when every connection is durably down.
|
|
584
|
+
const anyUsable = connections.some((c) => {
|
|
585
|
+
const status = typeof c.testStatus === "string" ? c.testStatus : "";
|
|
586
|
+
if (TERMINAL_PIN_STATUSES.has(status)) return false;
|
|
587
|
+
if (Number(c.backoffLevel ?? 0) >= backoffThreshold) return false;
|
|
588
|
+
const rl = c.rateLimitedUntil ? new Date(String(c.rateLimitedUntil)).getTime() : 0;
|
|
589
|
+
if (Number.isFinite(rl) && rl - now > graceMs) return false;
|
|
590
|
+
return true;
|
|
591
|
+
});
|
|
592
|
+
return !anyUsable;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Async wrapper: resolve the pinned model's provider, read its circuit state and
|
|
597
|
+
* active connections, and decide via {@link pinIsDurablyUnhealthy}. Fail-open
|
|
598
|
+
* (return false) on any error so a lookup bug never drops a healthy pin.
|
|
599
|
+
*/
|
|
600
|
+
async function isPinnedModelDurablyUnhealthy(pinnedModel: string): Promise<boolean> {
|
|
601
|
+
try {
|
|
602
|
+
const provider = parseModel(pinnedModel).provider;
|
|
603
|
+
if (!provider) return false;
|
|
604
|
+
const circuitState = getCircuitBreaker(provider)?.getStatus?.()?.state;
|
|
605
|
+
const connections = (await getProviderConnections({
|
|
606
|
+
provider,
|
|
607
|
+
isActive: true,
|
|
608
|
+
})) as Array<{
|
|
609
|
+
testStatus?: string | null;
|
|
610
|
+
backoffLevel?: number | null;
|
|
611
|
+
rateLimitedUntil?: string | null;
|
|
612
|
+
}>;
|
|
613
|
+
return pinIsDurablyUnhealthy(circuitState, connections || [], Date.now());
|
|
614
|
+
} catch {
|
|
615
|
+
return false;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Handle combo chat with fallback.
|
|
621
|
+
* @param {Object} options
|
|
622
|
+
* @param {Object} options.body - Request body
|
|
623
|
+
* @param {Object} options.combo - Full combo object { name, models, strategy, config }
|
|
624
|
+
* @param {Function} options.handleSingleModel - Function: (body, modelStr) => Promise<Response>
|
|
625
|
+
* @param {Function} [options.isModelAvailable] - Optional pre-check: (modelStr) => Promise<boolean>
|
|
626
|
+
* @param {Object} options.log - Logger object
|
|
627
|
+
* @returns {Promise<Response>}
|
|
628
|
+
*/
|
|
629
|
+
// #2101 guard helpers: a 400 caused by context overflow or parameter validation
|
|
630
|
+
// is NOT body-specific — different combo targets have different context windows /
|
|
631
|
+
// output limits, so the request should fall through to the next target instead of
|
|
632
|
+
// being short-circuited. Exported as pure predicates so the guard is unit-testable.
|
|
633
|
+
/** @param {string} errorText */
|
|
634
|
+
export function isContextOverflow400(errorText) {
|
|
635
|
+
return (
|
|
636
|
+
/\bcontext.*(?:length_exceeded|too long|overflow|exceeded|window|limit)\b/i.test(errorText) ||
|
|
637
|
+
/exceeds.*context/i.test(errorText) ||
|
|
638
|
+
/your input exceeds/i.test(errorText)
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
/** @param {string} errorText */
|
|
642
|
+
export function isParamValidation400(errorText) {
|
|
643
|
+
return (
|
|
644
|
+
/\bmax_tokens\b.*(?:illegal|must|range|invalid)/i.test(errorText) ||
|
|
645
|
+
/\bparameter is illegal\b/i.test(errorText) ||
|
|
646
|
+
/\bis illegal.*range\b/i.test(errorText)
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/** @param {object} options */
|
|
651
|
+
export async function handleComboChat({
|
|
652
|
+
body,
|
|
653
|
+
combo,
|
|
654
|
+
handleSingleModel,
|
|
655
|
+
isModelAvailable,
|
|
656
|
+
log,
|
|
657
|
+
settings,
|
|
658
|
+
allCombos,
|
|
659
|
+
relayOptions,
|
|
660
|
+
signal,
|
|
661
|
+
apiKeyAllowedConnections = null,
|
|
662
|
+
nesting = null,
|
|
663
|
+
}: HandleComboChatOptions): Promise<Response> {
|
|
664
|
+
const comboCtx = createComboContext({ body, combo, settings, relayOptions, log });
|
|
665
|
+
const {
|
|
666
|
+
strategy,
|
|
667
|
+
relayConfig,
|
|
668
|
+
resilienceSettings,
|
|
669
|
+
universalHandoffConfig,
|
|
670
|
+
effectiveSessionId,
|
|
671
|
+
pinnedModel,
|
|
672
|
+
clientRequestedStream,
|
|
673
|
+
config,
|
|
674
|
+
comboTargetTimeoutMs,
|
|
675
|
+
reasoningTokenBufferEnabled,
|
|
676
|
+
} = phaseComboSetup(comboCtx);
|
|
677
|
+
body = comboCtx.body;
|
|
678
|
+
|
|
679
|
+
const handleSingleModelWithTimeout = buildTargetTimeoutRunner({
|
|
680
|
+
handleSingleModel,
|
|
681
|
+
comboTargetTimeoutMs,
|
|
682
|
+
log,
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
// Route to pinned model if context caching specifies one (Fix #679)
|
|
686
|
+
if (pinnedModel) {
|
|
687
|
+
// The pin is read from session_model_history (a PRIOR turn) and may name a
|
|
688
|
+
// model that has since been removed from this combo, or a provider whose
|
|
689
|
+
// credentials are gone. Without this guard a stale pin bypasses the strategy
|
|
690
|
+
// and routes to a dead model forever — incident 2026-06-21: cli-claude-heavy
|
|
691
|
+
// pinned to a deepseek connection with no active credentials → instant fail,
|
|
692
|
+
// never falling through to the live targets; and combos re-pointed Opus→Sonnet
|
|
693
|
+
// kept serving the old model. Validate the pin is still reachable in THIS
|
|
694
|
+
// combo's resolved targets (refs flattened) before honoring it. Only validate
|
|
695
|
+
// when allCombos is authoritative (non-empty) so we can resolve combo-refs;
|
|
696
|
+
// the auto-combo redirect path passes an empty list and keeps prior behavior.
|
|
697
|
+
const haveFullCombos = Array.isArray(allCombos) ? allCombos.length > 0 : !!allCombos;
|
|
698
|
+
const pinInCombo =
|
|
699
|
+
!haveFullCombos ||
|
|
700
|
+
resolveComboTargets(combo, allCombos, clampComboDepth(config.maxComboDepth)).some(
|
|
701
|
+
(t) => t.modelStr === pinnedModel
|
|
702
|
+
);
|
|
703
|
+
// Honor the pin only if it is still a combo target AND its provider is not
|
|
704
|
+
// DURABLY down. Without the health gate a pin keeps routing a session to a
|
|
705
|
+
// dead/credits-exhausted/throttled account forever (strategy bypassed, no
|
|
706
|
+
// failover) — incident 2026-06-22: laila stuck on a throttled claude account
|
|
707
|
+
// and credits_exhausted accounts never failing over. A transient cooldown is
|
|
708
|
+
// tolerated (pin kept) so an unstable provider does not churn the pin.
|
|
709
|
+
const pinDurablyDown = pinInCombo ? await isPinnedModelDurablyUnhealthy(pinnedModel) : false;
|
|
710
|
+
if (pinInCombo && !pinDurablyDown) {
|
|
711
|
+
log.info(
|
|
712
|
+
"COMBO",
|
|
713
|
+
`Bypassing strategy — routing directly to pinned context model: ${pinnedModel}`
|
|
714
|
+
);
|
|
715
|
+
let pinnedResult: Response | null = null;
|
|
716
|
+
try {
|
|
717
|
+
pinnedResult = await handleSingleModelWithTimeout(body, pinnedModel, {
|
|
718
|
+
modelPinned: true,
|
|
719
|
+
} as SingleModelTarget);
|
|
720
|
+
} catch (pinErr) {
|
|
721
|
+
log.warn(
|
|
722
|
+
"COMBO",
|
|
723
|
+
`Pinned model ${pinnedModel} threw error: ${pinErr instanceof Error ? pinErr.message : String(pinErr)}, falling through to combo retry/fallback`
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
if (pinnedResult) {
|
|
727
|
+
if (pinnedResult.ok) {
|
|
728
|
+
let pinnedClone: Response;
|
|
729
|
+
try {
|
|
730
|
+
pinnedClone = pinnedResult.clone();
|
|
731
|
+
} catch {
|
|
732
|
+
pinnedClone = pinnedResult;
|
|
733
|
+
}
|
|
734
|
+
const pinnedQuality = await validateResponseQuality(
|
|
735
|
+
pinnedClone,
|
|
736
|
+
clientRequestedStream,
|
|
737
|
+
log,
|
|
738
|
+
config.responseValidation
|
|
739
|
+
);
|
|
740
|
+
releaseQualityClone(pinnedClone, pinnedResult, pinnedQuality);
|
|
741
|
+
if (pinnedQuality.valid) return pinnedResult;
|
|
742
|
+
log.warn(
|
|
743
|
+
"COMBO",
|
|
744
|
+
`Pinned model ${pinnedModel} returned 200 but failed quality check: ${pinnedQuality.reason}, falling through to combo retry/fallback`
|
|
745
|
+
);
|
|
746
|
+
} else {
|
|
747
|
+
const pinnedStatus = pinnedResult.status || 500;
|
|
748
|
+
if (![408, 429, 500, 502, 503, 504].includes(pinnedStatus)) {
|
|
749
|
+
return pinnedResult;
|
|
750
|
+
}
|
|
751
|
+
log.warn(
|
|
752
|
+
"COMBO",
|
|
753
|
+
`Pinned model ${pinnedModel} failed (${pinnedStatus}), falling through to combo retry/fallback`
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
// Fall through to the target iteration loop below — retries and sibling
|
|
758
|
+
// models will be tried via the normal combo machinery.
|
|
759
|
+
}
|
|
760
|
+
log.warn(
|
|
761
|
+
"COMBO",
|
|
762
|
+
pinInCombo
|
|
763
|
+
? `Context-cache pin "${pinnedModel}" provider durably unhealthy — dropping pin, using strategy`
|
|
764
|
+
: `Stale context-cache pin "${pinnedModel}" not in combo "${combo.name}" targets — dropping pin, using strategy`
|
|
765
|
+
);
|
|
766
|
+
// Fall through to the normal target iteration loop below — the pin is
|
|
767
|
+
// dropped, so the combo strategy picks the best available target.
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Fusion strategy: parallel panel + judge synthesis. Handled in a separate module
|
|
771
|
+
// because it neither iterates targets in order nor needs the failover/retry/credential
|
|
772
|
+
// gate machinery that follows — it fans out, then synthesizes once.
|
|
773
|
+
if (strategy === "fusion") {
|
|
774
|
+
const fusionModels = (combo.models || [])
|
|
775
|
+
.map((m) => {
|
|
776
|
+
if (typeof m === "string") return m;
|
|
777
|
+
if (m && typeof m === "object") {
|
|
778
|
+
const obj = m as Record<string, unknown>;
|
|
779
|
+
if (typeof obj.model === "string") return obj.model;
|
|
780
|
+
}
|
|
781
|
+
return null;
|
|
782
|
+
})
|
|
783
|
+
.filter((m): m is string => Boolean(m));
|
|
784
|
+
const cfg = config as Record<string, unknown>;
|
|
785
|
+
const judgeModel = typeof cfg.judgeModel === "string" ? cfg.judgeModel : undefined;
|
|
786
|
+
const tuning =
|
|
787
|
+
cfg.fusionTuning && typeof cfg.fusionTuning === "object"
|
|
788
|
+
? (cfg.fusionTuning as FusionTuning)
|
|
789
|
+
: undefined;
|
|
790
|
+
return handleFusionChat({
|
|
791
|
+
body,
|
|
792
|
+
models: fusionModels,
|
|
793
|
+
handleSingleModel: handleSingleModelWithTimeout,
|
|
794
|
+
log,
|
|
795
|
+
comboName: combo.name,
|
|
796
|
+
judgeModel,
|
|
797
|
+
tuning,
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Pipeline strategy: sequential chain — each step's output feeds the next step's
|
|
802
|
+
// input, only the final step's response is returned. Handled in a separate module
|
|
803
|
+
// because it neither iterates targets as fallbacks nor needs the failover/retry
|
|
804
|
+
// machinery below — it runs targets in order, threading output → input. The step
|
|
805
|
+
// list is `combo.models` (in order); an optional per-step `prompt` is read off the
|
|
806
|
+
// target object (comboModelStepInputSchema.prompt).
|
|
807
|
+
if (strategy === "pipeline") {
|
|
808
|
+
const pipelineSteps = (combo.models || [])
|
|
809
|
+
.map((m): PipelineStep | null => {
|
|
810
|
+
if (typeof m === "string") return { model: m };
|
|
811
|
+
if (m && typeof m === "object") {
|
|
812
|
+
const obj = m as Record<string, unknown>;
|
|
813
|
+
if (typeof obj.model === "string") {
|
|
814
|
+
return {
|
|
815
|
+
model: obj.model,
|
|
816
|
+
prompt: typeof obj.prompt === "string" ? obj.prompt : undefined,
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return null;
|
|
821
|
+
})
|
|
822
|
+
.filter((s): s is PipelineStep => Boolean(s));
|
|
823
|
+
return handlePipelineChat({
|
|
824
|
+
body,
|
|
825
|
+
steps: pipelineSteps,
|
|
826
|
+
handleSingleModel: handleSingleModelWithTimeout,
|
|
827
|
+
log,
|
|
828
|
+
comboName: combo.name,
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const nestingContext = nesting || {
|
|
833
|
+
depth: 0,
|
|
834
|
+
maxDepth: clampComboDepth(config.maxComboDepth),
|
|
835
|
+
visitedComboNames: [combo.name],
|
|
836
|
+
rootComboName: combo.name,
|
|
837
|
+
attemptBudget: { count: 0, limit: MAX_GLOBAL_ATTEMPTS },
|
|
838
|
+
};
|
|
839
|
+
const nestedComboMode = normalizeNestedComboMode(config.nestedComboMode);
|
|
840
|
+
|
|
841
|
+
const executeModeUnits =
|
|
842
|
+
nestedComboMode === "execute" && allCombos
|
|
843
|
+
? resolveComboRuntimeUnits(combo, allCombos, "execute", nestingContext.maxDepth)
|
|
844
|
+
: [];
|
|
845
|
+
const hasExecutableComboRef = executeModeUnits.some((unit) => unit.kind === "combo-ref");
|
|
846
|
+
const simpleExecuteStrategies = new Set([
|
|
847
|
+
"priority",
|
|
848
|
+
"round-robin",
|
|
849
|
+
"random",
|
|
850
|
+
"strict-random",
|
|
851
|
+
"weighted",
|
|
852
|
+
"fill-first",
|
|
853
|
+
]);
|
|
854
|
+
|
|
855
|
+
if (hasExecutableComboRef && simpleExecuteStrategies.has(strategy)) {
|
|
856
|
+
let runtimeUnits = executeModeUnits;
|
|
857
|
+
let unitExecutionStrategy = strategy;
|
|
858
|
+
if (strategy === "weighted") {
|
|
859
|
+
const stickyLimit = clampStickyWeightedTargetLimit(
|
|
860
|
+
(config as Record<string, unknown>).stickyWeightedLimit
|
|
861
|
+
);
|
|
862
|
+
const stickyKey = getStickyWeightedExecutionKey(combo.name, stickyLimit);
|
|
863
|
+
const stickyUnit = stickyKey
|
|
864
|
+
? runtimeUnits.find((unit) => unit.executionKey === stickyKey)
|
|
865
|
+
: null;
|
|
866
|
+
if (stickyUnit) {
|
|
867
|
+
runtimeUnits = [
|
|
868
|
+
stickyUnit,
|
|
869
|
+
...runtimeUnits.filter((unit) => unit.executionKey !== stickyUnit.executionKey),
|
|
870
|
+
];
|
|
871
|
+
unitExecutionStrategy = "priority";
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
if (strategy === "random") runtimeUnits = fisherYatesShuffle([...runtimeUnits]);
|
|
875
|
+
if (strategy === "strict-random") {
|
|
876
|
+
const key = await getNextFromDeck(
|
|
877
|
+
`combo:${combo.name}`,
|
|
878
|
+
runtimeUnits.map((unit) => unit.executionKey)
|
|
879
|
+
);
|
|
880
|
+
const selected = runtimeUnits.find((unit) => unit.executionKey === key) || runtimeUnits[0];
|
|
881
|
+
runtimeUnits = [
|
|
882
|
+
selected,
|
|
883
|
+
...runtimeUnits.filter((unit) => unit.executionKey !== selected.executionKey),
|
|
884
|
+
];
|
|
885
|
+
}
|
|
886
|
+
let runtimeStickyLimit: number | null = null;
|
|
887
|
+
let runtimeStickyTargets: ResolvedComboUnit[] = runtimeUnits;
|
|
888
|
+
if (strategy === "round-robin") {
|
|
889
|
+
const perComboStickyLimit = (config as Record<string, unknown>).stickyRoundRobinLimit;
|
|
890
|
+
runtimeStickyLimit = clampStickyRoundRobinTargetLimit(
|
|
891
|
+
perComboStickyLimit !== undefined && perComboStickyLimit !== null
|
|
892
|
+
? perComboStickyLimit
|
|
893
|
+
: (settings as Record<string, unknown> | null)?.stickyRoundRobinLimit
|
|
894
|
+
);
|
|
895
|
+
const { startIndex, counter } = getStickyRoundRobinStartIndex(
|
|
896
|
+
combo.name,
|
|
897
|
+
runtimeUnits,
|
|
898
|
+
runtimeStickyLimit
|
|
899
|
+
);
|
|
900
|
+
if (runtimeStickyLimit <= 1) rrCounters.set(combo.name, counter + 1);
|
|
901
|
+
runtimeUnits = runtimeUnits.map(
|
|
902
|
+
(_, offset) => runtimeUnits[(startIndex + offset) % runtimeUnits.length]
|
|
903
|
+
);
|
|
904
|
+
runtimeStickyTargets = executeModeUnits;
|
|
905
|
+
}
|
|
906
|
+
const execution = await executeRuntimeUnitCombo({
|
|
907
|
+
body,
|
|
908
|
+
combo,
|
|
909
|
+
strategy: unitExecutionStrategy,
|
|
910
|
+
effectiveComboStrategy: strategy,
|
|
911
|
+
units: runtimeUnits,
|
|
912
|
+
handleSingleModel: handleSingleModelWithTimeout,
|
|
913
|
+
isModelAvailable,
|
|
914
|
+
log,
|
|
915
|
+
config,
|
|
916
|
+
settings,
|
|
917
|
+
allCombos,
|
|
918
|
+
signal,
|
|
919
|
+
nesting: nestingContext,
|
|
920
|
+
baseOptions: {
|
|
921
|
+
body,
|
|
922
|
+
combo,
|
|
923
|
+
handleSingleModel,
|
|
924
|
+
isModelAvailable,
|
|
925
|
+
log,
|
|
926
|
+
settings,
|
|
927
|
+
allCombos,
|
|
928
|
+
relayOptions,
|
|
929
|
+
signal,
|
|
930
|
+
apiKeyAllowedConnections,
|
|
931
|
+
},
|
|
932
|
+
runCombo: handleComboChat,
|
|
933
|
+
});
|
|
934
|
+
if (strategy === "weighted" && execution.response.ok && execution.unit) {
|
|
935
|
+
const stickyLimit = clampStickyWeightedTargetLimit(
|
|
936
|
+
(config as Record<string, unknown>).stickyWeightedLimit
|
|
937
|
+
);
|
|
938
|
+
if (stickyLimit > 1)
|
|
939
|
+
recordStickyWeightedSuccess(combo.name, execution.unit.executionKey, stickyLimit);
|
|
940
|
+
}
|
|
941
|
+
if (
|
|
942
|
+
strategy === "round-robin" &&
|
|
943
|
+
execution.response.ok &&
|
|
944
|
+
execution.unit &&
|
|
945
|
+
runtimeStickyLimit &&
|
|
946
|
+
runtimeStickyLimit > 1
|
|
947
|
+
) {
|
|
948
|
+
recordStickyRoundRobinSuccess(
|
|
949
|
+
combo.name,
|
|
950
|
+
execution.unit,
|
|
951
|
+
runtimeStickyLimit,
|
|
952
|
+
runtimeStickyTargets
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
return execution.response;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// Route to round-robin handler if strategy matches
|
|
959
|
+
if (strategy === "round-robin") {
|
|
960
|
+
return handleRoundRobinCombo({
|
|
961
|
+
body,
|
|
962
|
+
combo,
|
|
963
|
+
handleSingleModel: handleSingleModelWithTimeout,
|
|
964
|
+
isModelAvailable,
|
|
965
|
+
log,
|
|
966
|
+
settings,
|
|
967
|
+
allCombos,
|
|
968
|
+
signal,
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
const maxRetries = config.maxRetries ?? 1;
|
|
973
|
+
const retryDelayMs = resolveDelayMs(config.retryDelayMs, 2000);
|
|
974
|
+
const fallbackDelayMs = resolveDelayMs(config.fallbackDelayMs, 0);
|
|
975
|
+
const maxSetRetries = config.maxSetRetries ?? 0;
|
|
976
|
+
const setRetryDelayMs = resolveDelayMs(config.setRetryDelayMs, 2000);
|
|
977
|
+
|
|
978
|
+
const isTargetSelectableForWeighted = async (target: ResolvedComboTarget): Promise<boolean> => {
|
|
979
|
+
const rawModel = parseModel(target.modelStr).model || target.modelStr;
|
|
980
|
+
if (target.provider && getCircuitBreaker(target.provider).getStatus().state === "OPEN")
|
|
981
|
+
return false;
|
|
982
|
+
if (
|
|
983
|
+
resilienceSettings.providerCooldown.enabled &&
|
|
984
|
+
Boolean(target.provider && target.provider !== "unknown") &&
|
|
985
|
+
isProviderInCooldown(target.provider, target.connectionId ?? undefined, resilienceSettings)
|
|
986
|
+
) {
|
|
987
|
+
return false;
|
|
988
|
+
}
|
|
989
|
+
if (
|
|
990
|
+
target.provider &&
|
|
991
|
+
rawModel &&
|
|
992
|
+
isModelLocked(target.provider, target.connectionId || "", rawModel)
|
|
993
|
+
) {
|
|
994
|
+
return false;
|
|
995
|
+
}
|
|
996
|
+
return isModelAvailable ? await isModelAvailable(target.modelStr, target) : true;
|
|
997
|
+
};
|
|
998
|
+
|
|
999
|
+
// #2562: Expand provider-wildcard steps (e.g. `fta/*`, `openai/gpt-4*`) into
|
|
1000
|
+
// concrete model entries sourced from the live synced-models catalog + registry.
|
|
1001
|
+
// Must run before any step-group / target resolution so that wildcard-originated
|
|
1002
|
+
// steps are treated identically to hand-authored entries by all downstream logic
|
|
1003
|
+
// (including the sticky-weighted eligibility pass below).
|
|
1004
|
+
const expandedCombo = await expandProviderWildcardsInCombo(combo);
|
|
1005
|
+
const expandedAllCombos = allCombos
|
|
1006
|
+
? Array.isArray(allCombos)
|
|
1007
|
+
? await expandProviderWildcardsInCollection(allCombos as ComboLike[])
|
|
1008
|
+
: {
|
|
1009
|
+
...allCombos,
|
|
1010
|
+
combos: await expandProviderWildcardsInCollection(
|
|
1011
|
+
((allCombos as { combos?: ComboLike[] }).combos || []) as ComboLike[]
|
|
1012
|
+
),
|
|
1013
|
+
}
|
|
1014
|
+
: allCombos;
|
|
1015
|
+
|
|
1016
|
+
const stickyWeightedLimit = clampStickyWeightedTargetLimit(
|
|
1017
|
+
(config as Record<string, unknown>).stickyWeightedLimit
|
|
1018
|
+
);
|
|
1019
|
+
if (
|
|
1020
|
+
strategy === "weighted" &&
|
|
1021
|
+
!weightedStickyTargets.has(combo.name) &&
|
|
1022
|
+
weightedStickyTargets.size >= MAX_RR_COUNTERS
|
|
1023
|
+
) {
|
|
1024
|
+
const oldest = weightedStickyTargets.keys().next().value;
|
|
1025
|
+
if (oldest !== undefined) weightedStickyTargets.delete(oldest);
|
|
1026
|
+
}
|
|
1027
|
+
let stepGroups: Array<{ step: ComboRuntimeStep; targets: ResolvedComboTarget[] }> | undefined;
|
|
1028
|
+
const weightedEligibleKeys = new Set<string>();
|
|
1029
|
+
if (strategy === "weighted") {
|
|
1030
|
+
stepGroups = resolveWeightedStepGroups(expandedCombo, expandedAllCombos);
|
|
1031
|
+
for (const group of stepGroups) {
|
|
1032
|
+
const availability = await Promise.all(group.targets.map(isTargetSelectableForWeighted));
|
|
1033
|
+
if (availability.some(Boolean)) weightedEligibleKeys.add(group.step.executionKey);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
const rawStickyWeightedKey =
|
|
1037
|
+
strategy === "weighted" ? getStickyWeightedExecutionKey(combo.name, stickyWeightedLimit) : null;
|
|
1038
|
+
const stickyWeightedKey =
|
|
1039
|
+
rawStickyWeightedKey && weightedEligibleKeys.has(rawStickyWeightedKey)
|
|
1040
|
+
? rawStickyWeightedKey
|
|
1041
|
+
: null;
|
|
1042
|
+
if (strategy !== "weighted" || stickyWeightedLimit <= 1) {
|
|
1043
|
+
weightedStickyTargets.delete(combo.name);
|
|
1044
|
+
} else if (rawStickyWeightedKey && !stickyWeightedKey) {
|
|
1045
|
+
weightedStickyTargets.delete(combo.name);
|
|
1046
|
+
}
|
|
1047
|
+
const weightedResolution =
|
|
1048
|
+
strategy === "weighted"
|
|
1049
|
+
? resolveWeightedTargets(
|
|
1050
|
+
expandedCombo,
|
|
1051
|
+
expandedAllCombos,
|
|
1052
|
+
stickyWeightedKey,
|
|
1053
|
+
weightedEligibleKeys,
|
|
1054
|
+
stepGroups
|
|
1055
|
+
)
|
|
1056
|
+
: null;
|
|
1057
|
+
const getWeightedStepKeyForTarget = (target: ResolvedComboTarget): string | null => {
|
|
1058
|
+
if (!weightedResolution?.orderedSteps) return null;
|
|
1059
|
+
const step = weightedResolution.orderedSteps.find(
|
|
1060
|
+
(entry) =>
|
|
1061
|
+
target.executionKey === entry.executionKey ||
|
|
1062
|
+
target.executionKey.startsWith(entry.executionKey + ">")
|
|
1063
|
+
);
|
|
1064
|
+
return step?.executionKey || null;
|
|
1065
|
+
};
|
|
1066
|
+
let orderedTargets =
|
|
1067
|
+
strategy === "weighted"
|
|
1068
|
+
? weightedResolution?.orderedTargets || []
|
|
1069
|
+
: resolveComboTargets(
|
|
1070
|
+
expandedCombo,
|
|
1071
|
+
expandedAllCombos,
|
|
1072
|
+
clampComboDepth(config.maxComboDepth)
|
|
1073
|
+
);
|
|
1074
|
+
|
|
1075
|
+
orderedTargets = await applyRequestTagRouting(orderedTargets, body, log);
|
|
1076
|
+
|
|
1077
|
+
if (strategy === "weighted") {
|
|
1078
|
+
log.info(
|
|
1079
|
+
"COMBO",
|
|
1080
|
+
`Weighted selection${stickyWeightedKey ? " (sticky)" : ""}${allCombos ? " with nested resolution" : ""}: ${orderedTargets.length} total targets`
|
|
1081
|
+
);
|
|
1082
|
+
} else if (allCombos) {
|
|
1083
|
+
log.info("COMBO", `${strategy} with nested resolution: ${orderedTargets.length} total targets`);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// Pipeline dispatch: route smart/pipeline-enabled combos through the multi-stage pipeline
|
|
1087
|
+
if (strategy === "auto") {
|
|
1088
|
+
const autoParsed = parseAutoPrefix(combo.name);
|
|
1089
|
+
const autoVariant = autoParsed.valid ? autoParsed.variant : undefined;
|
|
1090
|
+
if (autoVariant === "smart" || config.pipeline_enabled) {
|
|
1091
|
+
try {
|
|
1092
|
+
const pipelineRaw = await handlePipelineCombo({
|
|
1093
|
+
body,
|
|
1094
|
+
combo,
|
|
1095
|
+
handleChatCore: handleSingleModelWithTimeout,
|
|
1096
|
+
log: {
|
|
1097
|
+
info: log.info,
|
|
1098
|
+
warn: log.warn,
|
|
1099
|
+
error: log.error ?? log.warn,
|
|
1100
|
+
},
|
|
1101
|
+
settings: settings ?? {},
|
|
1102
|
+
signal: signal ?? undefined,
|
|
1103
|
+
});
|
|
1104
|
+
// handlePipelineCombo resolves to a PipelineResult (buffered text) or,
|
|
1105
|
+
// in the streaming-final-stage case, a Response. Callers downstream
|
|
1106
|
+
// (chat.ts → withSessionHeader) require a Response, so adapt the
|
|
1107
|
+
// PipelineResult here instead of leaking the raw object.
|
|
1108
|
+
return pipelineRaw instanceof Response
|
|
1109
|
+
? pipelineRaw
|
|
1110
|
+
: buildPipelineResponse(pipelineRaw, body);
|
|
1111
|
+
} catch (pipelineErr) {
|
|
1112
|
+
const pipelineMsg = pipelineErr instanceof Error ? pipelineErr.message : "";
|
|
1113
|
+
if (pipelineMsg === "PIPELINE_DISABLED") {
|
|
1114
|
+
log.info("COMBO", "Pipeline disabled, falling through to standard auto routing");
|
|
1115
|
+
} else if (pipelineMsg === "PIPELINE_TOKEN_THRESHOLD") {
|
|
1116
|
+
log.info(
|
|
1117
|
+
"COMBO",
|
|
1118
|
+
"Pipeline skipped (prompt below token threshold), falling through to standard auto routing"
|
|
1119
|
+
);
|
|
1120
|
+
} else {
|
|
1121
|
+
log.warn("COMBO", "Pipeline dispatch failed, falling through to standard auto routing", {
|
|
1122
|
+
err: pipelineErr,
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// #4945 regression guard: when an "auto" combo uses an EXPLICIT router
|
|
1130
|
+
// (routingStrategy lkgp/cost/etc, not the default "rules" scorer), that router
|
|
1131
|
+
// pins orderedTargets[0]. The task-aware reordering below must then refine only
|
|
1132
|
+
// the fallback order, never override the router's primary choice.
|
|
1133
|
+
let autoUsedExplicitRouter = false;
|
|
1134
|
+
if (strategy === "auto") {
|
|
1135
|
+
const autoResult = await resolveAutoStrategyOrder({
|
|
1136
|
+
orderedTargets,
|
|
1137
|
+
body,
|
|
1138
|
+
combo,
|
|
1139
|
+
settings,
|
|
1140
|
+
config,
|
|
1141
|
+
relayOptions,
|
|
1142
|
+
resilienceSettings,
|
|
1143
|
+
log,
|
|
1144
|
+
buildAutoCandidates,
|
|
1145
|
+
});
|
|
1146
|
+
if ("earlyResponse" in autoResult) return autoResult.earlyResponse;
|
|
1147
|
+
orderedTargets = autoResult.orderedTargets;
|
|
1148
|
+
autoUsedExplicitRouter = autoResult.autoUsedExplicitRouter;
|
|
1149
|
+
} else {
|
|
1150
|
+
orderedTargets = await applyStrategyOrdering(strategy, orderedTargets, {
|
|
1151
|
+
combo,
|
|
1152
|
+
config,
|
|
1153
|
+
body,
|
|
1154
|
+
log,
|
|
1155
|
+
apiKeyAllowedConnections,
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
// #6168: session stickiness opt-out. Per-combo `config.disableSessionStickiness`
|
|
1159
|
+
// overrides the global `settings.disableSessionStickiness` fallback (default false,
|
|
1160
|
+
// preserving the #3825 prompt-cache/504 fix). When disabled, skip the reorder and
|
|
1161
|
+
// treat the result as a no-op so the recordStickyBinding write-back below is skipped.
|
|
1162
|
+
const disableSessionStickiness = resolveDisableSessionStickiness(
|
|
1163
|
+
config as Record<string, unknown> | null | undefined,
|
|
1164
|
+
settings as Record<string, unknown> | null | undefined
|
|
1165
|
+
);
|
|
1166
|
+
const _sticky = disableSessionStickiness
|
|
1167
|
+
? ({ targets: orderedTargets, messageHash: null, stuck: false } as const)
|
|
1168
|
+
: await applySessionStickiness(
|
|
1169
|
+
orderedTargets,
|
|
1170
|
+
body.messages as Array<{ role?: string; content?: unknown }>
|
|
1171
|
+
);
|
|
1172
|
+
orderedTargets = _sticky.targets;
|
|
1173
|
+
orderedTargets = orderTargetsByEvalScores(orderedTargets, config.evalRouting, log);
|
|
1174
|
+
orderedTargets = filterTargetsByRequestCompatibility(orderedTargets, body, log);
|
|
1175
|
+
|
|
1176
|
+
// Task-aware reordering: only active for strategies ["smart","task","task-aware","task_aware","auto"].
|
|
1177
|
+
// Additive — does not affect any of the other 15 strategies.
|
|
1178
|
+
if (isTaskRoutingStrategy(strategy)) {
|
|
1179
|
+
const task = classifyTask(body);
|
|
1180
|
+
const conversationCacheKey = getConversationCacheKey(body);
|
|
1181
|
+
const taskReordered = reorderByTaskWeight(orderedTargets, task);
|
|
1182
|
+
// #4945 regression guard: when an explicit auto router (lkgp/cost/…) pinned
|
|
1183
|
+
// orderedTargets[0], keep that primary choice and let task-aware refine only
|
|
1184
|
+
// the fallback tail — otherwise task weighting silently defeats the operator's
|
|
1185
|
+
// chosen LKGP/cost selection. reorderByTaskWeight returns the same target
|
|
1186
|
+
// objects (no clone), so identity filtering is safe.
|
|
1187
|
+
const pinnedFirst = autoUsedExplicitRouter ? orderedTargets[0] : undefined;
|
|
1188
|
+
const nextOrder = pinnedFirst
|
|
1189
|
+
? [pinnedFirst, ...taskReordered.filter((t) => t !== pinnedFirst)]
|
|
1190
|
+
: taskReordered;
|
|
1191
|
+
if (nextOrder[0]?.modelStr !== orderedTargets[0]?.modelStr) {
|
|
1192
|
+
const reasons =
|
|
1193
|
+
Array.isArray(task.reasons) && task.reasons.length > 0
|
|
1194
|
+
? ` (${task.reasons.join(",")})`
|
|
1195
|
+
: "";
|
|
1196
|
+
log.info(
|
|
1197
|
+
"COMBO",
|
|
1198
|
+
`task-route task=${task.level}${reasons} cacheKey=${conversationCacheKey ?? "none"} → ${nextOrder[0]?.modelStr}`
|
|
1199
|
+
);
|
|
1200
|
+
}
|
|
1201
|
+
orderedTargets = nextOrder;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
// Parallel pre-screen: check provider profiles and model availability for all targets
|
|
1205
|
+
// Only runs for priority strategy where sequential checking causes latency
|
|
1206
|
+
const preScreenMap =
|
|
1207
|
+
strategy === "priority"
|
|
1208
|
+
? await preScreenTargets(orderedTargets, isModelAvailable).catch(
|
|
1209
|
+
() => new Map<string, PreScreenResult>()
|
|
1210
|
+
)
|
|
1211
|
+
: new Map<string, PreScreenResult>();
|
|
1212
|
+
|
|
1213
|
+
// #5923 (Finding #4) — reset-window config for the shared per-target quota-
|
|
1214
|
+
// exhaustion cutoff below. The "auto" strategy already applies its own cutoff
|
|
1215
|
+
// via buildAutoCandidates/routableCandidates, so this only affects the other
|
|
1216
|
+
// 16 strategies (priority, weighted, etc.) that funnel through executeTarget.
|
|
1217
|
+
const quotaCutoffResetWindowConfig = resolveResetWindowConfig(config as Record<string, unknown>);
|
|
1218
|
+
|
|
1219
|
+
if (orderedTargets.length === 0) {
|
|
1220
|
+
return comboModelNotFoundResponse("Combo has no executable targets");
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
scheduleShadowRouting(
|
|
1224
|
+
combo,
|
|
1225
|
+
config,
|
|
1226
|
+
body,
|
|
1227
|
+
resolveShadowTargets(combo, config, allCombos),
|
|
1228
|
+
handleSingleModel,
|
|
1229
|
+
isModelAvailable,
|
|
1230
|
+
strategy,
|
|
1231
|
+
log
|
|
1232
|
+
);
|
|
1233
|
+
|
|
1234
|
+
// G2: Collect execution keys registered by _registerExecutionCandidates above (auto strategy).
|
|
1235
|
+
// We snapshot them now so cleanup can happen after the attempt loop finishes.
|
|
1236
|
+
const _registeredExecutionKeys = orderedTargets.map((t) => t.executionKey).filter(Boolean);
|
|
1237
|
+
|
|
1238
|
+
let globalAttempts = 0;
|
|
1239
|
+
|
|
1240
|
+
// Quota-share cooldown-aware retry (Variante A). Only quota-share (qtSd/)
|
|
1241
|
+
// combos opt in: when the set loop would crystallize a 429 model_cooldown
|
|
1242
|
+
// because the target hit a SHORT transient cooldown, we wait it out and
|
|
1243
|
+
// re-run the whole set loop instead of propagating the 429. `globalAttempts`
|
|
1244
|
+
// persists across these waits so MAX_GLOBAL_ATTEMPTS still bounds total work.
|
|
1245
|
+
// The wait happens at the crystallization point. The only semaphore slot the
|
|
1246
|
+
// quota-share path may hold is the FASE 2.1 per-connection concurrency slot
|
|
1247
|
+
// (acquired once around dispatchWithCooldownRetry below); it is intentionally
|
|
1248
|
+
// kept across the wait so the account stays "busy", and is released by the
|
|
1249
|
+
// outer finally — not here.
|
|
1250
|
+
//
|
|
1251
|
+
// The set loop is wrapped in a small recursive closure rather than an extra
|
|
1252
|
+
// labelled `while (true)` so the loop body keeps its original indentation; a
|
|
1253
|
+
// wait+redispatch is a tail `return dispatchWithCooldownRetry()`, which
|
|
1254
|
+
// re-runs ONLY the set loop (selection / shadow routing / setup above stay
|
|
1255
|
+
// untouched), preserving the pre-existing `continue`-to-top-of-set-loop
|
|
1256
|
+
// semantics exactly.
|
|
1257
|
+
const comboCooldownWaitEnabled =
|
|
1258
|
+
strategy === "quota-share" && resilienceSettings.comboCooldownWait.enabled;
|
|
1259
|
+
let comboCooldownAttempt = 0;
|
|
1260
|
+
let comboCooldownBudgetLeftMs = resilienceSettings.comboCooldownWait.budgetMs;
|
|
1261
|
+
|
|
1262
|
+
// FASE 2.1: per-connection concurrency limit for quota-share. The gating in
|
|
1263
|
+
// selectQuotaShareTarget is fail-open and cannot hard-limit a single-connection
|
|
1264
|
+
// pool, so we serialize concurrent requests to the selected account through a
|
|
1265
|
+
// per-connection semaphore. Enabled only for quota-share combos (the cap is the
|
|
1266
|
+
// account's) and gated by the kill-switch; the slot wraps the whole dispatch.
|
|
1267
|
+
const quotaShareConcurrencyEnabled =
|
|
1268
|
+
strategy === "quota-share" && resilienceSettings.quotaShareConcurrencyLimit.enabled;
|
|
1269
|
+
|
|
1270
|
+
const dispatchWithCooldownRetry = async (): Promise<Response> => {
|
|
1271
|
+
for (let setTry = 0; setTry <= maxSetRetries; setTry++) {
|
|
1272
|
+
// #1731: Per-set-iteration set of providers whose quota is fully exhausted.
|
|
1273
|
+
// Reset each retry so providers excluded in a previous attempt get another chance.
|
|
1274
|
+
const exhaustedProviders = new Set<string>();
|
|
1275
|
+
const exhaustedConnections = new Set<string>();
|
|
1276
|
+
const transientRateLimitedProviders = new Set<string>();
|
|
1277
|
+
if (setTry > 0) {
|
|
1278
|
+
log.info("COMBO", `All targets failed — retrying set (${setTry}/${maxSetRetries})`);
|
|
1279
|
+
await new Promise((resolve) => {
|
|
1280
|
+
const timer = setTimeout(resolve, setRetryDelayMs);
|
|
1281
|
+
signal?.addEventListener(
|
|
1282
|
+
"abort",
|
|
1283
|
+
() => {
|
|
1284
|
+
clearTimeout(timer);
|
|
1285
|
+
resolve(undefined);
|
|
1286
|
+
},
|
|
1287
|
+
{ once: true }
|
|
1288
|
+
);
|
|
1289
|
+
});
|
|
1290
|
+
if (signal?.aborted) {
|
|
1291
|
+
log.info("COMBO", "Client disconnected during set retry delay — aborting");
|
|
1292
|
+
return errorResponse(499, "Client disconnected");
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
let lastError: string | null = null;
|
|
1297
|
+
let earliestRetryAfter: ComboRetryAfter | null = null;
|
|
1298
|
+
let lastStatus: number | null = null;
|
|
1299
|
+
const startTime = Date.now();
|
|
1300
|
+
let fallbackCount = 0;
|
|
1301
|
+
let recordedAttempts = 0;
|
|
1302
|
+
|
|
1303
|
+
let globalResolve: ((res: Response) => void) | null = null;
|
|
1304
|
+
const globalPromise = new Promise<Response>((res) => {
|
|
1305
|
+
globalResolve = res;
|
|
1306
|
+
});
|
|
1307
|
+
const runningTasks = new Set<Promise<void>>();
|
|
1308
|
+
let anySuccess = false;
|
|
1309
|
+
const abortControllers = new Map<number, AbortController>();
|
|
1310
|
+
const zeroLatencyOptimizationsEnabled = config.zeroLatencyOptimizationsEnabled === true;
|
|
1311
|
+
|
|
1312
|
+
const executeTarget = async (
|
|
1313
|
+
i: number
|
|
1314
|
+
): Promise<{ ok: boolean; response?: Response } | null> => {
|
|
1315
|
+
const target = orderedTargets[i];
|
|
1316
|
+
const modelStr = target.modelStr;
|
|
1317
|
+
const rawModel = parseModel(modelStr).model || modelStr;
|
|
1318
|
+
const provider = target.provider;
|
|
1319
|
+
|
|
1320
|
+
const cb = getCircuitBreaker(provider);
|
|
1321
|
+
if (cb.getStatus().state === "OPEN") {
|
|
1322
|
+
log.info("COMBO", `Skipping ${modelStr} — circuit breaker OPEN for ${provider}`);
|
|
1323
|
+
if (i > 0) fallbackCount++;
|
|
1324
|
+
return null;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
if (
|
|
1328
|
+
resilienceSettings.providerCooldown.enabled &&
|
|
1329
|
+
Boolean(provider && provider !== "unknown") &&
|
|
1330
|
+
isProviderInCooldown(provider, target.connectionId ?? undefined, resilienceSettings)
|
|
1331
|
+
) {
|
|
1332
|
+
log.info("COMBO", `Skipping ${modelStr} — provider ${provider} in global cooldown`);
|
|
1333
|
+
if (i > 0) fallbackCount++;
|
|
1334
|
+
return null;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
// Use pre-screened profile if available, otherwise fetch on demand
|
|
1338
|
+
const preScreenEntry = preScreenMap.get(target.executionKey);
|
|
1339
|
+
const profile = preScreenEntry?.profile ?? (await getRuntimeProviderProfile(provider));
|
|
1340
|
+
|
|
1341
|
+
const allowRateLimitedConnection =
|
|
1342
|
+
Boolean(provider && provider !== "unknown") &&
|
|
1343
|
+
transientRateLimitedProviders.has(provider);
|
|
1344
|
+
const targetForAttempt = allowRateLimitedConnection
|
|
1345
|
+
? {
|
|
1346
|
+
...target,
|
|
1347
|
+
allowRateLimitedConnection: true,
|
|
1348
|
+
modelAbortSignal: abortControllers.get(i)!.signal,
|
|
1349
|
+
}
|
|
1350
|
+
: { ...target, modelAbortSignal: abortControllers.get(i)!.signal };
|
|
1351
|
+
|
|
1352
|
+
// #1731 / #1731v2: skip targets already known-exhausted this request (shared predicate).
|
|
1353
|
+
const exhaustedSkip = getExhaustedTargetSkipReason(
|
|
1354
|
+
target,
|
|
1355
|
+
exhaustedProviders,
|
|
1356
|
+
exhaustedConnections
|
|
1357
|
+
);
|
|
1358
|
+
if (exhaustedSkip) {
|
|
1359
|
+
log.info("COMBO", exhaustedSkip);
|
|
1360
|
+
if (i > 0) fallbackCount++;
|
|
1361
|
+
return null;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// Pre-check: skip models locked by the resilience system (model-level lockout)
|
|
1365
|
+
if (provider && rawModel && isModelLocked(provider, target.connectionId || "", rawModel)) {
|
|
1366
|
+
log.info("COMBO", `Skipping ${modelStr} — model locked by resilience (cooldown active)`);
|
|
1367
|
+
if (i > 0) fallbackCount++;
|
|
1368
|
+
return null;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// #5923 (Finding #4) — honor the same opt-in quota-exhaustion cutoff the
|
|
1372
|
+
// "auto" strategy already applies (buildAutoCandidates), for every other
|
|
1373
|
+
// strategy (priority, weighted, etc.). Strictly scoped per (provider,
|
|
1374
|
+
// connectionId): a 0%-remaining connection is skipped here, but sibling
|
|
1375
|
+
// connections/models on the same provider are untouched — the provider
|
|
1376
|
+
// circuit breaker is never touched by this check. The "auto" strategy is
|
|
1377
|
+
// excluded to avoid a redundant duplicate fetch — it already filtered its
|
|
1378
|
+
// candidate pool via `routableCandidates` before reaching this loop.
|
|
1379
|
+
if (strategy !== "auto" && provider && target.connectionId) {
|
|
1380
|
+
const quotaCutoff = await resolveQuotaExhaustionCutoffForTarget(
|
|
1381
|
+
provider,
|
|
1382
|
+
target.connectionId,
|
|
1383
|
+
resilienceSettings,
|
|
1384
|
+
quotaCutoffResetWindowConfig,
|
|
1385
|
+
combo.name,
|
|
1386
|
+
log
|
|
1387
|
+
);
|
|
1388
|
+
if (quotaCutoff.blocked) {
|
|
1389
|
+
log.info(
|
|
1390
|
+
"COMBO",
|
|
1391
|
+
`Skipping ${modelStr} — quota exhaustion cutoff (${quotaCutoff.reason || "quota_exhausted"})`
|
|
1392
|
+
);
|
|
1393
|
+
if (i > 0) fallbackCount++;
|
|
1394
|
+
return null;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// Pre-screen snapshot is NOT used as a permanent skip — availability
|
|
1399
|
+
// is always re-checked via isModelAvailable below because connection
|
|
1400
|
+
// cooldowns can expire between setTry retries, making a previously
|
|
1401
|
+
// unavailable target available again. Circuit-breaker-OPEN providers
|
|
1402
|
+
// are already caught by the dedicated breaker check above.
|
|
1403
|
+
if (isModelAvailable) {
|
|
1404
|
+
const available = await isModelAvailable(modelStr, targetForAttempt);
|
|
1405
|
+
if (!available) {
|
|
1406
|
+
log.debug?.(
|
|
1407
|
+
"COMBO",
|
|
1408
|
+
`Skipping ${modelStr} — no credentials available or model excluded`
|
|
1409
|
+
);
|
|
1410
|
+
if (i > 0) fallbackCount++;
|
|
1411
|
+
return null;
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
// Credential gate: skip targets with known-bad credentials (fail-fast)
|
|
1416
|
+
const connectionId = target.connectionId as string | undefined;
|
|
1417
|
+
if (connectionId) {
|
|
1418
|
+
const gateResult = checkCredentialGate(connectionId, provider, modelStr);
|
|
1419
|
+
if (gateResult.allowed === false) {
|
|
1420
|
+
logCredentialSkip(log, modelStr, gateResult.reason || "Credential gate blocked");
|
|
1421
|
+
if (i > 0) fallbackCount++;
|
|
1422
|
+
return null;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// Retry loop for transient errors
|
|
1427
|
+
for (let retry = 0; retry <= maxRetries; retry++) {
|
|
1428
|
+
// Fix #1681: Bail out immediately if the client has disconnected
|
|
1429
|
+
if (signal?.aborted) {
|
|
1430
|
+
log.info("COMBO", `Client disconnected — aborting combo loop before model ${modelStr}`);
|
|
1431
|
+
return { ok: false, response: errorResponse(499, "Client disconnected") };
|
|
1432
|
+
}
|
|
1433
|
+
globalAttempts++;
|
|
1434
|
+
if (globalAttempts > MAX_GLOBAL_ATTEMPTS) {
|
|
1435
|
+
log.warn(
|
|
1436
|
+
"COMBO",
|
|
1437
|
+
`Maximum combo attempts (${MAX_GLOBAL_ATTEMPTS}) exceeded across all targets and fallbacks. Terminating loop to prevent runaway background requests.`
|
|
1438
|
+
);
|
|
1439
|
+
return { ok: false, response: errorResponse(503, "Maximum combo retry limit reached") };
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
// Predictive TTFT Circuit Breaker (skip slow models)
|
|
1443
|
+
if (
|
|
1444
|
+
zeroLatencyOptimizationsEnabled &&
|
|
1445
|
+
config.predictiveTtftMs &&
|
|
1446
|
+
config.predictiveTtftMs > 0 &&
|
|
1447
|
+
retry === 0
|
|
1448
|
+
) {
|
|
1449
|
+
const cMetrics = getComboMetrics(combo.name);
|
|
1450
|
+
if (cMetrics) {
|
|
1451
|
+
const targetKey = orderedTargets[i].executionKey || modelStr;
|
|
1452
|
+
const m = cMetrics.byTarget[targetKey] || cMetrics.byModel[modelStr];
|
|
1453
|
+
if (shouldSkipForPredictedTtft(m, config.predictiveTtftMs)) {
|
|
1454
|
+
log.warn(
|
|
1455
|
+
"COMBO",
|
|
1456
|
+
`Predictive TTFT Circuit Breaker: skipping ${modelStr} (avg ${m.avgLatencyMs}ms > max ${config.predictiveTtftMs}ms)`
|
|
1457
|
+
);
|
|
1458
|
+
return null;
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
if (retry > 0) {
|
|
1464
|
+
log.info(
|
|
1465
|
+
"COMBO",
|
|
1466
|
+
`Retrying ${modelStr} in ${retryDelayMs}ms (attempt ${retry + 1}/${maxRetries + 1})`
|
|
1467
|
+
);
|
|
1468
|
+
await new Promise((resolve) => {
|
|
1469
|
+
const timer = setTimeout(resolve, retryDelayMs);
|
|
1470
|
+
signal?.addEventListener(
|
|
1471
|
+
"abort",
|
|
1472
|
+
() => {
|
|
1473
|
+
clearTimeout(timer);
|
|
1474
|
+
resolve(undefined);
|
|
1475
|
+
},
|
|
1476
|
+
{ once: true }
|
|
1477
|
+
);
|
|
1478
|
+
});
|
|
1479
|
+
if (signal?.aborted) {
|
|
1480
|
+
log.info("COMBO", `Client disconnected during retry delay — aborting`);
|
|
1481
|
+
return { ok: false, response: errorResponse(499, "Client disconnected") };
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
log.info(
|
|
1486
|
+
"COMBO",
|
|
1487
|
+
`Trying model ${i + 1}/${orderedTargets.length}: ${modelStr}${retry > 0 ? ` (retry ${retry})` : ""}`
|
|
1488
|
+
);
|
|
1489
|
+
emit("combo.target.attempt", {
|
|
1490
|
+
comboName: combo.name,
|
|
1491
|
+
targetIndex: i,
|
|
1492
|
+
provider,
|
|
1493
|
+
model: modelStr,
|
|
1494
|
+
timestamp: Date.now(),
|
|
1495
|
+
strategy,
|
|
1496
|
+
});
|
|
1497
|
+
|
|
1498
|
+
// Deep clone the body to ensure context preservation and prevent mutations
|
|
1499
|
+
// from affecting other targets in the combo. structuredClone avoids the
|
|
1500
|
+
// full intermediate JSON string that JSON.parse(JSON.stringify(...)) builds
|
|
1501
|
+
// (a second multi-hundred-KB allocation per target on large agent payloads),
|
|
1502
|
+
// halving the per-target transient heap on the hot path (#5152).
|
|
1503
|
+
let attemptBody = structuredClone(body);
|
|
1504
|
+
|
|
1505
|
+
// Proactive Context Compression for fallbacks (Zero-Latency optimization)
|
|
1506
|
+
if (
|
|
1507
|
+
zeroLatencyOptimizationsEnabled &&
|
|
1508
|
+
i > 0 &&
|
|
1509
|
+
config.fallbackCompressionMode &&
|
|
1510
|
+
config.fallbackCompressionMode !== "off"
|
|
1511
|
+
) {
|
|
1512
|
+
const { estimateTokens } = await import("./contextManager.ts");
|
|
1513
|
+
const estimatedTokens = estimateTokens(JSON.stringify(attemptBody));
|
|
1514
|
+
if (estimatedTokens > (config.fallbackCompressionThreshold ?? 1000)) {
|
|
1515
|
+
const { applyCompression } = await import("./compression/strategySelector.ts");
|
|
1516
|
+
const compressionResult = applyCompression(
|
|
1517
|
+
attemptBody,
|
|
1518
|
+
config.fallbackCompressionMode as CompressionMode,
|
|
1519
|
+
// Opt into the TV1 bail-out so a throwing fallback engine is SKIPPED rather than
|
|
1520
|
+
// propagating out of executeTarget and being swallowed as a "Speculative task
|
|
1521
|
+
// error" (which silently drops this combo target). minGainPercent:0 keeps the
|
|
1522
|
+
// advance behavior identical to the default path — this only adds skip-on-throw.
|
|
1523
|
+
{ model: modelStr, bailout: { enabled: true, minGainPercent: 0 } }
|
|
1524
|
+
);
|
|
1525
|
+
if (compressionResult.compressed) {
|
|
1526
|
+
log.info(
|
|
1527
|
+
"COMBO",
|
|
1528
|
+
`Proactive fallback compression applied (${config.fallbackCompressionMode}): ${estimatedTokens} -> ${compressionResult.stats?.compressedTokens} tokens`
|
|
1529
|
+
);
|
|
1530
|
+
attemptBody = compressionResult.body;
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
// Universal handoff: inject existing handoff if model changed
|
|
1536
|
+
if (
|
|
1537
|
+
universalHandoffConfig.enabled &&
|
|
1538
|
+
relayOptions?.sessionId &&
|
|
1539
|
+
!(body as Record<string, unknown>)?.[SKIP_UNIVERSAL_HANDOFF_FLAG]
|
|
1540
|
+
) {
|
|
1541
|
+
const lastModel = getLastSessionModel(relayOptions.sessionId, combo.name);
|
|
1542
|
+
if (lastModel && lastModel !== modelStr) {
|
|
1543
|
+
const existingHandoff = getHandoff(relayOptions.sessionId, combo.name);
|
|
1544
|
+
attemptBody = injectUniversalHandoffBody(
|
|
1545
|
+
attemptBody, // Use the cloned body to maintain isolation
|
|
1546
|
+
lastModel,
|
|
1547
|
+
modelStr,
|
|
1548
|
+
`Model routing: ${lastModel} → ${modelStr}`,
|
|
1549
|
+
existingHandoff
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// Issue #3587: Reasoning models can spend the whole output budget on
|
|
1555
|
+
// reasoning. Only add headroom when the complete buffer fits inside the
|
|
1556
|
+
// model's known output cap; otherwise preserve the client's explicit limit.
|
|
1557
|
+
{
|
|
1558
|
+
const bodyRecord = attemptBody as Record<string, unknown>;
|
|
1559
|
+
const currentMaxTokens = toPositiveInteger(bodyRecord.max_tokens);
|
|
1560
|
+
const bufferedMaxTokens = resolveReasoningBufferedMaxTokens(
|
|
1561
|
+
modelStr,
|
|
1562
|
+
bodyRecord.max_tokens,
|
|
1563
|
+
{ enabled: reasoningTokenBufferEnabled }
|
|
1564
|
+
);
|
|
1565
|
+
if (currentMaxTokens !== null && bufferedMaxTokens !== null) {
|
|
1566
|
+
bodyRecord.max_tokens = bufferedMaxTokens;
|
|
1567
|
+
if (bufferedMaxTokens !== currentMaxTokens) {
|
|
1568
|
+
log.info(
|
|
1569
|
+
"COMBO",
|
|
1570
|
+
`Reasoning model ${modelStr}: adjusted max_tokens ${currentMaxTokens} -> ${bufferedMaxTokens}`
|
|
1571
|
+
);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
const result = await handleSingleModelWithTimeout(attemptBody, modelStr, {
|
|
1576
|
+
...targetForAttempt,
|
|
1577
|
+
effectiveComboStrategy: strategy,
|
|
1578
|
+
failoverBeforeRetry: config.failoverBeforeRetry,
|
|
1579
|
+
});
|
|
1580
|
+
|
|
1581
|
+
// Success — validate response quality before returning
|
|
1582
|
+
if (result.ok) {
|
|
1583
|
+
const selectedConnectionId =
|
|
1584
|
+
result.headers?.get("X-OmniRoute-Selected-Connection-Id") ||
|
|
1585
|
+
result.headers?.get("x-omniroute-selected-connection-id") ||
|
|
1586
|
+
undefined;
|
|
1587
|
+
const effectiveConnectionId = selectedConnectionId || target.connectionId || "";
|
|
1588
|
+
|
|
1589
|
+
// Clone BEFORE quality check — validateResponseQuality reads the body
|
|
1590
|
+
// via getReader() which locks the stream. The clone's body is consumed
|
|
1591
|
+
// by the quality check; the original stays unlocked for piping.
|
|
1592
|
+
let qualityClone: Response;
|
|
1593
|
+
try {
|
|
1594
|
+
qualityClone = result.clone();
|
|
1595
|
+
} catch {
|
|
1596
|
+
qualityClone = result;
|
|
1597
|
+
}
|
|
1598
|
+
const quality = await validateResponseQuality(
|
|
1599
|
+
qualityClone,
|
|
1600
|
+
clientRequestedStream,
|
|
1601
|
+
log,
|
|
1602
|
+
config.responseValidation
|
|
1603
|
+
);
|
|
1604
|
+
releaseQualityClone(qualityClone, result, quality);
|
|
1605
|
+
if (!quality.valid) {
|
|
1606
|
+
log.warn(
|
|
1607
|
+
"COMBO",
|
|
1608
|
+
`Model ${modelStr} returned 200 but failed quality check: ${quality.reason}`
|
|
1609
|
+
);
|
|
1610
|
+
recordComboRequest(combo.name, modelStr, {
|
|
1611
|
+
success: false,
|
|
1612
|
+
latencyMs: Date.now() - startTime,
|
|
1613
|
+
fallbackCount,
|
|
1614
|
+
strategy,
|
|
1615
|
+
target: toRecordedTarget(target),
|
|
1616
|
+
});
|
|
1617
|
+
recordedAttempts++;
|
|
1618
|
+
// Fix #1707: Set terminal state so the fallback doesn't emit
|
|
1619
|
+
// misleading ALL_ACCOUNTS_INACTIVE when the real issue is quality.
|
|
1620
|
+
lastError = `Upstream response failed quality validation: ${quality.reason}`;
|
|
1621
|
+
if (!lastStatus) lastStatus = 502;
|
|
1622
|
+
if (i > 0) fallbackCount++;
|
|
1623
|
+
if (provider && rawModel) {
|
|
1624
|
+
const mlSettings = resolveModelLockoutSettings(settings);
|
|
1625
|
+
if (mlSettings.enabled && mlSettings.errorCodes.includes(502)) {
|
|
1626
|
+
recordModelLockoutFailure(
|
|
1627
|
+
provider,
|
|
1628
|
+
target.connectionId || "",
|
|
1629
|
+
rawModel,
|
|
1630
|
+
"quality_failure",
|
|
1631
|
+
502,
|
|
1632
|
+
mlSettings.baseCooldownMs,
|
|
1633
|
+
profile,
|
|
1634
|
+
{
|
|
1635
|
+
exactCooldownMs: mlSettings.useExponentialBackoff
|
|
1636
|
+
? 0
|
|
1637
|
+
: mlSettings.baseCooldownMs,
|
|
1638
|
+
maxCooldownMs: mlSettings.maxCooldownMs,
|
|
1639
|
+
}
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
emit("combo.target.failed", {
|
|
1644
|
+
comboName: combo.name,
|
|
1645
|
+
targetIndex: i,
|
|
1646
|
+
provider,
|
|
1647
|
+
model: modelStr,
|
|
1648
|
+
error: `Quality: ${quality.reason}`,
|
|
1649
|
+
latencyMs: Date.now() - startTime,
|
|
1650
|
+
});
|
|
1651
|
+
return null;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// Success decay: a healthy response walks the model's lockout failure
|
|
1655
|
+
// count back down (and eventually clears an expired lockout entirely).
|
|
1656
|
+
if (provider && rawModel) {
|
|
1657
|
+
const dcResult = decayModelFailureCount(provider, effectiveConnectionId, rawModel);
|
|
1658
|
+
if (dcResult.cleared) {
|
|
1659
|
+
log.info("COMBO", `Model ${modelStr} fully recovered — lockout cleared`);
|
|
1660
|
+
} else if (dcResult.newFailureCount > 0) {
|
|
1661
|
+
log.debug(
|
|
1662
|
+
"COMBO",
|
|
1663
|
+
`Model ${modelStr} decayed to failureCount=${dcResult.newFailureCount}`
|
|
1664
|
+
);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
const latencyMs = Date.now() - startTime;
|
|
1669
|
+
emit("combo.target.succeeded", {
|
|
1670
|
+
comboName: combo.name,
|
|
1671
|
+
targetIndex: i,
|
|
1672
|
+
provider,
|
|
1673
|
+
model: modelStr,
|
|
1674
|
+
latencyMs,
|
|
1675
|
+
});
|
|
1676
|
+
log.info(
|
|
1677
|
+
"COMBO",
|
|
1678
|
+
`Model ${modelStr} succeeded (${latencyMs}ms, ${fallbackCount} fallbacks)`
|
|
1679
|
+
);
|
|
1680
|
+
recordComboRequest(combo.name, modelStr, {
|
|
1681
|
+
success: true,
|
|
1682
|
+
latencyMs,
|
|
1683
|
+
fallbackCount,
|
|
1684
|
+
strategy,
|
|
1685
|
+
target: toRecordedTarget(target),
|
|
1686
|
+
});
|
|
1687
|
+
recordedAttempts++;
|
|
1688
|
+
|
|
1689
|
+
// Reset cooldown on success
|
|
1690
|
+
if (provider && provider !== "unknown") {
|
|
1691
|
+
recordProviderSuccess(provider, effectiveConnectionId || undefined);
|
|
1692
|
+
}
|
|
1693
|
+
if (strategy === "weighted" && stickyWeightedLimit > 1) {
|
|
1694
|
+
const stickySuccessKey = getWeightedStepKeyForTarget(target);
|
|
1695
|
+
if (stickySuccessKey) {
|
|
1696
|
+
recordStickyWeightedSuccess(combo.name, stickySuccessKey, stickyWeightedLimit);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
// Webhook fan-out: best-effort, never blocks the response stream.
|
|
1700
|
+
notifyWebhookEvent("request.completed", {
|
|
1701
|
+
combo: combo.name,
|
|
1702
|
+
provider,
|
|
1703
|
+
model: modelStr,
|
|
1704
|
+
account:
|
|
1705
|
+
typeof target.label === "string" && target.label.trim().length > 0
|
|
1706
|
+
? target.label.trim()
|
|
1707
|
+
: "",
|
|
1708
|
+
accountId: effectiveConnectionId ?? "",
|
|
1709
|
+
latencyMs,
|
|
1710
|
+
fallbackCount,
|
|
1711
|
+
});
|
|
1712
|
+
|
|
1713
|
+
// Context cache pinning: record model usage for session-based pinning
|
|
1714
|
+
// (independent of universal handoff — always fires when context_cache_protection is on)
|
|
1715
|
+
// #3825: write under the SAME effectiveSessionId used by the read site so a
|
|
1716
|
+
// sessionless conversation re-pins to this model on its next turn.
|
|
1717
|
+
if (
|
|
1718
|
+
combo.context_cache_protection &&
|
|
1719
|
+
effectiveSessionId &&
|
|
1720
|
+
!(body as Record<string, unknown>)?.[SKIP_UNIVERSAL_HANDOFF_FLAG]
|
|
1721
|
+
) {
|
|
1722
|
+
recordSessionModelUsage(
|
|
1723
|
+
effectiveSessionId,
|
|
1724
|
+
combo.name,
|
|
1725
|
+
modelStr,
|
|
1726
|
+
provider,
|
|
1727
|
+
target.connectionId ?? undefined
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
// Universal handoff: record model usage for session
|
|
1732
|
+
if (
|
|
1733
|
+
universalHandoffConfig.enabled &&
|
|
1734
|
+
relayOptions?.sessionId &&
|
|
1735
|
+
!(body as Record<string, unknown>)?.[SKIP_UNIVERSAL_HANDOFF_FLAG]
|
|
1736
|
+
) {
|
|
1737
|
+
const prevModel = getLastSessionModel(relayOptions.sessionId, combo.name);
|
|
1738
|
+
recordSessionModelUsage(
|
|
1739
|
+
relayOptions.sessionId,
|
|
1740
|
+
combo.name,
|
|
1741
|
+
modelStr,
|
|
1742
|
+
provider,
|
|
1743
|
+
target.connectionId ?? undefined
|
|
1744
|
+
);
|
|
1745
|
+
if (prevModel && prevModel !== modelStr) {
|
|
1746
|
+
const handoffSourceMessages =
|
|
1747
|
+
Array.isArray(body?.messages) && body.messages.length > 0
|
|
1748
|
+
? body.messages
|
|
1749
|
+
: Array.isArray(body?.input)
|
|
1750
|
+
? body.input
|
|
1751
|
+
: [];
|
|
1752
|
+
|
|
1753
|
+
maybeGenerateUniversalHandoff({
|
|
1754
|
+
sessionId: relayOptions.sessionId,
|
|
1755
|
+
comboName: combo.name,
|
|
1756
|
+
messages: handoffSourceMessages as MessageLike[],
|
|
1757
|
+
prevModel,
|
|
1758
|
+
currModel: modelStr,
|
|
1759
|
+
universalConfig: universalHandoffConfig,
|
|
1760
|
+
handleSingleModel: handleSingleModelWithTimeout,
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
recordSessionModelUsage(
|
|
1765
|
+
relayOptions.sessionId,
|
|
1766
|
+
combo.name,
|
|
1767
|
+
modelStr,
|
|
1768
|
+
provider,
|
|
1769
|
+
target.connectionId ?? undefined
|
|
1770
|
+
);
|
|
1771
|
+
}
|
|
1772
|
+
// Context-relay intentionally splits responsibilities:
|
|
1773
|
+
// combo.ts decides whether a successful turn should generate a handoff,
|
|
1774
|
+
// while chat.ts injects the handoff after the real connectionId is resolved.
|
|
1775
|
+
if (
|
|
1776
|
+
strategy === "context-relay" &&
|
|
1777
|
+
relayOptions?.sessionId &&
|
|
1778
|
+
relayConfig &&
|
|
1779
|
+
relayConfig.handoffProviders.includes(provider) &&
|
|
1780
|
+
provider === "codex"
|
|
1781
|
+
) {
|
|
1782
|
+
const connectionId = getSessionConnection(relayOptions.sessionId);
|
|
1783
|
+
if (connectionId) {
|
|
1784
|
+
const quotaInfo = await fetchCodexQuota(connectionId).catch(() => null);
|
|
1785
|
+
if (quotaInfo) {
|
|
1786
|
+
const resetCandidates = [
|
|
1787
|
+
quotaInfo.windows?.session?.resetAt,
|
|
1788
|
+
quotaInfo.windows?.weekly?.resetAt,
|
|
1789
|
+
quotaInfo.resetAt,
|
|
1790
|
+
]
|
|
1791
|
+
.filter(
|
|
1792
|
+
(value): value is string => typeof value === "string" && value.length > 0
|
|
1793
|
+
)
|
|
1794
|
+
.sort((a, b) => a.localeCompare(b));
|
|
1795
|
+
const handoffSourceMessages =
|
|
1796
|
+
Array.isArray(body?.messages) && body.messages.length > 0
|
|
1797
|
+
? body.messages
|
|
1798
|
+
: Array.isArray(body?.input)
|
|
1799
|
+
? body.input
|
|
1800
|
+
: [];
|
|
1801
|
+
|
|
1802
|
+
maybeGenerateHandoff({
|
|
1803
|
+
sessionId: relayOptions.sessionId,
|
|
1804
|
+
comboName: combo.name,
|
|
1805
|
+
connectionId,
|
|
1806
|
+
percentUsed: quotaInfo.percentUsed,
|
|
1807
|
+
messages: handoffSourceMessages,
|
|
1808
|
+
model: modelStr,
|
|
1809
|
+
expiresAt: resetCandidates[0] || null,
|
|
1810
|
+
config: relayConfig,
|
|
1811
|
+
handleSingleModel: handleSingleModelWithTimeout,
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
if (_sticky.messageHash && target.connectionId)
|
|
1817
|
+
recordStickyBinding(_sticky.messageHash, target.connectionId); // LKGP (#919):
|
|
1818
|
+
if (provider) {
|
|
1819
|
+
const connId = effectiveConnectionId || undefined;
|
|
1820
|
+
void (async () => {
|
|
1821
|
+
try {
|
|
1822
|
+
const { setLKGP } = await import("../../src/lib/localDb");
|
|
1823
|
+
await Promise.all([
|
|
1824
|
+
setLKGP(combo.name, target.executionKey, provider, connId),
|
|
1825
|
+
setLKGP(combo.name, combo.id || combo.name, provider, connId),
|
|
1826
|
+
]);
|
|
1827
|
+
} catch (err) {
|
|
1828
|
+
log.warn(
|
|
1829
|
+
"COMBO",
|
|
1830
|
+
"Failed to record Last Known Good Provider. This is non-fatal.",
|
|
1831
|
+
{
|
|
1832
|
+
err,
|
|
1833
|
+
}
|
|
1834
|
+
);
|
|
1835
|
+
}
|
|
1836
|
+
})();
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
return { ok: true, response: result };
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
// Extract error info from response
|
|
1843
|
+
let errorText = result.statusText || "";
|
|
1844
|
+
let errorBody: ComboErrorBody = null;
|
|
1845
|
+
let retryAfter: ComboRetryAfter | null = null;
|
|
1846
|
+
try {
|
|
1847
|
+
const cloned = result.clone();
|
|
1848
|
+
try {
|
|
1849
|
+
const text = await cloned.text();
|
|
1850
|
+
if (text) {
|
|
1851
|
+
errorText = text.substring(0, 500);
|
|
1852
|
+
errorBody = JSON.parse(text);
|
|
1853
|
+
const parsedError = errorBody?.error;
|
|
1854
|
+
errorText =
|
|
1855
|
+
(typeof parsedError === "object" && parsedError?.message) ||
|
|
1856
|
+
(typeof parsedError === "string" ? parsedError : null) ||
|
|
1857
|
+
errorBody?.message ||
|
|
1858
|
+
errorText;
|
|
1859
|
+
retryAfter = errorBody?.retryAfter || null;
|
|
1860
|
+
}
|
|
1861
|
+
} catch {
|
|
1862
|
+
/* Clone parse failed */
|
|
1863
|
+
}
|
|
1864
|
+
} catch {
|
|
1865
|
+
/* Clone failed */
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
// Track earliest retryAfter
|
|
1869
|
+
if (
|
|
1870
|
+
retryAfter &&
|
|
1871
|
+
(!earliestRetryAfter || new Date(retryAfter) < new Date(earliestRetryAfter))
|
|
1872
|
+
) {
|
|
1873
|
+
earliestRetryAfter = retryAfter;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
// Normalize error text
|
|
1877
|
+
if (typeof errorText !== "string") {
|
|
1878
|
+
try {
|
|
1879
|
+
errorText = JSON.stringify(errorText);
|
|
1880
|
+
} catch {
|
|
1881
|
+
errorText = String(errorText);
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
const isStreamReadinessFailure =
|
|
1886
|
+
(result.status === 502 || result.status === 504) &&
|
|
1887
|
+
isStreamReadinessFailureErrorBody(errorBody);
|
|
1888
|
+
|
|
1889
|
+
// FIX 5: a local per-API-key token-limit 429 must not cool shared accounts.
|
|
1890
|
+
const isTokenLimitBreach =
|
|
1891
|
+
result.status === 429 && isTokenLimitBreachErrorBody(errorBody);
|
|
1892
|
+
|
|
1893
|
+
// Fix #1681: Status 499 means client disconnected — stop combo loop immediately.
|
|
1894
|
+
// There is no point trying fallback models when nobody is listening.
|
|
1895
|
+
if (result.status === 499) {
|
|
1896
|
+
log.info("COMBO", `Client disconnected (499) during ${modelStr} — stopping combo loop`);
|
|
1897
|
+
recordComboRequest(combo.name, modelStr, {
|
|
1898
|
+
success: false,
|
|
1899
|
+
latencyMs: Date.now() - startTime,
|
|
1900
|
+
fallbackCount,
|
|
1901
|
+
strategy,
|
|
1902
|
+
target: toRecordedTarget(target),
|
|
1903
|
+
});
|
|
1904
|
+
recordedAttempts++;
|
|
1905
|
+
// executeTarget must return the {ok,response} contract — a raw Response
|
|
1906
|
+
// here makes the speculative loop's res.ok/res.response checks both miss,
|
|
1907
|
+
// so the combo would wrongly fall through to the next model after a 499.
|
|
1908
|
+
return { ok: false, response: result };
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
// Combo fallback is target-level orchestration: a non-ok target response is
|
|
1912
|
+
// treated as local to that target and the combo continues to the next target.
|
|
1913
|
+
// Error classification is retained only for retry/cooldown pacing; it must
|
|
1914
|
+
// not decide whether fallback happens, including for generic 400 responses.
|
|
1915
|
+
const rawError = errorBody?.error;
|
|
1916
|
+
const structuredError =
|
|
1917
|
+
rawError && typeof rawError === "object"
|
|
1918
|
+
? {
|
|
1919
|
+
// Upstream JSON may carry a numeric `code`/`type` (e.g. {"code":40001}).
|
|
1920
|
+
// Coerce to string if present instead of discarding, so downstream string
|
|
1921
|
+
// ops (.toLowerCase, .startsWith) can run safely without type crashes.
|
|
1922
|
+
code:
|
|
1923
|
+
(rawError as Record<string, unknown>).code !== undefined &&
|
|
1924
|
+
(rawError as Record<string, unknown>).code !== null
|
|
1925
|
+
? String((rawError as Record<string, unknown>).code)
|
|
1926
|
+
: undefined,
|
|
1927
|
+
type:
|
|
1928
|
+
(rawError as Record<string, unknown>).type !== undefined &&
|
|
1929
|
+
(rawError as Record<string, unknown>).type !== null
|
|
1930
|
+
? String((rawError as Record<string, unknown>).type)
|
|
1931
|
+
: undefined,
|
|
1932
|
+
}
|
|
1933
|
+
: undefined;
|
|
1934
|
+
const fallbackResult = checkFallbackError(
|
|
1935
|
+
result.status,
|
|
1936
|
+
errorText,
|
|
1937
|
+
0,
|
|
1938
|
+
null,
|
|
1939
|
+
provider,
|
|
1940
|
+
result.headers,
|
|
1941
|
+
profile,
|
|
1942
|
+
structuredError
|
|
1943
|
+
);
|
|
1944
|
+
const { cooldownMs } = fallbackResult;
|
|
1945
|
+
const selectedConnectionId =
|
|
1946
|
+
result.headers?.get("X-OmniRoute-Selected-Connection-Id") ||
|
|
1947
|
+
result.headers?.get("x-omniroute-selected-connection-id") ||
|
|
1948
|
+
undefined;
|
|
1949
|
+
const targetWithConnection = selectedConnectionId
|
|
1950
|
+
? { ...target, connectionId: selectedConnectionId }
|
|
1951
|
+
: target;
|
|
1952
|
+
|
|
1953
|
+
// #1731 / #1731v2: classify the upstream error and update the exhaustion sets
|
|
1954
|
+
// (shared with handleRoundRobinCombo). Returns whether the provider is fully exhausted.
|
|
1955
|
+
const providerExhausted = applyComboTargetExhaustion(targetWithConnection, {
|
|
1956
|
+
result,
|
|
1957
|
+
fallbackResult,
|
|
1958
|
+
errorText,
|
|
1959
|
+
rawModel,
|
|
1960
|
+
isTokenLimitBreach,
|
|
1961
|
+
allAccountsRateLimited: false,
|
|
1962
|
+
sets: { exhaustedProviders, exhaustedConnections, transientRateLimitedProviders },
|
|
1963
|
+
log,
|
|
1964
|
+
tag: "COMBO",
|
|
1965
|
+
exhaustedLogLevel: "info",
|
|
1966
|
+
structuredError,
|
|
1967
|
+
});
|
|
1968
|
+
|
|
1969
|
+
// #2101: Prevent infinite fallback loops with 400 Bad Request errors that are genuinely
|
|
1970
|
+
// body-specific (malformed JSON, bad format, missing required fields).
|
|
1971
|
+
// Context overflow and parameter validation errors are NOT body-specific:
|
|
1972
|
+
// - Context overflow: different models have different context windows
|
|
1973
|
+
// - Max_tokens / param errors: different models have different output limits
|
|
1974
|
+
// - Model access denied: different providers serve different model sets
|
|
1975
|
+
// These should fall through so the next combo target can try.
|
|
1976
|
+
if (
|
|
1977
|
+
result.status === 400 &&
|
|
1978
|
+
fallbackResult.shouldFallback &&
|
|
1979
|
+
!isContextOverflow400(errorText) &&
|
|
1980
|
+
!isParamValidation400(errorText) &&
|
|
1981
|
+
(errorText.toLowerCase().includes("context") ||
|
|
1982
|
+
errorText.toLowerCase().includes("prompt") ||
|
|
1983
|
+
errorText.toLowerCase().includes("token") ||
|
|
1984
|
+
errorText.toLowerCase().includes("malformed") ||
|
|
1985
|
+
errorText.toLowerCase().includes("invalid") ||
|
|
1986
|
+
errorText.toLowerCase().includes("bad request"))
|
|
1987
|
+
) {
|
|
1988
|
+
log.warn(
|
|
1989
|
+
"COMBO",
|
|
1990
|
+
`400 Bad Request with body-specific error detected on ${modelStr} — skipping fallback to other targets to prevent infinite loop`
|
|
1991
|
+
);
|
|
1992
|
+
// Record the failure and break to avoid trying other targets with the same bad request
|
|
1993
|
+
recordComboRequest(combo.name, modelStr, {
|
|
1994
|
+
success: false,
|
|
1995
|
+
latencyMs: Date.now() - startTime,
|
|
1996
|
+
fallbackCount,
|
|
1997
|
+
strategy,
|
|
1998
|
+
target: toRecordedTarget(target),
|
|
1999
|
+
});
|
|
2000
|
+
recordedAttempts++;
|
|
2001
|
+
lastError = errorText || String(result.status);
|
|
2002
|
+
if (!lastStatus) lastStatus = result.status;
|
|
2003
|
+
if (i > 0) fallbackCount++;
|
|
2004
|
+
log.warn("COMBO", `Model ${modelStr} failed with body-specific error, stopping combo`);
|
|
2005
|
+
// #4279: surface the 400 via the {ok,response} contract so the OUTER
|
|
2006
|
+
// target loop resolves the combo and stops. A bare `break` here only
|
|
2007
|
+
// exits the inner retry loop; executeTarget then returns null, which
|
|
2008
|
+
// the outer loop treats as "this target produced nothing" and advances
|
|
2009
|
+
// to the next model — so the guard failed to stop fallback and a combo
|
|
2010
|
+
// of N body-rejecting targets tried all N. Mirrors the 499 path above.
|
|
2011
|
+
return { ok: false, response: result };
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
// Trigger shared provider circuit breaker for 5xx errors and connection failures.
|
|
2015
|
+
// If the next target in the combo is on the same provider, don't mark the provider
|
|
2016
|
+
// as failed — different models on the same provider may still succeed.
|
|
2017
|
+
// G-02: when fallbackResult.skipProviderBreaker is set (embedded service supervisor
|
|
2018
|
+
// outage signalled via X-Omni-Fallback-Hint: connection_cooldown) apply connection
|
|
2019
|
+
// cooldown only — do NOT trip the whole-provider breaker.
|
|
2020
|
+
const nextTarget = orderedTargets[i + 1];
|
|
2021
|
+
const sameProviderNext =
|
|
2022
|
+
typeof nextTarget?.provider === "string" && nextTarget.provider === provider;
|
|
2023
|
+
if (
|
|
2024
|
+
shouldRecordProviderBreakerFailure({
|
|
2025
|
+
isStreamReadinessFailure,
|
|
2026
|
+
status: result.status,
|
|
2027
|
+
sameProviderNext,
|
|
2028
|
+
skipProviderBreaker: fallbackResult.skipProviderBreaker,
|
|
2029
|
+
})
|
|
2030
|
+
) {
|
|
2031
|
+
recordProviderFailure(provider, log, targetWithConnection.connectionId, profile);
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
// Check if this is a transient error worth retrying on same model.
|
|
2035
|
+
// A token-limit 429 is terminal for the client — never retry it.
|
|
2036
|
+
const isTransient =
|
|
2037
|
+
!isStreamReadinessFailure &&
|
|
2038
|
+
!isTokenLimitBreach &&
|
|
2039
|
+
[408, 429, 500, 502, 503, 504].includes(result.status);
|
|
2040
|
+
if (retry < maxRetries && isTransient && !providerExhausted) {
|
|
2041
|
+
if (
|
|
2042
|
+
provider &&
|
|
2043
|
+
rawModel &&
|
|
2044
|
+
isModelLocked(provider, targetWithConnection.connectionId || "", rawModel)
|
|
2045
|
+
) {
|
|
2046
|
+
log.info("COMBO", `Skipping retry for ${modelStr} — model lockout active`);
|
|
2047
|
+
if (i > 0) fallbackCount++;
|
|
2048
|
+
return null;
|
|
2049
|
+
}
|
|
2050
|
+
// Record model lockout immediately on the first transient failure —
|
|
2051
|
+
// once the model is cooling down, retrying it would waste an upstream
|
|
2052
|
+
// call and extend the cooldown via exponential backoff.
|
|
2053
|
+
let lockoutRecorded = false;
|
|
2054
|
+
if (provider && rawModel && retry === 0) {
|
|
2055
|
+
const mlSettings = resolveModelLockoutSettings(settings);
|
|
2056
|
+
if (mlSettings.enabled && mlSettings.errorCodes.includes(result.status)) {
|
|
2057
|
+
recordModelLockoutFailure(
|
|
2058
|
+
provider,
|
|
2059
|
+
targetWithConnection.connectionId || "",
|
|
2060
|
+
rawModel,
|
|
2061
|
+
classifyLockoutReason(result.status),
|
|
2062
|
+
result.status,
|
|
2063
|
+
mlSettings.baseCooldownMs,
|
|
2064
|
+
profile,
|
|
2065
|
+
{
|
|
2066
|
+
// #1308: honor a long upstream reset (e.g. "Resets in 160h") over
|
|
2067
|
+
// the short base cooldown / exponential backoff when present.
|
|
2068
|
+
exactCooldownMs: selectLockoutCooldownMs(cooldownMs, mlSettings),
|
|
2069
|
+
maxCooldownMs: mlSettings.maxCooldownMs,
|
|
2070
|
+
}
|
|
2071
|
+
);
|
|
2072
|
+
lockoutRecorded = true;
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
if (lockoutRecorded) {
|
|
2076
|
+
log.info("COMBO", `Skipping retry for ${modelStr} — model lockout active`);
|
|
2077
|
+
if (i > 0) fallbackCount++;
|
|
2078
|
+
return null;
|
|
2079
|
+
}
|
|
2080
|
+
continue; // Retry same model (transient error, no lockout recorded)
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
// Done retrying this model
|
|
2084
|
+
recordComboRequest(combo.name, modelStr, {
|
|
2085
|
+
success: false,
|
|
2086
|
+
latencyMs: Date.now() - startTime,
|
|
2087
|
+
fallbackCount,
|
|
2088
|
+
strategy,
|
|
2089
|
+
target: toRecordedTarget(target),
|
|
2090
|
+
});
|
|
2091
|
+
recordedAttempts++;
|
|
2092
|
+
lastError = errorText || String(result.status);
|
|
2093
|
+
if (!lastStatus) lastStatus = result.status;
|
|
2094
|
+
if (i > 0) fallbackCount++;
|
|
2095
|
+
// Wire combo failures into the resilience dashboard (model-level lockout)
|
|
2096
|
+
// alongside the provider-level cooldown below — they govern different scopes.
|
|
2097
|
+
if (provider && rawModel) {
|
|
2098
|
+
const mlSettings = resolveModelLockoutSettings(settings);
|
|
2099
|
+
if (mlSettings.enabled && mlSettings.errorCodes.includes(result.status)) {
|
|
2100
|
+
recordModelLockoutFailure(
|
|
2101
|
+
provider,
|
|
2102
|
+
targetWithConnection.connectionId || "",
|
|
2103
|
+
rawModel,
|
|
2104
|
+
classifyLockoutReason(result.status),
|
|
2105
|
+
result.status,
|
|
2106
|
+
mlSettings.baseCooldownMs,
|
|
2107
|
+
profile,
|
|
2108
|
+
{
|
|
2109
|
+
// #1308: honor a long upstream reset over base/exponential cooldown.
|
|
2110
|
+
exactCooldownMs: selectLockoutCooldownMs(cooldownMs, mlSettings),
|
|
2111
|
+
maxCooldownMs: mlSettings.maxCooldownMs,
|
|
2112
|
+
}
|
|
2113
|
+
);
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
log.warn("COMBO", `Model ${modelStr} failed, trying next`, { status: result.status });
|
|
2117
|
+
|
|
2118
|
+
// #5976: per-model-quota providers (Gemini, GitHub, etc.) multiplex models
|
|
2119
|
+
// behind one connection. A model-level 500 must NOT cool down the entire
|
|
2120
|
+
// provider — sibling models may still succeed. Skip cooldown recording for
|
|
2121
|
+
// these providers on 500 errors so the next target can try.
|
|
2122
|
+
if (
|
|
2123
|
+
resilienceSettings.providerCooldown.enabled &&
|
|
2124
|
+
provider &&
|
|
2125
|
+
provider !== "unknown" &&
|
|
2126
|
+
!(result.status === 500 && hasPerModelQuota(provider, rawModel))
|
|
2127
|
+
) {
|
|
2128
|
+
recordProviderCooldown(
|
|
2129
|
+
provider,
|
|
2130
|
+
targetWithConnection.connectionId ?? undefined,
|
|
2131
|
+
resilienceSettings
|
|
2132
|
+
);
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
const fallbackWaitMs =
|
|
2136
|
+
fallbackDelayMs > 0 && cooldownMs > 0 && cooldownMs <= MAX_FALLBACK_WAIT_MS
|
|
2137
|
+
? Math.min(cooldownMs, fallbackDelayMs)
|
|
2138
|
+
: 0;
|
|
2139
|
+
if ([502, 503, 504].includes(result.status) && fallbackWaitMs > 0) {
|
|
2140
|
+
log.debug?.("COMBO", `Waiting ${fallbackWaitMs}ms before fallback to next model`);
|
|
2141
|
+
await new Promise((resolve) => {
|
|
2142
|
+
const timer = setTimeout(resolve, fallbackWaitMs);
|
|
2143
|
+
signal?.addEventListener(
|
|
2144
|
+
"abort",
|
|
2145
|
+
() => {
|
|
2146
|
+
clearTimeout(timer);
|
|
2147
|
+
resolve(undefined);
|
|
2148
|
+
},
|
|
2149
|
+
{ once: true }
|
|
2150
|
+
);
|
|
2151
|
+
});
|
|
2152
|
+
if (signal?.aborted) {
|
|
2153
|
+
log.info("COMBO", `Client disconnected during fallback wait — aborting`);
|
|
2154
|
+
return { ok: false, response: errorResponse(499, "Client disconnected") };
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
return null;
|
|
2159
|
+
}
|
|
2160
|
+
return null;
|
|
2161
|
+
};
|
|
2162
|
+
|
|
2163
|
+
for (let i = 0; i < orderedTargets.length; i++) {
|
|
2164
|
+
if (anySuccess) break;
|
|
2165
|
+
|
|
2166
|
+
const abortController = new AbortController();
|
|
2167
|
+
abortControllers.set(i, abortController);
|
|
2168
|
+
const onClientAbort = () => abortController.abort();
|
|
2169
|
+
signal?.addEventListener("abort", onClientAbort);
|
|
2170
|
+
|
|
2171
|
+
const task = (async () => {
|
|
2172
|
+
try {
|
|
2173
|
+
const res = await executeTarget(i);
|
|
2174
|
+
if (res && !anySuccess) {
|
|
2175
|
+
if (res.ok) {
|
|
2176
|
+
anySuccess = true;
|
|
2177
|
+
globalResolve!(res.response!);
|
|
2178
|
+
for (const [idx, ac] of abortControllers.entries()) {
|
|
2179
|
+
if (idx !== i) ac.abort();
|
|
2180
|
+
}
|
|
2181
|
+
} else if (res.response) {
|
|
2182
|
+
// Fatal error, abort combo
|
|
2183
|
+
anySuccess = true;
|
|
2184
|
+
globalResolve!(res.response);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
} finally {
|
|
2188
|
+
signal?.removeEventListener("abort", onClientAbort);
|
|
2189
|
+
}
|
|
2190
|
+
})().catch((err) => {
|
|
2191
|
+
const logError = log.error ?? log.warn;
|
|
2192
|
+
logError("COMBO", `Speculative task error for target ${i}`, err);
|
|
2193
|
+
});
|
|
2194
|
+
|
|
2195
|
+
runningTasks.add(task);
|
|
2196
|
+
task.finally(() => runningTasks.delete(task));
|
|
2197
|
+
|
|
2198
|
+
if (zeroLatencyOptimizationsEnabled && config.hedging && i + 1 < orderedTargets.length) {
|
|
2199
|
+
const hedgeDelay = resolveDelayMs(config.hedgeDelayMs, 500);
|
|
2200
|
+
let timeoutResolve: () => void;
|
|
2201
|
+
const timeoutPromise = new Promise<void>((r) => {
|
|
2202
|
+
timeoutResolve = r;
|
|
2203
|
+
setTimeout(r, hedgeDelay);
|
|
2204
|
+
});
|
|
2205
|
+
await Promise.race([task, globalPromise, timeoutPromise]);
|
|
2206
|
+
} else {
|
|
2207
|
+
await Promise.race([task, globalPromise]);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
if (!anySuccess && runningTasks.size > 0) {
|
|
2212
|
+
await Promise.race([globalPromise, Promise.all([...runningTasks])]);
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
if (anySuccess) {
|
|
2216
|
+
return await globalPromise;
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
// All models failed in this set try
|
|
2220
|
+
const latencyMs = Date.now() - startTime;
|
|
2221
|
+
if (recordedAttempts === 0) {
|
|
2222
|
+
recordComboRequest(combo.name, null, {
|
|
2223
|
+
success: false,
|
|
2224
|
+
latencyMs,
|
|
2225
|
+
fallbackCount,
|
|
2226
|
+
strategy,
|
|
2227
|
+
});
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
// Retry the entire set if more attempts remain
|
|
2231
|
+
if (setTry < maxSetRetries) continue;
|
|
2232
|
+
|
|
2233
|
+
// All set retries exhausted — return the final error
|
|
2234
|
+
if (!lastStatus) {
|
|
2235
|
+
notifyWebhookEvent("request.failed", {
|
|
2236
|
+
combo: combo.name,
|
|
2237
|
+
reason: "ALL_ACCOUNTS_INACTIVE",
|
|
2238
|
+
latencyMs,
|
|
2239
|
+
fallbackCount,
|
|
2240
|
+
});
|
|
2241
|
+
return new Response(
|
|
2242
|
+
JSON.stringify({
|
|
2243
|
+
error: {
|
|
2244
|
+
message: "Service temporarily unavailable: all upstream accounts are inactive",
|
|
2245
|
+
type: "service_unavailable",
|
|
2246
|
+
code: "ALL_ACCOUNTS_INACTIVE",
|
|
2247
|
+
},
|
|
2248
|
+
}),
|
|
2249
|
+
{ status: 503, headers: { "Content-Type": "application/json" } }
|
|
2250
|
+
);
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
const status = lastStatus;
|
|
2254
|
+
const msg = lastError || "All combo models unavailable";
|
|
2255
|
+
|
|
2256
|
+
if (earliestRetryAfter) {
|
|
2257
|
+
// Quota-share cooldown-aware retry: instead of crystallizing the 429,
|
|
2258
|
+
// wait out a SHORT transient cooldown and re-run the whole set loop.
|
|
2259
|
+
// Guarded by the helper (quota_exhausted/auth/not-found excluded,
|
|
2260
|
+
// ceiling, attempts, budget). MAX_GLOBAL_ATTEMPTS still bounds total
|
|
2261
|
+
// dispatches.
|
|
2262
|
+
if (comboCooldownWaitEnabled && status === 429) {
|
|
2263
|
+
const decision = resolveComboCooldownWaitDecision({
|
|
2264
|
+
targets: orderedTargets,
|
|
2265
|
+
earliestRetryAfter,
|
|
2266
|
+
attempt: comboCooldownAttempt,
|
|
2267
|
+
budgetLeftMs: comboCooldownBudgetLeftMs,
|
|
2268
|
+
settings: resilienceSettings.comboCooldownWait,
|
|
2269
|
+
lookupLock: (provider, connectionId) => {
|
|
2270
|
+
const rawModel = parseModel(orderedTargets[0]?.modelStr ?? "").model || "";
|
|
2271
|
+
return getModelLockoutInfo(provider, connectionId, rawModel);
|
|
2272
|
+
},
|
|
2273
|
+
computeWaitMs: (retryAfter) => computeClosestRetryAfter(retryAfter).waitMs,
|
|
2274
|
+
});
|
|
2275
|
+
if (decision.wait) {
|
|
2276
|
+
log.info(
|
|
2277
|
+
"COMBO",
|
|
2278
|
+
`Quota-share cooldown wait: ${msg} — waiting ${Math.ceil(
|
|
2279
|
+
decision.waitMs / 1000
|
|
2280
|
+
)}s (reason=${decision.reason ?? "?"}) then retrying (attempt ${
|
|
2281
|
+
comboCooldownAttempt + 1
|
|
2282
|
+
}/${resilienceSettings.comboCooldownWait.maxAttempts})`
|
|
2283
|
+
);
|
|
2284
|
+
const completed = await waitForCooldownAwareRetry(decision.waitMs, signal);
|
|
2285
|
+
if (!completed) {
|
|
2286
|
+
log.info("COMBO", "Quota-share cooldown wait aborted by client disconnect");
|
|
2287
|
+
return errorResponse(499, "Request aborted");
|
|
2288
|
+
}
|
|
2289
|
+
comboCooldownAttempt += 1;
|
|
2290
|
+
comboCooldownBudgetLeftMs = Math.max(0, comboCooldownBudgetLeftMs - decision.waitMs);
|
|
2291
|
+
return dispatchWithCooldownRetry();
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
const retryHuman = formatRetryAfter(toRetryAfterDisplayValue(earliestRetryAfter));
|
|
2295
|
+
log.warn("COMBO", `All models failed | ${msg} (${retryHuman})`);
|
|
2296
|
+
return unavailableResponse(status, msg, earliestRetryAfter, retryHuman);
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
log.warn("COMBO", `All models failed | ${msg}`);
|
|
2300
|
+
return new Response(JSON.stringify({ error: { message: msg } }), {
|
|
2301
|
+
status,
|
|
2302
|
+
headers: { "Content-Type": "application/json" },
|
|
2303
|
+
});
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
return errorResponse(503, "Combo routing completed without an upstream response");
|
|
2307
|
+
};
|
|
2308
|
+
|
|
2309
|
+
// FASE 2.1: acquire the per-connection concurrency slot for the selected
|
|
2310
|
+
// quota-share target once, around the whole dispatch (including any
|
|
2311
|
+
// cooldown-aware re-dispatch), so concurrent requests to one subscription
|
|
2312
|
+
// account are serialized through the connection's max_concurrent ceiling. The
|
|
2313
|
+
// cap is read fresh from the selected connection; a null cap (no limit) or a
|
|
2314
|
+
// saturated queue is a no-op (fail-open). Released in the finally below.
|
|
2315
|
+
let quotaShareConcurrencyRelease: (() => void) | null = null;
|
|
2316
|
+
const qsConnectionId = orderedTargets[0]?.connectionId;
|
|
2317
|
+
if (quotaShareConcurrencyEnabled && qsConnectionId) {
|
|
2318
|
+
const qsCap = await lookupPositiveCap(qsConnectionId);
|
|
2319
|
+
quotaShareConcurrencyRelease = await acquireQuotaShareConcurrencySlot(
|
|
2320
|
+
orderedTargets[0],
|
|
2321
|
+
qsCap,
|
|
2322
|
+
{
|
|
2323
|
+
queueTimeoutMs: config.queueTimeoutMs ?? 30000,
|
|
2324
|
+
maxQueueSize: resolveComboQueueDepth(config),
|
|
2325
|
+
},
|
|
2326
|
+
log
|
|
2327
|
+
);
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
try {
|
|
2331
|
+
return await dispatchWithCooldownRetry();
|
|
2332
|
+
} finally {
|
|
2333
|
+
quotaShareConcurrencyRelease?.();
|
|
2334
|
+
// G2: Clean up candidate registry to prevent unbounded memory growth.
|
|
2335
|
+
_unregisterExecutionCandidates(_registeredExecutionKeys);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
/**
|
|
2340
|
+
* Handle round-robin combo: each request goes to the next model in circular order.
|
|
2341
|
+
* Uses semaphore-based concurrency control with queue + rate-limit awareness.
|
|
2342
|
+
*
|
|
2343
|
+
* Flow:
|
|
2344
|
+
* 1. Pick target model via atomic counter (counter % models.length)
|
|
2345
|
+
* 2. Acquire semaphore slot (may queue if at max concurrency)
|
|
2346
|
+
* 3. Send request to target model
|
|
2347
|
+
* 4. On 429 → mark model rate-limited, try next model in rotation
|
|
2348
|
+
* 5. On semaphore timeout → fallback to next available model
|
|
2349
|
+
*/
|
|
2350
|
+
async function handleRoundRobinCombo({
|
|
2351
|
+
body,
|
|
2352
|
+
combo,
|
|
2353
|
+
handleSingleModel,
|
|
2354
|
+
isModelAvailable,
|
|
2355
|
+
log,
|
|
2356
|
+
settings,
|
|
2357
|
+
allCombos,
|
|
2358
|
+
signal,
|
|
2359
|
+
}: HandleRoundRobinOptions): Promise<Response> {
|
|
2360
|
+
const config = settings
|
|
2361
|
+
? resolveComboConfig(combo, settings)
|
|
2362
|
+
: { ...getDefaultComboConfig(), ...(combo.config || {}) };
|
|
2363
|
+
const concurrency = config.concurrencyPerModel ?? 3;
|
|
2364
|
+
// Honor each target connection's own maxConcurrent ceiling (cached per dispatch)
|
|
2365
|
+
// so a low-concurrency subscription account is not flooded; falls back to the
|
|
2366
|
+
// combo-level concurrency when the connection has no positive cap.
|
|
2367
|
+
const resolveTargetConcurrency = makeConnectionConcurrencyResolver(concurrency);
|
|
2368
|
+
const queueTimeout = config.queueTimeoutMs ?? 30000;
|
|
2369
|
+
// #3872: pre-cascade queue depth — lower values fail over to the next combo member
|
|
2370
|
+
// sooner under concurrency saturation (0 = never queue). Default 20 (backward-compat).
|
|
2371
|
+
const queueDepth = resolveComboQueueDepth(config);
|
|
2372
|
+
const maxRetries = config.maxRetries ?? 1;
|
|
2373
|
+
const retryDelayMs = resolveDelayMs(config.retryDelayMs, 2000);
|
|
2374
|
+
const fallbackDelayMs = resolveDelayMs(config.fallbackDelayMs, 0);
|
|
2375
|
+
const reasoningTokenBufferEnabled = config.reasoningTokenBufferEnabled !== false;
|
|
2376
|
+
|
|
2377
|
+
const resilienceSettings: ResilienceSettings = settings
|
|
2378
|
+
? resolveResilienceSettings(settings)
|
|
2379
|
+
: resolveResilienceSettings(null);
|
|
2380
|
+
|
|
2381
|
+
// #2562: Expand provider-wildcard steps before resolving targets.
|
|
2382
|
+
const rrExpandedCombo = await expandProviderWildcardsInCombo(combo);
|
|
2383
|
+
const rrExpandedAllCombos = allCombos
|
|
2384
|
+
? Array.isArray(allCombos)
|
|
2385
|
+
? await expandProviderWildcardsInCollection(allCombos as ComboLike[])
|
|
2386
|
+
: {
|
|
2387
|
+
...allCombos,
|
|
2388
|
+
combos: await expandProviderWildcardsInCollection(
|
|
2389
|
+
((allCombos as { combos?: ComboLike[] }).combos || []) as ComboLike[]
|
|
2390
|
+
),
|
|
2391
|
+
}
|
|
2392
|
+
: allCombos;
|
|
2393
|
+
|
|
2394
|
+
const orderedTargets = resolveComboTargets(
|
|
2395
|
+
rrExpandedCombo,
|
|
2396
|
+
rrExpandedAllCombos,
|
|
2397
|
+
clampComboDepth(config.maxComboDepth)
|
|
2398
|
+
);
|
|
2399
|
+
const tagFilteredTargets = await applyRequestTagRouting(orderedTargets, body, log);
|
|
2400
|
+
const evalRankedTargets = orderTargetsByEvalScores(tagFilteredTargets, config.evalRouting, log);
|
|
2401
|
+
const filteredTargets = filterTargetsByRequestCompatibility(
|
|
2402
|
+
evalRankedTargets,
|
|
2403
|
+
body,
|
|
2404
|
+
log,
|
|
2405
|
+
"Context-aware round-robin fallback"
|
|
2406
|
+
);
|
|
2407
|
+
// #6238: keep the targets the compat pre-filter rejected so they can serve as a
|
|
2408
|
+
// last-resort fallback tier. The pre-filter drops request-incompatible targets
|
|
2409
|
+
// BEFORE availability is known; if every compat-kept target then turns out to be
|
|
2410
|
+
// runtime-unavailable, we must reconsider these before returning 503, instead of
|
|
2411
|
+
// permanently dropping a compat-rejected-but-healthy provider.
|
|
2412
|
+
const compatKeptSet = new Set(filteredTargets);
|
|
2413
|
+
const compatRejectedTargets = evalRankedTargets.filter(
|
|
2414
|
+
(target) => !compatKeptSet.has(target)
|
|
2415
|
+
);
|
|
2416
|
+
const modelCount = filteredTargets.length;
|
|
2417
|
+
if (modelCount === 0) {
|
|
2418
|
+
return comboModelNotFoundResponse("Round-robin combo has no executable targets");
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
scheduleShadowRouting(
|
|
2422
|
+
combo,
|
|
2423
|
+
config,
|
|
2424
|
+
body,
|
|
2425
|
+
resolveShadowTargets(combo, config, allCombos),
|
|
2426
|
+
handleSingleModel,
|
|
2427
|
+
isModelAvailable,
|
|
2428
|
+
"round-robin",
|
|
2429
|
+
log
|
|
2430
|
+
);
|
|
2431
|
+
|
|
2432
|
+
// Sticky batch size at the combo level. A per-combo `stickyRoundRobinLimit` (in
|
|
2433
|
+
// combo.config, resolved through the cascade) overrides the global setting so one
|
|
2434
|
+
// combo can batch differently from the default. When the per-combo value is unset,
|
|
2435
|
+
// fall back to the global `stickyRoundRobinLimit` so the existing knob still controls
|
|
2436
|
+
// sticky batching for both account fallback and combo targets. Values <= 1 preserve
|
|
2437
|
+
// the historical one-request-per-target rotation.
|
|
2438
|
+
const perComboStickyLimit = (config as Record<string, unknown>).stickyRoundRobinLimit;
|
|
2439
|
+
const stickyLimit = clampStickyRoundRobinTargetLimit(
|
|
2440
|
+
perComboStickyLimit !== undefined && perComboStickyLimit !== null
|
|
2441
|
+
? perComboStickyLimit
|
|
2442
|
+
: (settings as Record<string, unknown> | null)?.stickyRoundRobinLimit
|
|
2443
|
+
);
|
|
2444
|
+
const stickyRoundRobinEnabled = stickyLimit > 1;
|
|
2445
|
+
// Exhaustion-aware sticky: if the currently sticky target is no longer
|
|
2446
|
+
// available (circuit breaker OPEN, provider cooldown, model lockout, or
|
|
2447
|
+
// isModelAvailable returns false), clear the sticky record so the rotation
|
|
2448
|
+
// starts at the counter position instead of probing a dead target.
|
|
2449
|
+
if (stickyRoundRobinEnabled) {
|
|
2450
|
+
const sticky = rrStickyTargets.get(combo.name);
|
|
2451
|
+
if (sticky) {
|
|
2452
|
+
const stickyTarget = filteredTargets.find(
|
|
2453
|
+
(target) => target.executionKey === sticky.executionKey
|
|
2454
|
+
);
|
|
2455
|
+
if (stickyTarget) {
|
|
2456
|
+
const rawModel = parseModel(stickyTarget.modelStr).model || stickyTarget.modelStr;
|
|
2457
|
+
const stickyAvailable =
|
|
2458
|
+
(!stickyTarget.provider ||
|
|
2459
|
+
getCircuitBreaker(stickyTarget.provider).getStatus().state !== "OPEN") &&
|
|
2460
|
+
!(
|
|
2461
|
+
resilienceSettings.providerCooldown.enabled &&
|
|
2462
|
+
Boolean(stickyTarget.provider && stickyTarget.provider !== "unknown") &&
|
|
2463
|
+
isProviderInCooldown(
|
|
2464
|
+
stickyTarget.provider,
|
|
2465
|
+
stickyTarget.connectionId ?? undefined,
|
|
2466
|
+
resilienceSettings
|
|
2467
|
+
)
|
|
2468
|
+
) &&
|
|
2469
|
+
!(
|
|
2470
|
+
stickyTarget.provider &&
|
|
2471
|
+
rawModel &&
|
|
2472
|
+
isModelLocked(stickyTarget.provider, stickyTarget.connectionId || "", rawModel)
|
|
2473
|
+
) &&
|
|
2474
|
+
(isModelAvailable ? await isModelAvailable(stickyTarget.modelStr, stickyTarget) : true);
|
|
2475
|
+
if (!stickyAvailable) {
|
|
2476
|
+
log.info(
|
|
2477
|
+
"COMBO-RR",
|
|
2478
|
+
`Clearing stale sticky target ${stickyTarget.modelStr} — unavailable`
|
|
2479
|
+
);
|
|
2480
|
+
rrStickyTargets.delete(combo.name);
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
if (
|
|
2486
|
+
!rrCounters.has(combo.name) &&
|
|
2487
|
+
!rrStickyTargets.has(combo.name) &&
|
|
2488
|
+
rrCounters.size >= MAX_RR_COUNTERS
|
|
2489
|
+
) {
|
|
2490
|
+
const oldest = rrCounters.keys().next().value;
|
|
2491
|
+
if (oldest !== undefined) {
|
|
2492
|
+
rrCounters.delete(oldest);
|
|
2493
|
+
rrStickyTargets.delete(oldest);
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
// Ensure rrCounters has an entry for this combo so the eviction logic above
|
|
2497
|
+
// applies to both maps even when sticky round-robin is enabled (in which
|
|
2498
|
+
// case rrCounters isn't incremented per request).
|
|
2499
|
+
if (!rrCounters.has(combo.name)) {
|
|
2500
|
+
rrCounters.set(combo.name, 0);
|
|
2501
|
+
}
|
|
2502
|
+
const { startIndex, counter } = getStickyRoundRobinStartIndex(
|
|
2503
|
+
combo.name,
|
|
2504
|
+
filteredTargets,
|
|
2505
|
+
stickyLimit
|
|
2506
|
+
);
|
|
2507
|
+
if (!stickyRoundRobinEnabled) {
|
|
2508
|
+
rrCounters.set(combo.name, counter + 1);
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
// #3825: per-conversation session stickiness for round-robin. weighted/priority honor a
|
|
2512
|
+
// sticky connection via applySessionStickiness, but this RR handler returns before that
|
|
2513
|
+
// call — so sessionless RR combos rotated every turn, busting the upstream prompt-cache.
|
|
2514
|
+
// Reuse the SAME mechanism: start the rotation at the conversation's sticky connection
|
|
2515
|
+
// (the loop still falls through to the other targets on failure → failover preserved).
|
|
2516
|
+
// #6168: honor the session-stickiness opt-out here too, otherwise round-robin would
|
|
2517
|
+
// still pin the conversation even when the flag is set. Per-combo `config` overrides
|
|
2518
|
+
// the global `settings.disableSessionStickiness` fallback (default false).
|
|
2519
|
+
const disableSessionStickiness = resolveDisableSessionStickiness(
|
|
2520
|
+
config as Record<string, unknown> | null | undefined,
|
|
2521
|
+
settings as Record<string, unknown> | null | undefined
|
|
2522
|
+
);
|
|
2523
|
+
const _rrSessionSticky = disableSessionStickiness
|
|
2524
|
+
? ({ targets: filteredTargets, messageHash: null, stuck: false } as const)
|
|
2525
|
+
: await applySessionStickiness(
|
|
2526
|
+
filteredTargets,
|
|
2527
|
+
body?.messages as Array<{ role?: string; content?: unknown }>
|
|
2528
|
+
);
|
|
2529
|
+
let rrStartIndex = startIndex;
|
|
2530
|
+
if (_rrSessionSticky.stuck) {
|
|
2531
|
+
const stickyIdx = filteredTargets.findIndex(
|
|
2532
|
+
(t) => t.connectionId === _rrSessionSticky.targets[0]?.connectionId
|
|
2533
|
+
);
|
|
2534
|
+
if (stickyIdx >= 0) rrStartIndex = stickyIdx;
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
const clientRequestedStream = body?.stream === true;
|
|
2538
|
+
const startTime = Date.now();
|
|
2539
|
+
let lastError: string | null = null;
|
|
2540
|
+
let lastStatus: number | null = null;
|
|
2541
|
+
let earliestRetryAfter: ComboRetryAfter | null = null;
|
|
2542
|
+
let globalAttempts = 0;
|
|
2543
|
+
let fallbackCount = 0;
|
|
2544
|
+
let recordedAttempts = 0;
|
|
2545
|
+
|
|
2546
|
+
// #1731: Per-request in-memory set of providers whose quota is fully exhausted.
|
|
2547
|
+
// When a target returns a quota-exhausted 429, remaining targets from the same
|
|
2548
|
+
// provider are skipped to avoid the cascade through N same-provider targets.
|
|
2549
|
+
const exhaustedProviders = new Set<string>();
|
|
2550
|
+
const exhaustedConnections = new Set<string>();
|
|
2551
|
+
const transientRateLimitedProviders = new Set<string>();
|
|
2552
|
+
|
|
2553
|
+
// Try each model starting from the round-robin target
|
|
2554
|
+
for (let offset = 0; offset < modelCount; offset++) {
|
|
2555
|
+
const modelIndex = (rrStartIndex + offset) % modelCount;
|
|
2556
|
+
const target = filteredTargets[modelIndex];
|
|
2557
|
+
const modelStr = target.modelStr;
|
|
2558
|
+
const provider = target.provider;
|
|
2559
|
+
const profile = await getRuntimeProviderProfile(provider);
|
|
2560
|
+
const semaphoreKey = `combo:${combo.name}:${target.executionKey}`;
|
|
2561
|
+
const allowRateLimitedConnection =
|
|
2562
|
+
Boolean(provider && provider !== "unknown") && transientRateLimitedProviders.has(provider);
|
|
2563
|
+
const targetForAttempt = allowRateLimitedConnection
|
|
2564
|
+
? { ...target, allowRateLimitedConnection: true }
|
|
2565
|
+
: target;
|
|
2566
|
+
|
|
2567
|
+
// Pre-check availability
|
|
2568
|
+
if (isModelAvailable) {
|
|
2569
|
+
const available = await isModelAvailable(modelStr, targetForAttempt);
|
|
2570
|
+
if (!available) {
|
|
2571
|
+
log.debug?.(
|
|
2572
|
+
"COMBO-RR",
|
|
2573
|
+
`Skipping ${modelStr} — no credentials available or model excluded`
|
|
2574
|
+
);
|
|
2575
|
+
if (offset > 0) fallbackCount++;
|
|
2576
|
+
continue;
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
if (
|
|
2581
|
+
resilienceSettings.providerCooldown.enabled &&
|
|
2582
|
+
Boolean(provider && provider !== "unknown") &&
|
|
2583
|
+
isProviderInCooldown(provider, target.connectionId as string | undefined, resilienceSettings)
|
|
2584
|
+
) {
|
|
2585
|
+
log.info("COMBO-RR", `Skipping ${modelStr} — provider ${provider} in global cooldown`);
|
|
2586
|
+
if (offset > 0) fallbackCount++;
|
|
2587
|
+
continue;
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
// #1731 / #1731v2: skip targets already known-exhausted this request (shared predicate).
|
|
2591
|
+
const exhaustedSkip = getExhaustedTargetSkipReason(
|
|
2592
|
+
target,
|
|
2593
|
+
exhaustedProviders,
|
|
2594
|
+
exhaustedConnections
|
|
2595
|
+
);
|
|
2596
|
+
if (exhaustedSkip) {
|
|
2597
|
+
log.info("COMBO-RR", exhaustedSkip);
|
|
2598
|
+
if (offset > 0) fallbackCount++;
|
|
2599
|
+
continue;
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
// Acquire semaphore slot (may wait in queue). Honor the connection's own
|
|
2603
|
+
// maxConcurrent cap when set; else fall back to the combo-level concurrency.
|
|
2604
|
+
const targetConcurrency = await resolveTargetConcurrency(target.connectionId);
|
|
2605
|
+
let release: () => void;
|
|
2606
|
+
try {
|
|
2607
|
+
release = await semaphore.acquire(semaphoreKey, {
|
|
2608
|
+
maxConcurrency: targetConcurrency,
|
|
2609
|
+
timeoutMs: queueTimeout,
|
|
2610
|
+
maxQueueSize: queueDepth,
|
|
2611
|
+
});
|
|
2612
|
+
} catch (err) {
|
|
2613
|
+
const errCode = isRecord(err) && typeof err.code === "string" ? err.code : null;
|
|
2614
|
+
if (errCode === "SEMAPHORE_TIMEOUT" || errCode === "SEMAPHORE_QUEUE_FULL") {
|
|
2615
|
+
log.warn(
|
|
2616
|
+
"COMBO-RR",
|
|
2617
|
+
`Semaphore ${errCode === "SEMAPHORE_QUEUE_FULL" ? "queue full" : "timeout"} for ${modelStr}, trying next model`
|
|
2618
|
+
);
|
|
2619
|
+
if (offset > 0) fallbackCount++;
|
|
2620
|
+
continue;
|
|
2621
|
+
}
|
|
2622
|
+
throw err;
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
// Retry loop within this model
|
|
2626
|
+
try {
|
|
2627
|
+
for (let retry = 0; retry <= maxRetries; retry++) {
|
|
2628
|
+
globalAttempts++;
|
|
2629
|
+
if (globalAttempts > MAX_GLOBAL_ATTEMPTS) {
|
|
2630
|
+
log.warn(
|
|
2631
|
+
"COMBO-RR",
|
|
2632
|
+
`Maximum combo attempts (${MAX_GLOBAL_ATTEMPTS}) exceeded. Terminating loop to prevent runaway requests.`
|
|
2633
|
+
);
|
|
2634
|
+
return errorResponse(503, "Maximum combo retry limit reached");
|
|
2635
|
+
}
|
|
2636
|
+
if (retry > 0) {
|
|
2637
|
+
log.info(
|
|
2638
|
+
"COMBO-RR",
|
|
2639
|
+
`Retrying ${modelStr} in ${retryDelayMs}ms (attempt ${retry + 1}/${maxRetries + 1})`
|
|
2640
|
+
);
|
|
2641
|
+
await new Promise((r) => setTimeout(r, retryDelayMs));
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
log.info(
|
|
2645
|
+
"COMBO-RR",
|
|
2646
|
+
`[RR #${counter}] → ${modelStr}${offset > 0 ? ` (fallback +${offset})` : ""}${retry > 0 ? ` (retry ${retry})` : ""}`
|
|
2647
|
+
);
|
|
2648
|
+
|
|
2649
|
+
// Issue #3587: Reasoning models can spend the whole output budget on
|
|
2650
|
+
// reasoning. Apply any safe buffer to a per-attempt copy so round-robin
|
|
2651
|
+
// retries never compound across models.
|
|
2652
|
+
let attemptBody = body;
|
|
2653
|
+
{
|
|
2654
|
+
const bodyRecord = body as Record<string, unknown>;
|
|
2655
|
+
const currentMaxTokens = toPositiveInteger(bodyRecord.max_tokens);
|
|
2656
|
+
const bufferedMaxTokens = resolveReasoningBufferedMaxTokens(
|
|
2657
|
+
modelStr,
|
|
2658
|
+
bodyRecord.max_tokens,
|
|
2659
|
+
{ enabled: reasoningTokenBufferEnabled }
|
|
2660
|
+
);
|
|
2661
|
+
if (
|
|
2662
|
+
currentMaxTokens !== null &&
|
|
2663
|
+
bufferedMaxTokens !== null &&
|
|
2664
|
+
bufferedMaxTokens !== currentMaxTokens
|
|
2665
|
+
) {
|
|
2666
|
+
attemptBody = {
|
|
2667
|
+
...bodyRecord,
|
|
2668
|
+
max_tokens: bufferedMaxTokens,
|
|
2669
|
+
} as typeof body;
|
|
2670
|
+
log.info(
|
|
2671
|
+
"COMBO-RR",
|
|
2672
|
+
`Reasoning model ${modelStr}: adjusted max_tokens ${currentMaxTokens} -> ${bufferedMaxTokens}`
|
|
2673
|
+
);
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
const result = await handleSingleModel(attemptBody, modelStr, {
|
|
2678
|
+
...targetForAttempt,
|
|
2679
|
+
effectiveComboStrategy: "round-robin",
|
|
2680
|
+
failoverBeforeRetry: config.failoverBeforeRetry,
|
|
2681
|
+
});
|
|
2682
|
+
|
|
2683
|
+
// Success — validate response quality before returning
|
|
2684
|
+
if (result.ok) {
|
|
2685
|
+
let rrClone: Response;
|
|
2686
|
+
try {
|
|
2687
|
+
rrClone = result.clone();
|
|
2688
|
+
} catch {
|
|
2689
|
+
rrClone = result;
|
|
2690
|
+
}
|
|
2691
|
+
const quality = await validateResponseQuality(
|
|
2692
|
+
rrClone,
|
|
2693
|
+
clientRequestedStream,
|
|
2694
|
+
log,
|
|
2695
|
+
config.responseValidation
|
|
2696
|
+
);
|
|
2697
|
+
releaseQualityClone(rrClone, result, quality);
|
|
2698
|
+
if (!quality.valid) {
|
|
2699
|
+
log.warn(
|
|
2700
|
+
"COMBO-RR",
|
|
2701
|
+
`${modelStr} returned 200 but failed quality check: ${quality.reason}`
|
|
2702
|
+
);
|
|
2703
|
+
recordComboRequest(combo.name, modelStr, {
|
|
2704
|
+
success: false,
|
|
2705
|
+
latencyMs: Date.now() - startTime,
|
|
2706
|
+
fallbackCount,
|
|
2707
|
+
strategy: "round-robin",
|
|
2708
|
+
target: toRecordedTarget(target),
|
|
2709
|
+
});
|
|
2710
|
+
recordedAttempts++;
|
|
2711
|
+
// Fix #1707: Set terminal state so the fallback doesn't emit
|
|
2712
|
+
// misleading ALL_ACCOUNTS_INACTIVE when the real issue is quality.
|
|
2713
|
+
lastError = `Upstream response failed quality validation: ${quality.reason}`;
|
|
2714
|
+
if (!lastStatus) lastStatus = 502;
|
|
2715
|
+
if (offset > 0) fallbackCount++;
|
|
2716
|
+
break; // move to next model
|
|
2717
|
+
}
|
|
2718
|
+
const latencyMs = Date.now() - startTime;
|
|
2719
|
+
log.info(
|
|
2720
|
+
"COMBO-RR",
|
|
2721
|
+
`${modelStr} succeeded (${latencyMs}ms, ${fallbackCount} fallbacks)`
|
|
2722
|
+
);
|
|
2723
|
+
recordComboRequest(combo.name, modelStr, {
|
|
2724
|
+
success: true,
|
|
2725
|
+
latencyMs,
|
|
2726
|
+
fallbackCount,
|
|
2727
|
+
strategy: "round-robin",
|
|
2728
|
+
target: toRecordedTarget(target),
|
|
2729
|
+
});
|
|
2730
|
+
recordedAttempts++;
|
|
2731
|
+
|
|
2732
|
+
const selectedConnectionId =
|
|
2733
|
+
result.headers?.get("X-OmniRoute-Selected-Connection-Id") ||
|
|
2734
|
+
result.headers?.get("x-omniroute-selected-connection-id") ||
|
|
2735
|
+
undefined;
|
|
2736
|
+
const effectiveConnectionId = selectedConnectionId || target.connectionId || "";
|
|
2737
|
+
|
|
2738
|
+
const rawModel = parseModel(modelStr).model || modelStr;
|
|
2739
|
+
if (provider && rawModel) {
|
|
2740
|
+
const dcResult = decayModelFailureCount(provider, effectiveConnectionId, rawModel);
|
|
2741
|
+
if (dcResult.cleared) {
|
|
2742
|
+
log.info("COMBO-RR", `Model ${modelStr} fully recovered — lockout cleared`);
|
|
2743
|
+
} else if (dcResult.newFailureCount > 0) {
|
|
2744
|
+
log.debug?.(
|
|
2745
|
+
"COMBO-RR",
|
|
2746
|
+
`Model ${modelStr} decayed to failureCount=${dcResult.newFailureCount}`
|
|
2747
|
+
);
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2751
|
+
if (provider && provider !== "unknown") {
|
|
2752
|
+
recordProviderSuccess(provider, effectiveConnectionId || undefined);
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
if (stickyRoundRobinEnabled) {
|
|
2756
|
+
recordStickyRoundRobinSuccess(combo.name, target, stickyLimit, filteredTargets);
|
|
2757
|
+
} else {
|
|
2758
|
+
// #948: true round-robin (stickyLimit <= 1). The counter was advanced
|
|
2759
|
+
// eagerly (+1 from the scheduled start index) before this loop ran, so
|
|
2760
|
+
// when the scheduled model failed and a *different* model served via
|
|
2761
|
+
// fallback, the next request reused the fallback-served model. Advance
|
|
2762
|
+
// the pointer past the model that ACTUALLY served (modelIndex) instead,
|
|
2763
|
+
// mirroring recordStickyRoundRobinSuccess's served-index logic. Read
|
|
2764
|
+
// side applies `% modelCount`, so storing modelIndex + 1 is correct.
|
|
2765
|
+
rrCounters.set(combo.name, modelIndex + 1);
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2768
|
+
// #3825: (re)record the sticky binding so the next turn re-pins (prompt-cache).
|
|
2769
|
+
if (_rrSessionSticky.messageHash) {
|
|
2770
|
+
const stickyConn = effectiveConnectionId || target.connectionId;
|
|
2771
|
+
if (stickyConn) recordStickyBinding(_rrSessionSticky.messageHash, stickyConn);
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
if (provider) {
|
|
2775
|
+
const connId = effectiveConnectionId || undefined;
|
|
2776
|
+
void (async () => {
|
|
2777
|
+
try {
|
|
2778
|
+
const { setLKGP } = await import("../../src/lib/localDb");
|
|
2779
|
+
await Promise.all([
|
|
2780
|
+
setLKGP(combo.name, target.executionKey, provider, connId),
|
|
2781
|
+
setLKGP(combo.name, combo.id || combo.name, provider, connId),
|
|
2782
|
+
]);
|
|
2783
|
+
} catch (err) {
|
|
2784
|
+
log.warn(
|
|
2785
|
+
"COMBO-RR",
|
|
2786
|
+
"Failed to record Last Known Good Provider. This is non-fatal.",
|
|
2787
|
+
{
|
|
2788
|
+
err,
|
|
2789
|
+
}
|
|
2790
|
+
);
|
|
2791
|
+
}
|
|
2792
|
+
})();
|
|
2793
|
+
}
|
|
2794
|
+
// Clone is consumed by quality check; original stays unlocked.
|
|
2795
|
+
return result;
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
// Extract error info
|
|
2799
|
+
let errorText = result.statusText || "";
|
|
2800
|
+
let retryAfter: ComboRetryAfter | null = null;
|
|
2801
|
+
let errorBody: ComboErrorBody = null;
|
|
2802
|
+
try {
|
|
2803
|
+
const cloned = result.clone();
|
|
2804
|
+
try {
|
|
2805
|
+
const text = await cloned.text();
|
|
2806
|
+
if (text) {
|
|
2807
|
+
errorText = text.substring(0, 500);
|
|
2808
|
+
errorBody = JSON.parse(text);
|
|
2809
|
+
const parsedError = errorBody?.error;
|
|
2810
|
+
errorText =
|
|
2811
|
+
(typeof parsedError === "object" && parsedError?.message) ||
|
|
2812
|
+
(typeof parsedError === "string" ? parsedError : null) ||
|
|
2813
|
+
errorBody?.message ||
|
|
2814
|
+
errorText;
|
|
2815
|
+
retryAfter = errorBody?.retryAfter || null;
|
|
2816
|
+
}
|
|
2817
|
+
} catch {
|
|
2818
|
+
/* Clone parse failed */
|
|
2819
|
+
}
|
|
2820
|
+
} catch {
|
|
2821
|
+
/* Clone failed */
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
if (result.status === 499) {
|
|
2825
|
+
log.info(
|
|
2826
|
+
"COMBO-RR",
|
|
2827
|
+
`Client disconnected (499) during ${modelStr} — stopping combo loop`
|
|
2828
|
+
);
|
|
2829
|
+
recordComboRequest(combo.name, modelStr, {
|
|
2830
|
+
success: false,
|
|
2831
|
+
latencyMs: Date.now() - startTime,
|
|
2832
|
+
fallbackCount,
|
|
2833
|
+
strategy: "round-robin",
|
|
2834
|
+
target: toRecordedTarget(target),
|
|
2835
|
+
});
|
|
2836
|
+
recordedAttempts++;
|
|
2837
|
+
return result;
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
if (
|
|
2841
|
+
retryAfter &&
|
|
2842
|
+
(!earliestRetryAfter || new Date(retryAfter) < new Date(earliestRetryAfter))
|
|
2843
|
+
) {
|
|
2844
|
+
earliestRetryAfter = retryAfter;
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
if (typeof errorText !== "string") {
|
|
2848
|
+
try {
|
|
2849
|
+
errorText = JSON.stringify(errorText);
|
|
2850
|
+
} catch {
|
|
2851
|
+
errorText = String(errorText);
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
const isStreamReadinessFailure =
|
|
2856
|
+
(result.status === 502 || result.status === 504) &&
|
|
2857
|
+
isStreamReadinessFailureErrorBody(errorBody);
|
|
2858
|
+
|
|
2859
|
+
// FIX 5: a local per-API-key token-limit 429 must not cool shared accounts.
|
|
2860
|
+
const isTokenLimitBreach = result.status === 429 && isTokenLimitBreachErrorBody(errorBody);
|
|
2861
|
+
|
|
2862
|
+
// Round-robin uses the same target-level fallback rule as other combo
|
|
2863
|
+
// strategies: non-ok target responses fall through to the next target.
|
|
2864
|
+
// Classification stays here only to support cooldown/semaphore pacing,
|
|
2865
|
+
// not to decide whether fallback is allowed.
|
|
2866
|
+
const rawError = errorBody?.error;
|
|
2867
|
+
const structuredError =
|
|
2868
|
+
rawError && typeof rawError === "object"
|
|
2869
|
+
? {
|
|
2870
|
+
// Upstream JSON may carry a numeric `code`/`type` (e.g. {"code":40001}).
|
|
2871
|
+
// Coerce to string if present instead of discarding, so downstream string
|
|
2872
|
+
// ops (.toLowerCase, .startsWith) can run safely without type crashes.
|
|
2873
|
+
code:
|
|
2874
|
+
(rawError as Record<string, unknown>).code !== undefined &&
|
|
2875
|
+
(rawError as Record<string, unknown>).code !== null
|
|
2876
|
+
? String((rawError as Record<string, unknown>).code)
|
|
2877
|
+
: undefined,
|
|
2878
|
+
type:
|
|
2879
|
+
(rawError as Record<string, unknown>).type !== undefined &&
|
|
2880
|
+
(rawError as Record<string, unknown>).type !== null
|
|
2881
|
+
? String((rawError as Record<string, unknown>).type)
|
|
2882
|
+
: undefined,
|
|
2883
|
+
}
|
|
2884
|
+
: undefined;
|
|
2885
|
+
const fallbackResult = checkFallbackError(
|
|
2886
|
+
result.status,
|
|
2887
|
+
errorText,
|
|
2888
|
+
0,
|
|
2889
|
+
null,
|
|
2890
|
+
provider,
|
|
2891
|
+
result.headers,
|
|
2892
|
+
profile,
|
|
2893
|
+
structuredError
|
|
2894
|
+
);
|
|
2895
|
+
const { cooldownMs } = fallbackResult;
|
|
2896
|
+
const selectedConnectionId =
|
|
2897
|
+
result.headers?.get("X-OmniRoute-Selected-Connection-Id") ||
|
|
2898
|
+
result.headers?.get("x-omniroute-selected-connection-id") ||
|
|
2899
|
+
undefined;
|
|
2900
|
+
const targetWithConnection = selectedConnectionId
|
|
2901
|
+
? { ...target, connectionId: selectedConnectionId }
|
|
2902
|
+
: target;
|
|
2903
|
+
|
|
2904
|
+
const isAllAccountsRateLimited = isAllAccountsRateLimitedResponse(
|
|
2905
|
+
result.status,
|
|
2906
|
+
result.headers?.get("content-type") ?? null,
|
|
2907
|
+
errorText
|
|
2908
|
+
);
|
|
2909
|
+
|
|
2910
|
+
// #1731: If the entire provider quota is exhausted, mark it so subsequent
|
|
2911
|
+
// same-provider targets are skipped immediately. API-key 429s still use
|
|
2912
|
+
// the short resilience cooldown, but explicit quota text should stop the
|
|
2913
|
+
// combo from trying another target for the same provider in this request.
|
|
2914
|
+
// #1731 / #1731v2: classify the upstream error and update the exhaustion sets
|
|
2915
|
+
// (shared with handleComboChat). Returns whether the provider is fully exhausted.
|
|
2916
|
+
const providerExhausted = applyComboTargetExhaustion(targetWithConnection, {
|
|
2917
|
+
result,
|
|
2918
|
+
fallbackResult,
|
|
2919
|
+
errorText,
|
|
2920
|
+
rawModel: parseModel(modelStr).model || modelStr,
|
|
2921
|
+
isTokenLimitBreach,
|
|
2922
|
+
allAccountsRateLimited: isAllAccountsRateLimited,
|
|
2923
|
+
sets: { exhaustedProviders, exhaustedConnections, transientRateLimitedProviders },
|
|
2924
|
+
log,
|
|
2925
|
+
tag: "COMBO-RR",
|
|
2926
|
+
exhaustedLogLevel: "debug",
|
|
2927
|
+
structuredError,
|
|
2928
|
+
});
|
|
2929
|
+
|
|
2930
|
+
// Transient errors → mark in semaphore so round-robin stops stampeding this target.
|
|
2931
|
+
if (
|
|
2932
|
+
!isStreamReadinessFailure &&
|
|
2933
|
+
!isTokenLimitBreach &&
|
|
2934
|
+
TRANSIENT_FOR_SEMAPHORE.includes(result.status) &&
|
|
2935
|
+
cooldownMs > 0
|
|
2936
|
+
) {
|
|
2937
|
+
semaphore.markRateLimited(semaphoreKey, cooldownMs);
|
|
2938
|
+
log.warn("COMBO-RR", `${modelStr} error ${result.status}, cooldown ${cooldownMs}ms`);
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
if (isAllAccountsRateLimited) {
|
|
2942
|
+
log.info(
|
|
2943
|
+
"COMBO-RR",
|
|
2944
|
+
`All accounts rate-limited for ${modelStr}, falling back to next model`
|
|
2945
|
+
);
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2948
|
+
// Transient error → retry same model.
|
|
2949
|
+
// A token-limit 429 is terminal for the client — never retry it.
|
|
2950
|
+
const isTransient =
|
|
2951
|
+
!isStreamReadinessFailure &&
|
|
2952
|
+
!isTokenLimitBreach &&
|
|
2953
|
+
[408, 429, 500, 502, 503, 504].includes(result.status);
|
|
2954
|
+
if (retry < maxRetries && isTransient && !providerExhausted) {
|
|
2955
|
+
continue;
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
// Done with this model
|
|
2959
|
+
recordComboRequest(combo.name, modelStr, {
|
|
2960
|
+
success: false,
|
|
2961
|
+
latencyMs: Date.now() - startTime,
|
|
2962
|
+
fallbackCount,
|
|
2963
|
+
strategy: "round-robin",
|
|
2964
|
+
target: toRecordedTarget(target),
|
|
2965
|
+
});
|
|
2966
|
+
recordedAttempts++;
|
|
2967
|
+
lastError = errorText || String(result.status);
|
|
2968
|
+
if (!lastStatus) lastStatus = result.status;
|
|
2969
|
+
if (offset > 0) fallbackCount++;
|
|
2970
|
+
log.warn("COMBO-RR", `${modelStr} failed, trying next model`, { status: result.status });
|
|
2971
|
+
|
|
2972
|
+
if (
|
|
2973
|
+
resilienceSettings.providerCooldown.enabled &&
|
|
2974
|
+
provider &&
|
|
2975
|
+
provider !== "unknown" &&
|
|
2976
|
+
!(result.status === 500 && hasPerModelQuota(provider, parseModel(modelStr).model || modelStr))
|
|
2977
|
+
) {
|
|
2978
|
+
recordProviderCooldown(
|
|
2979
|
+
provider,
|
|
2980
|
+
targetWithConnection.connectionId ?? undefined,
|
|
2981
|
+
resilienceSettings
|
|
2982
|
+
);
|
|
2983
|
+
}
|
|
2984
|
+
|
|
2985
|
+
const fallbackWaitMs =
|
|
2986
|
+
fallbackDelayMs > 0 && cooldownMs > 0 && cooldownMs <= MAX_FALLBACK_WAIT_MS
|
|
2987
|
+
? Math.min(cooldownMs, fallbackDelayMs)
|
|
2988
|
+
: 0;
|
|
2989
|
+
if ([502, 503, 504].includes(result.status) && fallbackWaitMs > 0) {
|
|
2990
|
+
log.debug?.("COMBO-RR", `Waiting ${fallbackWaitMs}ms before fallback to next model`);
|
|
2991
|
+
await new Promise((resolve) => {
|
|
2992
|
+
const timer = setTimeout(resolve, fallbackWaitMs);
|
|
2993
|
+
signal?.addEventListener(
|
|
2994
|
+
"abort",
|
|
2995
|
+
() => {
|
|
2996
|
+
clearTimeout(timer);
|
|
2997
|
+
resolve(undefined);
|
|
2998
|
+
},
|
|
2999
|
+
{ once: true }
|
|
3000
|
+
);
|
|
3001
|
+
});
|
|
3002
|
+
if (signal?.aborted) {
|
|
3003
|
+
log.info("COMBO-RR", `Client disconnected during fallback wait — aborting`);
|
|
3004
|
+
return errorResponse(499, "Client disconnected");
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
|
|
3008
|
+
break;
|
|
3009
|
+
}
|
|
3010
|
+
} finally {
|
|
3011
|
+
// ALWAYS release semaphore slot
|
|
3012
|
+
release();
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
// All models exhausted
|
|
3017
|
+
const latencyMs = Date.now() - startTime;
|
|
3018
|
+
|
|
3019
|
+
// #6238: every compat-kept target was skipped as unavailable and NONE was ever
|
|
3020
|
+
// attempted (recordedAttempts === 0). Before crystallizing 503, probe the targets
|
|
3021
|
+
// the compat pre-filter rejected — a compat-rejected-but-healthy provider is a
|
|
3022
|
+
// valid last-resort fallback tier, not a permanently dropped target.
|
|
3023
|
+
if (recordedAttempts === 0 && compatRejectedTargets.length > 0) {
|
|
3024
|
+
const compatFallbackResult = await attemptCompatRejectedFallback(compatRejectedTargets, body, {
|
|
3025
|
+
handleSingleModel,
|
|
3026
|
+
isModelAvailable,
|
|
3027
|
+
isProviderInCooldown: (target) =>
|
|
3028
|
+
resilienceSettings.providerCooldown.enabled &&
|
|
3029
|
+
Boolean(target.provider && target.provider !== "unknown") &&
|
|
3030
|
+
isProviderInCooldown(
|
|
3031
|
+
target.provider as string,
|
|
3032
|
+
target.connectionId as string | undefined,
|
|
3033
|
+
resilienceSettings
|
|
3034
|
+
),
|
|
3035
|
+
log,
|
|
3036
|
+
strategy: "round-robin",
|
|
3037
|
+
});
|
|
3038
|
+
if (compatFallbackResult) {
|
|
3039
|
+
recordComboRequest(combo.name, null, {
|
|
3040
|
+
success: true,
|
|
3041
|
+
latencyMs: Date.now() - startTime,
|
|
3042
|
+
fallbackCount,
|
|
3043
|
+
strategy: "round-robin",
|
|
3044
|
+
});
|
|
3045
|
+
return compatFallbackResult;
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
|
|
3049
|
+
if (recordedAttempts === 0) {
|
|
3050
|
+
recordComboRequest(combo.name, null, {
|
|
3051
|
+
success: false,
|
|
3052
|
+
latencyMs,
|
|
3053
|
+
fallbackCount,
|
|
3054
|
+
strategy: "round-robin",
|
|
3055
|
+
});
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
if (!lastStatus) {
|
|
3059
|
+
return new Response(
|
|
3060
|
+
JSON.stringify({
|
|
3061
|
+
error: {
|
|
3062
|
+
message: "Service temporarily unavailable: all upstream accounts are inactive",
|
|
3063
|
+
type: "service_unavailable",
|
|
3064
|
+
code: "ALL_ACCOUNTS_INACTIVE",
|
|
3065
|
+
},
|
|
3066
|
+
}),
|
|
3067
|
+
{ status: 503, headers: { "Content-Type": "application/json" } }
|
|
3068
|
+
);
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
const status = lastStatus;
|
|
3072
|
+
const msg = lastError || "All round-robin combo models unavailable";
|
|
3073
|
+
|
|
3074
|
+
if (earliestRetryAfter) {
|
|
3075
|
+
const retryHuman = formatRetryAfter(toRetryAfterDisplayValue(earliestRetryAfter));
|
|
3076
|
+
log.warn("COMBO-RR", `All models failed | ${msg} (${retryHuman})`);
|
|
3077
|
+
return unavailableResponse(status, msg, earliestRetryAfter, retryHuman);
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
log.warn("COMBO-RR", `All models failed | ${msg}`);
|
|
3081
|
+
return new Response(JSON.stringify({ error: { message: msg } }), {
|
|
3082
|
+
status,
|
|
3083
|
+
headers: { "Content-Type": "application/json" },
|
|
3084
|
+
});
|
|
3085
|
+
}
|