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,4452 @@
|
|
|
1
|
+
import { injectMemoryAndSkills } from "./chatCore/memorySkillsInjection.ts";
|
|
2
|
+
import { resolveChatCoreRequestSetup } from "./chatCore/requestSetup.ts";
|
|
3
|
+
import { buildFailureUsageRecord } from "./chatCore/failureUsage.ts";
|
|
4
|
+
import { extractSystemRoleMessages } from "./chatCore/claudeSystemRole.ts";
|
|
5
|
+
export { extractSystemRoleMessages } from "./chatCore/claudeSystemRole.ts";
|
|
6
|
+
import { checkIdempotencyCache } from "./chatCore/idempotency.ts";
|
|
7
|
+
import { checkSemanticCache } from "./chatCore/semanticCache.ts";
|
|
8
|
+
import {
|
|
9
|
+
shouldDefaultAllowClassifier,
|
|
10
|
+
buildDefaultAllowClaudeMessage,
|
|
11
|
+
} from "./chatCore/claudeClassifierCompat.ts";
|
|
12
|
+
import { applyClientUsageBuffer } from "./chatCore/clientUsageBuffer.ts";
|
|
13
|
+
import { buildPostCallGuardrailContext } from "./chatCore/postCallGuardrailContext.ts";
|
|
14
|
+
import { storeSemanticCacheResponse } from "./chatCore/semanticCacheStore.ts";
|
|
15
|
+
import { buildNonStreamingResponseHeaders } from "./chatCore/nonStreamingResponseHeaders.ts";
|
|
16
|
+
import { buildNonStreamingJsonResponse } from "./chatCore/nonStreamingJsonResponse.ts";
|
|
17
|
+
import { maybeConvertJsonBodyToSse } from "./chatCore/jsonBodyToSse.ts";
|
|
18
|
+
import { assembleStreamingResponseHeaders } from "./chatCore/streamingResponseHeaders.ts";
|
|
19
|
+
import { storeStreamingSemanticCacheResponse } from "./chatCore/streamingSemanticCacheStore.ts";
|
|
20
|
+
import { assembleStreamingPipeline } from "./chatCore/streamingPipeline.ts";
|
|
21
|
+
import { sanitizeChatRequestBody } from "./chatCore/sanitization.ts";
|
|
22
|
+
import {
|
|
23
|
+
getHeaderValueCaseInsensitive,
|
|
24
|
+
isNoMemoryRequested,
|
|
25
|
+
resolveCompressionHeader,
|
|
26
|
+
isStripReasoningRequested,
|
|
27
|
+
} from "./chatCore/headers.ts";
|
|
28
|
+
import { markCodexScopeRateLimited } from "./chatCore/codexFailover.ts";
|
|
29
|
+
import { trackDevice, extractIpFromHeaders } from "../services/deviceTracker.ts";
|
|
30
|
+
import { getCombosCached } from "./chatCore/comboContextCache.ts";
|
|
31
|
+
export { clearCombosCache, clearUpstreamProxyConfigCache } from "./chatCore/comboContextCache.ts";
|
|
32
|
+
import {
|
|
33
|
+
resolveAccountSemaphoreKey,
|
|
34
|
+
resolveAccountSemaphoreMaxConcurrency,
|
|
35
|
+
buildClaudePromptCacheLogMeta,
|
|
36
|
+
} from "./chatCore/executorHelpers.ts";
|
|
37
|
+
import {
|
|
38
|
+
shouldUseNativeCodexPassthrough,
|
|
39
|
+
redactPassthroughThinkingSignatures,
|
|
40
|
+
isClaudeCodeSemanticPassthroughRequest,
|
|
41
|
+
} from "./chatCore/passthroughHelpers.ts";
|
|
42
|
+
import {
|
|
43
|
+
buildStreamingResponseHeaders,
|
|
44
|
+
materializeDeduplicatedExecutionResult,
|
|
45
|
+
stripNextMiddlewareControlHeaders,
|
|
46
|
+
stripStaleForwardingHeaders,
|
|
47
|
+
} from "./chatCore/responseHeaders.ts";
|
|
48
|
+
import {
|
|
49
|
+
forwardDashboardEventToLiveWs,
|
|
50
|
+
maybeSyncClaudeExtraUsageState,
|
|
51
|
+
} from "./chatCore/telemetryHelpers.ts";
|
|
52
|
+
// Re-export the previously inline-defined helpers so existing importers of these
|
|
53
|
+
// symbols from chatCore.ts (tests, sibling modules) keep resolving after the split.
|
|
54
|
+
export {
|
|
55
|
+
shouldUseNativeCodexPassthrough,
|
|
56
|
+
redactPassthroughThinkingSignatures,
|
|
57
|
+
isClaudeCodeSemanticPassthroughRequest,
|
|
58
|
+
buildStreamingResponseHeaders,
|
|
59
|
+
stripStaleForwardingHeaders,
|
|
60
|
+
};
|
|
61
|
+
import {
|
|
62
|
+
extractMemoryTextFromResponse,
|
|
63
|
+
extractMemoryTextFromRequestBody,
|
|
64
|
+
resolveMemoryOwnerId,
|
|
65
|
+
} from "./chatCore/memoryExtraction.ts";
|
|
66
|
+
import { CORS_HEADERS } from "../utils/cors.ts";
|
|
67
|
+
import { checkHeapPressureGuard } from "../utils/heapPressure.ts";
|
|
68
|
+
import { normalizeHeaders } from "../utils/headers.ts";
|
|
69
|
+
import { resolveChatCoreRequestFormat } from "./chatCore/requestFormat.ts";
|
|
70
|
+
import { resolveChatCoreTargetFormat } from "./chatCore/targetFormat.ts";
|
|
71
|
+
import { defaultClaudeToolType } from "./chatCore/claudeToolDefaults.ts";
|
|
72
|
+
import { injectSystemPrompt, injectCustomSystemPrompt } from "../services/systemPrompt.ts";
|
|
73
|
+
import { translateRequest, needsTranslation } from "../translator/index.ts";
|
|
74
|
+
import { FORMATS } from "../translator/formats.ts";
|
|
75
|
+
import { sanitizeKiroTools } from "../utils/kiroSanitizer.ts";
|
|
76
|
+
import { splitMisplacedToolResults } from "../translator/helpers/claudeHelper.ts";
|
|
77
|
+
import {
|
|
78
|
+
createSSETransformStreamWithLogger,
|
|
79
|
+
createPassthroughStreamWithLogger,
|
|
80
|
+
COLORS,
|
|
81
|
+
withBodyTimeout,
|
|
82
|
+
} from "../utils/stream.ts";
|
|
83
|
+
import { ensureStreamReadiness } from "../utils/streamReadiness.ts";
|
|
84
|
+
import { resolveSuppressThinkClose, THINKING_MARKER_HEADER } from "../utils/thinkCloseMarker.ts";
|
|
85
|
+
import { resolveStreamReadinessTimeout } from "../utils/streamReadinessPolicy.ts";
|
|
86
|
+
import { resolveAgentGoalPolicy } from "../utils/agentGoalPolicy.ts";
|
|
87
|
+
import { createStreamController } from "../utils/streamHandler.ts";
|
|
88
|
+
import * as streamFailure from "../utils/streamFailureFinalization.ts";
|
|
89
|
+
import { createSseHeartbeatTransform, shapeForClientFormat } from "../utils/sseHeartbeat.ts";
|
|
90
|
+
import { addBufferToUsage, filterUsageForFormat, estimateUsage } from "../utils/usageTracking.ts";
|
|
91
|
+
import {
|
|
92
|
+
refreshWithRetry,
|
|
93
|
+
isUnrecoverableRefreshError,
|
|
94
|
+
runWithOnPersist,
|
|
95
|
+
runWithCasGuard,
|
|
96
|
+
} from "../services/tokenRefresh.ts";
|
|
97
|
+
import { createRequestLogger } from "../utils/requestLogger.ts";
|
|
98
|
+
import { createPreparedRequestLogger, runWithCapture } from "../utils/providerRequestLogging.ts";
|
|
99
|
+
import { summarizeToolSources } from "../utils/toolSources.ts";
|
|
100
|
+
import { applyResponsesPreviousResponseIdPolicy } from "../utils/responsesStatePolicy.ts";
|
|
101
|
+
import { applyClaudeEffortVariant } from "./chatCore/claudeEffortVariant.ts";
|
|
102
|
+
import { DEFAULT_THINKING_CLAUDE_SIGNATURE } from "../config/defaultThinkingSignature.ts";
|
|
103
|
+
import {
|
|
104
|
+
getStripTypesForProviderModel,
|
|
105
|
+
stripIncompatibleMessageContent,
|
|
106
|
+
} from "../services/modelStrip.ts";
|
|
107
|
+
import { resolveModelAlias } from "../services/modelDeprecation.ts";
|
|
108
|
+
import { normalizeMimoThinking } from "../services/mimoThinking.ts";
|
|
109
|
+
import { normalizeClaudeAdaptiveThinking } from "../services/claudeAdaptiveThinking.ts";
|
|
110
|
+
import { normalizeClaudeHaikuConstraints } from "../services/claudeHaikuConstraints.ts";
|
|
111
|
+
import { echoModelInObject } from "../services/responseModelEcho.ts";
|
|
112
|
+
import { stripGpt5SamplingWhenReasoning } from "../services/gpt5SamplingGuard.ts";
|
|
113
|
+
import { getUnsupportedParams, REGISTRY } from "../config/providerRegistry.ts";
|
|
114
|
+
import { supportsMaxTokens } from "@/lib/modelCapabilities.ts";
|
|
115
|
+
import { normalizeThinkingForModel } from "@/shared/constants/modelSpecs.ts";
|
|
116
|
+
import {
|
|
117
|
+
buildErrorBody,
|
|
118
|
+
createErrorResult,
|
|
119
|
+
parseUpstreamError,
|
|
120
|
+
formatProviderError,
|
|
121
|
+
sanitizeErrorMessage,
|
|
122
|
+
} from "../utils/error.ts";
|
|
123
|
+
import { reportMalformed200, detectMalformedNonStream } from "../utils/diagnostics.ts";
|
|
124
|
+
import {
|
|
125
|
+
checkTokenLimits,
|
|
126
|
+
recordTokenUsage,
|
|
127
|
+
} from "@omniroute/open-sse/services/tokenLimitCounter.ts";
|
|
128
|
+
import {
|
|
129
|
+
COOLDOWN_MS,
|
|
130
|
+
HTTP_STATUS,
|
|
131
|
+
FETCH_BODY_TIMEOUT_MS,
|
|
132
|
+
PROVIDER_MAX_TOKENS,
|
|
133
|
+
STREAM_IDLE_TIMEOUT_MS,
|
|
134
|
+
STREAM_READINESS_MAX_TIMEOUT_MS,
|
|
135
|
+
STREAM_READINESS_TIMEOUT_MS,
|
|
136
|
+
ANTIGRAVITY_PRE_RESPONSE_TIMEOUT_CODE,
|
|
137
|
+
STREAM_RECOVERY,
|
|
138
|
+
} from "../config/constants.ts";
|
|
139
|
+
import { createRecoverableStream, makeContinuationBody } from "../services/streamRecovery.ts";
|
|
140
|
+
import {
|
|
141
|
+
resolveResilienceSettings,
|
|
142
|
+
isStreamRecoveryExplicitlyConfigured,
|
|
143
|
+
} from "@/lib/resilience/settings";
|
|
144
|
+
import {
|
|
145
|
+
classifyProviderError,
|
|
146
|
+
PROVIDER_ERROR_TYPES,
|
|
147
|
+
isEmptyContentResponse,
|
|
148
|
+
} from "../services/errorClassifier.ts";
|
|
149
|
+
import { updateProviderConnection, getProviderConnectionById } from "@/lib/db/providers";
|
|
150
|
+
import { wasRefreshTokenRotated } from "@omniroute/open-sse/services/refreshSerializer.ts";
|
|
151
|
+
import { connectionHasExtraKeys } from "../services/apiKeyRotator.ts";
|
|
152
|
+
import { recordKeyHealthStatus as recordKeyHealthStatusFor } from "./chatCore/keyHealth.ts";
|
|
153
|
+
import { getSkillsModelIdForFormat } from "./chatCore/skillsFormat.ts";
|
|
154
|
+
import { readNonStreamingResponseBody } from "./chatCore/nonStreamingResponseBody.ts";
|
|
155
|
+
import {
|
|
156
|
+
isSemaphoreCapacityError,
|
|
157
|
+
createStreamingErrorResult,
|
|
158
|
+
getUpstreamErrorIdentifier,
|
|
159
|
+
} from "./chatCore/streamErrorResult.ts";
|
|
160
|
+
import { wrapReadableStreamWithFinalize } from "./chatCore/streamFinalize.ts";
|
|
161
|
+
import { buildCacheUsageLogMeta } from "./chatCore/cacheUsageMeta.ts";
|
|
162
|
+
import { buildExecutorClientHeaders } from "./chatCore/executorClientHeaders.ts";
|
|
163
|
+
import { resolveExecutionCredentials as resolveExecutionCredentialsFor } from "./chatCore/executionCredentials.ts";
|
|
164
|
+
import { resolveExecutorWithProxy as resolveExecutorWithProxyFor } from "./chatCore/executorProxy.ts";
|
|
165
|
+
import type { ClaudeMessage } from "./chatCore/claudeMessageTypes.ts";
|
|
166
|
+
import { normalizeClaudeUpstreamMessages as normalizeClaudeUpstreamMessagesFor } from "./chatCore/claudeUpstreamMessages.ts";
|
|
167
|
+
import {
|
|
168
|
+
persistAttemptLogs as persistAttemptLogsFor,
|
|
169
|
+
type PersistAttemptLogsArgs,
|
|
170
|
+
} from "./chatCore/attemptLogging.ts";
|
|
171
|
+
import { stageTrace } from "./chatCore/stageTrace.ts";
|
|
172
|
+
import { attachCompressionUsageReceiptAfterAnalytics as attachCompressionUsageReceiptAfterAnalyticsFor } from "./chatCore/compressionUsageReceipt.ts";
|
|
173
|
+
import { prepareUpstreamBody } from "./chatCore/upstreamBody.ts";
|
|
174
|
+
import { getQuotaScopeLabelForProvider } from "../services/antigravityQuotaFamily.ts";
|
|
175
|
+
|
|
176
|
+
import {
|
|
177
|
+
getCallLogPipelineCaptureStreamChunks,
|
|
178
|
+
getCallLogPipelineMaxSizeBytes,
|
|
179
|
+
} from "@/lib/logEnv";
|
|
180
|
+
import { logAuditEvent } from "@/lib/compliance";
|
|
181
|
+
import { emit } from "@/lib/events/eventBus";
|
|
182
|
+
import { adaptBodyForCompression } from "../services/compression/bodyAdapter.ts";
|
|
183
|
+
import { ensureEngineBreakdown } from "../services/compression/engineBreakdown.ts";
|
|
184
|
+
import { handleBypassRequest } from "../utils/bypassHandler.ts";
|
|
185
|
+
import { saveRequestUsage, trackPendingRequest, appendRequestLog } from "@/lib/usageDb";
|
|
186
|
+
import { finalizePendingScope, updatePendingScope } from "@/lib/usage/pendingRequestScope";
|
|
187
|
+
import { recordCost } from "@/domain/costRules";
|
|
188
|
+
import { calculateCost } from "@/lib/usage/costCalculator";
|
|
189
|
+
import {
|
|
190
|
+
buildClaudePassthroughToolNameMap,
|
|
191
|
+
restoreClaudePassthroughToolNames,
|
|
192
|
+
mergeResponseToolNameMap,
|
|
193
|
+
} from "./chatCore/passthroughToolNames.ts";
|
|
194
|
+
import { resolveCompressionSettings } from "./chatCore/compressionSettings.ts";
|
|
195
|
+
import {
|
|
196
|
+
isBuiltinStackedPipeline,
|
|
197
|
+
isStackedCompressionCombo,
|
|
198
|
+
type RuntimeCompressionCombo,
|
|
199
|
+
} from "./chatCore/compressionComboPredicates.ts";
|
|
200
|
+
import { emitOutputStyleTelemetry } from "./chatCore/outputStyleTelemetry.ts";
|
|
201
|
+
import {
|
|
202
|
+
writeCompressionAnalytics,
|
|
203
|
+
writeCompressionSkip,
|
|
204
|
+
} from "./chatCore/compressionAnalyticsWrite.ts";
|
|
205
|
+
import { runPluginOnRequestHook } from "./chatCore/pluginOnRequest.ts";
|
|
206
|
+
import { recordContextEditingTelemetryHook } from "./chatCore/contextEditingTelemetry.ts";
|
|
207
|
+
import { recordCompressionCacheStats } from "./chatCore/compressionCacheStats.ts";
|
|
208
|
+
import { writeCavemanOutputAnalytics } from "./chatCore/cavemanOutputAnalytics.ts";
|
|
209
|
+
import { scheduleQuotaShareConsumption } from "./chatCore/quotaShareConsumption.ts";
|
|
210
|
+
import { emitRequestGamificationEvent } from "./chatCore/gamificationEvent.ts";
|
|
211
|
+
import { runPluginOnResponseHook } from "./chatCore/pluginOnResponse.ts";
|
|
212
|
+
import { scheduleStreamingQuotaShareConsumption } from "./chatCore/streamingQuotaShare.ts";
|
|
213
|
+
import { recordStreamingUsageStats } from "./chatCore/streamingUsageStats.ts";
|
|
214
|
+
import { recordStreamingCost } from "./chatCore/streamingCost.ts";
|
|
215
|
+
import {
|
|
216
|
+
appendNonStreamingSseTerminalSignal,
|
|
217
|
+
type NonStreamingSseTerminalState,
|
|
218
|
+
} from "./chatCore/nonStreamingSse.ts";
|
|
219
|
+
import { parseNonStreamingResponseBody } from "./chatCore/nonStreamingResponseParse.ts";
|
|
220
|
+
import { unwrapClinepassEnvelope } from "../utils/clinepassEnvelope.ts";
|
|
221
|
+
import { recordNonStreamingUsageStats } from "./chatCore/nonStreamingUsageStats.ts";
|
|
222
|
+
import {
|
|
223
|
+
createBodyTimeoutError,
|
|
224
|
+
readStreamChunkWithTimeout,
|
|
225
|
+
computeBillableTokens,
|
|
226
|
+
normalizeExecutorResult,
|
|
227
|
+
executeWithUpstreamStartTimeout,
|
|
228
|
+
} from "./chatCore/upstreamTimeouts.ts";
|
|
229
|
+
import { getModelNormalizeToolCallId, getModelPreserveOpenAIDeveloperRole } from "@/lib/localDb";
|
|
230
|
+
import { getProviderCredentials, extractSessionAffinityKey } from "@/sse/services/auth";
|
|
231
|
+
import { deleteSessionAccountAffinity } from "@/lib/db/sessionAccountAffinity";
|
|
232
|
+
import { getCacheControlSettings } from "@/lib/cacheControlSettings";
|
|
233
|
+
import { guardrailRegistry } from "@/lib/guardrails";
|
|
234
|
+
import { shouldPreserveCacheControl } from "../utils/cacheControlPolicy.ts";
|
|
235
|
+
import { getCachedSettings } from "@/lib/db/readCache";
|
|
236
|
+
import { applyCodexGlobalFastServiceTier } from "@/lib/providers/codexFastTier";
|
|
237
|
+
import { buildUpstreamHeadersForExecute as buildUpstreamHeadersForExecuteFor } from "./chatCore/upstreamExecuteHeaders.ts";
|
|
238
|
+
import {
|
|
239
|
+
resolveEffectiveServiceTier as resolveEffectiveServiceTierFor,
|
|
240
|
+
resolveReportedServiceTier as resolveReportedServiceTierFor,
|
|
241
|
+
type EffectiveServiceTier,
|
|
242
|
+
} from "./chatCore/serviceTier.ts";
|
|
243
|
+
import { cacheReasoningFromAssistantMessage } from "../services/reasoningCache.ts";
|
|
244
|
+
import { sanitizeOpenAITool } from "../services/toolSchemaSanitizer.ts";
|
|
245
|
+
import {
|
|
246
|
+
setDetectedToolLimit,
|
|
247
|
+
parseToolLimitFromError,
|
|
248
|
+
shouldDetectLimit,
|
|
249
|
+
} from "../services/toolLimitDetector.ts";
|
|
250
|
+
|
|
251
|
+
import { isCompactResponsesEndpoint } from "../executors/codex.ts";
|
|
252
|
+
import { buildCodexQuotaPersistence } from "./chatCore/codexQuota.ts";
|
|
253
|
+
import { invalidateCodexQuotaCache } from "../services/codexQuotaFetcher.ts";
|
|
254
|
+
import { translateNonStreamingResponse } from "./responseTranslator.ts";
|
|
255
|
+
import { unwrapClineNonStreamingEnvelope } from "./chatCore/clineResponseEnvelope.ts";
|
|
256
|
+
import { extractUsageFromResponse } from "./usageExtractor.ts";
|
|
257
|
+
import {
|
|
258
|
+
sanitizeOpenAIResponse,
|
|
259
|
+
sanitizeResponsesApiResponse,
|
|
260
|
+
shouldParseTextualReasoningTags,
|
|
261
|
+
} from "./responseSanitizer.ts";
|
|
262
|
+
import {
|
|
263
|
+
withRateLimit,
|
|
264
|
+
updateFromHeaders,
|
|
265
|
+
updateFromResponseBody,
|
|
266
|
+
initializeRateLimits,
|
|
267
|
+
} from "../services/rateLimitManager.ts";
|
|
268
|
+
import {
|
|
269
|
+
acquire as acquireAccountSemaphore,
|
|
270
|
+
markBlocked as markAccountSemaphoreBlocked,
|
|
271
|
+
} from "../services/accountSemaphore.ts";
|
|
272
|
+
import { lockModel, lockModelIfPerModelQuota } from "../services/accountFallback.ts";
|
|
273
|
+
import {
|
|
274
|
+
generateSignature,
|
|
275
|
+
getCachedResponse,
|
|
276
|
+
setCachedResponse,
|
|
277
|
+
isCacheableForRead,
|
|
278
|
+
isCacheableForWrite,
|
|
279
|
+
} from "@/lib/semanticCache";
|
|
280
|
+
import { saveIdempotency } from "@/lib/idempotencyLayer";
|
|
281
|
+
import {
|
|
282
|
+
isModelUnavailableError,
|
|
283
|
+
getNextFamilyFallback,
|
|
284
|
+
isContextOverflowError,
|
|
285
|
+
findLargerContextModel,
|
|
286
|
+
getModelFamily,
|
|
287
|
+
} from "../services/modelFamilyFallback.ts";
|
|
288
|
+
import { computeRequestHash, deduplicate, shouldDeduplicate } from "../services/requestDedup.ts";
|
|
289
|
+
import {
|
|
290
|
+
compressContext,
|
|
291
|
+
estimateTokens,
|
|
292
|
+
getTokenLimit,
|
|
293
|
+
resolveComboContextLimit,
|
|
294
|
+
} from "../services/contextManager.ts";
|
|
295
|
+
import { resolveBackgroundTaskRedirect } from "./chatCore/backgroundRedirect.ts";
|
|
296
|
+
import type { CompressionConfig, CompressionPipelineStep } from "../services/compression/types.ts";
|
|
297
|
+
import { prepareWebSearchFallbackBody } from "../services/webSearchFallback.ts";
|
|
298
|
+
import {
|
|
299
|
+
resolveExplicitStreamAlias,
|
|
300
|
+
resolveStreamFlag,
|
|
301
|
+
stripMarkdownCodeFence,
|
|
302
|
+
} from "../utils/aiSdkCompat.ts";
|
|
303
|
+
import { generateRequestId } from "@/shared/utils/requestId";
|
|
304
|
+
import { extractFacts } from "@/lib/memory/extraction";
|
|
305
|
+
import { handleToolCallExecution } from "@/lib/skills/interception";
|
|
306
|
+
import { OMNIROUTE_RESPONSE_HEADERS } from "@/shared/constants/headers";
|
|
307
|
+
import { getClaudeCodeCompatibleRequestDefaults } from "@/lib/providers/requestDefaults";
|
|
308
|
+
import {
|
|
309
|
+
buildClaudeCodeCompatibleRequest,
|
|
310
|
+
isClaudeCodeCompatibleProvider,
|
|
311
|
+
resolveClaudeCodeCompatibleSessionId,
|
|
312
|
+
} from "../services/claudeCodeCompatible.ts";
|
|
313
|
+
import { setGeminiThoughtSignatureMode } from "../services/geminiThoughtSignatureStore.ts";
|
|
314
|
+
import { fetchLiveProviderLimits } from "@/lib/usage/providerLimits";
|
|
315
|
+
import { isClaudeExtraUsageBlockEnabled } from "@/lib/providers/claudeExtraUsage";
|
|
316
|
+
import {
|
|
317
|
+
classifyModelScope429,
|
|
318
|
+
getModelScopeRetryDelayMs,
|
|
319
|
+
isModelScopeProvider,
|
|
320
|
+
} from "../services/modelscopePolicy.ts";
|
|
321
|
+
import { incrementRequestCount } from "../services/geminiRateLimitTracker.ts";
|
|
322
|
+
|
|
323
|
+
// ── Global memory pressure guard ────────────────────────────────────────
|
|
324
|
+
// Prevents OOM by rejecting new requests when V8 heap exceeds threshold.
|
|
325
|
+
// Self-healing: no counters to leak, no cleanup needed. The threshold
|
|
326
|
+
// auto-calibrates to 85% of the actual V8 heap ceiling (see heapPressure.ts) so
|
|
327
|
+
// it tracks --max-old-space-size across 1GB/2GB/large VPS instead of a fixed
|
|
328
|
+
// 200MB that sat below the app's own ~260MB baseline and rejected every request.
|
|
329
|
+
|
|
330
|
+
import { isSmallEnoughForSemanticCache } from "../utils/estimateSize.ts";
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Core chat handler - shared between SSE and Worker
|
|
334
|
+
* Returns { success, response, status, error } for caller to handle fallback
|
|
335
|
+
* @param {object} options
|
|
336
|
+
* @param {object} options.body - Request body
|
|
337
|
+
* @param {object} options.modelInfo - { provider, model }
|
|
338
|
+
* @param {object} options.credentials - Provider credentials
|
|
339
|
+
* @param {object} options.log - Logger instance (optional)
|
|
340
|
+
* @param {function} options.onCredentialsRefreshed - Callback when credentials are refreshed
|
|
341
|
+
* @param {function} options.onRequestSuccess - Callback when request succeeds (to clear error status)
|
|
342
|
+
* @param {function} options.onDisconnect - Callback when client disconnects
|
|
343
|
+
* @param {string} options.connectionId - Connection ID for usage tracking
|
|
344
|
+
* @param {object} options.apiKeyInfo - API key metadata for usage attribution
|
|
345
|
+
* @param {string} options.userAgent - Client user agent for caching decisions
|
|
346
|
+
* @param {string} options.comboName - Combo name if this is a combo request
|
|
347
|
+
* @param {string} options.comboStrategy - Combo routing strategy (e.g., 'priority', 'cost-optimized')
|
|
348
|
+
* @param {boolean} options.isCombo - Whether this request is from a combo
|
|
349
|
+
* @param {string} options.connectionId - Connection ID for settings lookup
|
|
350
|
+
*/
|
|
351
|
+
|
|
352
|
+
// extractSystemRoleMessages extracted to chatCore/claudeSystemRole.ts (#3501); re-exported above so
|
|
353
|
+
// existing importers (e.g. tests/unit/system-role-extraction.test.ts) keep resolving it from here.
|
|
354
|
+
|
|
355
|
+
export async function handleChatCore({
|
|
356
|
+
body,
|
|
357
|
+
modelInfo,
|
|
358
|
+
credentials,
|
|
359
|
+
log,
|
|
360
|
+
onCredentialsRefreshed,
|
|
361
|
+
onRequestSuccess,
|
|
362
|
+
onStreamFailure,
|
|
363
|
+
onDisconnect,
|
|
364
|
+
clientRawRequest,
|
|
365
|
+
connectionId,
|
|
366
|
+
apiKeyInfo = null,
|
|
367
|
+
userAgent,
|
|
368
|
+
comboName,
|
|
369
|
+
comboStrategy = null,
|
|
370
|
+
isCombo = false,
|
|
371
|
+
comboStepId = null,
|
|
372
|
+
comboExecutionKey = null,
|
|
373
|
+
cachedSettings = null,
|
|
374
|
+
skipUpstreamRetry = false,
|
|
375
|
+
createPiiTransform = null,
|
|
376
|
+
correlationId = null,
|
|
377
|
+
modelPinned = false,
|
|
378
|
+
}) {
|
|
379
|
+
let { provider, model, extendedContext } = modelInfo;
|
|
380
|
+
// ── Memory pressure guard ────────────────────────────────────────────
|
|
381
|
+
// Reject early if V8 heap is already near the 256MB limit. Prevents
|
|
382
|
+
// cascading OOM when many large-context requests arrive concurrently.
|
|
383
|
+
try {
|
|
384
|
+
const heapUsedMB = process.memoryUsage().heapUsed / (1024 * 1024);
|
|
385
|
+
const heapGuard = checkHeapPressureGuard(heapUsedMB);
|
|
386
|
+
if (heapGuard) return heapGuard;
|
|
387
|
+
} catch {
|
|
388
|
+
/* memoryUsage() never throws */
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Per-request model-routing metadata (first extracted slice of the request-setup phase).
|
|
392
|
+
const { apiFormat, customModelTargetFormat, requestedModel } = resolveChatCoreRequestSetup(
|
|
393
|
+
modelInfo,
|
|
394
|
+
body,
|
|
395
|
+
model
|
|
396
|
+
);
|
|
397
|
+
const isModelScope = () => isModelScopeProvider(provider, credentials?.providerSpecificData);
|
|
398
|
+
const startTime = Date.now();
|
|
399
|
+
// Per-request trace id + checkpoint helper. Lets us see exactly which await
|
|
400
|
+
// a hung request was sitting on in `[STAGE_TRACE]` log lines. Uses crypto RNG
|
|
401
|
+
// (not Math.random) purely to satisfy CodeQL js/insecure-randomness — this id
|
|
402
|
+
// is a log-correlation token, not a security secret.
|
|
403
|
+
const traceId = globalThis.crypto.randomUUID().slice(0, 6);
|
|
404
|
+
|
|
405
|
+
// Emit request.started event for real-time dashboard
|
|
406
|
+
setImmediate(() => {
|
|
407
|
+
emit("request.started", {
|
|
408
|
+
id: traceId,
|
|
409
|
+
model: model || "unknown",
|
|
410
|
+
provider: provider || "unknown",
|
|
411
|
+
timestamp: startTime,
|
|
412
|
+
comboName: comboName || undefined,
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
const traceEnabled = process.env.OMNIROUTE_TRACE === "true" || process.env.DEBUG === "true";
|
|
416
|
+
// Stage trace extracted to chatCore/stageTrace.ts (#3501); bind the per-request inputs once so the
|
|
417
|
+
// call sites stay byte-identical.
|
|
418
|
+
const trace = (label: string, extra?: Record<string, unknown>) =>
|
|
419
|
+
stageTrace(label, extra, { traceEnabled, startTime, traceId, log });
|
|
420
|
+
const getCurrentConnectionId = () => {
|
|
421
|
+
const credentialConnectionId =
|
|
422
|
+
typeof credentials?.connectionId === "string" && credentials.connectionId.trim().length > 0
|
|
423
|
+
? credentials.connectionId.trim()
|
|
424
|
+
: null;
|
|
425
|
+
return credentialConnectionId || connectionId || null;
|
|
426
|
+
};
|
|
427
|
+
let tokensCompressed: number | null = null;
|
|
428
|
+
body = injectSystemPrompt(body);
|
|
429
|
+
// ── Per-endpoint custom system prompt (port of upstream #2063) ──
|
|
430
|
+
// Reads from cachedSettings if available (passed in from combo/chat layer)
|
|
431
|
+
// to avoid an extra DB read on the hot path. Falls through to getCachedSettings()
|
|
432
|
+
// only when this function is called outside the normal chat dispatch.
|
|
433
|
+
{
|
|
434
|
+
const _s = cachedSettings ?? (await getCachedSettings());
|
|
435
|
+
if (
|
|
436
|
+
_s.customSystemPromptEnabled === true &&
|
|
437
|
+
typeof _s.customSystemPrompt === "string" &&
|
|
438
|
+
_s.customSystemPrompt
|
|
439
|
+
) {
|
|
440
|
+
body = injectCustomSystemPrompt(body as Record<string, unknown>, _s.customSystemPrompt);
|
|
441
|
+
log?.debug?.("CUSTOMSP", "custom system prompt injected");
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
// ── Plugin onRequest hook ──
|
|
445
|
+
// Dynamic import cached by Node.js after first call — minimal overhead
|
|
446
|
+
const pluginGate = await runPluginOnRequestHook({
|
|
447
|
+
requestId: traceId,
|
|
448
|
+
body,
|
|
449
|
+
model,
|
|
450
|
+
provider,
|
|
451
|
+
apiKeyInfo,
|
|
452
|
+
log,
|
|
453
|
+
});
|
|
454
|
+
if (pluginGate.blocked) {
|
|
455
|
+
return {
|
|
456
|
+
success: false,
|
|
457
|
+
status: 403,
|
|
458
|
+
error: "Request blocked by plugin",
|
|
459
|
+
response: pluginGate.response,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
if (pluginGate.body) {
|
|
463
|
+
body = pluginGate.body;
|
|
464
|
+
}
|
|
465
|
+
// Per-API-key device/connection tracking (port of upstream 9router#931,
|
|
466
|
+
// thanks @mugnimaestra). In-memory only, never blocks the request path.
|
|
467
|
+
if (apiKeyInfo?.id) {
|
|
468
|
+
trackDevice(
|
|
469
|
+
apiKeyInfo.id,
|
|
470
|
+
extractIpFromHeaders(clientRawRequest?.headers ?? null),
|
|
471
|
+
userAgent ?? null
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
const agentGoalPolicy = resolveAgentGoalPolicy(body, clientRawRequest?.headers ?? null);
|
|
475
|
+
if (agentGoalPolicy.detected) {
|
|
476
|
+
log?.debug?.(
|
|
477
|
+
"AGENT_GOAL",
|
|
478
|
+
`long-running goal mode enabled: readinessMax=${agentGoalPolicy.readinessMaxTimeoutMs}ms streamRecovery=${agentGoalPolicy.streamRecoveryEnabled}`
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
let effectiveServiceTier: EffectiveServiceTier = "standard";
|
|
483
|
+
// Codex service-tier resolvers extracted to chatCore/serviceTier.ts (#3501); bind the per-request
|
|
484
|
+
// provider/credentials once and delegate so the existing call sites stay byte-identical.
|
|
485
|
+
const resolveEffectiveServiceTier = (requestBody?: unknown): EffectiveServiceTier =>
|
|
486
|
+
resolveEffectiveServiceTierFor(provider, credentials?.providerSpecificData, requestBody);
|
|
487
|
+
const resolveReportedServiceTier = (
|
|
488
|
+
payload?: unknown,
|
|
489
|
+
maxDepth = 3
|
|
490
|
+
): EffectiveServiceTier | null => resolveReportedServiceTierFor(provider, payload, maxDepth);
|
|
491
|
+
// Failure usage record building extracted to chatCore/failureUsage.ts (#3501); the handler keeps
|
|
492
|
+
// the fire-and-forget save + computes latencyMs, so the call sites stay byte-identical.
|
|
493
|
+
const persistFailureUsage = (statusCode: number, errorCode?: string | null) => {
|
|
494
|
+
saveRequestUsage(
|
|
495
|
+
buildFailureUsageRecord({
|
|
496
|
+
provider,
|
|
497
|
+
model,
|
|
498
|
+
connectionId: getCurrentConnectionId(),
|
|
499
|
+
apiKeyInfo,
|
|
500
|
+
effectiveServiceTier,
|
|
501
|
+
isCombo,
|
|
502
|
+
comboStrategy,
|
|
503
|
+
statusCode,
|
|
504
|
+
errorCode,
|
|
505
|
+
latencyMs: Date.now() - startTime,
|
|
506
|
+
endpoint: endpointPath,
|
|
507
|
+
})
|
|
508
|
+
).catch(() => {});
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
// Key-health updater extracted to chatCore/keyHealth.ts (#3501); bind the per-request log once
|
|
512
|
+
// and delegate so the existing call sites stay byte-identical.
|
|
513
|
+
const recordKeyHealthStatus = (
|
|
514
|
+
status: number,
|
|
515
|
+
creds: Record<string, unknown> | null | undefined
|
|
516
|
+
): void => recordKeyHealthStatusFor(status, creds, log);
|
|
517
|
+
|
|
518
|
+
const persistCodexQuotaState = async (headers: Record<string, string> | null, status = 0) => {
|
|
519
|
+
const currentConnectionId = getCurrentConnectionId();
|
|
520
|
+
if (provider !== "codex" || !currentConnectionId || !headers) return;
|
|
521
|
+
|
|
522
|
+
try {
|
|
523
|
+
const existingProviderData =
|
|
524
|
+
credentials?.providerSpecificData && typeof credentials.providerSpecificData === "object"
|
|
525
|
+
? (credentials.providerSpecificData as Record<string, unknown>)
|
|
526
|
+
: {};
|
|
527
|
+
// Pure payload build extracted to chatCore/codexQuota.ts (#3501). Returns null when the
|
|
528
|
+
// response carries no quota headers (nothing to persist).
|
|
529
|
+
const built = buildCodexQuotaPersistence({
|
|
530
|
+
headers,
|
|
531
|
+
existingProviderData,
|
|
532
|
+
modelForScope: model || requestedModel || "",
|
|
533
|
+
status,
|
|
534
|
+
});
|
|
535
|
+
if (!built) return;
|
|
536
|
+
|
|
537
|
+
if (built.exhaustionLog) {
|
|
538
|
+
log?.debug?.("CODEX", built.exhaustionLog);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Invalidate the preflight cache for this connection so the next
|
|
542
|
+
// isModelAvailable check fetches fresh quota data.
|
|
543
|
+
if (status === 429) {
|
|
544
|
+
invalidateCodexQuotaCache(currentConnectionId);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
await updateProviderConnection(currentConnectionId, {
|
|
548
|
+
providerSpecificData: built.nextProviderData,
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
credentials.providerSpecificData = built.nextProviderData;
|
|
552
|
+
} catch (err) {
|
|
553
|
+
const errMessage = err instanceof Error ? err.message : String(err);
|
|
554
|
+
log?.debug?.("CODEX", `Failed to persist codex quota state: ${errMessage}`);
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
// ── Phase 9.2: Idempotency check ──
|
|
559
|
+
// Resolve the idempotency key once here and reuse it at the Phase 9.2 save site below,
|
|
560
|
+
// rather than re-deriving it. (#3821-review LEDGER-6)
|
|
561
|
+
const { hit: idempotencyHit, idempotencyKey } = await checkIdempotencyCache({
|
|
562
|
+
clientRawRequest,
|
|
563
|
+
provider,
|
|
564
|
+
model,
|
|
565
|
+
effectiveServiceTier,
|
|
566
|
+
startTime,
|
|
567
|
+
log,
|
|
568
|
+
});
|
|
569
|
+
if (idempotencyHit) {
|
|
570
|
+
return idempotencyHit;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// T07: Inject connectionId into credentials so executors can rotate API keys
|
|
574
|
+
// using providerSpecificData.extraApiKeys (API Key Round-Robin feature)
|
|
575
|
+
if (connectionId && credentials && !credentials.connectionId) {
|
|
576
|
+
credentials.connectionId = connectionId;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Endpoint/format resolution extracted to chatCore/requestFormat.ts (#3501); pure derivation
|
|
580
|
+
// from the inbound request, destructured so every downstream use stays byte-identical.
|
|
581
|
+
const {
|
|
582
|
+
endpointPath,
|
|
583
|
+
sourceFormat,
|
|
584
|
+
isResponsesEndpoint,
|
|
585
|
+
nativeCodexPassthrough,
|
|
586
|
+
isDroidCLI,
|
|
587
|
+
isOpencodeClient,
|
|
588
|
+
copilotCompatibleReasoning,
|
|
589
|
+
clientResponseFormat,
|
|
590
|
+
} = resolveChatCoreRequestFormat({ clientRawRequest, body, provider, userAgent });
|
|
591
|
+
|
|
592
|
+
// Check for bypass patterns (warmup, skip) - return fake response
|
|
593
|
+
const bypassResponse = handleBypassRequest(body, model, userAgent);
|
|
594
|
+
if (bypassResponse) {
|
|
595
|
+
return bypassResponse;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// ── Claude Code auto-mode classifier compat (opt-in, default "off") ──
|
|
599
|
+
// Claude Code's `--permission-mode auto` sends an internal classifier request that
|
|
600
|
+
// requires the response to START with `<block>no</block>`/`<block>yes</block>`.
|
|
601
|
+
// When a combo/fallback route sends that call to a cheap model returning 200 with
|
|
602
|
+
// empty content, Claude Code fails closed on every gated action. Detect the
|
|
603
|
+
// classifier request and short-circuit with a synthetic ALLOW response, WITHOUT
|
|
604
|
+
// calling the upstream provider. See chatCore/claudeClassifierCompat.ts.
|
|
605
|
+
{
|
|
606
|
+
const classifierSettings = cachedSettings ?? (await getCachedSettings());
|
|
607
|
+
if (
|
|
608
|
+
shouldDefaultAllowClassifier(
|
|
609
|
+
sourceFormat,
|
|
610
|
+
body as Record<string, unknown>,
|
|
611
|
+
classifierSettings.claudeClassifierCompat as string | undefined
|
|
612
|
+
)
|
|
613
|
+
) {
|
|
614
|
+
log?.warn?.(
|
|
615
|
+
"CHAT",
|
|
616
|
+
`classifier compat=${classifierSettings.claudeClassifierCompat} | short-circuit default-allow`
|
|
617
|
+
);
|
|
618
|
+
return buildDefaultAllowClaudeMessage(requestedModel);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Detect source format and get target format
|
|
623
|
+
// Model-specific targetFormat takes priority over provider default
|
|
624
|
+
|
|
625
|
+
// ── Background Task Redirection (T41) — decision extracted to chatCore/backgroundRedirect.ts (#3501)
|
|
626
|
+
// backgroundReason is the detection signal (threaded into memory/skills injection below); redirect
|
|
627
|
+
// is the actual model downgrade to apply, if any.
|
|
628
|
+
const { backgroundReason, redirect: bgRedirect } = resolveBackgroundTaskRedirect({
|
|
629
|
+
body,
|
|
630
|
+
headers: clientRawRequest?.headers,
|
|
631
|
+
model,
|
|
632
|
+
});
|
|
633
|
+
if (bgRedirect) {
|
|
634
|
+
const originalModel = model;
|
|
635
|
+
log?.info?.(
|
|
636
|
+
"BACKGROUND",
|
|
637
|
+
`Background task redirect (${bgRedirect.reason}): ${originalModel} → ${bgRedirect.degradedModel}`
|
|
638
|
+
);
|
|
639
|
+
model = bgRedirect.degradedModel;
|
|
640
|
+
if (body && typeof body === "object") {
|
|
641
|
+
body.model = model;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
logAuditEvent({
|
|
645
|
+
action: "routing.background_task_redirect",
|
|
646
|
+
actor: apiKeyInfo?.name || "system",
|
|
647
|
+
target: connectionId || provider || "chat",
|
|
648
|
+
details: {
|
|
649
|
+
original_model: originalModel,
|
|
650
|
+
redirected_to: bgRedirect.degradedModel,
|
|
651
|
+
reason: bgRedirect.reason,
|
|
652
|
+
},
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Apply custom model aliases (Settings → Model Aliases → Pattern→Target) before routing (#315, #472)
|
|
657
|
+
// Custom aliases take priority over built-in and must be resolved here so the
|
|
658
|
+
// downstream getModelTargetFormat() lookup AND the actual provider request use
|
|
659
|
+
// the correct, aliased model ID. Without this, aliases only affect format detection.
|
|
660
|
+
const resolvedModel = resolveModelAlias(model);
|
|
661
|
+
// Use resolvedModel for all downstream operations (routing, provider requests, logging)
|
|
662
|
+
let effectiveModel = resolvedModel === model ? model : resolvedModel;
|
|
663
|
+
if (resolvedModel !== model) {
|
|
664
|
+
log?.info?.("ALIAS", `Model alias applied: ${model} → ${resolvedModel}`);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Effort-variant model ids: the Claude / Claude-Code model picker (e.g. VS Code's
|
|
668
|
+
// "Effort" slider) advertises claude-...-{low,medium,high,xhigh,max}. Anthropic has
|
|
669
|
+
// no such model, so the suffixed id 404s upstream. Strip it back to the real base id
|
|
670
|
+
// (forwarded as the upstream model via finalModelToUpstream below) and surface the
|
|
671
|
+
// level as reasoning_effort so the OpenAI→Claude translator / Claude-Code bridge turn
|
|
672
|
+
// it into Claude thinking/effort config. An explicit client-supplied effort always
|
|
673
|
+
// wins; native Claude passthrough is left untouched (it carries its own `thinking`),
|
|
674
|
+
// and non-thinking base models are cleaned up later by normalizeThinkingForModel().
|
|
675
|
+
// Extracted to chatCore/claudeEffortVariant.ts (#3501); mutates body in place and returns the
|
|
676
|
+
// stripped model + an optional log line, keeping behaviour byte-identical.
|
|
677
|
+
{
|
|
678
|
+
const effortVariant = applyClaudeEffortVariant({
|
|
679
|
+
provider,
|
|
680
|
+
effectiveModel,
|
|
681
|
+
body,
|
|
682
|
+
sourceFormat,
|
|
683
|
+
});
|
|
684
|
+
effectiveModel = effortVariant.effectiveModel;
|
|
685
|
+
if (effortVariant.log) {
|
|
686
|
+
log?.info?.("PARAMS", effortVariant.log);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Wire target-format resolution extracted to chatCore/targetFormat.ts (#3501); `alias` is reused
|
|
691
|
+
// downstream when stripping the alias/ prefix off the upstream model id.
|
|
692
|
+
const { alias, targetFormat } = resolveChatCoreTargetFormat({
|
|
693
|
+
provider,
|
|
694
|
+
resolvedModel,
|
|
695
|
+
apiFormat,
|
|
696
|
+
customModelTargetFormat,
|
|
697
|
+
providerSpecificData: credentials?.providerSpecificData,
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
const initialProviderRequest =
|
|
701
|
+
body && typeof body === "object" && !Array.isArray(body)
|
|
702
|
+
? {
|
|
703
|
+
...(body as Record<string, unknown>),
|
|
704
|
+
model:
|
|
705
|
+
typeof (body as Record<string, unknown>).model === "string"
|
|
706
|
+
? (body as Record<string, unknown>).model
|
|
707
|
+
: effectiveModel,
|
|
708
|
+
}
|
|
709
|
+
: body;
|
|
710
|
+
|
|
711
|
+
// Track pending requests before slower optional enrichment (settings, logging,
|
|
712
|
+
// compression) so internal usage/runtime counters stay accurate even when
|
|
713
|
+
// upstream never returns response headers.
|
|
714
|
+
// Use credentials.connectionId as a fallback so that requests without an
|
|
715
|
+
// explicit session-level connectionId still register in the pendingRequests map.
|
|
716
|
+
const pendingConnId = connectionId || credentials?.connectionId || null;
|
|
717
|
+
const pendingRequestId =
|
|
718
|
+
trackPendingRequest(model, provider, pendingConnId, true, {
|
|
719
|
+
clientEndpoint: clientRawRequest?.endpoint || "/v1/chat/completions",
|
|
720
|
+
clientRequest: clientRawRequest?.body ?? body,
|
|
721
|
+
providerRequest: initialProviderRequest,
|
|
722
|
+
stage: "registered",
|
|
723
|
+
correlationId,
|
|
724
|
+
}) || generateRequestId();
|
|
725
|
+
|
|
726
|
+
// Initialize rate limit settings from persisted DB (once, lazy)
|
|
727
|
+
await initializeRateLimits();
|
|
728
|
+
|
|
729
|
+
const { body: bodyWithWebSearchFallback, fallback: webSearchFallbackPlan } =
|
|
730
|
+
prepareWebSearchFallbackBody(body as Record<string, unknown>, {
|
|
731
|
+
provider,
|
|
732
|
+
sourceFormat,
|
|
733
|
+
targetFormat,
|
|
734
|
+
nativeCodexPassthrough,
|
|
735
|
+
});
|
|
736
|
+
if (webSearchFallbackPlan.enabled) {
|
|
737
|
+
body = bodyWithWebSearchFallback as typeof body;
|
|
738
|
+
log?.info?.(
|
|
739
|
+
"TOOLS",
|
|
740
|
+
`Converted ${webSearchFallbackPlan.convertedToolCount} web_search tool(s) to OmniRoute fallback for ${provider}`
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
const noLogEnabled = apiKeyInfo?.noLog === true;
|
|
744
|
+
// Consolidate settings reads — fetch once, reuse throughout the request
|
|
745
|
+
const settings = cachedSettings ?? (await getCachedSettings());
|
|
746
|
+
// Opt-in tool-source diagnostics (#1825): summarize the request's tool definitions
|
|
747
|
+
// (count + MCP/hosted/client source breakdown + first names) as a single debug line.
|
|
748
|
+
if (settings.logToolSources === true) {
|
|
749
|
+
const toolSummary = summarizeToolSources((body as { tools?: unknown }).tools);
|
|
750
|
+
if (toolSummary) log?.debug?.("TOOLS", toolSummary);
|
|
751
|
+
}
|
|
752
|
+
// #1311 (opt-in): echo the client-requested alias/combo name in the response `model`
|
|
753
|
+
// field instead of the upstream model, so strict clients (Claude Desktop) that validate
|
|
754
|
+
// response.model === request.model stop rejecting alias/combo requests with a 401.
|
|
755
|
+
const echoModel =
|
|
756
|
+
settings.echoRequestedModelName === true && typeof requestedModel === "string" && requestedModel
|
|
757
|
+
? requestedModel
|
|
758
|
+
: null;
|
|
759
|
+
const detailedLoggingEnabled =
|
|
760
|
+
!noLogEnabled &&
|
|
761
|
+
(settings.call_log_pipeline_enabled === true ||
|
|
762
|
+
settings.call_log_pipeline_enabled === "1" ||
|
|
763
|
+
settings.call_log_pipeline_enabled === "true");
|
|
764
|
+
const capturePipelineStreamChunks =
|
|
765
|
+
detailedLoggingEnabled && getCallLogPipelineCaptureStreamChunks();
|
|
766
|
+
const skillRequestId = generateRequestId();
|
|
767
|
+
let compressionAnalyticsWritePromise: Promise<void> | null = null;
|
|
768
|
+
// Compression usage-receipt attachment extracted to chatCore/compressionUsageReceipt.ts (#3501);
|
|
769
|
+
// pass the in-flight analytics write + request id so behaviour stays byte-identical.
|
|
770
|
+
const attachCompressionUsageReceiptAfterAnalytics = (
|
|
771
|
+
usage: Record<string, unknown>,
|
|
772
|
+
source: "provider" | "estimated" | "stream"
|
|
773
|
+
) =>
|
|
774
|
+
attachCompressionUsageReceiptAfterAnalyticsFor(usage, source, {
|
|
775
|
+
pendingWrite: compressionAnalyticsWritePromise,
|
|
776
|
+
skillRequestId,
|
|
777
|
+
});
|
|
778
|
+
const pipelineSessionId =
|
|
779
|
+
(clientRawRequest?.headers && typeof clientRawRequest.headers.get === "function"
|
|
780
|
+
? clientRawRequest.headers.get("x-omniroute-session-id")
|
|
781
|
+
: getHeaderValueCaseInsensitive(
|
|
782
|
+
clientRawRequest?.headers ?? null,
|
|
783
|
+
"x-omniroute-session-id"
|
|
784
|
+
)) || skillRequestId;
|
|
785
|
+
// persistAttemptLogs extracted to chatCore/attemptLogging.ts (#3501); bind the per-request context
|
|
786
|
+
// once so the 16 call sites keep passing only the per-attempt args (byte-identical).
|
|
787
|
+
const persistAttemptLogs = (args: PersistAttemptLogsArgs) =>
|
|
788
|
+
persistAttemptLogsFor(args, {
|
|
789
|
+
provider,
|
|
790
|
+
connectionId,
|
|
791
|
+
model,
|
|
792
|
+
skillRequestId,
|
|
793
|
+
detailedLoggingEnabled,
|
|
794
|
+
reqLogger,
|
|
795
|
+
pendingRequestId,
|
|
796
|
+
clientRawRequest,
|
|
797
|
+
requestedModel,
|
|
798
|
+
credentials,
|
|
799
|
+
startTime,
|
|
800
|
+
body,
|
|
801
|
+
sourceFormat,
|
|
802
|
+
targetFormat,
|
|
803
|
+
comboName,
|
|
804
|
+
comboStepId,
|
|
805
|
+
comboExecutionKey,
|
|
806
|
+
tokensCompressed,
|
|
807
|
+
apiKeyInfo,
|
|
808
|
+
noLogEnabled,
|
|
809
|
+
correlationId,
|
|
810
|
+
modelPinned,
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
// Primary path: merge client model id + alias target so config on either key applies; resolved
|
|
814
|
+
// id wins on same header name. T5 family fallback uses only (nextModel, resolveModelAlias(next))
|
|
815
|
+
// so A-model headers are not sent to B — see buildUpstreamHeadersForExecute.
|
|
816
|
+
const connectionCustomUserAgent =
|
|
817
|
+
credentials?.providerSpecificData &&
|
|
818
|
+
typeof credentials.providerSpecificData === "object" &&
|
|
819
|
+
typeof credentials.providerSpecificData.customUserAgent === "string"
|
|
820
|
+
? credentials.providerSpecificData.customUserAgent.trim()
|
|
821
|
+
: "";
|
|
822
|
+
|
|
823
|
+
// Upstream extra-header building extracted to chatCore/upstreamExecuteHeaders.ts (#3501); bind the
|
|
824
|
+
// per-request inputs once and delegate so the existing call sites stay byte-identical.
|
|
825
|
+
const buildUpstreamHeadersForExecute = (modelToCall: string): Record<string, string> =>
|
|
826
|
+
buildUpstreamHeadersForExecuteFor({
|
|
827
|
+
modelToCall,
|
|
828
|
+
effectiveModel,
|
|
829
|
+
provider,
|
|
830
|
+
model,
|
|
831
|
+
resolvedModel,
|
|
832
|
+
sourceFormat,
|
|
833
|
+
connectionCustomUserAgent,
|
|
834
|
+
settings,
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
// Default to false unless client explicitly sets stream: true (OpenAI spec compliant)
|
|
838
|
+
const acceptHeader =
|
|
839
|
+
clientRawRequest?.headers && typeof clientRawRequest.headers.get === "function"
|
|
840
|
+
? clientRawRequest.headers.get("accept") || clientRawRequest.headers.get("Accept")
|
|
841
|
+
: clientRawRequest?.headers?.["accept"] || clientRawRequest?.headers?.["Accept"];
|
|
842
|
+
const streamUserAgent = [
|
|
843
|
+
typeof userAgent === "string" ? userAgent : "",
|
|
844
|
+
getHeaderValueCaseInsensitive(clientRawRequest?.headers ?? null, "user-agent") || "",
|
|
845
|
+
]
|
|
846
|
+
.filter(Boolean)
|
|
847
|
+
.join(" ");
|
|
848
|
+
|
|
849
|
+
// Explicit per-request opt-in/out for the `</think>` close marker
|
|
850
|
+
// (#5312 / #5245): `x-omniroute-thinking-marker: off` suppresses it for
|
|
851
|
+
// reasoning_content-native clients (e.g. Cursor's OpenAI path) that the UA
|
|
852
|
+
// allowlist does not cover; absent the header, the UA policy applies.
|
|
853
|
+
const thinkingMarkerHeader = getHeaderValueCaseInsensitive(
|
|
854
|
+
clientRawRequest?.headers ?? null,
|
|
855
|
+
THINKING_MARKER_HEADER
|
|
856
|
+
);
|
|
857
|
+
|
|
858
|
+
const explicitStreamAlias = resolveExplicitStreamAlias(body);
|
|
859
|
+
|
|
860
|
+
// Remove non-standard non-stream aliases before provider translation/execution.
|
|
861
|
+
// They are accepted for compatibility at the OmniRoute API boundary only.
|
|
862
|
+
if (body && typeof body === "object") {
|
|
863
|
+
const b = body as Record<string, unknown>;
|
|
864
|
+
if (explicitStreamAlias !== undefined) {
|
|
865
|
+
b.stream = explicitStreamAlias;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
delete b.non_stream;
|
|
869
|
+
delete b.disable_stream;
|
|
870
|
+
delete b.disable_streaming;
|
|
871
|
+
delete b.streaming;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Codex /responses/compact is JSON-only: Codex CLI does not send stream=false,
|
|
875
|
+
// so route shape must override the usual Accept/header fallback.
|
|
876
|
+
// sourceFormat="claude" applies the Anthropic Messages spec default (stream=false
|
|
877
|
+
// when body omits stream), preventing STREAM_EARLY_EOF on /v1/messages when
|
|
878
|
+
// clients send Accept: */* without an explicit stream flag.
|
|
879
|
+
// providerRequiresStreaming: providers with forceStream:true (cline/clinepass)
|
|
880
|
+
// only implement upstream streaming — a non-streaming request returns
|
|
881
|
+
// "generateText is not implemented" / an empty body. This flag forces the
|
|
882
|
+
// UPSTREAM request to stream (see `upstreamStream` below), but it MUST NOT
|
|
883
|
+
// force the client-facing `stream` flag: a stream:false client (e.g. the
|
|
884
|
+
// model-test button, plain JSON API callers) still expects a JSON response.
|
|
885
|
+
// The client-side `if (!stream)` branch drains the forced upstream SSE and
|
|
886
|
+
// converts it back to JSON via readNonStreamingResponseBody. Passing this
|
|
887
|
+
// flag into resolveStreamFlag would force `stream=true` and skip that
|
|
888
|
+
// conversion, yielding STREAM_EARLY_EOF for JSON callers. (#2081, #6126)
|
|
889
|
+
const providerRequiresStreaming = REGISTRY[provider]?.forceStream === true;
|
|
890
|
+
const stream =
|
|
891
|
+
nativeCodexPassthrough && isCompactResponsesEndpoint(endpointPath)
|
|
892
|
+
? false
|
|
893
|
+
: resolveStreamFlag(body?.stream, acceptHeader, sourceFormat, {
|
|
894
|
+
userAgent: streamUserAgent,
|
|
895
|
+
streamDefaultMode: apiKeyInfo?.streamDefaultMode,
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
// `settings` is already consolidated once near the top of handleChatCore
|
|
899
|
+
// (the "fetch once, reuse" const). A second `const settings` here was a
|
|
900
|
+
// duplicate same-scope declaration that broke the esbuild/tsx transform
|
|
901
|
+
// ("settings has already been declared") and the production build. Reuse it.
|
|
902
|
+
credentials = applyCodexGlobalFastServiceTier(provider, credentials, settings, {
|
|
903
|
+
model: requestedModel,
|
|
904
|
+
body: body && typeof body === "object" ? (body as Record<string, unknown>) : null,
|
|
905
|
+
});
|
|
906
|
+
effectiveServiceTier = resolveEffectiveServiceTier(body);
|
|
907
|
+
setGeminiThoughtSignatureMode(settings.antigravitySignatureCacheMode);
|
|
908
|
+
const semanticCacheEnabled = settings.semanticCacheEnabled !== false;
|
|
909
|
+
|
|
910
|
+
const reqLogger = await createRequestLogger(sourceFormat, targetFormat, model, {
|
|
911
|
+
enabled: detailedLoggingEnabled,
|
|
912
|
+
captureStreamChunks: capturePipelineStreamChunks,
|
|
913
|
+
maxStreamChunkBytes: getCallLogPipelineMaxSizeBytes(),
|
|
914
|
+
requestId: pendingRequestId,
|
|
915
|
+
model,
|
|
916
|
+
provider: provider || undefined,
|
|
917
|
+
connectionId: connectionId || credentials?.connectionId || undefined,
|
|
918
|
+
});
|
|
919
|
+
const pendingScope = { id: pendingRequestId, model, provider, connectionId: pendingConnId };
|
|
920
|
+
const providerRequestCapture = createPreparedRequestLogger(reqLogger, pendingScope);
|
|
921
|
+
// 0. Log client raw request (before format conversion)
|
|
922
|
+
if (clientRawRequest) {
|
|
923
|
+
reqLogger.logClientRawRequest(
|
|
924
|
+
clientRawRequest.endpoint,
|
|
925
|
+
clientRawRequest.body,
|
|
926
|
+
clientRawRequest.headers
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
log?.debug?.("FORMAT", `${sourceFormat} → ${targetFormat} | stream=${stream}`);
|
|
931
|
+
|
|
932
|
+
// ── Phase 9.1: Semantic cache check (temp=0, any streaming mode) ──
|
|
933
|
+
const cacheHit = await checkSemanticCache({
|
|
934
|
+
semanticCacheEnabled,
|
|
935
|
+
body,
|
|
936
|
+
clientRawRequest,
|
|
937
|
+
model,
|
|
938
|
+
provider,
|
|
939
|
+
stream: !!stream,
|
|
940
|
+
reqLogger,
|
|
941
|
+
effectiveServiceTier,
|
|
942
|
+
connectionId,
|
|
943
|
+
startTime,
|
|
944
|
+
log,
|
|
945
|
+
persistAttemptLogs,
|
|
946
|
+
apiKeyId: apiKeyInfo?.id ?? undefined,
|
|
947
|
+
});
|
|
948
|
+
if (cacheHit) {
|
|
949
|
+
return cacheHit;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
body = sanitizeChatRequestBody(body, sourceFormat, targetFormat);
|
|
953
|
+
// Per-request opt-out: clients that manage their own context send
|
|
954
|
+
// `x-omniroute-no-memory: true` to skip memory+skills injection (a null owner
|
|
955
|
+
// disables both branches in injectMemoryAndSkills). See PRD-2026-06-19-no-memory-header.
|
|
956
|
+
const memoryOwnerId = isNoMemoryRequested(clientRawRequest?.headers ?? null)
|
|
957
|
+
? null
|
|
958
|
+
: resolveMemoryOwnerId(apiKeyInfo as Record<string, unknown> | null);
|
|
959
|
+
const injectionResult = await injectMemoryAndSkills({
|
|
960
|
+
body,
|
|
961
|
+
memoryOwnerId,
|
|
962
|
+
provider,
|
|
963
|
+
effectiveModel,
|
|
964
|
+
sourceFormat,
|
|
965
|
+
targetFormat,
|
|
966
|
+
backgroundReason,
|
|
967
|
+
log,
|
|
968
|
+
});
|
|
969
|
+
body = injectionResult.body;
|
|
970
|
+
const memorySettings = injectionResult.memorySettings;
|
|
971
|
+
|
|
972
|
+
// Translate request (pass reqLogger for intermediate logging)
|
|
973
|
+
// ── Proactive Context Compression (Phase 4) ──
|
|
974
|
+
// Check if context exceeds 70% of limit and compress proactively before sending to provider.
|
|
975
|
+
// This prevents "prompt too long" errors for large-but-not-full contexts.
|
|
976
|
+
const compressionBody = body
|
|
977
|
+
? adaptBodyForCompression(body as Record<string, unknown>).body
|
|
978
|
+
: null;
|
|
979
|
+
const allMessages = compressionBody?.messages || body?.contents || body?.request?.contents || [];
|
|
980
|
+
let cavemanOutputModeApplied = false;
|
|
981
|
+
let cavemanOutputModeIntensity: string | null = null;
|
|
982
|
+
let preCompressionBody: typeof body | null = null;
|
|
983
|
+
let compressionResponseMeta: string | null = null;
|
|
984
|
+
// Delegated Context Editing (Claude only): captured at the canonical compression
|
|
985
|
+
// settings read below, then threaded to executor.execute() further down. Lives at
|
|
986
|
+
// function scope because the read happens inside the per-message compression block.
|
|
987
|
+
let contextEditingEnabled = false;
|
|
988
|
+
if (body && Array.isArray(allMessages) && allMessages.length > 0) {
|
|
989
|
+
let estimatedTokens = estimateTokens(allMessages);
|
|
990
|
+
const compressionSettingsResult = await resolveCompressionSettings(log);
|
|
991
|
+
const compressionSettings: CompressionConfig | null = compressionSettingsResult.settings;
|
|
992
|
+
const promptCompressionEnabled = compressionSettingsResult.enabled;
|
|
993
|
+
contextEditingEnabled = compressionSettingsResult.contextEditingEnabled;
|
|
994
|
+
|
|
995
|
+
// --- Modular Compression Pipeline (Phase 1 Lite + Phase 2 Standard/Caveman + Phase 3 Aggressive) ---
|
|
996
|
+
// Runs BEFORE the existing reactive compressContext() to proactively reduce tokens.
|
|
997
|
+
try {
|
|
998
|
+
const {
|
|
999
|
+
selectCompressionStrategy,
|
|
1000
|
+
selectCompressionPlan,
|
|
1001
|
+
enginesMapDerivesStackedPipeline,
|
|
1002
|
+
activeComboResolves,
|
|
1003
|
+
applyCompressionAsync,
|
|
1004
|
+
resolveCacheAwareConfig,
|
|
1005
|
+
formatCompressionMeta,
|
|
1006
|
+
buildNamedComboLookup,
|
|
1007
|
+
formatCompressionAnnotation,
|
|
1008
|
+
} = await import("../services/compression/strategySelector.ts");
|
|
1009
|
+
const { trackCompressionStats } = await import("../services/compression/stats.ts");
|
|
1010
|
+
let config: CompressionConfig = compressionSettings ?? {
|
|
1011
|
+
enabled: false,
|
|
1012
|
+
defaultMode: "off",
|
|
1013
|
+
autoTriggerTokens: 0,
|
|
1014
|
+
cacheMinutes: 5,
|
|
1015
|
+
preserveSystemPrompt: true,
|
|
1016
|
+
comboOverrides: {},
|
|
1017
|
+
};
|
|
1018
|
+
if (!promptCompressionEnabled || !compressionSettings) {
|
|
1019
|
+
log?.debug?.("COMPRESSION", "Prompt compression disabled or unavailable");
|
|
1020
|
+
}
|
|
1021
|
+
let compressionComboKey = comboName ?? null;
|
|
1022
|
+
let compressionComboApplied = false;
|
|
1023
|
+
const applyCompressionComboConfig = (
|
|
1024
|
+
compressionCombo: RuntimeCompressionCombo | null,
|
|
1025
|
+
routingOverrideIds: string[] = []
|
|
1026
|
+
): boolean => {
|
|
1027
|
+
if (!compressionCombo || compressionCombo.pipeline.length === 0) return false;
|
|
1028
|
+
const comboLanguagePacks = [
|
|
1029
|
+
...new Set(
|
|
1030
|
+
compressionCombo.languagePacks
|
|
1031
|
+
.map((pack) => pack.trim())
|
|
1032
|
+
.filter((pack) => pack.length > 0)
|
|
1033
|
+
),
|
|
1034
|
+
];
|
|
1035
|
+
const comboOutputIntensity = (
|
|
1036
|
+
["lite", "full", "ultra"].includes(compressionCombo.outputModeIntensity)
|
|
1037
|
+
? compressionCombo.outputModeIntensity
|
|
1038
|
+
: (config.cavemanOutputMode?.intensity ?? "full")
|
|
1039
|
+
) as "lite" | "full" | "ultra";
|
|
1040
|
+
const comboDefaultLanguage =
|
|
1041
|
+
comboLanguagePacks.find((pack) => pack === config.languageConfig?.defaultLanguage) ??
|
|
1042
|
+
comboLanguagePacks[0] ??
|
|
1043
|
+
config.languageConfig?.defaultLanguage ??
|
|
1044
|
+
"en";
|
|
1045
|
+
const comboOverrides = { ...(config.comboOverrides ?? {}) };
|
|
1046
|
+
for (const id of routingOverrideIds) {
|
|
1047
|
+
if (id) comboOverrides[id] = "stacked";
|
|
1048
|
+
}
|
|
1049
|
+
config = {
|
|
1050
|
+
...config,
|
|
1051
|
+
compressionComboId: compressionCombo.id,
|
|
1052
|
+
stackedPipeline: compressionCombo.pipeline,
|
|
1053
|
+
languageConfig: {
|
|
1054
|
+
...(config.languageConfig ?? {
|
|
1055
|
+
enabled: false,
|
|
1056
|
+
defaultLanguage: "en",
|
|
1057
|
+
autoDetect: true,
|
|
1058
|
+
enabledPacks: ["en"],
|
|
1059
|
+
}),
|
|
1060
|
+
enabled: true,
|
|
1061
|
+
defaultLanguage: comboDefaultLanguage,
|
|
1062
|
+
enabledPacks:
|
|
1063
|
+
comboLanguagePacks.length > 0
|
|
1064
|
+
? comboLanguagePacks
|
|
1065
|
+
: (config.languageConfig?.enabledPacks ?? ["en"]),
|
|
1066
|
+
},
|
|
1067
|
+
cavemanOutputMode: {
|
|
1068
|
+
...(config.cavemanOutputMode ?? {
|
|
1069
|
+
enabled: false,
|
|
1070
|
+
intensity: "full",
|
|
1071
|
+
autoClarity: true,
|
|
1072
|
+
}),
|
|
1073
|
+
enabled: compressionCombo.outputMode,
|
|
1074
|
+
intensity: comboOutputIntensity,
|
|
1075
|
+
},
|
|
1076
|
+
comboOverrides,
|
|
1077
|
+
};
|
|
1078
|
+
compressionComboApplied = true;
|
|
1079
|
+
return true;
|
|
1080
|
+
};
|
|
1081
|
+
if (isCombo && comboName) {
|
|
1082
|
+
try {
|
|
1083
|
+
const { getComboByName } = await import("../../src/lib/localDb");
|
|
1084
|
+
let comboConfig = await getComboByName(comboName);
|
|
1085
|
+
if (!comboConfig && comboName.startsWith("combo/")) {
|
|
1086
|
+
comboConfig = await getComboByName(comboName.substring(6));
|
|
1087
|
+
}
|
|
1088
|
+
const comboRuntimeConfig =
|
|
1089
|
+
comboConfig?.config && typeof comboConfig.config === "object"
|
|
1090
|
+
? (comboConfig.config as Record<string, unknown>)
|
|
1091
|
+
: {};
|
|
1092
|
+
const comboMode =
|
|
1093
|
+
typeof comboRuntimeConfig.compressionMode === "string"
|
|
1094
|
+
? comboRuntimeConfig.compressionMode
|
|
1095
|
+
: typeof comboConfig?.compressionOverride === "string"
|
|
1096
|
+
? comboConfig.compressionOverride
|
|
1097
|
+
: null;
|
|
1098
|
+
if (
|
|
1099
|
+
comboMode === "off" ||
|
|
1100
|
+
comboMode === "lite" ||
|
|
1101
|
+
comboMode === "standard" ||
|
|
1102
|
+
comboMode === "aggressive" ||
|
|
1103
|
+
comboMode === "ultra" ||
|
|
1104
|
+
comboMode === "rtk" ||
|
|
1105
|
+
comboMode === "stacked"
|
|
1106
|
+
) {
|
|
1107
|
+
config = {
|
|
1108
|
+
...config,
|
|
1109
|
+
comboOverrides: {
|
|
1110
|
+
...(config.comboOverrides ?? {}),
|
|
1111
|
+
...(comboName ? { [comboName]: comboMode } : {}),
|
|
1112
|
+
...(comboConfig?.id ? { [String(comboConfig.id)]: comboMode } : {}),
|
|
1113
|
+
},
|
|
1114
|
+
};
|
|
1115
|
+
compressionComboKey = comboName;
|
|
1116
|
+
}
|
|
1117
|
+
const routingComboIds = [
|
|
1118
|
+
comboConfig?.id,
|
|
1119
|
+
comboName,
|
|
1120
|
+
comboName.startsWith("combo/") ? comboName.substring(6) : null,
|
|
1121
|
+
].filter((id): id is string => typeof id === "string" && id.length > 0);
|
|
1122
|
+
if (routingComboIds.length > 0) {
|
|
1123
|
+
const { getCompressionComboForRoutingCombo } =
|
|
1124
|
+
await import("../../src/lib/db/compressionCombos.ts");
|
|
1125
|
+
const assignedCompressionCombo =
|
|
1126
|
+
routingComboIds
|
|
1127
|
+
.map((id) => getCompressionComboForRoutingCombo(id))
|
|
1128
|
+
.find((combo) => combo !== null) ?? null;
|
|
1129
|
+
if (
|
|
1130
|
+
applyCompressionComboConfig(
|
|
1131
|
+
assignedCompressionCombo as RuntimeCompressionCombo | null,
|
|
1132
|
+
routingComboIds
|
|
1133
|
+
)
|
|
1134
|
+
) {
|
|
1135
|
+
compressionComboKey = comboName;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
} catch (err) {
|
|
1139
|
+
log?.debug?.(
|
|
1140
|
+
"COMPRESSION",
|
|
1141
|
+
"Combo compression override lookup skipped: " +
|
|
1142
|
+
(err instanceof Error ? err.message : String(err))
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
let namedCombos: Record<string, CompressionPipelineStep[]> = {};
|
|
1147
|
+
try {
|
|
1148
|
+
const { listCompressionCombos } = await import("../../src/lib/db/compressionCombos.ts");
|
|
1149
|
+
namedCombos = buildNamedComboLookup(listCompressionCombos());
|
|
1150
|
+
} catch (err) {
|
|
1151
|
+
log?.debug?.(
|
|
1152
|
+
"COMPRESSION",
|
|
1153
|
+
"Named combos load skipped: " + (err instanceof Error ? err.message : String(err))
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
// Phase 3: per-request override. Unknown values fall through in the resolver (never error).
|
|
1157
|
+
const compressionHeader = resolveCompressionHeader(clientRawRequest?.headers ?? null);
|
|
1158
|
+
if (compressionHeader) {
|
|
1159
|
+
log?.debug?.("COMPRESSION", `x-omniroute-compression header: ${compressionHeader}`);
|
|
1160
|
+
}
|
|
1161
|
+
const modeBeforeOutputTransform = selectCompressionStrategy(
|
|
1162
|
+
config,
|
|
1163
|
+
compressionComboKey,
|
|
1164
|
+
estimatedTokens,
|
|
1165
|
+
body as Record<string, unknown>,
|
|
1166
|
+
{ provider, targetFormat, model: effectiveModel },
|
|
1167
|
+
namedCombos,
|
|
1168
|
+
compressionHeader
|
|
1169
|
+
);
|
|
1170
|
+
if (
|
|
1171
|
+
modeBeforeOutputTransform === "stacked" &&
|
|
1172
|
+
!compressionComboApplied &&
|
|
1173
|
+
!config.compressionComboId &&
|
|
1174
|
+
isBuiltinStackedPipeline(config.stackedPipeline) &&
|
|
1175
|
+
// Don't let the legacy default combo override a panel-configured engines map: when the
|
|
1176
|
+
// operator's explicit engines derive their own stacked pipeline, that pipeline (applied
|
|
1177
|
+
// below from compressionPlan.stackedPipeline) is authoritative. Legacy/backfilled
|
|
1178
|
+
// installs (enginesExplicit false) still fall through to the seeded default combo.
|
|
1179
|
+
!enginesMapDerivesStackedPipeline(config) &&
|
|
1180
|
+
// Never let the legacy seeded default combo shadow the operator's active profile.
|
|
1181
|
+
!activeComboResolves(config, namedCombos)
|
|
1182
|
+
) {
|
|
1183
|
+
try {
|
|
1184
|
+
const { getDefaultCompressionCombo } =
|
|
1185
|
+
await import("../../src/lib/db/compressionCombos.ts");
|
|
1186
|
+
const defaultCompressionCombo = getDefaultCompressionCombo();
|
|
1187
|
+
if (
|
|
1188
|
+
isStackedCompressionCombo(defaultCompressionCombo as RuntimeCompressionCombo | null) &&
|
|
1189
|
+
applyCompressionComboConfig(defaultCompressionCombo as RuntimeCompressionCombo | null)
|
|
1190
|
+
) {
|
|
1191
|
+
log?.debug?.(
|
|
1192
|
+
"COMPRESSION",
|
|
1193
|
+
`Default compression combo applied: ${defaultCompressionCombo?.id}`
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1196
|
+
} catch (err) {
|
|
1197
|
+
log?.debug?.(
|
|
1198
|
+
"COMPRESSION",
|
|
1199
|
+
"Default compression combo lookup skipped: " +
|
|
1200
|
+
(err instanceof Error ? err.message : String(err))
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
// Phase 4A: unified output styles (supersedes cavemanOutputMode via the back-compat shim).
|
|
1205
|
+
let outputStyleResult:
|
|
1206
|
+
import("../services/compression/outputStyles/apply.ts").OutputStylesResult | null = null;
|
|
1207
|
+
if (config.enabled) {
|
|
1208
|
+
try {
|
|
1209
|
+
const { resolveOutputStyleSelection } =
|
|
1210
|
+
await import("../services/compression/outputStyles/backCompat.ts");
|
|
1211
|
+
const selection = resolveOutputStyleSelection(config);
|
|
1212
|
+
if (selection.length > 0) {
|
|
1213
|
+
const { applyOutputStyles } =
|
|
1214
|
+
await import("../services/compression/outputStyles/apply.ts");
|
|
1215
|
+
const outputStyleLanguage =
|
|
1216
|
+
config.languageConfig?.enabled === true
|
|
1217
|
+
? config.languageConfig.defaultLanguage
|
|
1218
|
+
: "en";
|
|
1219
|
+
outputStyleResult = applyOutputStyles(
|
|
1220
|
+
body as Parameters<typeof applyOutputStyles>[0],
|
|
1221
|
+
selection,
|
|
1222
|
+
outputStyleLanguage
|
|
1223
|
+
);
|
|
1224
|
+
if (outputStyleResult.applied) {
|
|
1225
|
+
body = outputStyleResult.body as typeof body;
|
|
1226
|
+
cavemanOutputModeApplied = true;
|
|
1227
|
+
cavemanOutputModeIntensity =
|
|
1228
|
+
outputStyleResult.appliedStyles?.map((s) => `${s.id}:${s.level}`).join(",") ?? null;
|
|
1229
|
+
estimatedTokens = estimateTokens(body?.messages ?? body?.input ?? []);
|
|
1230
|
+
log?.debug?.("COMPRESSION", "Output styles applied");
|
|
1231
|
+
} else if (
|
|
1232
|
+
outputStyleResult.skippedReason &&
|
|
1233
|
+
outputStyleResult.skippedReason !== "no_styles"
|
|
1234
|
+
) {
|
|
1235
|
+
log?.debug?.(
|
|
1236
|
+
"COMPRESSION",
|
|
1237
|
+
`Output styles skipped: ${outputStyleResult.skippedReason}`
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
} catch (err) {
|
|
1242
|
+
log?.debug?.(
|
|
1243
|
+
"COMPRESSION",
|
|
1244
|
+
"Output styles skipped: " + (err instanceof Error ? err.message : String(err))
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
const compressionInputBody = body as Record<string, unknown>;
|
|
1249
|
+
// Adaptive context-budget (Sub-project C): model context window + request max_tokens drive
|
|
1250
|
+
// the budget target. getTokenLimit is already imported; provider/effectiveModel resolved above.
|
|
1251
|
+
const adaptiveModelContextLimit =
|
|
1252
|
+
provider && effectiveModel ? getTokenLimit(provider, effectiveModel) : null;
|
|
1253
|
+
const requestMaxTokens =
|
|
1254
|
+
typeof (compressionInputBody as Record<string, unknown>)?.max_tokens === "number"
|
|
1255
|
+
? ((compressionInputBody as Record<string, unknown>).max_tokens as number)
|
|
1256
|
+
: null;
|
|
1257
|
+
let adaptiveTelemetry:
|
|
1258
|
+
import("../services/compression/adaptiveCompression/types.ts").AdaptiveTelemetry | null =
|
|
1259
|
+
null;
|
|
1260
|
+
const compressionPlan = selectCompressionPlan(
|
|
1261
|
+
config,
|
|
1262
|
+
compressionComboKey,
|
|
1263
|
+
estimatedTokens,
|
|
1264
|
+
compressionInputBody,
|
|
1265
|
+
{ provider, targetFormat, model: effectiveModel },
|
|
1266
|
+
namedCombos,
|
|
1267
|
+
compressionHeader,
|
|
1268
|
+
{
|
|
1269
|
+
modelContextLimit: adaptiveModelContextLimit,
|
|
1270
|
+
requestMaxTokens: requestMaxTokens,
|
|
1271
|
+
onAdaptive: (t) => {
|
|
1272
|
+
adaptiveTelemetry = t;
|
|
1273
|
+
},
|
|
1274
|
+
}
|
|
1275
|
+
);
|
|
1276
|
+
const mode = compressionPlan.mode as CompressionConfig["defaultMode"];
|
|
1277
|
+
if (adaptiveTelemetry && adaptiveTelemetry.fit === false) {
|
|
1278
|
+
log?.warn?.(
|
|
1279
|
+
"COMPRESSION",
|
|
1280
|
+
`adaptive budget-exceeded: target=${adaptiveTelemetry.target} headroomAfter=${adaptiveTelemetry.headroomAfter} stages=${adaptiveTelemetry.stagesApplied.join(",")} (best-effort plan sent, content preserved)`
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
compressionResponseMeta = formatCompressionMeta(compressionPlan);
|
|
1284
|
+
// When the per-engine toggle map derives a stacked pipeline (and no named/routing
|
|
1285
|
+
// combo already set config.stackedPipeline), feed that derived pipeline through so
|
|
1286
|
+
// applyCompressionAsync (which reads config.stackedPipeline for stacked mode) runs the
|
|
1287
|
+
// engines the operator actually toggled on instead of the built-in rtk+caveman default.
|
|
1288
|
+
if (
|
|
1289
|
+
mode === "stacked" &&
|
|
1290
|
+
compressionPlan.stackedPipeline.length > 0 &&
|
|
1291
|
+
!compressionComboApplied &&
|
|
1292
|
+
!config.compressionComboId
|
|
1293
|
+
) {
|
|
1294
|
+
config = {
|
|
1295
|
+
...config,
|
|
1296
|
+
stackedPipeline: compressionPlan.stackedPipeline as CompressionConfig["stackedPipeline"],
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
let compressionAnalyticsRecorded = false;
|
|
1300
|
+
if (mode !== "off") {
|
|
1301
|
+
// #3890: in a caching context, never compress the system prompt (cacheable prefix)
|
|
1302
|
+
// even if the operator disabled preserveSystemPrompt — honors the cache-aware flag
|
|
1303
|
+
// that selectCompressionStrategy can only partially apply via the mode string.
|
|
1304
|
+
const cacheCtx = { provider, targetFormat, model: effectiveModel };
|
|
1305
|
+
const compressionConfig = resolveCacheAwareConfig(config, compressionInputBody, cacheCtx);
|
|
1306
|
+
const result = await applyCompressionAsync(compressionInputBody, mode, {
|
|
1307
|
+
model: effectiveModel,
|
|
1308
|
+
config: compressionConfig,
|
|
1309
|
+
cachingContext: cacheCtx,
|
|
1310
|
+
principalId: apiKeyInfo?.id ? String(apiKeyInfo.id) : undefined,
|
|
1311
|
+
// F3.3: stream per-engine progress live (best-effort) before compression.completed.
|
|
1312
|
+
onEngineStep: (s) => {
|
|
1313
|
+
try {
|
|
1314
|
+
const stepPayload = {
|
|
1315
|
+
requestId: traceId,
|
|
1316
|
+
comboId: null,
|
|
1317
|
+
mode,
|
|
1318
|
+
stepIndex: s.stepIndex,
|
|
1319
|
+
totalSteps: s.totalSteps,
|
|
1320
|
+
engine: s.engine,
|
|
1321
|
+
state: s.state,
|
|
1322
|
+
originalTokens: s.originalTokens,
|
|
1323
|
+
compressedTokens: s.compressedTokens,
|
|
1324
|
+
savingsPercent: s.savingsPercent,
|
|
1325
|
+
...(s.durationMs !== undefined ? { durationMs: s.durationMs } : {}),
|
|
1326
|
+
timestamp: Date.now(),
|
|
1327
|
+
};
|
|
1328
|
+
emit("compression.step", stepPayload);
|
|
1329
|
+
void forwardDashboardEventToLiveWs("compression.step", stepPayload);
|
|
1330
|
+
} catch (_stepErr) {
|
|
1331
|
+
// best-effort live event — never fail the request
|
|
1332
|
+
}
|
|
1333
|
+
},
|
|
1334
|
+
});
|
|
1335
|
+
if (result.stats) {
|
|
1336
|
+
const annotation = formatCompressionAnnotation(result.stats);
|
|
1337
|
+
if (annotation) {
|
|
1338
|
+
compressionResponseMeta = `${compressionResponseMeta}; ${annotation}`;
|
|
1339
|
+
}
|
|
1340
|
+
if (result.compressed) {
|
|
1341
|
+
body = result.body as typeof body;
|
|
1342
|
+
estimatedTokens = result.stats.compressedTokens;
|
|
1343
|
+
tokensCompressed = Math.max(
|
|
1344
|
+
0,
|
|
1345
|
+
result.stats.originalTokens - result.stats.compressedTokens
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// Fire-and-forget: emit live compression event for dashboard (U5).
|
|
1350
|
+
// Guard: only emit when compression actually ran and produced stats.
|
|
1351
|
+
if (result.compressed && result.stats) {
|
|
1352
|
+
try {
|
|
1353
|
+
const compressionCompletedPayload = {
|
|
1354
|
+
requestId: traceId,
|
|
1355
|
+
comboId: result.stats.compressionComboId ?? null,
|
|
1356
|
+
mode,
|
|
1357
|
+
originalTokens: result.stats.originalTokens,
|
|
1358
|
+
compressedTokens: result.stats.compressedTokens,
|
|
1359
|
+
savingsPercent: result.stats.savingsPercent,
|
|
1360
|
+
// Single-engine modes leave engineBreakdown empty; synthesize a 1-entry
|
|
1361
|
+
// breakdown so the studio shows a real engine node instead of an empty pipeline.
|
|
1362
|
+
engineBreakdown: ensureEngineBreakdown(result.stats),
|
|
1363
|
+
validationWarnings: result.stats.validationWarnings,
|
|
1364
|
+
fallbackApplied: result.stats.fallbackApplied,
|
|
1365
|
+
...(adaptiveTelemetry ? { adaptive: adaptiveTelemetry } : {}),
|
|
1366
|
+
timestamp: Date.now(),
|
|
1367
|
+
};
|
|
1368
|
+
emit("compression.completed", compressionCompletedPayload);
|
|
1369
|
+
void forwardDashboardEventToLiveWs(
|
|
1370
|
+
"compression.completed",
|
|
1371
|
+
compressionCompletedPayload
|
|
1372
|
+
);
|
|
1373
|
+
} catch (_emitErr) {
|
|
1374
|
+
// never propagate into the hot path — but log like the sibling
|
|
1375
|
+
// fire-and-forget blocks so a throwing event bus isn't fully silent.
|
|
1376
|
+
log?.debug?.(
|
|
1377
|
+
"COMPRESSION",
|
|
1378
|
+
"compression.completed emit skipped: " +
|
|
1379
|
+
(_emitErr instanceof Error ? _emitErr.message : String(_emitErr))
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
if (result.compressed || result.stats.fallbackApplied || cavemanOutputModeApplied) {
|
|
1385
|
+
trackCompressionStats(result.stats);
|
|
1386
|
+
compressionAnalyticsRecorded = true;
|
|
1387
|
+
compressionAnalyticsWritePromise = writeCompressionAnalytics({
|
|
1388
|
+
stats: result.stats,
|
|
1389
|
+
provider,
|
|
1390
|
+
effectiveModel,
|
|
1391
|
+
effectiveServiceTier,
|
|
1392
|
+
comboName,
|
|
1393
|
+
mode,
|
|
1394
|
+
compressionComboId: config.compressionComboId,
|
|
1395
|
+
skillRequestId,
|
|
1396
|
+
cavemanOutputModeApplied,
|
|
1397
|
+
cavemanOutputModeIntensity,
|
|
1398
|
+
log,
|
|
1399
|
+
});
|
|
1400
|
+
} else {
|
|
1401
|
+
// Compression was attempted (mode active, engines ran) but produced no
|
|
1402
|
+
// recordable saving — e.g. a Stacked RTK→Caveman pipeline on already-compact
|
|
1403
|
+
// context. Record a skip row so analytics can distinguish "ran but saved
|
|
1404
|
+
// nothing" from "never ran" instead of dropping it silently (#4268).
|
|
1405
|
+
compressionAnalyticsRecorded = true;
|
|
1406
|
+
compressionAnalyticsWritePromise = writeCompressionSkip(
|
|
1407
|
+
{
|
|
1408
|
+
stats: result.stats,
|
|
1409
|
+
provider,
|
|
1410
|
+
effectiveModel,
|
|
1411
|
+
effectiveServiceTier,
|
|
1412
|
+
comboName,
|
|
1413
|
+
mode,
|
|
1414
|
+
compressionComboId: config.compressionComboId,
|
|
1415
|
+
skillRequestId,
|
|
1416
|
+
cavemanOutputModeApplied,
|
|
1417
|
+
cavemanOutputModeIntensity,
|
|
1418
|
+
log,
|
|
1419
|
+
},
|
|
1420
|
+
"no_savings"
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
if (result.compressed) {
|
|
1425
|
+
recordCompressionCacheStats({
|
|
1426
|
+
compressionInputBody,
|
|
1427
|
+
provider,
|
|
1428
|
+
targetFormat,
|
|
1429
|
+
effectiveModel,
|
|
1430
|
+
mode,
|
|
1431
|
+
stats: result.stats,
|
|
1432
|
+
log,
|
|
1433
|
+
});
|
|
1434
|
+
log?.info?.(
|
|
1435
|
+
"COMPRESSION",
|
|
1436
|
+
`Prompt compressed (${mode}): ${result.stats.originalTokens} -> ${result.stats.compressedTokens} tokens (${result.stats.savingsPercent}% saved, techniques: ${result.stats.techniquesUsed.join(",")})`
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
if (cavemanOutputModeApplied && !compressionAnalyticsRecorded) {
|
|
1442
|
+
compressionAnalyticsWritePromise = writeCavemanOutputAnalytics({
|
|
1443
|
+
comboName,
|
|
1444
|
+
provider,
|
|
1445
|
+
compressionComboId: config.compressionComboId,
|
|
1446
|
+
estimatedTokens,
|
|
1447
|
+
skillRequestId,
|
|
1448
|
+
cavemanOutputModeIntensity,
|
|
1449
|
+
log,
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
emitOutputStyleTelemetry({
|
|
1453
|
+
outputStyleResult,
|
|
1454
|
+
skillRequestId,
|
|
1455
|
+
traceId,
|
|
1456
|
+
effectiveModel,
|
|
1457
|
+
provider,
|
|
1458
|
+
compressionComboId: config.compressionComboId,
|
|
1459
|
+
estimatedTokens,
|
|
1460
|
+
log,
|
|
1461
|
+
});
|
|
1462
|
+
} catch (err) {
|
|
1463
|
+
log?.warn?.(
|
|
1464
|
+
"COMPRESSION",
|
|
1465
|
+
"Compression pipeline error (non-fatal): " +
|
|
1466
|
+
(err instanceof Error ? err.message : String(err))
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
// --- End Modular Compression Pipeline ---
|
|
1470
|
+
|
|
1471
|
+
if (!promptCompressionEnabled) {
|
|
1472
|
+
log?.debug?.(
|
|
1473
|
+
"CONTEXT",
|
|
1474
|
+
"Skipping proactive context compression: Prompt Compression disabled"
|
|
1475
|
+
);
|
|
1476
|
+
}
|
|
1477
|
+
let contextLimit = getTokenLimit(provider, effectiveModel);
|
|
1478
|
+
|
|
1479
|
+
if (isCombo && comboName) {
|
|
1480
|
+
log?.info?.("CONTEXT", `Attempting to resolve combo limits for comboName=${comboName}`);
|
|
1481
|
+
try {
|
|
1482
|
+
const { getComboByName } = await import("../../src/lib/localDb");
|
|
1483
|
+
const { parseModel } = await import("../services/model.ts");
|
|
1484
|
+
const { resolveComboTargets } = await import("../services/combo.ts");
|
|
1485
|
+
let comboConfig = await getComboByName(comboName);
|
|
1486
|
+
if (!comboConfig && comboName.startsWith("combo/")) {
|
|
1487
|
+
comboConfig = await getComboByName(comboName.substring(6));
|
|
1488
|
+
}
|
|
1489
|
+
let comboTargetLimits: number[] = [];
|
|
1490
|
+
if (comboConfig) {
|
|
1491
|
+
const allCombosData = await getCombosCached();
|
|
1492
|
+
const targets = resolveComboTargets(
|
|
1493
|
+
comboConfig as unknown as { name: string; models: unknown[] },
|
|
1494
|
+
allCombosData as unknown as { name: string; models: unknown[] }[]
|
|
1495
|
+
);
|
|
1496
|
+
comboTargetLimits = targets.map((t: { modelStr?: string }) => {
|
|
1497
|
+
const parsed = parseModel(t.modelStr);
|
|
1498
|
+
return getTokenLimit(parsed.provider, parsed.model);
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1501
|
+
// chatCore executes per concrete target (handleSingleModel resolves
|
|
1502
|
+
// provider/effectiveModel before delegating). Compress against THIS
|
|
1503
|
+
// target's window; min(...allTargets) is only a defensive fallback —
|
|
1504
|
+
// the old unconditional min compressed a 1M-target request at the
|
|
1505
|
+
// smallest sibling's window ("agent keeps forgetting things").
|
|
1506
|
+
const resolved = resolveComboContextLimit({
|
|
1507
|
+
provider,
|
|
1508
|
+
model: effectiveModel,
|
|
1509
|
+
comboTargetLimits,
|
|
1510
|
+
});
|
|
1511
|
+
contextLimit = resolved.limit;
|
|
1512
|
+
log?.info?.(
|
|
1513
|
+
"CONTEXT",
|
|
1514
|
+
`Combo context limit: ${resolved.limit} (source=${resolved.source})`
|
|
1515
|
+
);
|
|
1516
|
+
} catch (err) {
|
|
1517
|
+
log?.warn?.("CONTEXT", "Failed to resolve combo limits for compression: " + err);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
const COMPRESSION_THRESHOLD = 0.7;
|
|
1522
|
+
let reservedTokens = 0;
|
|
1523
|
+
if (Array.isArray(body.tools)) {
|
|
1524
|
+
reservedTokens = estimateTokens(body.tools);
|
|
1525
|
+
}
|
|
1526
|
+
const threshold = Math.max(
|
|
1527
|
+
1,
|
|
1528
|
+
Math.floor((Math.max(1, contextLimit) - reservedTokens) * COMPRESSION_THRESHOLD)
|
|
1529
|
+
);
|
|
1530
|
+
|
|
1531
|
+
log?.debug?.(
|
|
1532
|
+
"CONTEXT",
|
|
1533
|
+
`Checking compression: ${estimatedTokens} tokens vs ${threshold} threshold (${contextLimit} limit, ${reservedTokens} reserved)`
|
|
1534
|
+
);
|
|
1535
|
+
|
|
1536
|
+
// Capture pre-compression body so translators can access original message
|
|
1537
|
+
// content even after compression alters it (e.g. stable Kiro conversationId).
|
|
1538
|
+
preCompressionBody = body;
|
|
1539
|
+
|
|
1540
|
+
if (promptCompressionEnabled && estimatedTokens > threshold) {
|
|
1541
|
+
log?.info?.(
|
|
1542
|
+
"CONTEXT",
|
|
1543
|
+
`Proactive compression triggered: ${estimatedTokens} tokens > ${threshold} threshold (${contextLimit} limit)`
|
|
1544
|
+
);
|
|
1545
|
+
|
|
1546
|
+
const compressionResult = compressContext(body, {
|
|
1547
|
+
provider,
|
|
1548
|
+
model: effectiveModel,
|
|
1549
|
+
maxTokens: threshold,
|
|
1550
|
+
reserveTokens: 0,
|
|
1551
|
+
});
|
|
1552
|
+
|
|
1553
|
+
if (compressionResult.compressed) {
|
|
1554
|
+
body = compressionResult.body;
|
|
1555
|
+
const stats = compressionResult.stats;
|
|
1556
|
+
tokensCompressed = Math.max(0, (stats?.original ?? 0) - (stats?.final ?? 0));
|
|
1557
|
+
const layersInfo =
|
|
1558
|
+
stats && "layers" in stats && Array.isArray(stats.layers)
|
|
1559
|
+
? ` (layers: ${stats.layers.map((l: { name: string }) => l.name).join(", ")})`
|
|
1560
|
+
: "";
|
|
1561
|
+
|
|
1562
|
+
log?.info?.(
|
|
1563
|
+
"CONTEXT",
|
|
1564
|
+
`Context compressed: ${stats.original} → ${stats.final} tokens${layersInfo}`
|
|
1565
|
+
);
|
|
1566
|
+
|
|
1567
|
+
logAuditEvent({
|
|
1568
|
+
action: "context.proactive_compression",
|
|
1569
|
+
actor: apiKeyInfo?.name || "system",
|
|
1570
|
+
target: connectionId || provider || "chat",
|
|
1571
|
+
details: {
|
|
1572
|
+
provider,
|
|
1573
|
+
model: effectiveModel,
|
|
1574
|
+
original_tokens: stats.original,
|
|
1575
|
+
final_tokens: stats.final,
|
|
1576
|
+
layers: "layers" in stats ? stats.layers : undefined,
|
|
1577
|
+
},
|
|
1578
|
+
});
|
|
1579
|
+
} else {
|
|
1580
|
+
log?.debug?.("CONTEXT", `Compression not applied: context already fits within target`);
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
} else {
|
|
1584
|
+
log?.debug?.(
|
|
1585
|
+
"CONTEXT",
|
|
1586
|
+
`Skipping compression check: body=${!!body}, hasMessages=${Array.isArray(allMessages)}`
|
|
1587
|
+
);
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
let translatedBody = body;
|
|
1591
|
+
const isClaudePassthrough = sourceFormat === FORMATS.CLAUDE && targetFormat === FORMATS.CLAUDE;
|
|
1592
|
+
const isClaudeCodeCompatible = isClaudeCodeCompatibleProvider(provider);
|
|
1593
|
+
const isClaudeCodeSemanticPassthrough = isClaudeCodeSemanticPassthroughRequest({
|
|
1594
|
+
provider,
|
|
1595
|
+
sourceFormat,
|
|
1596
|
+
targetFormat,
|
|
1597
|
+
headers: clientRawRequest?.headers,
|
|
1598
|
+
userAgent,
|
|
1599
|
+
});
|
|
1600
|
+
// `forceStream` providers (e.g. Cline / ClinePass) only implement upstream
|
|
1601
|
+
// streaming — a non-streaming request returns "generateText is not implemented"
|
|
1602
|
+
// / an empty body. Force the upstream request to stream even when the client
|
|
1603
|
+
// wants JSON; the non-streaming branch below accumulates the SSE and converts
|
|
1604
|
+
// it back to JSON (same mechanism already used for Claude-Code-compatible
|
|
1605
|
+
// providers via isClaudeCodeCompatible).
|
|
1606
|
+
const upstreamStream = stream || isClaudeCodeCompatible || providerRequiresStreaming;
|
|
1607
|
+
let ccSessionId: string | null = null;
|
|
1608
|
+
const stripTypes = getStripTypesForProviderModel(provider || "", model || "");
|
|
1609
|
+
|
|
1610
|
+
if (Array.isArray(translatedBody?.messages) && stripTypes.length > 0) {
|
|
1611
|
+
const stripResult = stripIncompatibleMessageContent(translatedBody.messages, stripTypes);
|
|
1612
|
+
if (stripResult.removedParts > 0) {
|
|
1613
|
+
translatedBody = {
|
|
1614
|
+
...translatedBody,
|
|
1615
|
+
messages: stripResult.messages,
|
|
1616
|
+
};
|
|
1617
|
+
log?.warn?.(
|
|
1618
|
+
"CONTENT",
|
|
1619
|
+
`Stripped ${stripResult.removedParts} incompatible content part(s) for ${provider}/${model}`
|
|
1620
|
+
);
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
// Determine if we should preserve client-side cache_control headers
|
|
1625
|
+
// Fetch settings from DB to get user preference
|
|
1626
|
+
const cacheControlMode = await getCacheControlSettings().catch(() => "auto" as const);
|
|
1627
|
+
const preserveCacheControl = shouldPreserveCacheControl({
|
|
1628
|
+
userAgent,
|
|
1629
|
+
isCombo,
|
|
1630
|
+
comboStrategy,
|
|
1631
|
+
targetProvider: provider,
|
|
1632
|
+
targetFormat,
|
|
1633
|
+
settings: { alwaysPreserveClientCache: cacheControlMode },
|
|
1634
|
+
});
|
|
1635
|
+
|
|
1636
|
+
if (preserveCacheControl) {
|
|
1637
|
+
log?.debug?.(
|
|
1638
|
+
"CACHE",
|
|
1639
|
+
`Preserving client cache_control (client=${userAgent?.substring(0, 20)}, combo=${isCombo}, strategy=${comboStrategy}, provider=${provider})`
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
// extractSystemMessagesToBody + normalizeClaudeUpstreamMessages extracted to
|
|
1644
|
+
// chatCore/claudeUpstreamMessages.ts (#3501); bind `log` once so the call sites stay byte-identical.
|
|
1645
|
+
const normalizeClaudeUpstreamMessages = (
|
|
1646
|
+
payload: Record<string, unknown>,
|
|
1647
|
+
options?: { preserveToolResultBlocks?: boolean }
|
|
1648
|
+
) => normalizeClaudeUpstreamMessagesFor(payload, options, log);
|
|
1649
|
+
|
|
1650
|
+
try {
|
|
1651
|
+
if (nativeCodexPassthrough) {
|
|
1652
|
+
translatedBody = { ...body, _nativeCodexPassthrough: true };
|
|
1653
|
+
log?.debug?.("FORMAT", "native codex passthrough enabled");
|
|
1654
|
+
} else if (isClaudeCodeCompatible) {
|
|
1655
|
+
let normalizedForCc = { ...body };
|
|
1656
|
+
|
|
1657
|
+
// Claude Code-compatible providers expect Anthropic Messages-shaped payloads,
|
|
1658
|
+
// but we extract only role/text/max_tokens/effort from an OpenAI-like view first.
|
|
1659
|
+
if (sourceFormat === FORMATS.CLAUDE && isClaudeCodeSemanticPassthrough) {
|
|
1660
|
+
log?.debug?.("FORMAT", "claude-code semantic passthrough enabled for compatible bridge");
|
|
1661
|
+
} else if (sourceFormat !== FORMATS.OPENAI) {
|
|
1662
|
+
const normalizeToolCallId = getModelNormalizeToolCallId(
|
|
1663
|
+
provider || "",
|
|
1664
|
+
model || "",
|
|
1665
|
+
sourceFormat
|
|
1666
|
+
);
|
|
1667
|
+
const preserveDeveloperRole = getModelPreserveOpenAIDeveloperRole(
|
|
1668
|
+
provider || "",
|
|
1669
|
+
model || "",
|
|
1670
|
+
sourceFormat
|
|
1671
|
+
);
|
|
1672
|
+
normalizedForCc = translateRequest(
|
|
1673
|
+
sourceFormat,
|
|
1674
|
+
FORMATS.OPENAI,
|
|
1675
|
+
model,
|
|
1676
|
+
{ ...body },
|
|
1677
|
+
stream,
|
|
1678
|
+
credentials,
|
|
1679
|
+
provider,
|
|
1680
|
+
reqLogger,
|
|
1681
|
+
{
|
|
1682
|
+
normalizeToolCallId,
|
|
1683
|
+
preserveDeveloperRole,
|
|
1684
|
+
preserveCacheControl,
|
|
1685
|
+
copilotClient: copilotCompatibleReasoning,
|
|
1686
|
+
}
|
|
1687
|
+
);
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
ccSessionId = resolveClaudeCodeCompatibleSessionId(clientRawRequest?.headers);
|
|
1691
|
+
const ccRequestDefaults = getClaudeCodeCompatibleRequestDefaults(
|
|
1692
|
+
credentials?.providerSpecificData
|
|
1693
|
+
);
|
|
1694
|
+
translatedBody = buildClaudeCodeCompatibleRequest({
|
|
1695
|
+
sourceBody: body,
|
|
1696
|
+
normalizedBody: normalizedForCc,
|
|
1697
|
+
claudeBody: sourceFormat === FORMATS.CLAUDE ? body : null,
|
|
1698
|
+
model,
|
|
1699
|
+
stream: upstreamStream,
|
|
1700
|
+
sessionId: ccSessionId,
|
|
1701
|
+
cwd: process.cwd(),
|
|
1702
|
+
now: new Date(),
|
|
1703
|
+
preserveCacheControl,
|
|
1704
|
+
preserveClaudeMessages: sourceFormat === FORMATS.CLAUDE && isClaudeCodeSemanticPassthrough,
|
|
1705
|
+
summarizeThinking: ccRequestDefaults.summarizeThinking === true,
|
|
1706
|
+
});
|
|
1707
|
+
log?.debug?.("FORMAT", "claude-code-compatible bridge enabled");
|
|
1708
|
+
|
|
1709
|
+
if (isClaudeCodeSemanticPassthrough) {
|
|
1710
|
+
// Semantic passthrough: only lift system/developer role messages
|
|
1711
|
+
// without converting file/document blocks, tool history, etc.
|
|
1712
|
+
extractSystemRoleMessages(translatedBody);
|
|
1713
|
+
} else {
|
|
1714
|
+
// Non-CC path: full normalization including content type conversion.
|
|
1715
|
+
normalizeClaudeUpstreamMessages(translatedBody, { preserveToolResultBlocks: true });
|
|
1716
|
+
}
|
|
1717
|
+
} else if (isClaudePassthrough) {
|
|
1718
|
+
// Pure passthrough: forward the body as-is without OpenAI round-trip.
|
|
1719
|
+
// The Claude→OpenAI→Claude double translation was lossy and corrupted
|
|
1720
|
+
// payloads at high context (150+ msgs, 100+ tools). Fix: #1359.
|
|
1721
|
+
// Claude Code sends well-formed Messages API payloads — trust them
|
|
1722
|
+
// regardless of combo strategy or cache_control settings.
|
|
1723
|
+
translatedBody = { ...body };
|
|
1724
|
+
translatedBody._disableToolPrefix = true;
|
|
1725
|
+
|
|
1726
|
+
// Sanitize historical thinking-block signatures for Anthropic-native Claude OAuth.
|
|
1727
|
+
// Only Anthropic's first-party API validates these signatures (token-bound); third-party
|
|
1728
|
+
// Claude-shape providers do not. See redactPassthroughThinkingSignatures + issue #2454.
|
|
1729
|
+
if (provider === "claude") {
|
|
1730
|
+
translatedBody.messages = redactPassthroughThinkingSignatures(
|
|
1731
|
+
translatedBody.messages,
|
|
1732
|
+
DEFAULT_THINKING_CLAUDE_SIGNATURE
|
|
1733
|
+
) as typeof translatedBody.messages;
|
|
1734
|
+
|
|
1735
|
+
// Anthropic API rejects requests with both temperature and top_p.
|
|
1736
|
+
// VS Code Claude extension and similar clients send both; strip top_p.
|
|
1737
|
+
if (translatedBody.temperature !== undefined && translatedBody.top_p !== undefined) {
|
|
1738
|
+
delete translatedBody.top_p;
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
// Fix #2468: always extract role:"system" → top-level system.
|
|
1743
|
+
// The semantic passthrough correctly skips the Claude→OpenAI→Claude
|
|
1744
|
+
// round-trip, but even pure Claude bodies may carry system content as
|
|
1745
|
+
// role:"system" messages rather than the top-level system field, which
|
|
1746
|
+
// Anthropic's Messages API now rejects with a 400.
|
|
1747
|
+
if (isClaudeCodeSemanticPassthrough) {
|
|
1748
|
+
// Only lift system/developer messages — preserves Claude Code's
|
|
1749
|
+
// native payload structure (documents, tool chains, thinking, etc.)
|
|
1750
|
+
extractSystemRoleMessages(translatedBody);
|
|
1751
|
+
if (Array.isArray(translatedBody.messages)) {
|
|
1752
|
+
translatedBody.messages = splitMisplacedToolResults(
|
|
1753
|
+
translatedBody.messages as ClaudeMessage[]
|
|
1754
|
+
) as typeof translatedBody.messages;
|
|
1755
|
+
}
|
|
1756
|
+
} else {
|
|
1757
|
+
normalizeClaudeUpstreamMessages(translatedBody, { preserveToolResultBlocks: true });
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
log?.debug?.("FORMAT", `claude passthrough (preserveCache=${preserveCacheControl})`);
|
|
1761
|
+
|
|
1762
|
+
// Migrate deprecated top-level `output_format` → `output_config.format`.
|
|
1763
|
+
// Anthropic returns a 400 on the legacy field; some clients (e.g. ForgeCode)
|
|
1764
|
+
// still emit it. Preserves an existing output_config.format if present.
|
|
1765
|
+
if (translatedBody.output_format !== undefined) {
|
|
1766
|
+
const oc =
|
|
1767
|
+
translatedBody.output_config && typeof translatedBody.output_config === "object"
|
|
1768
|
+
? (translatedBody.output_config as Record<string, unknown>)
|
|
1769
|
+
: {};
|
|
1770
|
+
if (oc.format === undefined) oc.format = translatedBody.output_format;
|
|
1771
|
+
translatedBody.output_config = oc;
|
|
1772
|
+
delete translatedBody.output_format;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
// Fix #1719: Strip output_config.format for non-Anthropic Claude-compatible providers.
|
|
1776
|
+
// Third-party Claude endpoints (MiniMax, DeepSeek via aggregators) reject this field
|
|
1777
|
+
// with 400 errors since they don't support Anthropic's structured output / json_schema.
|
|
1778
|
+
if (
|
|
1779
|
+
provider !== "claude" &&
|
|
1780
|
+
translatedBody.output_config &&
|
|
1781
|
+
typeof translatedBody.output_config === "object"
|
|
1782
|
+
) {
|
|
1783
|
+
const oc = translatedBody.output_config as Record<string, unknown>;
|
|
1784
|
+
delete oc.format;
|
|
1785
|
+
if (Object.keys(oc).length === 0) {
|
|
1786
|
+
delete translatedBody.output_config;
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
} else {
|
|
1790
|
+
translatedBody = { ...body };
|
|
1791
|
+
|
|
1792
|
+
// Issue #199 + #618: Always disable tool name prefix in Claude passthrough.
|
|
1793
|
+
// The proxy_ prefix was designed for OpenAI→Claude translation to avoid
|
|
1794
|
+
// conflicts with Claude OAuth tools, but in the passthrough path the tools
|
|
1795
|
+
// are already in Claude format. Applying the prefix turns "Bash" into
|
|
1796
|
+
// "proxy_Bash", which Claude rejects ("No such tool available: proxy_Bash").
|
|
1797
|
+
if (targetFormat === FORMATS.CLAUDE) {
|
|
1798
|
+
translatedBody._disableToolPrefix = true;
|
|
1799
|
+
normalizeClaudeUpstreamMessages(translatedBody);
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
// OpenAI-compatible providers only support function tools.
|
|
1803
|
+
// Non-function tool types (computer, mcp, web_search, custom, etc.) are handled:
|
|
1804
|
+
// - tools with a name → converted to function format in-place before translation
|
|
1805
|
+
// - tools without a name AND without .function → dropped (unconvertible)
|
|
1806
|
+
// This must happen before translateRequest, which validates and throws on unknown types.
|
|
1807
|
+
if (provider?.startsWith("openai-compatible-") && Array.isArray(translatedBody.tools)) {
|
|
1808
|
+
const before = (translatedBody.tools as unknown[]).length;
|
|
1809
|
+
translatedBody.tools = (translatedBody.tools as Record<string, unknown>[])
|
|
1810
|
+
.filter((t) => !t.type || t.type === "function" || !!t.function || !!t.name)
|
|
1811
|
+
.map((t) => {
|
|
1812
|
+
if (!t.type || t.type === "function" || t.function) return t;
|
|
1813
|
+
// Named non-function tool: normalise to function format so the translator
|
|
1814
|
+
// does not throw on the unknown type.
|
|
1815
|
+
return {
|
|
1816
|
+
type: "function",
|
|
1817
|
+
function: {
|
|
1818
|
+
name: t.name,
|
|
1819
|
+
...(t.description === undefined ? {} : { description: t.description }),
|
|
1820
|
+
...(t.parameters !== undefined || t.input_schema !== undefined
|
|
1821
|
+
? { parameters: t.parameters ?? t.input_schema ?? {} }
|
|
1822
|
+
: {}),
|
|
1823
|
+
...(t.strict === undefined ? {} : { strict: t.strict }),
|
|
1824
|
+
},
|
|
1825
|
+
};
|
|
1826
|
+
});
|
|
1827
|
+
const dropped = before - (translatedBody.tools as unknown[]).length;
|
|
1828
|
+
if (dropped > 0) {
|
|
1829
|
+
log?.debug?.(
|
|
1830
|
+
"TOOLS",
|
|
1831
|
+
`Dropped ${dropped} unconvertible tool(s) for openai-compatible provider`
|
|
1832
|
+
);
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
const normalizeToolCallId = getModelNormalizeToolCallId(
|
|
1837
|
+
provider || "",
|
|
1838
|
+
model || "",
|
|
1839
|
+
sourceFormat
|
|
1840
|
+
);
|
|
1841
|
+
const preserveDeveloperRole = getModelPreserveOpenAIDeveloperRole(
|
|
1842
|
+
provider || "",
|
|
1843
|
+
model || "",
|
|
1844
|
+
sourceFormat
|
|
1845
|
+
);
|
|
1846
|
+
translatedBody = translateRequest(
|
|
1847
|
+
sourceFormat,
|
|
1848
|
+
targetFormat,
|
|
1849
|
+
model,
|
|
1850
|
+
translatedBody,
|
|
1851
|
+
stream,
|
|
1852
|
+
credentials,
|
|
1853
|
+
provider,
|
|
1854
|
+
reqLogger,
|
|
1855
|
+
{
|
|
1856
|
+
normalizeToolCallId,
|
|
1857
|
+
preserveDeveloperRole,
|
|
1858
|
+
preserveCacheControl,
|
|
1859
|
+
signatureNamespace: connectionId,
|
|
1860
|
+
copilotClient: copilotCompatibleReasoning,
|
|
1861
|
+
...(preCompressionBody ? { preCompressionBody } : {}),
|
|
1862
|
+
}
|
|
1863
|
+
);
|
|
1864
|
+
}
|
|
1865
|
+
} catch (error) {
|
|
1866
|
+
// ── Plugin onError hook ──
|
|
1867
|
+
try {
|
|
1868
|
+
const { runOnError } = await import("@/lib/plugins/hooks");
|
|
1869
|
+
await runOnError(
|
|
1870
|
+
{ requestId: traceId, body, model, provider, apiKeyInfo, metadata: {} },
|
|
1871
|
+
error instanceof Error ? error : new Error(String(error))
|
|
1872
|
+
);
|
|
1873
|
+
} catch (pluginErr) {
|
|
1874
|
+
log?.debug?.(
|
|
1875
|
+
"PLUGIN",
|
|
1876
|
+
`onError hook error (non-fatal): ${pluginErr instanceof Error ? pluginErr.message : String(pluginErr)}`
|
|
1877
|
+
);
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
const parsedStatus = Number(error?.statusCode);
|
|
1881
|
+
const statusCode =
|
|
1882
|
+
Number.isInteger(parsedStatus) && parsedStatus >= 400 && parsedStatus <= 599
|
|
1883
|
+
? parsedStatus
|
|
1884
|
+
: HTTP_STATUS.SERVER_ERROR;
|
|
1885
|
+
const message = error?.message || "Invalid request";
|
|
1886
|
+
const errorType = typeof error?.errorType === "string" ? error.errorType : null;
|
|
1887
|
+
|
|
1888
|
+
log?.warn?.("TRANSLATE", `Request translation failed: ${message}`);
|
|
1889
|
+
|
|
1890
|
+
if (errorType) {
|
|
1891
|
+
trackPendingRequest(model, provider, connectionId, false);
|
|
1892
|
+
return {
|
|
1893
|
+
success: false,
|
|
1894
|
+
status: statusCode,
|
|
1895
|
+
error: message,
|
|
1896
|
+
response: new Response(
|
|
1897
|
+
JSON.stringify({
|
|
1898
|
+
error: {
|
|
1899
|
+
message,
|
|
1900
|
+
type: errorType,
|
|
1901
|
+
code: errorType,
|
|
1902
|
+
},
|
|
1903
|
+
}),
|
|
1904
|
+
{
|
|
1905
|
+
status: statusCode,
|
|
1906
|
+
headers: {
|
|
1907
|
+
"Content-Type": "application/json",
|
|
1908
|
+
},
|
|
1909
|
+
}
|
|
1910
|
+
),
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
trackPendingRequest(model, provider, connectionId, false);
|
|
1915
|
+
return createErrorResult(statusCode, message);
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
trace("post_translation");
|
|
1919
|
+
|
|
1920
|
+
// Kiro: sanitize tool schemas before dispatch. Kiro returns 400 "Improperly
|
|
1921
|
+
// formed request" for unsupported JSON-Schema keywords (anyOf/$ref/if-then,
|
|
1922
|
+
// etc.) and tool names >64 chars. Strip those keys and hash-truncate long
|
|
1923
|
+
// names; merge the truncated→original nameMap into the existing
|
|
1924
|
+
// `_toolNameMap` so kiro-to-openai maps streamed tool-call names back (#1375).
|
|
1925
|
+
if (targetFormat === FORMATS.KIRO) {
|
|
1926
|
+
const kiroTools =
|
|
1927
|
+
translatedBody?.conversationState?.currentMessage?.userInputMessage?.userInputMessageContext
|
|
1928
|
+
?.tools;
|
|
1929
|
+
if (kiroTools) {
|
|
1930
|
+
const { tools: sanitizedKiroTools, nameMap: kiroNameMap } = sanitizeKiroTools(kiroTools);
|
|
1931
|
+
translatedBody.conversationState.currentMessage.userInputMessage.userInputMessageContext.tools =
|
|
1932
|
+
sanitizedKiroTools;
|
|
1933
|
+
if (kiroNameMap.size > 0) {
|
|
1934
|
+
const existing =
|
|
1935
|
+
translatedBody._toolNameMap instanceof Map
|
|
1936
|
+
? translatedBody._toolNameMap
|
|
1937
|
+
: new Map<string, string>();
|
|
1938
|
+
kiroNameMap.forEach((original, truncated) => existing.set(truncated, original));
|
|
1939
|
+
translatedBody._toolNameMap = existing;
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
// Claude: strict Anthropic-compatible gateways (e.g. MiniMax) reject tool
|
|
1945
|
+
// definitions that omit the required `type` discriminator with HTTP 400. Default
|
|
1946
|
+
// a missing `type` to "custom" before dispatch, mirroring Anthropic's own
|
|
1947
|
+
// inference, so legacy Claude-format tool payloads survive strict gateways (#2195).
|
|
1948
|
+
if (targetFormat === FORMATS.CLAUDE && Array.isArray(translatedBody.tools)) {
|
|
1949
|
+
translatedBody.tools = defaultClaudeToolType(
|
|
1950
|
+
translatedBody.tools
|
|
1951
|
+
) as typeof translatedBody.tools;
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
// Extract toolNameMap for response translation (Claude OAuth)
|
|
1955
|
+
const translatedToolNameMap = translatedBody._toolNameMap;
|
|
1956
|
+
const nativeClaudeToolNameMap = isClaudePassthrough
|
|
1957
|
+
? buildClaudePassthroughToolNameMap(body)
|
|
1958
|
+
: null;
|
|
1959
|
+
const toolNameMap =
|
|
1960
|
+
translatedToolNameMap instanceof Map && translatedToolNameMap.size > 0
|
|
1961
|
+
? translatedToolNameMap
|
|
1962
|
+
: nativeClaudeToolNameMap;
|
|
1963
|
+
delete translatedBody._toolNameMap;
|
|
1964
|
+
delete translatedBody._disableToolPrefix;
|
|
1965
|
+
|
|
1966
|
+
// Update model in body — use resolved alias so the provider gets the correct model ID (#472)
|
|
1967
|
+
// Strip provider/alias prefix if it exactly matches the routing prefix so upstream receives the raw model name (#1261)
|
|
1968
|
+
let finalModelToUpstream = effectiveModel;
|
|
1969
|
+
// Defense-in-depth: only string-strip when effectiveModel is actually a string.
|
|
1970
|
+
// The API guards `model` via Zod (z.string()), but internal callers could pass a
|
|
1971
|
+
// non-string and a bare `.startsWith` would crash with `startsWith is not a
|
|
1972
|
+
// function` (same class as #2359 / #2463). Mirrors 9router's `?.startsWith?.()`.
|
|
1973
|
+
if (typeof finalModelToUpstream === "string") {
|
|
1974
|
+
if (finalModelToUpstream.startsWith(`${provider}/`)) {
|
|
1975
|
+
finalModelToUpstream = finalModelToUpstream.slice(provider.length + 1);
|
|
1976
|
+
} else if (alias && finalModelToUpstream.startsWith(`${alias}/`)) {
|
|
1977
|
+
finalModelToUpstream = finalModelToUpstream.slice(alias.length + 1);
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
translatedBody.model = finalModelToUpstream;
|
|
1981
|
+
|
|
1982
|
+
// #3554: a combo/route may substitute the upstream model AFTER the client chose its
|
|
1983
|
+
// `thinking` value. Claude Code sends `thinking:{type:"disabled"}` for internal calls,
|
|
1984
|
+
// which claude-fable-5 (adaptive-only) rejects with a 400. Drop the now-invalid value
|
|
1985
|
+
// when the resolved target model rejects it; models that accept `disabled` are untouched.
|
|
1986
|
+
if (typeof finalModelToUpstream === "string") {
|
|
1987
|
+
translatedBody = normalizeThinkingForModel(translatedBody, finalModelToUpstream);
|
|
1988
|
+
// Claude Opus 4.7+/Fable 5 removed manual extended thinking: `thinking.type:"enabled"`
|
|
1989
|
+
// or any `thinking.budget_tokens` is a hard 400. Collapse any manual thinking that
|
|
1990
|
+
// reached this point (passthrough legacy shape, reasoning_effort buckets, per-model
|
|
1991
|
+
// defaults) to `{type:"adaptive"}` — effort stays on `output_config.effort`. Keyed on
|
|
1992
|
+
// the resolved upstream model, so it covers every routing mode. See claudeAdaptiveThinking.ts.
|
|
1993
|
+
translatedBody = normalizeClaudeAdaptiveThinking(translatedBody, finalModelToUpstream);
|
|
1994
|
+
// Claude Haiku rejects `thinking.type:"adaptive"` and `output_config.effort`
|
|
1995
|
+
// (both Sonnet 4.6 / Opus 4.5+ only). Several paths can still emit those
|
|
1996
|
+
// shapes on a Haiku target — native passthrough, reasoning_effort buckets,
|
|
1997
|
+
// per-model defaults — so collapse them to a Haiku-valid shape here, after
|
|
1998
|
+
// model substitution. Mirrors upstream 9router 401d93bd5. See
|
|
1999
|
+
// services/claudeHaikuConstraints.ts.
|
|
2000
|
+
translatedBody = normalizeClaudeHaikuConstraints(translatedBody, finalModelToUpstream);
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
// Xiaomi MiMo controls reasoning ONLY via `thinking:{type:"enabled"|"disabled"}` and
|
|
2004
|
+
// rejects unknown/extra params with a strict "400 Param Incorrect". Map OmniRoute's
|
|
2005
|
+
// OpenAI reasoning signals onto that native shape: reduce any thinking object to
|
|
2006
|
+
// `{type}` and drop `reasoning_effort`/`reasoning`. See services/mimoThinking.ts.
|
|
2007
|
+
if (provider === "xiaomi-mimo") {
|
|
2008
|
+
translatedBody = normalizeMimoThinking(translatedBody);
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
const previousResponseIdPolicy = applyResponsesPreviousResponseIdPolicy(translatedBody, {
|
|
2012
|
+
mode: settings.responsesPreviousResponseIdMode,
|
|
2013
|
+
sourceFormat,
|
|
2014
|
+
targetFormat,
|
|
2015
|
+
credentials,
|
|
2016
|
+
});
|
|
2017
|
+
translatedBody = previousResponseIdPolicy.body as typeof translatedBody;
|
|
2018
|
+
|
|
2019
|
+
// #1789: Prevent output_config.effort from overriding effort encoded in model name (Codex)
|
|
2020
|
+
if (provider === "codex" || provider?.startsWith("codex")) {
|
|
2021
|
+
const hasEffortSuffix = finalModelToUpstream.match(/-(low|medium|high|xhigh)$/i);
|
|
2022
|
+
if (
|
|
2023
|
+
hasEffortSuffix &&
|
|
2024
|
+
translatedBody.output_config &&
|
|
2025
|
+
typeof translatedBody.output_config === "object"
|
|
2026
|
+
) {
|
|
2027
|
+
const oc = translatedBody.output_config as Record<string, unknown>;
|
|
2028
|
+
if (oc.effort) {
|
|
2029
|
+
log?.warn?.(
|
|
2030
|
+
"PARAMS",
|
|
2031
|
+
`Stripped output_config.effort="${oc.effort}" because model "${finalModelToUpstream}" already encodes effort`
|
|
2032
|
+
);
|
|
2033
|
+
delete oc.effort;
|
|
2034
|
+
if (Object.keys(oc).length === 0) {
|
|
2035
|
+
delete translatedBody.output_config;
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
// Strip unsupported parameters for reasoning models (o1, o3, etc.)
|
|
2042
|
+
const unsupported = getUnsupportedParams(provider, model);
|
|
2043
|
+
if (unsupported.length > 0) {
|
|
2044
|
+
const stripped: string[] = [];
|
|
2045
|
+
for (const param of unsupported) {
|
|
2046
|
+
if (Object.hasOwn(translatedBody, param)) {
|
|
2047
|
+
stripped.push(param);
|
|
2048
|
+
delete translatedBody[param];
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
if (stripped.length > 0) {
|
|
2052
|
+
log?.warn?.("PARAMS", `Stripped unsupported params for ${model}: ${stripped.join(", ")}`);
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
// GPT-5 reasoning models (openai Chat Completions) reject temperature/top_p with a 400
|
|
2057
|
+
// whenever a reasoning effort is active, yet accept them under reasoning_effort=none (the
|
|
2058
|
+
// GPT-5.1+ default). A static unsupportedParams list can't express that, so strip sampling
|
|
2059
|
+
// conditionally here. The codex Responses path is already covered by the executor allowlist.
|
|
2060
|
+
translatedBody = stripGpt5SamplingWhenReasoning(
|
|
2061
|
+
translatedBody,
|
|
2062
|
+
provider,
|
|
2063
|
+
finalModelToUpstream,
|
|
2064
|
+
log
|
|
2065
|
+
);
|
|
2066
|
+
|
|
2067
|
+
// Rename max_tokens to max_completion_tokens if not supported (#1961)
|
|
2068
|
+
if (!supportsMaxTokens({ provider, model })) {
|
|
2069
|
+
if (translatedBody.max_tokens !== undefined) {
|
|
2070
|
+
if (translatedBody.max_completion_tokens === undefined) {
|
|
2071
|
+
translatedBody.max_completion_tokens = translatedBody.max_tokens;
|
|
2072
|
+
}
|
|
2073
|
+
delete translatedBody.max_tokens;
|
|
2074
|
+
log?.debug?.("PARAMS", `Renamed max_tokens to max_completion_tokens for ${model}`);
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
// OpenAI's `store` parameter is not supported by most compatible providers and breaks them
|
|
2079
|
+
if (provider !== "openai" && "store" in translatedBody) {
|
|
2080
|
+
delete translatedBody.store;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
// Chat clients may send stream_options.include_usage, but OpenAI Responses
|
|
2084
|
+
// upstreams (including Azure AI Foundry /responses) reject stream_options.
|
|
2085
|
+
if (targetFormat === FORMATS.OPENAI_RESPONSES && "stream_options" in translatedBody) {
|
|
2086
|
+
delete translatedBody.stream_options;
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
// Provider-specific max_tokens caps (#711)
|
|
2090
|
+
// Some providers reject requests when max_tokens exceeds their API limit.
|
|
2091
|
+
// Cap before sending to avoid upstream HTTP 400 errors.
|
|
2092
|
+
const providerCap = PROVIDER_MAX_TOKENS[provider];
|
|
2093
|
+
if (providerCap) {
|
|
2094
|
+
for (const field of ["max_tokens", "max_completion_tokens"] as const) {
|
|
2095
|
+
if (typeof translatedBody[field] === "number" && translatedBody[field] > providerCap) {
|
|
2096
|
+
log?.debug?.(
|
|
2097
|
+
"PARAMS",
|
|
2098
|
+
`Capping ${field} from ${translatedBody[field]} to ${providerCap} for ${provider}`
|
|
2099
|
+
);
|
|
2100
|
+
translatedBody[field] = providerCap;
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
// Resolve executor with optional upstream proxy (CLIProxyAPI) routing.
|
|
2106
|
+
// mode="native" (default): returns the native executor unchanged.
|
|
2107
|
+
// mode="cliproxyapi": returns the CLIProxyAPI executor instead.
|
|
2108
|
+
// mode="fallback": returns a wrapper that tries native first, falls back to CLIProxyAPI on 5xx/network errors.
|
|
2109
|
+
|
|
2110
|
+
// #6339: pass the resolved connection's providerSpecificData so a per-connection
|
|
2111
|
+
// cliproxyapiMode="claude-native" override can deep-route this single connection
|
|
2112
|
+
// through CLIProxyAPI regardless of the provider-level upstream_proxy_config mode.
|
|
2113
|
+
const resolveExecutorWithProxy = (prov: string) =>
|
|
2114
|
+
resolveExecutorWithProxyFor(
|
|
2115
|
+
prov,
|
|
2116
|
+
log,
|
|
2117
|
+
(credentials?.providerSpecificData as Record<string, unknown> | null | undefined) ?? null
|
|
2118
|
+
);
|
|
2119
|
+
|
|
2120
|
+
// === Quota Share enforcement PRE-hook (B/F7) ===
|
|
2121
|
+
// Runs after provider/model/credentials/apiKeyInfo are fully resolved,
|
|
2122
|
+
// before dispatcher. Fail-open per B16: errors → allow.
|
|
2123
|
+
let quotaSoftDeprioritize = false;
|
|
2124
|
+
if (apiKeyInfo?.id && credentials?.connectionId) {
|
|
2125
|
+
try {
|
|
2126
|
+
const { enforceQuotaShare } = await import("@/lib/quota/enforce");
|
|
2127
|
+
const decision = await enforceQuotaShare({
|
|
2128
|
+
apiKeyId: apiKeyInfo.id,
|
|
2129
|
+
connectionId: credentials.connectionId,
|
|
2130
|
+
provider: provider ?? "unknown",
|
|
2131
|
+
// Resolved model id (post background-redirect / alias) — the same scope the
|
|
2132
|
+
// router/log use. Operators configure per-(key,model) caps against THIS id.
|
|
2133
|
+
model: model || undefined,
|
|
2134
|
+
estimatedCost: {},
|
|
2135
|
+
}).catch((err: unknown) => {
|
|
2136
|
+
log?.warn?.(
|
|
2137
|
+
"QUOTA_SHARE",
|
|
2138
|
+
`enforceQuotaShare failed; fail-open: ${err instanceof Error ? err.message : String(err)}`
|
|
2139
|
+
);
|
|
2140
|
+
return { kind: "allow" as const };
|
|
2141
|
+
});
|
|
2142
|
+
|
|
2143
|
+
if (decision.kind === "block") {
|
|
2144
|
+
const { buildErrorBody } = await import("../utils/error.ts");
|
|
2145
|
+
log?.warn?.(
|
|
2146
|
+
"QUOTA_SHARE",
|
|
2147
|
+
`[quotaShare] blocked apiKeyId=${apiKeyInfo.id} provider=${provider ?? "unknown"}: ${decision.reason}`
|
|
2148
|
+
);
|
|
2149
|
+
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
2150
|
+
if (decision.retryAfterSeconds) {
|
|
2151
|
+
headers["Retry-After"] = String(decision.retryAfterSeconds);
|
|
2152
|
+
}
|
|
2153
|
+
return new Response(JSON.stringify(buildErrorBody(429, decision.reason)), {
|
|
2154
|
+
status: 429,
|
|
2155
|
+
headers,
|
|
2156
|
+
});
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
if (decision.kind === "allow" && decision.deprioritize) {
|
|
2160
|
+
quotaSoftDeprioritize = true;
|
|
2161
|
+
log?.info?.(
|
|
2162
|
+
"QUOTA_SHARE",
|
|
2163
|
+
`[quotaShare] soft deprioritize active for apiKeyId=${apiKeyInfo.id} provider=${provider ?? "unknown"}`
|
|
2164
|
+
);
|
|
2165
|
+
}
|
|
2166
|
+
} catch (err) {
|
|
2167
|
+
// Outer fail-open guard — should not be reached (inner .catch covers it)
|
|
2168
|
+
log?.warn?.(
|
|
2169
|
+
"QUOTA_SHARE",
|
|
2170
|
+
`[quotaShare] enforceQuotaShare unexpected error; fail-open: ${err instanceof Error ? err.message : String(err)}`
|
|
2171
|
+
);
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
// G2: Propagate soft penalty to the current candidate so combo scoring can deprioritize.
|
|
2175
|
+
if (quotaSoftDeprioritize && isCombo && comboStepId) {
|
|
2176
|
+
try {
|
|
2177
|
+
const { setCandidateQuotaSoftPenalty } = await import("../services/combo");
|
|
2178
|
+
setCandidateQuotaSoftPenalty(comboExecutionKey, comboStepId, true);
|
|
2179
|
+
} catch (err) {
|
|
2180
|
+
log?.warn?.(
|
|
2181
|
+
"QUOTA_SHARE",
|
|
2182
|
+
`[quotaShare] could not set soft penalty on candidate: ${err instanceof Error ? err.message : String(err)}`
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
// === /Quota Share enforcement PRE-hook ===
|
|
2187
|
+
|
|
2188
|
+
// Get executor for this provider (with optional upstream proxy routing)
|
|
2189
|
+
const executor = await resolveExecutorWithProxy(provider);
|
|
2190
|
+
const getExecutionCredentials = () =>
|
|
2191
|
+
resolveExecutionCredentialsFor({
|
|
2192
|
+
credentials,
|
|
2193
|
+
nativeCodexPassthrough,
|
|
2194
|
+
endpointPath,
|
|
2195
|
+
targetFormat,
|
|
2196
|
+
provider,
|
|
2197
|
+
ccSessionId,
|
|
2198
|
+
});
|
|
2199
|
+
|
|
2200
|
+
let onPipelineStreamError: streamFailure.PipelineStreamErrorHandler | null = null;
|
|
2201
|
+
let onClientDisconnectFinalize:
|
|
2202
|
+
((event: { reason: string; duration: number }) => boolean) | null = null;
|
|
2203
|
+
|
|
2204
|
+
// Create stream controller for disconnect detection
|
|
2205
|
+
const streamController = createStreamController({
|
|
2206
|
+
onDisconnect: (event) => {
|
|
2207
|
+
let finalized = false;
|
|
2208
|
+
try {
|
|
2209
|
+
finalized = onClientDisconnectFinalize?.(event) === true;
|
|
2210
|
+
} catch {}
|
|
2211
|
+
if (!finalized) {
|
|
2212
|
+
try {
|
|
2213
|
+
finalizePendingScope(pendingScope, {
|
|
2214
|
+
status: 499,
|
|
2215
|
+
error: `Client disconnected: ${event.reason}`,
|
|
2216
|
+
errorCode: "client_disconnected",
|
|
2217
|
+
});
|
|
2218
|
+
finalized = true;
|
|
2219
|
+
} catch {}
|
|
2220
|
+
}
|
|
2221
|
+
try {
|
|
2222
|
+
onDisconnect?.(event);
|
|
2223
|
+
} catch {}
|
|
2224
|
+
return finalized;
|
|
2225
|
+
},
|
|
2226
|
+
onError: (event) => onPipelineStreamError?.(event),
|
|
2227
|
+
provider,
|
|
2228
|
+
model,
|
|
2229
|
+
connectionId,
|
|
2230
|
+
clientResponseFormat,
|
|
2231
|
+
clientAbortSignal: clientRawRequest?.signal,
|
|
2232
|
+
});
|
|
2233
|
+
|
|
2234
|
+
const dedupRequestBody = { ...translatedBody, model: `${provider}/${model}`, stream };
|
|
2235
|
+
const dedupEnabled = shouldDeduplicate(dedupRequestBody);
|
|
2236
|
+
const dedupHash = dedupEnabled ? computeRequestHash(dedupRequestBody) : null;
|
|
2237
|
+
|
|
2238
|
+
const executeProviderRequest = async (modelToCall = effectiveModel, allowDedup = false) => {
|
|
2239
|
+
const execute = async () => {
|
|
2240
|
+
// Upstream body preparation extracted to chatCore/upstreamBody.ts (#3501 — first internal
|
|
2241
|
+
// sub-slice of executeProviderRequest); produces the body sent upstream (payload rules +
|
|
2242
|
+
// tool-limit truncation + qwen oauth user backfill + prompt_cache_key injection).
|
|
2243
|
+
let bodyToSend = await prepareUpstreamBody({
|
|
2244
|
+
translatedBody,
|
|
2245
|
+
modelToCall,
|
|
2246
|
+
provider,
|
|
2247
|
+
targetFormat,
|
|
2248
|
+
credentials,
|
|
2249
|
+
log,
|
|
2250
|
+
bypassDefaultToolLimit: isOpencodeClient,
|
|
2251
|
+
});
|
|
2252
|
+
|
|
2253
|
+
updatePendingScope(pendingScope, {
|
|
2254
|
+
providerRequest: bodyToSend,
|
|
2255
|
+
stage: "payload_prepared",
|
|
2256
|
+
});
|
|
2257
|
+
|
|
2258
|
+
let releaseRawResultAccountSemaphore = () => {};
|
|
2259
|
+
try {
|
|
2260
|
+
const rawResult = await (async () => {
|
|
2261
|
+
let attempts = 0;
|
|
2262
|
+
const isModelScopeForRequest = isModelScope();
|
|
2263
|
+
const maxAttempts = isModelScopeForRequest
|
|
2264
|
+
? 3
|
|
2265
|
+
: provider === "qwen"
|
|
2266
|
+
? 3
|
|
2267
|
+
: provider === "codex"
|
|
2268
|
+
? 3
|
|
2269
|
+
: 1;
|
|
2270
|
+
|
|
2271
|
+
// ── Codex 429 account-rotation state ─────────────────────────────────
|
|
2272
|
+
// Track excluded connection IDs for codex failover across attempts.
|
|
2273
|
+
const codexExcludedIds: string[] = [];
|
|
2274
|
+
// Derive session affinity key once for codex failover (used to clear affinity on 429).
|
|
2275
|
+
const codexSessionAffinityKey =
|
|
2276
|
+
provider === "codex"
|
|
2277
|
+
? (extractSessionAffinityKey(body, clientRawRequest?.headers) ?? null)
|
|
2278
|
+
: null;
|
|
2279
|
+
|
|
2280
|
+
while (attempts < maxAttempts) {
|
|
2281
|
+
trace("pre_executor", { attempt: attempts });
|
|
2282
|
+
updatePendingScope(pendingScope, {
|
|
2283
|
+
stage: "sending_to_provider",
|
|
2284
|
+
});
|
|
2285
|
+
const execCreds = getExecutionCredentials();
|
|
2286
|
+
const attemptConnectionId = execCreds?.connectionId || connectionId;
|
|
2287
|
+
const accountSemaphoreMaxConcurrency = resolveAccountSemaphoreMaxConcurrency(execCreds);
|
|
2288
|
+
const accountSemaphoreKey = resolveAccountSemaphoreKey({
|
|
2289
|
+
provider,
|
|
2290
|
+
model: modelToCall,
|
|
2291
|
+
connectionId: attemptConnectionId,
|
|
2292
|
+
credentials: execCreds,
|
|
2293
|
+
});
|
|
2294
|
+
|
|
2295
|
+
trace("pre_semaphore", {
|
|
2296
|
+
semaphoreKey: accountSemaphoreKey,
|
|
2297
|
+
max: accountSemaphoreMaxConcurrency,
|
|
2298
|
+
});
|
|
2299
|
+
if (accountSemaphoreKey && accountSemaphoreMaxConcurrency != null) {
|
|
2300
|
+
updatePendingScope(pendingScope, {
|
|
2301
|
+
stage: "waiting_account_slot",
|
|
2302
|
+
});
|
|
2303
|
+
}
|
|
2304
|
+
const releaseAccountSemaphore =
|
|
2305
|
+
accountSemaphoreKey && accountSemaphoreMaxConcurrency != null
|
|
2306
|
+
? await acquireAccountSemaphore(accountSemaphoreKey, {
|
|
2307
|
+
maxConcurrency: accountSemaphoreMaxConcurrency,
|
|
2308
|
+
signal: streamController.signal,
|
|
2309
|
+
})
|
|
2310
|
+
: () => {};
|
|
2311
|
+
trace("post_semaphore");
|
|
2312
|
+
updatePendingScope(pendingScope, {
|
|
2313
|
+
stage: "waiting_rate_limit",
|
|
2314
|
+
});
|
|
2315
|
+
|
|
2316
|
+
try {
|
|
2317
|
+
trace("pre_rate_limit", { connectionId: attemptConnectionId });
|
|
2318
|
+
const rawExecutorResult = await withRateLimit(
|
|
2319
|
+
provider,
|
|
2320
|
+
attemptConnectionId,
|
|
2321
|
+
modelToCall,
|
|
2322
|
+
async () => {
|
|
2323
|
+
trace("inside_rate_limit", { connectionId: attemptConnectionId });
|
|
2324
|
+
updatePendingScope(pendingScope, {
|
|
2325
|
+
stage: "rate_limit_slot_acquired",
|
|
2326
|
+
});
|
|
2327
|
+
return executeWithUpstreamStartTimeout({
|
|
2328
|
+
executor,
|
|
2329
|
+
provider,
|
|
2330
|
+
model: modelToCall,
|
|
2331
|
+
signal: streamController.signal,
|
|
2332
|
+
log,
|
|
2333
|
+
execute: (signal) =>
|
|
2334
|
+
runWithCapture(providerRequestCapture, () =>
|
|
2335
|
+
executor.execute({
|
|
2336
|
+
model: modelToCall,
|
|
2337
|
+
body: bodyToSend,
|
|
2338
|
+
stream: upstreamStream,
|
|
2339
|
+
credentials: execCreds,
|
|
2340
|
+
signal,
|
|
2341
|
+
log,
|
|
2342
|
+
extendedContext,
|
|
2343
|
+
upstreamExtraHeaders: buildUpstreamHeadersForExecute(modelToCall),
|
|
2344
|
+
clientHeaders: buildExecutorClientHeaders(
|
|
2345
|
+
clientRawRequest?.headers,
|
|
2346
|
+
userAgent
|
|
2347
|
+
),
|
|
2348
|
+
onCredentialsRefreshed,
|
|
2349
|
+
skipUpstreamRetry,
|
|
2350
|
+
contextEditing: { enabled: contextEditingEnabled },
|
|
2351
|
+
})
|
|
2352
|
+
),
|
|
2353
|
+
});
|
|
2354
|
+
},
|
|
2355
|
+
streamController.signal
|
|
2356
|
+
);
|
|
2357
|
+
const res = normalizeExecutorResult(rawExecutorResult);
|
|
2358
|
+
trace("post_executor", { status: res?.response?.status });
|
|
2359
|
+
|
|
2360
|
+
// Track Gemini RPM + RPD request counts for 429 classification
|
|
2361
|
+
if (provider === "gemini") {
|
|
2362
|
+
incrementRequestCount(modelToCall);
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
updatePendingScope(pendingScope, {
|
|
2366
|
+
stage: "provider_response_started",
|
|
2367
|
+
});
|
|
2368
|
+
|
|
2369
|
+
if (res.response.status === 401 && execCreds?.connectionId) {
|
|
2370
|
+
recordKeyHealthStatus(401, execCreds);
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
// Qwen 429 strict quota backoff (wait 1.5s, 3s and retry)
|
|
2374
|
+
if (
|
|
2375
|
+
provider === "qwen" &&
|
|
2376
|
+
res.response.status === 429 &&
|
|
2377
|
+
attempts < maxAttempts - 1
|
|
2378
|
+
) {
|
|
2379
|
+
const bodyPeek = await res.response
|
|
2380
|
+
.clone()
|
|
2381
|
+
.text()
|
|
2382
|
+
.catch(() => "");
|
|
2383
|
+
if (bodyPeek.toLowerCase().includes("exceeded your current quota")) {
|
|
2384
|
+
const delay = 1500 * (attempts + 1);
|
|
2385
|
+
log?.warn?.("QWEN_RETRY", `Quota 429 hit. Retrying in ${delay}ms...`);
|
|
2386
|
+
releaseAccountSemaphore();
|
|
2387
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
2388
|
+
attempts++;
|
|
2389
|
+
continue;
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
if (isModelScope() && res.response.status === 429 && attempts < maxAttempts - 1) {
|
|
2394
|
+
const bodyPeek = await res.response
|
|
2395
|
+
.clone()
|
|
2396
|
+
.text()
|
|
2397
|
+
.catch(() => "");
|
|
2398
|
+
const normalizedHeaders = normalizeHeaders(res.response.headers);
|
|
2399
|
+
const decision = classifyModelScope429(bodyPeek, normalizedHeaders);
|
|
2400
|
+
if (decision.retryable) {
|
|
2401
|
+
const delay = getModelScopeRetryDelayMs(normalizedHeaders, attempts);
|
|
2402
|
+
log?.warn?.(
|
|
2403
|
+
"MODELSCOPE_RETRY",
|
|
2404
|
+
`429 ${decision.kind}; retrying in ${delay}ms (model remaining: ${decision.snapshot.modelRemaining ?? "unknown"})`
|
|
2405
|
+
);
|
|
2406
|
+
releaseAccountSemaphore();
|
|
2407
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
2408
|
+
attempts++;
|
|
2409
|
+
continue;
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
// Codex 429 account-rotation failover (disabled for context-relay so combo.ts can inject handoff)
|
|
2414
|
+
if (
|
|
2415
|
+
provider === "codex" &&
|
|
2416
|
+
comboStrategy !== "context-relay" &&
|
|
2417
|
+
res.response.status === 429 &&
|
|
2418
|
+
attempts < maxAttempts - 1
|
|
2419
|
+
) {
|
|
2420
|
+
const failedConnectionId =
|
|
2421
|
+
execCreds?.connectionId || credentials?.connectionId || connectionId;
|
|
2422
|
+
const normalizedHeaders = normalizeHeaders(res.response.headers);
|
|
2423
|
+
const retryAfterHeader = normalizedHeaders["retry-after"] ?? null;
|
|
2424
|
+
const retryAfterMs = retryAfterHeader
|
|
2425
|
+
? Number.parseFloat(retryAfterHeader) * 1000
|
|
2426
|
+
: null;
|
|
2427
|
+
|
|
2428
|
+
log?.warn?.(
|
|
2429
|
+
"CODEX_FAILOVER",
|
|
2430
|
+
`429 on connection ${String(failedConnectionId).slice(0, 8)} (attempt ${attempts + 1}/${maxAttempts}), rotating account`
|
|
2431
|
+
);
|
|
2432
|
+
|
|
2433
|
+
// Mark only the current Codex model scope as rate-limited.
|
|
2434
|
+
if (failedConnectionId) {
|
|
2435
|
+
await markCodexScopeRateLimited({
|
|
2436
|
+
failedConnectionId: String(failedConnectionId),
|
|
2437
|
+
model: modelToCall || model || requestedModel || null,
|
|
2438
|
+
rateLimitedUntil: new Date(Date.now() + (retryAfterMs || 60_000)).toISOString(),
|
|
2439
|
+
credentials,
|
|
2440
|
+
});
|
|
2441
|
+
// Fix B: also persist the cooldown to
|
|
2442
|
+
// `provider_connections.rate_limited_until`. Without this,
|
|
2443
|
+
// the Codex 429 cascade survives the current request (via
|
|
2444
|
+
// `markCodexScopeRateLimited`'s in-memory Map) but is lost
|
|
2445
|
+
// on process restart — the same exhausted Codex key is
|
|
2446
|
+
// re-picked on the very next request. Mirrors
|
|
2447
|
+
// `open-sse/executors/antigravity.ts:343`.
|
|
2448
|
+
// Best-effort: never crash the chat path on DB write failure.
|
|
2449
|
+
try {
|
|
2450
|
+
const { setConnectionRateLimitUntil } = await import("@/lib/db/providers");
|
|
2451
|
+
const untilMs = Date.now() + (retryAfterMs || 60_000);
|
|
2452
|
+
setConnectionRateLimitUntil(String(failedConnectionId), untilMs);
|
|
2453
|
+
} catch {
|
|
2454
|
+
// ignore — best effort
|
|
2455
|
+
}
|
|
2456
|
+
if (!codexExcludedIds.includes(String(failedConnectionId))) {
|
|
2457
|
+
codexExcludedIds.push(String(failedConnectionId));
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
// Clear session affinity so next request won't be pinned to the failing account
|
|
2462
|
+
if (codexSessionAffinityKey) {
|
|
2463
|
+
try {
|
|
2464
|
+
deleteSessionAccountAffinity(codexSessionAffinityKey, "codex");
|
|
2465
|
+
} catch {
|
|
2466
|
+
// best-effort
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
// Fetch next available codex connection (excluding all previously failed ones)
|
|
2471
|
+
const nextCreds = await getProviderCredentials(
|
|
2472
|
+
"codex",
|
|
2473
|
+
null,
|
|
2474
|
+
null,
|
|
2475
|
+
modelToCall || model || requestedModel || null,
|
|
2476
|
+
{
|
|
2477
|
+
excludeConnectionIds: [...codexExcludedIds],
|
|
2478
|
+
}
|
|
2479
|
+
).catch(() => null);
|
|
2480
|
+
|
|
2481
|
+
if (!nextCreds || nextCreds.allRateLimited) {
|
|
2482
|
+
log?.warn?.("CODEX_FAILOVER", "No more codex accounts available — returning 429");
|
|
2483
|
+
if (stream) {
|
|
2484
|
+
releaseAccountSemaphore();
|
|
2485
|
+
return {
|
|
2486
|
+
...res,
|
|
2487
|
+
_executionCredentials: execCreds,
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2490
|
+
return {
|
|
2491
|
+
...res,
|
|
2492
|
+
_accountSemaphoreRelease: releaseAccountSemaphore,
|
|
2493
|
+
_executionCredentials: execCreds,
|
|
2494
|
+
};
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
const newConnectionId = nextCreds.connectionId;
|
|
2498
|
+
log?.info?.(
|
|
2499
|
+
"CODEX_FAILOVER",
|
|
2500
|
+
`Rotating codex account: ${String(failedConnectionId).slice(0, 8)} → ${newConnectionId.slice(0, 8)} (attempt ${attempts + 2}/${maxAttempts})`
|
|
2501
|
+
);
|
|
2502
|
+
|
|
2503
|
+
logAuditEvent({
|
|
2504
|
+
action: "codex.account_rotation",
|
|
2505
|
+
actor: apiKeyInfo?.name || "system",
|
|
2506
|
+
target: newConnectionId,
|
|
2507
|
+
details: {
|
|
2508
|
+
failed_connection_id: failedConnectionId,
|
|
2509
|
+
new_connection_id: newConnectionId,
|
|
2510
|
+
attempt: attempts + 1,
|
|
2511
|
+
retry_after_ms: retryAfterMs,
|
|
2512
|
+
},
|
|
2513
|
+
});
|
|
2514
|
+
|
|
2515
|
+
// Update credentials in-place so getExecutionCredentials() picks up the new account
|
|
2516
|
+
Object.assign(credentials, nextCreds);
|
|
2517
|
+
|
|
2518
|
+
releaseAccountSemaphore();
|
|
2519
|
+
attempts++;
|
|
2520
|
+
continue;
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
// For streaming: release the semaphore when the client drains or cancels the stream.
|
|
2524
|
+
if (stream) {
|
|
2525
|
+
const originalBody = res.response.body;
|
|
2526
|
+
if (!originalBody) {
|
|
2527
|
+
releaseAccountSemaphore();
|
|
2528
|
+
return res;
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
// Opt-in transparent stream recovery (free-claude-code port, default OFF).
|
|
2532
|
+
// Only engages for a successful (2xx) stream — an error body must never be
|
|
2533
|
+
// held or replayed. Setting is read once here from the cached resolved
|
|
2534
|
+
// resilience settings; the default path is byte-for-byte unchanged.
|
|
2535
|
+
const okStatus = res.response.status >= 200 && res.response.status < 300;
|
|
2536
|
+
let streamRecoveryEnabled = false;
|
|
2537
|
+
let continueMidStreamEnabled = false;
|
|
2538
|
+
if (okStatus) {
|
|
2539
|
+
try {
|
|
2540
|
+
// Reuse the request-consolidated settings read (see line ~2076) — no
|
|
2541
|
+
// second DB/cache hit. Default OFF when the setting is absent.
|
|
2542
|
+
const sr = resolveResilienceSettings(settings).streamRecovery;
|
|
2543
|
+
// Fail-closed: the agent-goal-policy heuristic may only ADD recovery
|
|
2544
|
+
// when the operator has no explicit configuration. If the operator
|
|
2545
|
+
// explicitly configured stream recovery (env var or DB/settings
|
|
2546
|
+
// override), that value always wins — the goal policy must never
|
|
2547
|
+
// re-enable recovery the operator explicitly turned off.
|
|
2548
|
+
const operatorExplicit = isStreamRecoveryExplicitlyConfigured(settings);
|
|
2549
|
+
const goalOverride = !operatorExplicit && agentGoalPolicy.streamRecoveryEnabled;
|
|
2550
|
+
streamRecoveryEnabled = sr.enabled || goalOverride;
|
|
2551
|
+
continueMidStreamEnabled = sr.continueMidStream === true;
|
|
2552
|
+
if (goalOverride && !sr.enabled) {
|
|
2553
|
+
log?.info?.(
|
|
2554
|
+
"AGENT_GOAL",
|
|
2555
|
+
`agentGoalPolicy override: stream recovery enabled for goal request requestId=${traceId} model=${modelToCall || model || requestedModel || "unknown"}`
|
|
2556
|
+
);
|
|
2557
|
+
}
|
|
2558
|
+
} catch {
|
|
2559
|
+
streamRecoveryEnabled = false;
|
|
2560
|
+
continueMidStreamEnabled = false;
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
let clientBody: ReadableStream<Uint8Array>;
|
|
2565
|
+
if (streamRecoveryEnabled) {
|
|
2566
|
+
// Run the SAME upstream (same account/creds) with a given body and return
|
|
2567
|
+
// its 2xx stream, or null. Used both by the early-retry re-open (same body)
|
|
2568
|
+
// and the mid-stream continuation (assistant-prefilled body).
|
|
2569
|
+
const runUpstreamStream = async (
|
|
2570
|
+
body: unknown
|
|
2571
|
+
): Promise<ReadableStream<Uint8Array> | null> => {
|
|
2572
|
+
try {
|
|
2573
|
+
const retryRaw = await executeWithUpstreamStartTimeout({
|
|
2574
|
+
executor,
|
|
2575
|
+
provider,
|
|
2576
|
+
model: modelToCall,
|
|
2577
|
+
signal: streamController.signal,
|
|
2578
|
+
log,
|
|
2579
|
+
execute: (signal) =>
|
|
2580
|
+
runWithCapture(providerRequestCapture, () =>
|
|
2581
|
+
executor.execute({
|
|
2582
|
+
model: modelToCall,
|
|
2583
|
+
body,
|
|
2584
|
+
stream: upstreamStream,
|
|
2585
|
+
credentials: execCreds,
|
|
2586
|
+
signal,
|
|
2587
|
+
log,
|
|
2588
|
+
extendedContext,
|
|
2589
|
+
upstreamExtraHeaders: buildUpstreamHeadersForExecute(modelToCall),
|
|
2590
|
+
clientHeaders: buildExecutorClientHeaders(
|
|
2591
|
+
clientRawRequest?.headers,
|
|
2592
|
+
userAgent
|
|
2593
|
+
),
|
|
2594
|
+
onCredentialsRefreshed,
|
|
2595
|
+
skipUpstreamRetry,
|
|
2596
|
+
contextEditing: { enabled: contextEditingEnabled },
|
|
2597
|
+
})
|
|
2598
|
+
),
|
|
2599
|
+
});
|
|
2600
|
+
const retryRes = normalizeExecutorResult(retryRaw);
|
|
2601
|
+
const retryOk =
|
|
2602
|
+
retryRes.response.status >= 200 && retryRes.response.status < 300;
|
|
2603
|
+
if (retryOk && retryRes.response.body) {
|
|
2604
|
+
return retryRes.response.body as ReadableStream<Uint8Array>;
|
|
2605
|
+
}
|
|
2606
|
+
await retryRes.response.body?.cancel().catch(() => {});
|
|
2607
|
+
return null;
|
|
2608
|
+
} catch {
|
|
2609
|
+
return null;
|
|
2610
|
+
}
|
|
2611
|
+
};
|
|
2612
|
+
|
|
2613
|
+
// Mid-stream continuation (Fase 4.4): re-request with the partial text as an
|
|
2614
|
+
// assistant prefill. Gated by its own setting and only for OpenAI-compatible
|
|
2615
|
+
// bodies (makeContinuationBody returns null otherwise).
|
|
2616
|
+
const continueStream = continueMidStreamEnabled
|
|
2617
|
+
? (assistantSoFar: string) => {
|
|
2618
|
+
const continuationBody = makeContinuationBody(
|
|
2619
|
+
bodyToSend as Record<string, unknown>,
|
|
2620
|
+
assistantSoFar
|
|
2621
|
+
);
|
|
2622
|
+
return continuationBody
|
|
2623
|
+
? runUpstreamStream(continuationBody)
|
|
2624
|
+
: Promise.resolve(null);
|
|
2625
|
+
}
|
|
2626
|
+
: undefined;
|
|
2627
|
+
|
|
2628
|
+
clientBody = createRecoverableStream(
|
|
2629
|
+
originalBody as ReadableStream<Uint8Array>,
|
|
2630
|
+
() => runUpstreamStream(bodyToSend),
|
|
2631
|
+
{
|
|
2632
|
+
finalize: releaseAccountSemaphore,
|
|
2633
|
+
onRetry: (attempt, err) =>
|
|
2634
|
+
log?.warn?.(
|
|
2635
|
+
"STREAM_RECOVERY",
|
|
2636
|
+
`transparent early-retry ${attempt}/${STREAM_RECOVERY.EARLY_RETRY_MAX} after ${
|
|
2637
|
+
(err as { name?: string })?.name || "truncation"
|
|
2638
|
+
}`
|
|
2639
|
+
),
|
|
2640
|
+
continueStream,
|
|
2641
|
+
onContinue: (attempt) =>
|
|
2642
|
+
log?.warn?.(
|
|
2643
|
+
"STREAM_RECOVERY",
|
|
2644
|
+
`mid-stream continuation attempt ${attempt}/${STREAM_RECOVERY.EARLY_RETRY_MAX}`
|
|
2645
|
+
),
|
|
2646
|
+
}
|
|
2647
|
+
);
|
|
2648
|
+
} else {
|
|
2649
|
+
clientBody = wrapReadableStreamWithFinalize(
|
|
2650
|
+
originalBody,
|
|
2651
|
+
releaseAccountSemaphore
|
|
2652
|
+
);
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
return {
|
|
2656
|
+
...res,
|
|
2657
|
+
_executionCredentials: execCreds,
|
|
2658
|
+
response: new Response(clientBody, {
|
|
2659
|
+
status: res.response.status,
|
|
2660
|
+
statusText: res.response.statusText,
|
|
2661
|
+
headers: new Headers(normalizeHeaders(res.response.headers)),
|
|
2662
|
+
}),
|
|
2663
|
+
};
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
return {
|
|
2667
|
+
...res,
|
|
2668
|
+
_executionCredentials: execCreds,
|
|
2669
|
+
_accountSemaphoreRelease: releaseAccountSemaphore,
|
|
2670
|
+
};
|
|
2671
|
+
} catch (error) {
|
|
2672
|
+
releaseAccountSemaphore();
|
|
2673
|
+
throw error;
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
})();
|
|
2677
|
+
|
|
2678
|
+
if (stream) {
|
|
2679
|
+
return rawResult;
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
// Non-stream: release semaphore immediately after reading full response body.
|
|
2683
|
+
const status = rawResult.response.status;
|
|
2684
|
+
|
|
2685
|
+
// Use execution credentials captured during request processing
|
|
2686
|
+
if (
|
|
2687
|
+
rawResult._executionCredentials?.connectionId &&
|
|
2688
|
+
rawResult._executionCredentials?.apiKey
|
|
2689
|
+
) {
|
|
2690
|
+
recordKeyHealthStatus(status, rawResult._executionCredentials);
|
|
2691
|
+
}
|
|
2692
|
+
releaseRawResultAccountSemaphore =
|
|
2693
|
+
typeof rawResult._accountSemaphoreRelease === "function"
|
|
2694
|
+
? rawResult._accountSemaphoreRelease
|
|
2695
|
+
: () => {};
|
|
2696
|
+
|
|
2697
|
+
const statusText = rawResult.response.statusText;
|
|
2698
|
+
const headersObj = normalizeHeaders(rawResult.response.headers);
|
|
2699
|
+
const responseHeaders = new Headers(headersObj);
|
|
2700
|
+
stripStaleForwardingHeaders(responseHeaders);
|
|
2701
|
+
stripNextMiddlewareControlHeaders(responseHeaders);
|
|
2702
|
+
const contentType = (responseHeaders.get("content-type") || "").toLowerCase();
|
|
2703
|
+
const payload = await readNonStreamingResponseBody(
|
|
2704
|
+
rawResult.response,
|
|
2705
|
+
contentType,
|
|
2706
|
+
upstreamStream
|
|
2707
|
+
);
|
|
2708
|
+
releaseRawResultAccountSemaphore();
|
|
2709
|
+
releaseRawResultAccountSemaphore = () => {};
|
|
2710
|
+
|
|
2711
|
+
return {
|
|
2712
|
+
...rawResult,
|
|
2713
|
+
response: new Response(payload, { status, statusText, headers: responseHeaders }),
|
|
2714
|
+
_dedupSnapshot: {
|
|
2715
|
+
status,
|
|
2716
|
+
statusText,
|
|
2717
|
+
headers: (() => {
|
|
2718
|
+
const arr: [string, string][] = [];
|
|
2719
|
+
responseHeaders.forEach((v, k) => arr.push([k, v]));
|
|
2720
|
+
return arr;
|
|
2721
|
+
})(),
|
|
2722
|
+
payload,
|
|
2723
|
+
},
|
|
2724
|
+
};
|
|
2725
|
+
} catch (error) {
|
|
2726
|
+
releaseRawResultAccountSemaphore();
|
|
2727
|
+
throw error;
|
|
2728
|
+
}
|
|
2729
|
+
};
|
|
2730
|
+
|
|
2731
|
+
if (allowDedup && dedupEnabled && dedupHash) {
|
|
2732
|
+
const dedupResult = await deduplicate(dedupHash, execute);
|
|
2733
|
+
if (dedupResult.wasDeduplicated) {
|
|
2734
|
+
log?.debug?.("DEDUP", `Joined in-flight request hash=${dedupHash}`);
|
|
2735
|
+
}
|
|
2736
|
+
return materializeDeduplicatedExecutionResult(dedupResult.result);
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
return execute();
|
|
2740
|
+
};
|
|
2741
|
+
|
|
2742
|
+
const registeredProviderRequest =
|
|
2743
|
+
translatedBody && typeof translatedBody === "object" && !Array.isArray(translatedBody)
|
|
2744
|
+
? {
|
|
2745
|
+
...(translatedBody as Record<string, unknown>),
|
|
2746
|
+
model:
|
|
2747
|
+
typeof (translatedBody as Record<string, unknown>).model === "string"
|
|
2748
|
+
? (translatedBody as Record<string, unknown>).model
|
|
2749
|
+
: effectiveModel,
|
|
2750
|
+
...(!Array.isArray((translatedBody as Record<string, unknown>).messages) &&
|
|
2751
|
+
Array.isArray((body as Record<string, unknown>).messages)
|
|
2752
|
+
? { messages: (body as Record<string, unknown>).messages }
|
|
2753
|
+
: {}),
|
|
2754
|
+
}
|
|
2755
|
+
: translatedBody;
|
|
2756
|
+
|
|
2757
|
+
updatePendingScope(pendingScope, {
|
|
2758
|
+
providerRequest: registeredProviderRequest,
|
|
2759
|
+
});
|
|
2760
|
+
// T5: track which models we've tried for intra-family fallback
|
|
2761
|
+
const triedModels = new Set<string>([effectiveModel]);
|
|
2762
|
+
let currentModel = effectiveModel;
|
|
2763
|
+
|
|
2764
|
+
// Log start
|
|
2765
|
+
appendRequestLog({ model, provider, connectionId, status: "PENDING" }).catch(() => {});
|
|
2766
|
+
|
|
2767
|
+
const msgCount =
|
|
2768
|
+
translatedBody.messages?.length ||
|
|
2769
|
+
translatedBody.contents?.length ||
|
|
2770
|
+
translatedBody.request?.contents?.length ||
|
|
2771
|
+
(translatedBody.conversationState?.history?.length ?? 0) +
|
|
2772
|
+
(translatedBody.conversationState?.currentMessage ? 1 : 0) ||
|
|
2773
|
+
0;
|
|
2774
|
+
log?.debug?.("REQUEST", `${provider?.toUpperCase()} | ${model} | ${msgCount} msgs`);
|
|
2775
|
+
|
|
2776
|
+
// ── Tier 2: Authoritative per-model/provider token-limit check (provider now resolved) ──
|
|
2777
|
+
if (apiKeyInfo?.id) {
|
|
2778
|
+
try {
|
|
2779
|
+
const tokenBreach = checkTokenLimits(
|
|
2780
|
+
apiKeyInfo.id,
|
|
2781
|
+
provider || undefined,
|
|
2782
|
+
model || undefined
|
|
2783
|
+
);
|
|
2784
|
+
if (tokenBreach) {
|
|
2785
|
+
const scopeLabel =
|
|
2786
|
+
tokenBreach.scopeType === "global"
|
|
2787
|
+
? "account"
|
|
2788
|
+
: `${tokenBreach.scopeType} "${tokenBreach.scopeValue}"`;
|
|
2789
|
+
// FIX 6: clear the pending request marker before the early return so we do
|
|
2790
|
+
// not leak a phantom pending request (start was tracked at line ~1847).
|
|
2791
|
+
trackPendingRequest(model, provider, connectionId, false);
|
|
2792
|
+
// FIX 5: tag this as a per-API-key token-limit breach (errorCode
|
|
2793
|
+
// TOKEN_LIMIT_EXCEEDED) so the combo loop can distinguish it from an
|
|
2794
|
+
// upstream 429 and NOT cool shared accounts / retry it transiently.
|
|
2795
|
+
return createErrorResult(
|
|
2796
|
+
HTTP_STATUS.RATE_LIMITED,
|
|
2797
|
+
`Token limit exceeded for ${scopeLabel}: ${tokenBreach.tokensUsed}/${tokenBreach.limitValue} tokens used in the current window. Please try again later.`,
|
|
2798
|
+
null,
|
|
2799
|
+
"TOKEN_LIMIT_EXCEEDED"
|
|
2800
|
+
);
|
|
2801
|
+
}
|
|
2802
|
+
} catch (err) {
|
|
2803
|
+
// Fail-open at Tier 2: Tier 1 already enforced the model/global limit pre-dispatch.
|
|
2804
|
+
// A transient counter read error here must not break an otherwise-valid request.
|
|
2805
|
+
log?.warn?.("TOKEN_LIMIT", "Tier 2 token-limit check failed; allowing request", { err });
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
// Execute request using executor (handles URL building, headers, fallback, transform)
|
|
2810
|
+
let providerResponse;
|
|
2811
|
+
let providerUrl;
|
|
2812
|
+
let providerHeaders;
|
|
2813
|
+
let finalBody;
|
|
2814
|
+
let claudePromptCacheLogMeta = null;
|
|
2815
|
+
|
|
2816
|
+
try {
|
|
2817
|
+
const result = await executeProviderRequest(effectiveModel, true);
|
|
2818
|
+
|
|
2819
|
+
providerResponse = result.response;
|
|
2820
|
+
providerUrl = result.url;
|
|
2821
|
+
providerHeaders = result.headers;
|
|
2822
|
+
finalBody = providerRequestCapture.body(result.transformedBody);
|
|
2823
|
+
const responseConnectionId = getCurrentConnectionId();
|
|
2824
|
+
effectiveServiceTier = resolveEffectiveServiceTier(finalBody);
|
|
2825
|
+
claudePromptCacheLogMeta = buildClaudePromptCacheLogMeta(
|
|
2826
|
+
targetFormat,
|
|
2827
|
+
finalBody,
|
|
2828
|
+
providerHeaders,
|
|
2829
|
+
clientRawRequest?.headers
|
|
2830
|
+
);
|
|
2831
|
+
|
|
2832
|
+
// Log target request (final request to provider)
|
|
2833
|
+
reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody);
|
|
2834
|
+
updatePendingScope(pendingScope, {
|
|
2835
|
+
providerRequest: finalBody,
|
|
2836
|
+
providerUrl,
|
|
2837
|
+
stage: "provider_response_started",
|
|
2838
|
+
});
|
|
2839
|
+
// Update rate limiter from response headers (learn limits dynamically)
|
|
2840
|
+
updateFromHeaders(
|
|
2841
|
+
provider,
|
|
2842
|
+
responseConnectionId,
|
|
2843
|
+
providerResponse.headers,
|
|
2844
|
+
providerResponse.status,
|
|
2845
|
+
model
|
|
2846
|
+
);
|
|
2847
|
+
|
|
2848
|
+
// Store rate-limit headers for quota saturation signals
|
|
2849
|
+
try {
|
|
2850
|
+
const { storeRateLimitHeaders } = await import("@/lib/quota/saturationSignals");
|
|
2851
|
+
storeRateLimitHeaders(
|
|
2852
|
+
responseConnectionId,
|
|
2853
|
+
provider,
|
|
2854
|
+
providerResponse.headers as Record<string, string>
|
|
2855
|
+
);
|
|
2856
|
+
} catch {
|
|
2857
|
+
// fail-open: saturation signal is best-effort
|
|
2858
|
+
}
|
|
2859
|
+
} catch (error) {
|
|
2860
|
+
trackPendingRequest(model, provider, connectionId, false);
|
|
2861
|
+
if (isSemaphoreCapacityError(error)) {
|
|
2862
|
+
appendRequestLog({
|
|
2863
|
+
model,
|
|
2864
|
+
provider,
|
|
2865
|
+
connectionId,
|
|
2866
|
+
status: `FAILED ${error.code}`,
|
|
2867
|
+
}).catch(() => {});
|
|
2868
|
+
const failureMessage = error.message || "Semaphore timeout";
|
|
2869
|
+
persistAttemptLogs({
|
|
2870
|
+
status: HTTP_STATUS.RATE_LIMITED,
|
|
2871
|
+
error: failureMessage,
|
|
2872
|
+
providerRequest: finalBody || translatedBody,
|
|
2873
|
+
clientResponse: buildErrorBody(HTTP_STATUS.RATE_LIMITED, failureMessage),
|
|
2874
|
+
claudeCacheMeta: claudePromptCacheLogMeta,
|
|
2875
|
+
cacheSource: "upstream",
|
|
2876
|
+
});
|
|
2877
|
+
persistFailureUsage(HTTP_STATUS.RATE_LIMITED, error.code);
|
|
2878
|
+
const result = stream
|
|
2879
|
+
? createStreamingErrorResult(HTTP_STATUS.RATE_LIMITED, failureMessage, error.code)
|
|
2880
|
+
: createErrorResult(HTTP_STATUS.RATE_LIMITED, failureMessage);
|
|
2881
|
+
return {
|
|
2882
|
+
...result,
|
|
2883
|
+
errorType: "account_semaphore_capacity",
|
|
2884
|
+
errorCode: error.code,
|
|
2885
|
+
};
|
|
2886
|
+
}
|
|
2887
|
+
const failureStatus =
|
|
2888
|
+
error.name === "AbortError"
|
|
2889
|
+
? 499
|
|
2890
|
+
: error.name === "TimeoutError" || error.name === "BodyTimeoutError"
|
|
2891
|
+
? HTTP_STATUS.GATEWAY_TIMEOUT
|
|
2892
|
+
: error.status && typeof error.status === "number"
|
|
2893
|
+
? error.status
|
|
2894
|
+
: HTTP_STATUS.BAD_GATEWAY;
|
|
2895
|
+
const failureMessage =
|
|
2896
|
+
error.name === "AbortError"
|
|
2897
|
+
? "Request aborted"
|
|
2898
|
+
: formatProviderError(error, provider, model, failureStatus);
|
|
2899
|
+
const upstreamErrorCode = getUpstreamErrorIdentifier(error);
|
|
2900
|
+
// Tag our own deadline timeouts (fetch-start TimeoutError / body BodyTimeoutError,
|
|
2901
|
+
// both surfaced as a 504) as "upstream_timeout" so the cooldown layer can tell a
|
|
2902
|
+
// slow-but-not-failed request apart from a real provider 5xx. (Antigravity already
|
|
2903
|
+
// tags its pre-response timeout via the code below.)
|
|
2904
|
+
const isOwnDeadlineTimeout =
|
|
2905
|
+
failureStatus === HTTP_STATUS.GATEWAY_TIMEOUT &&
|
|
2906
|
+
(error.name === "TimeoutError" || error.name === "BodyTimeoutError");
|
|
2907
|
+
const upstreamErrorType =
|
|
2908
|
+
upstreamErrorCode === ANTIGRAVITY_PRE_RESPONSE_TIMEOUT_CODE || isOwnDeadlineTimeout
|
|
2909
|
+
? "upstream_timeout"
|
|
2910
|
+
: failureStatus === 401
|
|
2911
|
+
? "authentication_error"
|
|
2912
|
+
: undefined;
|
|
2913
|
+
appendRequestLog({
|
|
2914
|
+
model,
|
|
2915
|
+
provider,
|
|
2916
|
+
connectionId,
|
|
2917
|
+
status: `FAILED ${failureStatus}`,
|
|
2918
|
+
}).catch(() => {});
|
|
2919
|
+
persistAttemptLogs({
|
|
2920
|
+
status: failureStatus,
|
|
2921
|
+
error: failureMessage,
|
|
2922
|
+
providerRequest: finalBody || translatedBody,
|
|
2923
|
+
clientResponse: buildErrorBody(failureStatus, failureMessage),
|
|
2924
|
+
claudeCacheMeta: claudePromptCacheLogMeta,
|
|
2925
|
+
cacheSource: "upstream",
|
|
2926
|
+
});
|
|
2927
|
+
if (error.name === "AbortError") {
|
|
2928
|
+
streamController.handleError(error);
|
|
2929
|
+
return createErrorResult(499, "Request aborted");
|
|
2930
|
+
}
|
|
2931
|
+
persistFailureUsage(
|
|
2932
|
+
failureStatus,
|
|
2933
|
+
upstreamErrorCode || (error instanceof Error && error.name ? error.name : "upstream_error")
|
|
2934
|
+
);
|
|
2935
|
+
console.log(`${COLORS.red}[ERROR] ${failureMessage}${COLORS.reset}`);
|
|
2936
|
+
if (stream && upstreamErrorCode) {
|
|
2937
|
+
const result = createStreamingErrorResult(
|
|
2938
|
+
failureStatus,
|
|
2939
|
+
failureMessage,
|
|
2940
|
+
upstreamErrorCode,
|
|
2941
|
+
upstreamErrorType
|
|
2942
|
+
);
|
|
2943
|
+
return {
|
|
2944
|
+
...result,
|
|
2945
|
+
errorType: upstreamErrorType,
|
|
2946
|
+
errorCode: upstreamErrorCode,
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
return createErrorResult(
|
|
2950
|
+
failureStatus,
|
|
2951
|
+
failureMessage,
|
|
2952
|
+
null,
|
|
2953
|
+
upstreamErrorCode,
|
|
2954
|
+
upstreamErrorType
|
|
2955
|
+
);
|
|
2956
|
+
}
|
|
2957
|
+
// We need to peek at the error text if it's 400 for Qwen
|
|
2958
|
+
let upstreamErrorParsed = false;
|
|
2959
|
+
let parsedStatusCode = providerResponse.status;
|
|
2960
|
+
let parsedMessage = "";
|
|
2961
|
+
let parsedRetryAfterMs: number | null = null;
|
|
2962
|
+
let upstreamErrorBody: unknown = null;
|
|
2963
|
+
|
|
2964
|
+
if (provider === "qwen" && providerResponse.status === HTTP_STATUS.BAD_REQUEST) {
|
|
2965
|
+
const errorDetails = await parseUpstreamError(providerResponse, provider);
|
|
2966
|
+
parsedStatusCode = errorDetails.statusCode;
|
|
2967
|
+
parsedMessage = errorDetails.message;
|
|
2968
|
+
parsedRetryAfterMs = errorDetails.retryAfterMs;
|
|
2969
|
+
upstreamErrorBody = errorDetails.responseBody;
|
|
2970
|
+
upstreamErrorParsed = true;
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2973
|
+
const errorMessageForToolDetection =
|
|
2974
|
+
typeof upstreamErrorBody === "string"
|
|
2975
|
+
? upstreamErrorBody
|
|
2976
|
+
: JSON.stringify(upstreamErrorBody ?? {});
|
|
2977
|
+
if (shouldDetectLimit(errorMessageForToolDetection, parsedStatusCode)) {
|
|
2978
|
+
const detectedLimit = parseToolLimitFromError(errorMessageForToolDetection);
|
|
2979
|
+
if (detectedLimit) {
|
|
2980
|
+
setDetectedToolLimit(provider, detectedLimit);
|
|
2981
|
+
log?.info?.("TOOL_LIMIT", `Detected tool limit ${detectedLimit} for ${provider}`);
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
|
|
2985
|
+
const isQwenExpiredError =
|
|
2986
|
+
provider === "qwen" &&
|
|
2987
|
+
parsedStatusCode === HTTP_STATUS.BAD_REQUEST &&
|
|
2988
|
+
parsedMessage?.toLowerCase().includes("session has expired");
|
|
2989
|
+
|
|
2990
|
+
// Track whether stream_options was present and stripped — if so, 401/403 after
|
|
2991
|
+
// that may be from the modification rather than a genuine auth failure, so we
|
|
2992
|
+
// skip the credential refresh attempt in that case.
|
|
2993
|
+
const hadStreamOptions =
|
|
2994
|
+
targetFormat === FORMATS.OPENAI_RESPONSES && "stream_options" in translatedBody;
|
|
2995
|
+
if (hadStreamOptions) {
|
|
2996
|
+
delete translatedBody.stream_options;
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
// Handle 401/403 (and Qwen explicit expiration) - try token refresh using executor
|
|
3000
|
+
if (
|
|
3001
|
+
(providerResponse.status === HTTP_STATUS.UNAUTHORIZED ||
|
|
3002
|
+
providerResponse.status === HTTP_STATUS.FORBIDDEN ||
|
|
3003
|
+
isQwenExpiredError) &&
|
|
3004
|
+
!hadStreamOptions // Skip refresh if failure may be from stream_options removal, not auth
|
|
3005
|
+
) {
|
|
3006
|
+
// Fix A: wrap refreshCredentials in runWithOnPersist so the persist callback
|
|
3007
|
+
// executes INSIDE the per-connection mutex held by getAccessToken. This makes
|
|
3008
|
+
// [network refresh + DB write + outer-state mutation] one atomic step and
|
|
3009
|
+
// prevents concurrent requests from reading a stale refreshToken before the
|
|
3010
|
+
// DB has been updated (refresh_token_reused on Codex/OpenAI).
|
|
3011
|
+
//
|
|
3012
|
+
// Not every executor routes refresh through getAccessToken (e.g. github.ts
|
|
3013
|
+
// calls refreshCopilotToken directly). When the persistFn doesn't fire from
|
|
3014
|
+
// inside getAccessToken, we still need to do the credentials mutation + user
|
|
3015
|
+
// callback after refreshCredentials returns. The `persistFnRan` flag tracks
|
|
3016
|
+
// which path executed so we don't double-fire (race-prone) or skip (regression).
|
|
3017
|
+
// Front 3: remember the refresh_token we are about to present so that, if the
|
|
3018
|
+
// refresh fails as unrecoverable, we can tell a genuine death apart from a
|
|
3019
|
+
// stale-token reuse that a concurrent/sibling refresh already rotated past.
|
|
3020
|
+
const attemptedRefreshToken =
|
|
3021
|
+
typeof credentials?.refreshToken === "string" ? credentials.refreshToken : null;
|
|
3022
|
+
let persistFnRan = false;
|
|
3023
|
+
const persistFn = onCredentialsRefreshed
|
|
3024
|
+
? async (refreshResult: Record<string, unknown>) => {
|
|
3025
|
+
persistFnRan = true;
|
|
3026
|
+
// Mutate the shared credentials object so subsequent executor calls
|
|
3027
|
+
// in this request see the new tokens. Runs INSIDE the mutex.
|
|
3028
|
+
Object.assign(credentials, refreshResult);
|
|
3029
|
+
await onCredentialsRefreshed(refreshResult);
|
|
3030
|
+
}
|
|
3031
|
+
: undefined;
|
|
3032
|
+
|
|
3033
|
+
// #4038: build a compare-and-swap reread so getAccessToken can skip the persist if a
|
|
3034
|
+
// concurrent writer (sibling request / HealthCheck / replica) already rotated this
|
|
3035
|
+
// connection's refresh_token past the one we presented — overwriting would revert it
|
|
3036
|
+
// and revoke the token family. No connectionId ⇒ no guard (behavior unchanged).
|
|
3037
|
+
const casConnectionId =
|
|
3038
|
+
typeof credentials?.connectionId === "string" ? credentials.connectionId.trim() : "";
|
|
3039
|
+
const casReread = casConnectionId
|
|
3040
|
+
? async () => {
|
|
3041
|
+
const latest = await getProviderConnectionById(casConnectionId);
|
|
3042
|
+
return typeof latest?.refreshToken === "string" ? latest.refreshToken : null;
|
|
3043
|
+
}
|
|
3044
|
+
: null;
|
|
3045
|
+
|
|
3046
|
+
const newCredentials = (await refreshWithRetry(
|
|
3047
|
+
() =>
|
|
3048
|
+
runWithCasGuard(
|
|
3049
|
+
casReread ? { expectedRefreshToken: attemptedRefreshToken, reread: casReread } : null,
|
|
3050
|
+
() => runWithOnPersist(persistFn, () => executor.refreshCredentials(credentials, log))
|
|
3051
|
+
),
|
|
3052
|
+
3,
|
|
3053
|
+
log,
|
|
3054
|
+
provider // Explicitly pass the provider to avoid universally tripping the "unknown" circuit breaker
|
|
3055
|
+
)) as null | {
|
|
3056
|
+
accessToken?: string;
|
|
3057
|
+
copilotToken?: string;
|
|
3058
|
+
};
|
|
3059
|
+
|
|
3060
|
+
if (newCredentials?.accessToken || newCredentials?.copilotToken) {
|
|
3061
|
+
log?.info?.("TOKEN", `${provider?.toUpperCase()} | refreshed`);
|
|
3062
|
+
|
|
3063
|
+
// Fall back to post-mutex mutation only for executors that don't route
|
|
3064
|
+
// through getAccessToken (and therefore never fire onPersist). For
|
|
3065
|
+
// executors that DO route through it (Codex, Claude, Gemini, etc.) the
|
|
3066
|
+
// mutation already happened atomically inside the mutex.
|
|
3067
|
+
if (!persistFnRan) {
|
|
3068
|
+
Object.assign(credentials, newCredentials);
|
|
3069
|
+
if (onCredentialsRefreshed) {
|
|
3070
|
+
await onCredentialsRefreshed(newCredentials);
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
|
|
3074
|
+
// Retry with new credentials — model + extra headers follow translatedBody.model so they
|
|
3075
|
+
// stay aligned if this block ever runs after a path that mutates body.model (e.g. fallback).
|
|
3076
|
+
try {
|
|
3077
|
+
const retryModelId = String(translatedBody.model || effectiveModel);
|
|
3078
|
+
const retryResult = await runWithCapture(providerRequestCapture, () =>
|
|
3079
|
+
executor.execute({
|
|
3080
|
+
model: retryModelId,
|
|
3081
|
+
body: translatedBody,
|
|
3082
|
+
stream: upstreamStream,
|
|
3083
|
+
credentials: getExecutionCredentials(),
|
|
3084
|
+
signal: streamController.signal,
|
|
3085
|
+
log,
|
|
3086
|
+
extendedContext,
|
|
3087
|
+
upstreamExtraHeaders: buildUpstreamHeadersForExecute(retryModelId),
|
|
3088
|
+
clientHeaders: buildExecutorClientHeaders(clientRawRequest?.headers, userAgent),
|
|
3089
|
+
onCredentialsRefreshed,
|
|
3090
|
+
skipUpstreamRetry: isCombo,
|
|
3091
|
+
contextEditing: { enabled: contextEditingEnabled },
|
|
3092
|
+
})
|
|
3093
|
+
);
|
|
3094
|
+
|
|
3095
|
+
if (retryResult.response.ok) {
|
|
3096
|
+
providerResponse = retryResult.response;
|
|
3097
|
+
providerUrl = retryResult.url;
|
|
3098
|
+
providerHeaders = new Headers(retryResult.headers || {});
|
|
3099
|
+
finalBody = providerRequestCapture.body(retryResult.transformedBody);
|
|
3100
|
+
reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody);
|
|
3101
|
+
updatePendingScope(pendingScope, {
|
|
3102
|
+
providerRequest: finalBody,
|
|
3103
|
+
providerUrl,
|
|
3104
|
+
stage: "provider_response_started",
|
|
3105
|
+
});
|
|
3106
|
+
upstreamErrorParsed = false; // Reset since new response is OK
|
|
3107
|
+
} else {
|
|
3108
|
+
providerResponse = retryResult.response;
|
|
3109
|
+
upstreamErrorParsed = false; // Let it be parsed downstream
|
|
3110
|
+
}
|
|
3111
|
+
} catch (retryErr) {
|
|
3112
|
+
// Refresh succeeded but the retry leg failed (network blip, AbortError,
|
|
3113
|
+
// executor throw). Don't swallow — the operator-visible signal "the user
|
|
3114
|
+
// saw 401 even though auth was actually fixed" is much more confusing
|
|
3115
|
+
// than the original 401 alone. Surface at error level with sanitization.
|
|
3116
|
+
log?.error?.(
|
|
3117
|
+
"TOKEN",
|
|
3118
|
+
`${provider?.toUpperCase()} | retry after refresh failed: ${sanitizeErrorMessage(retryErr)}`
|
|
3119
|
+
);
|
|
3120
|
+
}
|
|
3121
|
+
} else {
|
|
3122
|
+
log?.warn?.("TOKEN", `${provider?.toUpperCase()} | refresh failed`);
|
|
3123
|
+
if (isUnrecoverableRefreshError(newCredentials) && onCredentialsRefreshed) {
|
|
3124
|
+
// Front 3 (reuse-race tolerance): before deactivating, re-read the DB.
|
|
3125
|
+
// If a sibling/concurrent refresh already rotated this connection's
|
|
3126
|
+
// refresh_token (common for Codex/OpenAI under one shared Auth0 client),
|
|
3127
|
+
// the failure we saw was a stale-token reuse — the account is healthy
|
|
3128
|
+
// with the newer token, so keep it active instead of killing it.
|
|
3129
|
+
let alreadyRotated = false;
|
|
3130
|
+
if (typeof connectionId === "string" && connectionId && attemptedRefreshToken) {
|
|
3131
|
+
try {
|
|
3132
|
+
const latest = await getProviderConnectionById(connectionId);
|
|
3133
|
+
if (wasRefreshTokenRotated(attemptedRefreshToken, latest?.refreshToken)) {
|
|
3134
|
+
alreadyRotated = true;
|
|
3135
|
+
log?.warn?.(
|
|
3136
|
+
"TOKEN",
|
|
3137
|
+
`${provider.toUpperCase()} | refresh_token already rotated by a concurrent refresh — keeping connection active`
|
|
3138
|
+
);
|
|
3139
|
+
}
|
|
3140
|
+
} catch {
|
|
3141
|
+
// DB read failed — fall through to the safe default (deactivate).
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
if (!alreadyRotated) {
|
|
3145
|
+
await onCredentialsRefreshed({ testStatus: "expired", isActive: false });
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
await persistCodexQuotaState(normalizeHeaders(providerResponse.headers), providerResponse.status);
|
|
3152
|
+
|
|
3153
|
+
// Check provider response - return error info for fallback handling
|
|
3154
|
+
if (!providerResponse.ok) {
|
|
3155
|
+
trackPendingRequest(model, provider, connectionId, false);
|
|
3156
|
+
|
|
3157
|
+
let statusCode = providerResponse.status;
|
|
3158
|
+
let message = "";
|
|
3159
|
+
let retryAfterMs: number | null = null;
|
|
3160
|
+
let upstreamErrorCode: string | undefined;
|
|
3161
|
+
let upstreamErrorType: string | undefined;
|
|
3162
|
+
|
|
3163
|
+
if (upstreamErrorParsed) {
|
|
3164
|
+
statusCode = parsedStatusCode;
|
|
3165
|
+
message = parsedMessage;
|
|
3166
|
+
retryAfterMs = parsedRetryAfterMs;
|
|
3167
|
+
} else {
|
|
3168
|
+
const details = await parseUpstreamError(providerResponse, provider);
|
|
3169
|
+
statusCode = details.statusCode;
|
|
3170
|
+
message = details.message;
|
|
3171
|
+
retryAfterMs = details.retryAfterMs;
|
|
3172
|
+
upstreamErrorBody = details.responseBody;
|
|
3173
|
+
upstreamErrorCode = details.errorCode as string | undefined;
|
|
3174
|
+
upstreamErrorType = details.errorType as string | undefined;
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
// T06/T10/T36: classify provider errors and persist terminal account states.
|
|
3178
|
+
let errorType = classifyProviderError(statusCode, message, provider);
|
|
3179
|
+
if (statusCode === 429 && isModelScope()) {
|
|
3180
|
+
const decision = classifyModelScope429(message, normalizeHeaders(providerResponse.headers));
|
|
3181
|
+
errorType =
|
|
3182
|
+
decision.kind === "quota_exhausted"
|
|
3183
|
+
? PROVIDER_ERROR_TYPES.QUOTA_EXHAUSTED
|
|
3184
|
+
: PROVIDER_ERROR_TYPES.RATE_LIMITED;
|
|
3185
|
+
log?.warn?.(
|
|
3186
|
+
"MODELSCOPE_429",
|
|
3187
|
+
`${decision.kind} (model remaining: ${decision.snapshot.modelRemaining ?? "unknown"}, total remaining: ${decision.snapshot.totalRemaining ?? "unknown"})`
|
|
3188
|
+
);
|
|
3189
|
+
}
|
|
3190
|
+
const errorConnectionId = getCurrentConnectionId();
|
|
3191
|
+
if (errorConnectionId && errorType) {
|
|
3192
|
+
try {
|
|
3193
|
+
if (errorType === PROVIDER_ERROR_TYPES.FORBIDDEN) {
|
|
3194
|
+
await updateProviderConnection(errorConnectionId, {
|
|
3195
|
+
isActive: false,
|
|
3196
|
+
testStatus: "banned",
|
|
3197
|
+
lastErrorType: errorType,
|
|
3198
|
+
lastError: message,
|
|
3199
|
+
errorCode: statusCode,
|
|
3200
|
+
});
|
|
3201
|
+
console.warn(
|
|
3202
|
+
`[provider] Node ${errorConnectionId} banned (${statusCode}) — disabling permanently`
|
|
3203
|
+
);
|
|
3204
|
+
} else if (errorType === PROVIDER_ERROR_TYPES.ACCOUNT_DEACTIVATED) {
|
|
3205
|
+
// Plan A: if connection has extra API keys, don't disable — only the failing key is affected.
|
|
3206
|
+
// Single-key connections still get disabled as before.
|
|
3207
|
+
if (
|
|
3208
|
+
connectionHasExtraKeys(
|
|
3209
|
+
errorConnectionId,
|
|
3210
|
+
(credentials?.providerSpecificData as Record<string, unknown> | undefined)
|
|
3211
|
+
?.extraApiKeys as string[] | undefined
|
|
3212
|
+
)
|
|
3213
|
+
) {
|
|
3214
|
+
await updateProviderConnection(errorConnectionId, {
|
|
3215
|
+
lastErrorType: errorType,
|
|
3216
|
+
lastError: message,
|
|
3217
|
+
errorCode: statusCode,
|
|
3218
|
+
});
|
|
3219
|
+
console.warn(
|
|
3220
|
+
`[provider] Node ${errorConnectionId} account deactivated (${statusCode}) — has extra keys, keeping connection active`
|
|
3221
|
+
);
|
|
3222
|
+
} else {
|
|
3223
|
+
await updateProviderConnection(errorConnectionId, {
|
|
3224
|
+
isActive: false,
|
|
3225
|
+
testStatus: "deactivated",
|
|
3226
|
+
lastErrorType: errorType,
|
|
3227
|
+
lastError: message,
|
|
3228
|
+
errorCode: statusCode,
|
|
3229
|
+
});
|
|
3230
|
+
console.warn(
|
|
3231
|
+
`[provider] Node ${errorConnectionId} account deactivated (${statusCode}) — disabling permanently`
|
|
3232
|
+
);
|
|
3233
|
+
}
|
|
3234
|
+
} else if (errorType === PROVIDER_ERROR_TYPES.QUOTA_EXHAUSTED) {
|
|
3235
|
+
// Providers with per-model quotas — lock the model only, not the connection
|
|
3236
|
+
const quotaCooldownMs = retryAfterMs || COOLDOWN_MS.rateLimit;
|
|
3237
|
+
const accountSemaphoreKey = resolveAccountSemaphoreKey({
|
|
3238
|
+
provider,
|
|
3239
|
+
model: currentModel,
|
|
3240
|
+
connectionId: errorConnectionId,
|
|
3241
|
+
credentials,
|
|
3242
|
+
});
|
|
3243
|
+
if (accountSemaphoreKey) {
|
|
3244
|
+
markAccountSemaphoreBlocked(accountSemaphoreKey, quotaCooldownMs);
|
|
3245
|
+
}
|
|
3246
|
+
if (isModelScope() && errorConnectionId) {
|
|
3247
|
+
lockModel(provider, errorConnectionId, model, "quota_exhausted", quotaCooldownMs);
|
|
3248
|
+
console.warn(
|
|
3249
|
+
`[provider] Node ${errorConnectionId} ModelScope model quota exhausted (${statusCode}) for ${model} - ${Math.ceil(quotaCooldownMs / 1000)}s (connection stays active)`
|
|
3250
|
+
);
|
|
3251
|
+
} else if (
|
|
3252
|
+
lockModelIfPerModelQuota(
|
|
3253
|
+
provider,
|
|
3254
|
+
errorConnectionId,
|
|
3255
|
+
model,
|
|
3256
|
+
"quota_exhausted",
|
|
3257
|
+
quotaCooldownMs
|
|
3258
|
+
)
|
|
3259
|
+
) {
|
|
3260
|
+
const quotaScope = getQuotaScopeLabelForProvider(provider, model);
|
|
3261
|
+
console.warn(
|
|
3262
|
+
`[provider] Node ${errorConnectionId} ${quotaScope}-only quota exhausted (${statusCode}) for ${model} - ${Math.ceil(quotaCooldownMs / 1000)}s (cooldown_scope=${quotaScope}, ttl_source=${retryAfterMs ? "upstream" : "inferred"}, connection stays active)`
|
|
3263
|
+
);
|
|
3264
|
+
} else {
|
|
3265
|
+
await updateProviderConnection(errorConnectionId, {
|
|
3266
|
+
testStatus: "credits_exhausted",
|
|
3267
|
+
lastErrorType: errorType,
|
|
3268
|
+
lastError: message,
|
|
3269
|
+
errorCode: statusCode,
|
|
3270
|
+
});
|
|
3271
|
+
console.warn(`[provider] Node ${errorConnectionId} exhausted quota (${statusCode})`);
|
|
3272
|
+
}
|
|
3273
|
+
} else if (errorType === PROVIDER_ERROR_TYPES.UNAUTHORIZED) {
|
|
3274
|
+
// Normal 401 (token/session auth issue): keep account active for refresh/re-auth.
|
|
3275
|
+
await updateProviderConnection(errorConnectionId, {
|
|
3276
|
+
lastErrorType: errorType,
|
|
3277
|
+
lastError: message,
|
|
3278
|
+
errorCode: statusCode,
|
|
3279
|
+
});
|
|
3280
|
+
} else if (errorType === PROVIDER_ERROR_TYPES.OAUTH_INVALID_TOKEN) {
|
|
3281
|
+
// OAuth 401 with invalid credentials - token refresh can recover
|
|
3282
|
+
await updateProviderConnection(errorConnectionId, {
|
|
3283
|
+
lastErrorType: errorType,
|
|
3284
|
+
lastError: message,
|
|
3285
|
+
errorCode: statusCode,
|
|
3286
|
+
});
|
|
3287
|
+
console.warn(
|
|
3288
|
+
`[provider] Node ${errorConnectionId} OAuth token invalid (${statusCode}) — token refresh available`
|
|
3289
|
+
);
|
|
3290
|
+
} else if (errorType === PROVIDER_ERROR_TYPES.PROJECT_ROUTE_ERROR) {
|
|
3291
|
+
// Cloud Code 403 with stale project: not a ban, keep account active.
|
|
3292
|
+
await updateProviderConnection(errorConnectionId, {
|
|
3293
|
+
lastErrorType: errorType,
|
|
3294
|
+
lastError: message,
|
|
3295
|
+
errorCode: statusCode,
|
|
3296
|
+
});
|
|
3297
|
+
console.warn(
|
|
3298
|
+
`[provider] Node ${errorConnectionId} project routing error (${statusCode}) — not banning`
|
|
3299
|
+
);
|
|
3300
|
+
}
|
|
3301
|
+
} catch {
|
|
3302
|
+
// Best-effort state update; request flow should continue with fallback handling.
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
|
|
3306
|
+
appendRequestLog({
|
|
3307
|
+
model,
|
|
3308
|
+
provider,
|
|
3309
|
+
connectionId: errorConnectionId,
|
|
3310
|
+
status: `FAILED ${statusCode}`,
|
|
3311
|
+
}).catch(() => {});
|
|
3312
|
+
|
|
3313
|
+
const errMsg = formatProviderError(new Error(message), provider, model, statusCode);
|
|
3314
|
+
console.log(`${COLORS.red}[ERROR] ${errMsg}${COLORS.reset}`);
|
|
3315
|
+
|
|
3316
|
+
// Log Antigravity retry time if available
|
|
3317
|
+
if (retryAfterMs && provider === "antigravity") {
|
|
3318
|
+
const retrySeconds = Math.ceil(retryAfterMs / 1000);
|
|
3319
|
+
log?.debug?.("RETRY", `Antigravity quota reset in ${retrySeconds}s (${retryAfterMs}ms)`);
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
// Log error with full request body for debugging
|
|
3323
|
+
reqLogger.logError(new Error(message), finalBody || translatedBody);
|
|
3324
|
+
reqLogger.logProviderResponse(
|
|
3325
|
+
providerResponse.status,
|
|
3326
|
+
providerResponse.statusText,
|
|
3327
|
+
providerResponse.headers,
|
|
3328
|
+
upstreamErrorBody
|
|
3329
|
+
);
|
|
3330
|
+
|
|
3331
|
+
// Update rate limiter from error response headers
|
|
3332
|
+
updateFromHeaders(provider, errorConnectionId, providerResponse.headers, statusCode, model);
|
|
3333
|
+
if (errorConnectionId && upstreamErrorBody !== null && upstreamErrorBody !== undefined) {
|
|
3334
|
+
updateFromResponseBody(provider, errorConnectionId, upstreamErrorBody, statusCode, model);
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
// ── T5: Intra-family model fallback ──────────────────────────────────────
|
|
3338
|
+
// Before returning a model-unavailable error upstream, try sibling models
|
|
3339
|
+
// from the same family. This keeps the request alive on the same account
|
|
3340
|
+
// instead of failing the entire combo.
|
|
3341
|
+
if (isModelUnavailableError(statusCode, message)) {
|
|
3342
|
+
const nextModel = getNextFamilyFallback(currentModel, triedModels);
|
|
3343
|
+
if (nextModel) {
|
|
3344
|
+
triedModels.add(nextModel);
|
|
3345
|
+
currentModel = nextModel;
|
|
3346
|
+
translatedBody.model = nextModel;
|
|
3347
|
+
log?.info?.("MODEL_FALLBACK", `${model} unavailable (${statusCode}) → trying ${nextModel}`);
|
|
3348
|
+
// Re-execute with the fallback model
|
|
3349
|
+
try {
|
|
3350
|
+
const fallbackResult = await executeProviderRequest(nextModel, false);
|
|
3351
|
+
if (fallbackResult.response.ok) {
|
|
3352
|
+
providerResponse = fallbackResult.response;
|
|
3353
|
+
providerUrl = fallbackResult.url;
|
|
3354
|
+
providerHeaders = fallbackResult.headers;
|
|
3355
|
+
finalBody = providerRequestCapture.body(fallbackResult.transformedBody);
|
|
3356
|
+
reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody);
|
|
3357
|
+
updatePendingScope(pendingScope, {
|
|
3358
|
+
providerRequest: finalBody,
|
|
3359
|
+
providerUrl,
|
|
3360
|
+
stage: "provider_response_started",
|
|
3361
|
+
});
|
|
3362
|
+
// Continue processing with the fallback response — skip error return
|
|
3363
|
+
log?.info?.("MODEL_FALLBACK", `Serving ${nextModel} as fallback for ${model}`);
|
|
3364
|
+
// Jump to streaming/non-streaming handling below
|
|
3365
|
+
// We fall through by NOT returning here
|
|
3366
|
+
} else {
|
|
3367
|
+
// Fallback also failed — return original error
|
|
3368
|
+
persistAttemptLogs({
|
|
3369
|
+
status: statusCode,
|
|
3370
|
+
error: errMsg,
|
|
3371
|
+
providerRequest: finalBody || translatedBody,
|
|
3372
|
+
providerResponse: upstreamErrorBody,
|
|
3373
|
+
clientResponse: buildErrorBody(statusCode, errMsg),
|
|
3374
|
+
cacheSource: "upstream",
|
|
3375
|
+
});
|
|
3376
|
+
persistFailureUsage(statusCode, "model_unavailable");
|
|
3377
|
+
return createErrorResult(
|
|
3378
|
+
statusCode,
|
|
3379
|
+
errMsg,
|
|
3380
|
+
retryAfterMs,
|
|
3381
|
+
upstreamErrorCode,
|
|
3382
|
+
upstreamErrorType,
|
|
3383
|
+
upstreamErrorBody
|
|
3384
|
+
);
|
|
3385
|
+
}
|
|
3386
|
+
} catch {
|
|
3387
|
+
persistAttemptLogs({
|
|
3388
|
+
status: statusCode,
|
|
3389
|
+
error: errMsg,
|
|
3390
|
+
providerRequest: finalBody || translatedBody,
|
|
3391
|
+
providerResponse: upstreamErrorBody,
|
|
3392
|
+
clientResponse: buildErrorBody(statusCode, errMsg),
|
|
3393
|
+
cacheSource: "upstream",
|
|
3394
|
+
});
|
|
3395
|
+
persistFailureUsage(statusCode, "model_unavailable");
|
|
3396
|
+
return createErrorResult(
|
|
3397
|
+
statusCode,
|
|
3398
|
+
errMsg,
|
|
3399
|
+
retryAfterMs,
|
|
3400
|
+
upstreamErrorCode,
|
|
3401
|
+
upstreamErrorType,
|
|
3402
|
+
upstreamErrorBody
|
|
3403
|
+
);
|
|
3404
|
+
}
|
|
3405
|
+
} else {
|
|
3406
|
+
persistAttemptLogs({
|
|
3407
|
+
status: statusCode,
|
|
3408
|
+
error: errMsg,
|
|
3409
|
+
providerRequest: finalBody || translatedBody,
|
|
3410
|
+
providerResponse: upstreamErrorBody,
|
|
3411
|
+
clientResponse: buildErrorBody(statusCode, errMsg),
|
|
3412
|
+
cacheSource: "upstream",
|
|
3413
|
+
});
|
|
3414
|
+
persistFailureUsage(statusCode, "model_unavailable");
|
|
3415
|
+
return createErrorResult(
|
|
3416
|
+
statusCode,
|
|
3417
|
+
errMsg,
|
|
3418
|
+
retryAfterMs,
|
|
3419
|
+
upstreamErrorCode,
|
|
3420
|
+
upstreamErrorType,
|
|
3421
|
+
upstreamErrorBody
|
|
3422
|
+
);
|
|
3423
|
+
}
|
|
3424
|
+
} else if (isContextOverflowError(statusCode, message)) {
|
|
3425
|
+
const familyCandidates = getModelFamily(currentModel).filter(
|
|
3426
|
+
(m) => m !== currentModel && !triedModels.has(m)
|
|
3427
|
+
);
|
|
3428
|
+
const nextModel =
|
|
3429
|
+
findLargerContextModel(currentModel, familyCandidates) ??
|
|
3430
|
+
getNextFamilyFallback(currentModel, triedModels);
|
|
3431
|
+
if (nextModel) {
|
|
3432
|
+
triedModels.add(nextModel);
|
|
3433
|
+
currentModel = nextModel;
|
|
3434
|
+
translatedBody.model = nextModel;
|
|
3435
|
+
log?.info?.("CONTEXT_OVERFLOW_FALLBACK", `${model} context overflow → trying ${nextModel}`);
|
|
3436
|
+
try {
|
|
3437
|
+
const fallbackResult = await executeProviderRequest(nextModel, false);
|
|
3438
|
+
if (fallbackResult.response.ok) {
|
|
3439
|
+
providerResponse = fallbackResult.response;
|
|
3440
|
+
providerUrl = fallbackResult.url;
|
|
3441
|
+
providerHeaders = fallbackResult.headers;
|
|
3442
|
+
finalBody = providerRequestCapture.body(fallbackResult.transformedBody);
|
|
3443
|
+
reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody);
|
|
3444
|
+
updatePendingScope(pendingScope, {
|
|
3445
|
+
providerRequest: finalBody,
|
|
3446
|
+
providerUrl,
|
|
3447
|
+
stage: "provider_response_started",
|
|
3448
|
+
});
|
|
3449
|
+
log?.info?.(
|
|
3450
|
+
"CONTEXT_OVERFLOW_FALLBACK",
|
|
3451
|
+
`Serving ${nextModel} as fallback for ${model}`
|
|
3452
|
+
);
|
|
3453
|
+
} else {
|
|
3454
|
+
persistAttemptLogs({
|
|
3455
|
+
status: statusCode,
|
|
3456
|
+
error: errMsg,
|
|
3457
|
+
providerRequest: finalBody || translatedBody,
|
|
3458
|
+
providerResponse: upstreamErrorBody,
|
|
3459
|
+
clientResponse: buildErrorBody(statusCode, errMsg),
|
|
3460
|
+
cacheSource: "upstream",
|
|
3461
|
+
});
|
|
3462
|
+
persistFailureUsage(statusCode, "context_overflow");
|
|
3463
|
+
return createErrorResult(
|
|
3464
|
+
statusCode,
|
|
3465
|
+
errMsg,
|
|
3466
|
+
retryAfterMs,
|
|
3467
|
+
upstreamErrorCode,
|
|
3468
|
+
upstreamErrorType,
|
|
3469
|
+
upstreamErrorBody
|
|
3470
|
+
);
|
|
3471
|
+
}
|
|
3472
|
+
} catch {
|
|
3473
|
+
persistAttemptLogs({
|
|
3474
|
+
status: statusCode,
|
|
3475
|
+
error: errMsg,
|
|
3476
|
+
providerRequest: finalBody || translatedBody,
|
|
3477
|
+
providerResponse: upstreamErrorBody,
|
|
3478
|
+
clientResponse: buildErrorBody(statusCode, errMsg),
|
|
3479
|
+
cacheSource: "upstream",
|
|
3480
|
+
});
|
|
3481
|
+
persistFailureUsage(statusCode, "context_overflow");
|
|
3482
|
+
return createErrorResult(
|
|
3483
|
+
statusCode,
|
|
3484
|
+
errMsg,
|
|
3485
|
+
retryAfterMs,
|
|
3486
|
+
upstreamErrorCode,
|
|
3487
|
+
upstreamErrorType,
|
|
3488
|
+
upstreamErrorBody
|
|
3489
|
+
);
|
|
3490
|
+
}
|
|
3491
|
+
} else {
|
|
3492
|
+
persistAttemptLogs({
|
|
3493
|
+
status: statusCode,
|
|
3494
|
+
error: errMsg,
|
|
3495
|
+
providerRequest: finalBody || translatedBody,
|
|
3496
|
+
providerResponse: upstreamErrorBody,
|
|
3497
|
+
clientResponse: buildErrorBody(statusCode, errMsg),
|
|
3498
|
+
cacheSource: "upstream",
|
|
3499
|
+
});
|
|
3500
|
+
persistFailureUsage(statusCode, "context_overflow");
|
|
3501
|
+
return createErrorResult(
|
|
3502
|
+
statusCode,
|
|
3503
|
+
errMsg,
|
|
3504
|
+
retryAfterMs,
|
|
3505
|
+
upstreamErrorCode,
|
|
3506
|
+
upstreamErrorType,
|
|
3507
|
+
upstreamErrorBody
|
|
3508
|
+
);
|
|
3509
|
+
}
|
|
3510
|
+
} else {
|
|
3511
|
+
persistAttemptLogs({
|
|
3512
|
+
status: statusCode,
|
|
3513
|
+
error: errMsg,
|
|
3514
|
+
providerRequest: finalBody || translatedBody,
|
|
3515
|
+
providerResponse: upstreamErrorBody,
|
|
3516
|
+
clientResponse: buildErrorBody(statusCode, errMsg),
|
|
3517
|
+
cacheSource: "upstream",
|
|
3518
|
+
});
|
|
3519
|
+
persistFailureUsage(statusCode, `upstream_${statusCode}`);
|
|
3520
|
+
|
|
3521
|
+
// Emergency budget fallback is orchestrated exclusively by the routing layer
|
|
3522
|
+
// (src/sse/handlers/chat.ts), which resolves credentials FOR the emergency
|
|
3523
|
+
// provider through account selection. The executor-level hop that used to
|
|
3524
|
+
// live here re-sent the FAILING provider's credentials to the emergency
|
|
3525
|
+
// provider's endpoint (e.g. the OpenAI API key to integrate.api.nvidia.com)
|
|
3526
|
+
// — a cross-provider credential leak that also never succeeded upstream.
|
|
3527
|
+
return createErrorResult(
|
|
3528
|
+
statusCode,
|
|
3529
|
+
errMsg,
|
|
3530
|
+
retryAfterMs,
|
|
3531
|
+
upstreamErrorCode,
|
|
3532
|
+
upstreamErrorType,
|
|
3533
|
+
upstreamErrorBody
|
|
3534
|
+
);
|
|
3535
|
+
}
|
|
3536
|
+
// ── End T5 ───────────────────────────────────────────────────────────────
|
|
3537
|
+
}
|
|
3538
|
+
|
|
3539
|
+
// Non-streaming response
|
|
3540
|
+
if (!stream) {
|
|
3541
|
+
const parsed = await parseNonStreamingResponseBody({
|
|
3542
|
+
providerResponse,
|
|
3543
|
+
upstreamStream,
|
|
3544
|
+
providerHeaders,
|
|
3545
|
+
finalBody,
|
|
3546
|
+
targetFormat,
|
|
3547
|
+
model,
|
|
3548
|
+
log,
|
|
3549
|
+
});
|
|
3550
|
+
const normalizedProviderPayload = parsed.normalizedProviderPayload;
|
|
3551
|
+
const looksLikeSSE = parsed.looksLikeSSE;
|
|
3552
|
+
|
|
3553
|
+
if (parsed.kind === "invalid_sse") {
|
|
3554
|
+
appendRequestLog({
|
|
3555
|
+
model,
|
|
3556
|
+
provider,
|
|
3557
|
+
connectionId,
|
|
3558
|
+
status: `FAILED ${HTTP_STATUS.BAD_GATEWAY}`,
|
|
3559
|
+
}).catch(() => {});
|
|
3560
|
+
const invalidSseMessage = parsed.message;
|
|
3561
|
+
persistAttemptLogs({
|
|
3562
|
+
status: HTTP_STATUS.BAD_GATEWAY,
|
|
3563
|
+
error: invalidSseMessage,
|
|
3564
|
+
providerRequest: finalBody || translatedBody,
|
|
3565
|
+
providerResponse: normalizedProviderPayload,
|
|
3566
|
+
clientResponse: buildErrorBody(HTTP_STATUS.BAD_GATEWAY, invalidSseMessage),
|
|
3567
|
+
cacheSource: "upstream",
|
|
3568
|
+
});
|
|
3569
|
+
persistFailureUsage(HTTP_STATUS.BAD_GATEWAY, "invalid_sse_payload");
|
|
3570
|
+
trackPendingRequest(model, provider, pendingConnId, false);
|
|
3571
|
+
return createErrorResult(HTTP_STATUS.BAD_GATEWAY, invalidSseMessage);
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
if (parsed.kind === "invalid_json") {
|
|
3575
|
+
appendRequestLog({
|
|
3576
|
+
model,
|
|
3577
|
+
provider,
|
|
3578
|
+
connectionId,
|
|
3579
|
+
status: `FAILED ${HTTP_STATUS.BAD_GATEWAY}`,
|
|
3580
|
+
}).catch(() => {});
|
|
3581
|
+
const detailedError = parsed.detailedError;
|
|
3582
|
+
const invalidJsonMessage = parsed.message;
|
|
3583
|
+
persistAttemptLogs({
|
|
3584
|
+
status: HTTP_STATUS.BAD_GATEWAY,
|
|
3585
|
+
error: detailedError,
|
|
3586
|
+
providerRequest: finalBody || translatedBody,
|
|
3587
|
+
providerResponse: normalizedProviderPayload,
|
|
3588
|
+
clientResponse: buildErrorBody(HTTP_STATUS.BAD_GATEWAY, invalidJsonMessage),
|
|
3589
|
+
cacheSource: "upstream",
|
|
3590
|
+
});
|
|
3591
|
+
persistFailureUsage(HTTP_STATUS.BAD_GATEWAY, "invalid_json_payload");
|
|
3592
|
+
trackPendingRequest(model, provider, connectionId, false);
|
|
3593
|
+
return createErrorResult(HTTP_STATUS.BAD_GATEWAY, invalidJsonMessage);
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3596
|
+
let responseBody = parsed.responseBody;
|
|
3597
|
+
let responsePayloadFormat = parsed.responsePayloadFormat;
|
|
3598
|
+
|
|
3599
|
+
// ── ClinePass {success,data} envelope unwrap (before translation) ──────────
|
|
3600
|
+
// ClinePass wraps non-streaming JSON in a {success, data} envelope; errors
|
|
3601
|
+
// use {success:false, error}. Transient {success:false, error:"empty..."}
|
|
3602
|
+
// responses get one 2s retry before surfacing. CLINEPASS-GATED — untouched
|
|
3603
|
+
// for every other provider. Envelope errors route through createErrorResult
|
|
3604
|
+
// (→ buildErrorBody/sanitizeErrorMessage, Rule #12).
|
|
3605
|
+
if (provider === "clinepass") {
|
|
3606
|
+
let { body: unwrapped, error: envError } = unwrapClinepassEnvelope(responseBody, provider);
|
|
3607
|
+
if (envError && /empty/i.test(envError.message || "")) {
|
|
3608
|
+
log?.warn?.("RETRY", "clinepass returned empty content, retrying once after 2s");
|
|
3609
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
3610
|
+
try {
|
|
3611
|
+
const retryResult = await executeProviderRequest(effectiveModel, false);
|
|
3612
|
+
if (retryResult?.response?.ok) {
|
|
3613
|
+
const retryParsed = await parseNonStreamingResponseBody({
|
|
3614
|
+
providerResponse: retryResult.response,
|
|
3615
|
+
upstreamStream: undefined,
|
|
3616
|
+
providerHeaders: retryResult.headers,
|
|
3617
|
+
finalBody: retryResult.transformedBody,
|
|
3618
|
+
targetFormat,
|
|
3619
|
+
model,
|
|
3620
|
+
log,
|
|
3621
|
+
});
|
|
3622
|
+
if (retryParsed.kind !== "invalid_sse" && retryParsed.kind !== "invalid_json") {
|
|
3623
|
+
providerResponse = retryResult.response;
|
|
3624
|
+
providerUrl = retryResult.url;
|
|
3625
|
+
providerHeaders = retryResult.headers;
|
|
3626
|
+
finalBody = providerRequestCapture.body(retryResult.transformedBody);
|
|
3627
|
+
({ body: unwrapped, error: envError } = unwrapClinepassEnvelope(
|
|
3628
|
+
retryParsed.responseBody,
|
|
3629
|
+
provider
|
|
3630
|
+
));
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
} catch (retryErr) {
|
|
3634
|
+
log?.warn?.(
|
|
3635
|
+
"RETRY",
|
|
3636
|
+
`clinepass retry failed: ${
|
|
3637
|
+
retryErr instanceof Error ? retryErr.message : String(retryErr)
|
|
3638
|
+
}`
|
|
3639
|
+
);
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3642
|
+
if (envError) {
|
|
3643
|
+
appendRequestLog({
|
|
3644
|
+
model,
|
|
3645
|
+
provider,
|
|
3646
|
+
connectionId,
|
|
3647
|
+
status: `FAILED ${HTTP_STATUS.BAD_GATEWAY}`,
|
|
3648
|
+
}).catch(() => {});
|
|
3649
|
+
persistFailureUsage(HTTP_STATUS.BAD_GATEWAY, "clinepass_envelope_error");
|
|
3650
|
+
trackPendingRequest(model, provider, connectionId, false);
|
|
3651
|
+
return createErrorResult(HTTP_STATUS.BAD_GATEWAY, envError.message);
|
|
3652
|
+
}
|
|
3653
|
+
responseBody = unwrapped;
|
|
3654
|
+
}
|
|
3655
|
+
responseBody = unwrapClineNonStreamingEnvelope(provider, responseBody);
|
|
3656
|
+
|
|
3657
|
+
// Check for empty content response (fake success) - trigger fallback
|
|
3658
|
+
if (isEmptyContentResponse(responseBody)) {
|
|
3659
|
+
appendRequestLog({
|
|
3660
|
+
model,
|
|
3661
|
+
provider,
|
|
3662
|
+
connectionId,
|
|
3663
|
+
status: `FAILED ${HTTP_STATUS.BAD_GATEWAY}`,
|
|
3664
|
+
}).catch(() => {});
|
|
3665
|
+
const emptyContentMessage = "Provider returned empty content";
|
|
3666
|
+
persistAttemptLogs({
|
|
3667
|
+
status: HTTP_STATUS.BAD_GATEWAY,
|
|
3668
|
+
error: emptyContentMessage,
|
|
3669
|
+
providerRequest: finalBody || translatedBody,
|
|
3670
|
+
providerResponse: normalizedProviderPayload,
|
|
3671
|
+
clientResponse: buildErrorBody(HTTP_STATUS.BAD_GATEWAY, emptyContentMessage),
|
|
3672
|
+
cacheSource: "upstream",
|
|
3673
|
+
});
|
|
3674
|
+
persistFailureUsage(HTTP_STATUS.BAD_GATEWAY, "empty_content");
|
|
3675
|
+
|
|
3676
|
+
// Trigger non-recursive fallback for empty content
|
|
3677
|
+
const nextModel = getNextFamilyFallback(currentModel, triedModels);
|
|
3678
|
+
if (nextModel) {
|
|
3679
|
+
triedModels.add(nextModel);
|
|
3680
|
+
currentModel = nextModel;
|
|
3681
|
+
translatedBody.model = nextModel;
|
|
3682
|
+
log?.info?.(
|
|
3683
|
+
"EMPTY_CONTENT_FALLBACK",
|
|
3684
|
+
`${model} returned empty content → trying ${nextModel}`
|
|
3685
|
+
);
|
|
3686
|
+
try {
|
|
3687
|
+
const fallbackResult = await executeProviderRequest(nextModel, false);
|
|
3688
|
+
if (fallbackResult.response.ok) {
|
|
3689
|
+
const fallbackRaw = await withBodyTimeout<string>(fallbackResult.response.text());
|
|
3690
|
+
try {
|
|
3691
|
+
responseBody = fallbackRaw ? JSON.parse(fallbackRaw) : {};
|
|
3692
|
+
providerUrl = fallbackResult.url;
|
|
3693
|
+
providerHeaders = fallbackResult.headers;
|
|
3694
|
+
finalBody = providerRequestCapture.body(fallbackResult.transformedBody);
|
|
3695
|
+
reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody);
|
|
3696
|
+
log?.info?.(
|
|
3697
|
+
"EMPTY_CONTENT_FALLBACK",
|
|
3698
|
+
`Serving ${nextModel} as fallback for ${model}`
|
|
3699
|
+
);
|
|
3700
|
+
// Fall through — continue processing with the new responseBody
|
|
3701
|
+
} catch {
|
|
3702
|
+
trackPendingRequest(model, provider, connectionId, false);
|
|
3703
|
+
return createErrorResult(HTTP_STATUS.BAD_GATEWAY, emptyContentMessage);
|
|
3704
|
+
}
|
|
3705
|
+
} else {
|
|
3706
|
+
trackPendingRequest(model, provider, connectionId, false);
|
|
3707
|
+
return createErrorResult(HTTP_STATUS.BAD_GATEWAY, emptyContentMessage);
|
|
3708
|
+
}
|
|
3709
|
+
} catch {
|
|
3710
|
+
trackPendingRequest(model, provider, connectionId, false);
|
|
3711
|
+
return createErrorResult(HTTP_STATUS.BAD_GATEWAY, emptyContentMessage);
|
|
3712
|
+
}
|
|
3713
|
+
} else {
|
|
3714
|
+
trackPendingRequest(model, provider, connectionId, false);
|
|
3715
|
+
return createErrorResult(HTTP_STATUS.BAD_GATEWAY, emptyContentMessage);
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
|
|
3719
|
+
const responseToolNameMap = mergeResponseToolNameMap(
|
|
3720
|
+
toolNameMap,
|
|
3721
|
+
(finalBody as Record<string, unknown> | null | undefined) ?? null
|
|
3722
|
+
);
|
|
3723
|
+
|
|
3724
|
+
if (sourceFormat === FORMATS.CLAUDE && targetFormat === FORMATS.CLAUDE) {
|
|
3725
|
+
responseBody = restoreClaudePassthroughToolNames(responseBody, responseToolNameMap);
|
|
3726
|
+
}
|
|
3727
|
+
reqLogger.logProviderResponse(
|
|
3728
|
+
providerResponse.status,
|
|
3729
|
+
providerResponse.statusText,
|
|
3730
|
+
providerResponse.headers,
|
|
3731
|
+
looksLikeSSE
|
|
3732
|
+
? {
|
|
3733
|
+
_streamed: true,
|
|
3734
|
+
_format: "sse-json",
|
|
3735
|
+
summary: responseBody,
|
|
3736
|
+
}
|
|
3737
|
+
: responseBody
|
|
3738
|
+
);
|
|
3739
|
+
effectiveServiceTier = resolveReportedServiceTier(responseBody) ?? effectiveServiceTier;
|
|
3740
|
+
|
|
3741
|
+
// Notify success - caller can clear error status if needed
|
|
3742
|
+
if (onRequestSuccess) {
|
|
3743
|
+
await onRequestSuccess();
|
|
3744
|
+
}
|
|
3745
|
+
const successConnectionId = getCurrentConnectionId();
|
|
3746
|
+
await maybeSyncClaudeExtraUsageState({
|
|
3747
|
+
provider,
|
|
3748
|
+
connectionId: successConnectionId,
|
|
3749
|
+
providerSpecificData: credentials?.providerSpecificData,
|
|
3750
|
+
log,
|
|
3751
|
+
});
|
|
3752
|
+
|
|
3753
|
+
// Log usage for non-streaming responses
|
|
3754
|
+
const usage = extractUsageFromResponse(responseBody, provider);
|
|
3755
|
+
if (usage && typeof usage === "object") {
|
|
3756
|
+
attachCompressionUsageReceiptAfterAnalytics(usage as Record<string, unknown>, "provider");
|
|
3757
|
+
}
|
|
3758
|
+
|
|
3759
|
+
// Context Editing telemetry: when the delegated server-side clear actually ran,
|
|
3760
|
+
// record the provider's cleared-token receipt under engine "context-editing" so
|
|
3761
|
+
// it surfaces in compression analytics. Best-effort, Claude-only, non-streaming.
|
|
3762
|
+
recordContextEditingTelemetryHook({
|
|
3763
|
+
contextEditingEnabled,
|
|
3764
|
+
provider,
|
|
3765
|
+
responseBody,
|
|
3766
|
+
skillRequestId,
|
|
3767
|
+
log,
|
|
3768
|
+
});
|
|
3769
|
+
appendRequestLog({
|
|
3770
|
+
model,
|
|
3771
|
+
provider,
|
|
3772
|
+
connectionId: successConnectionId,
|
|
3773
|
+
tokens: usage,
|
|
3774
|
+
status: "200 OK",
|
|
3775
|
+
}).catch(() => {});
|
|
3776
|
+
|
|
3777
|
+
// Save structured call log with full payloads
|
|
3778
|
+
const cacheUsageLogMeta = buildCacheUsageLogMeta(usage);
|
|
3779
|
+
recordNonStreamingUsageStats(usage, {
|
|
3780
|
+
traceEnabled,
|
|
3781
|
+
provider,
|
|
3782
|
+
connectionId: successConnectionId,
|
|
3783
|
+
model,
|
|
3784
|
+
startTime,
|
|
3785
|
+
apiKeyInfo,
|
|
3786
|
+
effectiveServiceTier,
|
|
3787
|
+
isCombo,
|
|
3788
|
+
comboStrategy,
|
|
3789
|
+
endpoint: endpointPath,
|
|
3790
|
+
});
|
|
3791
|
+
|
|
3792
|
+
// Translate response to client's expected format (usually OpenAI)
|
|
3793
|
+
// Pass toolNameMap so Claude OAuth proxy_ prefix is stripped in tool_use blocks (#605)
|
|
3794
|
+
let translatedResponse = needsTranslation(responsePayloadFormat, clientResponseFormat)
|
|
3795
|
+
? translateNonStreamingResponse(
|
|
3796
|
+
responseBody,
|
|
3797
|
+
responsePayloadFormat,
|
|
3798
|
+
clientResponseFormat,
|
|
3799
|
+
responseToolNameMap
|
|
3800
|
+
)
|
|
3801
|
+
: responseBody;
|
|
3802
|
+
const memoryExtractionResponse = translatedResponse;
|
|
3803
|
+
|
|
3804
|
+
// T26: Strip markdown code blocks if provider format is Claude
|
|
3805
|
+
if (sourceFormat === "claude" && !stream) {
|
|
3806
|
+
if (typeof translatedResponse?.choices?.[0]?.message?.content === "string") {
|
|
3807
|
+
translatedResponse.choices[0].message.content = stripMarkdownCodeFence(
|
|
3808
|
+
translatedResponse.choices[0].message.content
|
|
3809
|
+
) as string;
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3813
|
+
// T18: Normalize finish_reason to 'tool_calls' if tool calls are present
|
|
3814
|
+
if (translatedResponse?.choices) {
|
|
3815
|
+
for (const choice of translatedResponse.choices) {
|
|
3816
|
+
if (
|
|
3817
|
+
choice.message?.tool_calls &&
|
|
3818
|
+
choice.message.tool_calls.length > 0 &&
|
|
3819
|
+
choice.finish_reason !== "tool_calls"
|
|
3820
|
+
) {
|
|
3821
|
+
choice.finish_reason = "tool_calls";
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
|
|
3826
|
+
// Reasoning Replay Cache (#1628): Capture reasoning_content from non-streaming responses
|
|
3827
|
+
// with tool_calls so it can be replayed on subsequent turns (DeepSeek V4, Kimi K2, etc.)
|
|
3828
|
+
try {
|
|
3829
|
+
const firstChoice = translatedResponse?.choices?.[0];
|
|
3830
|
+
const msg = firstChoice?.message;
|
|
3831
|
+
cacheReasoningFromAssistantMessage(msg, provider, model, {
|
|
3832
|
+
requestId: skillRequestId,
|
|
3833
|
+
messageIndex: 0,
|
|
3834
|
+
});
|
|
3835
|
+
} catch {
|
|
3836
|
+
// Cache capture is non-critical — never block the response
|
|
3837
|
+
}
|
|
3838
|
+
// Sanitize response for OpenAI SDK compatibility
|
|
3839
|
+
// Strips non-standard fields (x_groq, usage_breakdown, service_tier, etc.)
|
|
3840
|
+
// Extracts <think> and <thinking> tags into reasoning_content
|
|
3841
|
+
// Source format determines output shape. If we are outputting OpenAI shape or pseudo-OpenAI shape, sanitize.
|
|
3842
|
+
if (clientResponseFormat === FORMATS.OPENAI_RESPONSES) {
|
|
3843
|
+
translatedResponse = sanitizeResponsesApiResponse(translatedResponse);
|
|
3844
|
+
} else if (clientResponseFormat === FORMATS.OPENAI) {
|
|
3845
|
+
// Port of decolua/9router#517: opt-in `x-omniroute-strip-reasoning` header
|
|
3846
|
+
// unconditionally drops `reasoning_content` from the final non-streaming
|
|
3847
|
+
// JSON for clients (e.g. Firecrawl AI SDK) whose JSON parsers break on
|
|
3848
|
+
// that non-standard field. Reasoning replay cache is captured above this
|
|
3849
|
+
// sanitize step, so the cache feature is unaffected.
|
|
3850
|
+
const stripReasoning = isStripReasoningRequested(clientRawRequest?.headers ?? null);
|
|
3851
|
+
translatedResponse = sanitizeOpenAIResponse(translatedResponse, {
|
|
3852
|
+
stripReasoning,
|
|
3853
|
+
parseTextualReasoningTags: shouldParseTextualReasoningTags(provider, model),
|
|
3854
|
+
});
|
|
3855
|
+
}
|
|
3856
|
+
|
|
3857
|
+
applyClientUsageBuffer(translatedResponse, body, clientResponseFormat);
|
|
3858
|
+
|
|
3859
|
+
if (memoryOwnerId && memorySettings?.enabled && memorySettings.maxTokens > 0) {
|
|
3860
|
+
const requestMemoryText = extractMemoryTextFromRequestBody(body as Record<string, unknown>);
|
|
3861
|
+
if (requestMemoryText) {
|
|
3862
|
+
extractFacts(requestMemoryText, memoryOwnerId, pipelineSessionId);
|
|
3863
|
+
}
|
|
3864
|
+
|
|
3865
|
+
const memoryText = extractMemoryTextFromResponse(memoryExtractionResponse);
|
|
3866
|
+
if (memoryText) {
|
|
3867
|
+
extractFacts(memoryText, memoryOwnerId, pipelineSessionId);
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3871
|
+
const customSkillExecutionEnabled =
|
|
3872
|
+
Boolean(memoryOwnerId) && memorySettings?.skillsEnabled === true;
|
|
3873
|
+
const builtinToolNames = webSearchFallbackPlan.toolName ? [webSearchFallbackPlan.toolName] : [];
|
|
3874
|
+
if (customSkillExecutionEnabled || builtinToolNames.length > 0) {
|
|
3875
|
+
const skillSessionId = pipelineSessionId;
|
|
3876
|
+
|
|
3877
|
+
translatedResponse = await handleToolCallExecution(
|
|
3878
|
+
translatedResponse,
|
|
3879
|
+
getSkillsModelIdForFormat(sourceFormat),
|
|
3880
|
+
{
|
|
3881
|
+
apiKeyId: memoryOwnerId || "local",
|
|
3882
|
+
sessionId: skillSessionId,
|
|
3883
|
+
requestId: skillRequestId,
|
|
3884
|
+
builtinToolNames,
|
|
3885
|
+
customSkillExecutionEnabled,
|
|
3886
|
+
}
|
|
3887
|
+
);
|
|
3888
|
+
}
|
|
3889
|
+
|
|
3890
|
+
const guardrailContext = buildPostCallGuardrailContext({
|
|
3891
|
+
apiKeyInfo,
|
|
3892
|
+
body,
|
|
3893
|
+
clientRawRequest,
|
|
3894
|
+
log,
|
|
3895
|
+
model,
|
|
3896
|
+
provider,
|
|
3897
|
+
responsePayloadFormat,
|
|
3898
|
+
clientResponseFormat,
|
|
3899
|
+
});
|
|
3900
|
+
const postCallGuardrails = await guardrailRegistry.runPostCallHooks(
|
|
3901
|
+
translatedResponse,
|
|
3902
|
+
guardrailContext
|
|
3903
|
+
);
|
|
3904
|
+
translatedResponse = postCallGuardrails.response;
|
|
3905
|
+
|
|
3906
|
+
const responseUsage =
|
|
3907
|
+
(usage && typeof usage === "object" ? usage : null) ||
|
|
3908
|
+
(translatedResponse?.usage && typeof translatedResponse.usage === "object"
|
|
3909
|
+
? translatedResponse.usage
|
|
3910
|
+
: null);
|
|
3911
|
+
const estimatedCost = responseUsage
|
|
3912
|
+
? await calculateCost(provider, model, responseUsage, { serviceTier: effectiveServiceTier })
|
|
3913
|
+
: 0;
|
|
3914
|
+
|
|
3915
|
+
if (postCallGuardrails.blocked) {
|
|
3916
|
+
const guardrailMessage = postCallGuardrails.message || "Response blocked by guardrail";
|
|
3917
|
+
persistAttemptLogs({
|
|
3918
|
+
status: HTTP_STATUS.BAD_REQUEST,
|
|
3919
|
+
tokens: usage,
|
|
3920
|
+
responseBody,
|
|
3921
|
+
providerRequest: finalBody || translatedBody,
|
|
3922
|
+
providerResponse: looksLikeSSE
|
|
3923
|
+
? {
|
|
3924
|
+
_streamed: true,
|
|
3925
|
+
_format: "sse-json",
|
|
3926
|
+
summary: responseBody,
|
|
3927
|
+
}
|
|
3928
|
+
: responseBody,
|
|
3929
|
+
clientResponse: buildErrorBody(HTTP_STATUS.BAD_REQUEST, guardrailMessage),
|
|
3930
|
+
claudeCacheMeta: claudePromptCacheLogMeta,
|
|
3931
|
+
claudeCacheUsageMeta: cacheUsageLogMeta,
|
|
3932
|
+
cacheSource: "upstream",
|
|
3933
|
+
});
|
|
3934
|
+
if (apiKeyInfo?.id && estimatedCost > 0) {
|
|
3935
|
+
recordCost(apiKeyInfo.id, estimatedCost);
|
|
3936
|
+
}
|
|
3937
|
+
log?.warn?.(
|
|
3938
|
+
"GUARDRAIL",
|
|
3939
|
+
`Response blocked by ${postCallGuardrails.guardrail || "guardrail"}: ${guardrailMessage}`
|
|
3940
|
+
);
|
|
3941
|
+
finalizePendingScope(pendingScope, {
|
|
3942
|
+
providerResponse: responseBody,
|
|
3943
|
+
clientResponse: translatedResponse,
|
|
3944
|
+
});
|
|
3945
|
+
return createErrorResult(HTTP_STATUS.BAD_REQUEST, guardrailMessage);
|
|
3946
|
+
}
|
|
3947
|
+
|
|
3948
|
+
// Validate the *translated* response actually carries client-usable output.
|
|
3949
|
+
// isEmptyContentResponse (above) runs on the raw responseBody before translation;
|
|
3950
|
+
// this check runs after translation + sanitization + tool-call execution to catch
|
|
3951
|
+
// cases where a provider returns a structurally valid raw body that translates into
|
|
3952
|
+
// choices:[] or output:[] with no usable content (Responses API shape included).
|
|
3953
|
+
const malformedTranslatedReason = detectMalformedNonStream(translatedResponse);
|
|
3954
|
+
if (malformedTranslatedReason) {
|
|
3955
|
+
const totalLatency = Date.now() - startTime;
|
|
3956
|
+
const rawBytes = (() => {
|
|
3957
|
+
try {
|
|
3958
|
+
return JSON.stringify(responseBody || {}).length;
|
|
3959
|
+
} catch {
|
|
3960
|
+
return -1;
|
|
3961
|
+
}
|
|
3962
|
+
})();
|
|
3963
|
+
reportMalformed200({
|
|
3964
|
+
mode: "nonstream",
|
|
3965
|
+
provider,
|
|
3966
|
+
model,
|
|
3967
|
+
connectionId,
|
|
3968
|
+
reason: malformedTranslatedReason,
|
|
3969
|
+
recvBytes: rawBytes,
|
|
3970
|
+
recvLines: -1,
|
|
3971
|
+
emitted: -1,
|
|
3972
|
+
events: {},
|
|
3973
|
+
ttftMs: totalLatency,
|
|
3974
|
+
elapsedMs: totalLatency,
|
|
3975
|
+
});
|
|
3976
|
+
appendRequestLog({
|
|
3977
|
+
model,
|
|
3978
|
+
provider,
|
|
3979
|
+
connectionId,
|
|
3980
|
+
status: `FAILED ${HTTP_STATUS.BAD_GATEWAY}`,
|
|
3981
|
+
}).catch(() => {});
|
|
3982
|
+
const malformedMessage = `[${provider}/${model}] returned an empty response (no usable choices/output)`;
|
|
3983
|
+
persistAttemptLogs({
|
|
3984
|
+
status: HTTP_STATUS.BAD_GATEWAY,
|
|
3985
|
+
tokens: usage,
|
|
3986
|
+
responseBody,
|
|
3987
|
+
providerRequest: finalBody || translatedBody,
|
|
3988
|
+
providerResponse: looksLikeSSE
|
|
3989
|
+
? { _streamed: true, _format: "sse-json", summary: responseBody }
|
|
3990
|
+
: responseBody,
|
|
3991
|
+
clientResponse: buildErrorBody(HTTP_STATUS.BAD_GATEWAY, malformedMessage),
|
|
3992
|
+
claudeCacheMeta: claudePromptCacheLogMeta,
|
|
3993
|
+
claudeCacheUsageMeta: cacheUsageLogMeta,
|
|
3994
|
+
cacheSource: "upstream",
|
|
3995
|
+
});
|
|
3996
|
+
persistFailureUsage(HTTP_STATUS.BAD_GATEWAY, "malformed_translated_response");
|
|
3997
|
+
trackPendingRequest(model, provider, pendingConnId, false);
|
|
3998
|
+
return createErrorResult(HTTP_STATUS.BAD_GATEWAY, malformedMessage);
|
|
3999
|
+
}
|
|
4000
|
+
|
|
4001
|
+
// ── Phase 9.1: Cache store (non-streaming, temp=0) ──
|
|
4002
|
+
storeSemanticCacheResponse({
|
|
4003
|
+
enabled: semanticCacheEnabled,
|
|
4004
|
+
body,
|
|
4005
|
+
headers: clientRawRequest?.headers,
|
|
4006
|
+
translatedResponse,
|
|
4007
|
+
model,
|
|
4008
|
+
apiKeyId: apiKeyInfo?.id ?? undefined,
|
|
4009
|
+
usage,
|
|
4010
|
+
log,
|
|
4011
|
+
});
|
|
4012
|
+
|
|
4013
|
+
// ── Phase 9.2: Save for idempotency ──
|
|
4014
|
+
// Reuse the key resolved by checkIdempotencyCache() above (single derivation per
|
|
4015
|
+
// request). (#3821-review LEDGER-6)
|
|
4016
|
+
saveIdempotency(idempotencyKey, translatedResponse, 200);
|
|
4017
|
+
reqLogger.logConvertedResponse(translatedResponse);
|
|
4018
|
+
persistAttemptLogs({
|
|
4019
|
+
status: 200,
|
|
4020
|
+
tokens: usage,
|
|
4021
|
+
responseBody,
|
|
4022
|
+
providerRequest: finalBody || translatedBody,
|
|
4023
|
+
providerResponse: looksLikeSSE
|
|
4024
|
+
? {
|
|
4025
|
+
_streamed: true,
|
|
4026
|
+
_format: "sse-json",
|
|
4027
|
+
summary: responseBody,
|
|
4028
|
+
}
|
|
4029
|
+
: responseBody,
|
|
4030
|
+
clientResponse: translatedResponse,
|
|
4031
|
+
claudeCacheMeta: claudePromptCacheLogMeta,
|
|
4032
|
+
claudeCacheUsageMeta: cacheUsageLogMeta,
|
|
4033
|
+
cacheSource: "upstream",
|
|
4034
|
+
});
|
|
4035
|
+
if (apiKeyInfo?.id && estimatedCost > 0) {
|
|
4036
|
+
recordCost(apiKeyInfo.id, estimatedCost);
|
|
4037
|
+
}
|
|
4038
|
+
|
|
4039
|
+
// === Quota Share POST-hook (B/F7) — fire-and-forget, fail-open ===
|
|
4040
|
+
await scheduleQuotaShareConsumption({
|
|
4041
|
+
apiKeyId: apiKeyInfo?.id,
|
|
4042
|
+
connectionId: credentials?.connectionId,
|
|
4043
|
+
provider,
|
|
4044
|
+
model,
|
|
4045
|
+
usage,
|
|
4046
|
+
estimatedCost,
|
|
4047
|
+
log,
|
|
4048
|
+
});
|
|
4049
|
+
// === /Quota Share POST-hook ===
|
|
4050
|
+
|
|
4051
|
+
// ── Gamification event (fire-and-forget) ──
|
|
4052
|
+
await emitRequestGamificationEvent({ apiKeyId: apiKeyInfo?.id, model, provider });
|
|
4053
|
+
|
|
4054
|
+
finalizePendingScope(pendingScope, {
|
|
4055
|
+
providerResponse: responseBody,
|
|
4056
|
+
clientResponse: translatedResponse,
|
|
4057
|
+
});
|
|
4058
|
+
const responseHeaders = buildNonStreamingResponseHeaders({
|
|
4059
|
+
provider,
|
|
4060
|
+
model,
|
|
4061
|
+
startTime,
|
|
4062
|
+
responseUsage,
|
|
4063
|
+
estimatedCost,
|
|
4064
|
+
requestId: skillRequestId,
|
|
4065
|
+
compressionResponseMeta,
|
|
4066
|
+
});
|
|
4067
|
+
// #6426: align response body `model` with the `X-OmniRoute-Model` header
|
|
4068
|
+
// (both must be the resolved backend model). Some upstreams (notably legacy
|
|
4069
|
+
// /v1/completions text-completion path) return a body `model` field that
|
|
4070
|
+
// differs from the resolved backend id we advertised in the header, leaving
|
|
4071
|
+
// strict clients unable to reconcile the two. Rewrite body.model to `model`
|
|
4072
|
+
// FIRST, then let #1311 echo override it when the opt-in setting is on.
|
|
4073
|
+
if (typeof model === "string" && model) echoModelInObject(translatedResponse, model);
|
|
4074
|
+
// #1311: echo the requested alias/combo name in the non-streaming response model.
|
|
4075
|
+
if (echoModel) echoModelInObject(translatedResponse, echoModel);
|
|
4076
|
+
return {
|
|
4077
|
+
success: true,
|
|
4078
|
+
response: buildNonStreamingJsonResponse(translatedResponse, responseHeaders),
|
|
4079
|
+
};
|
|
4080
|
+
}
|
|
4081
|
+
|
|
4082
|
+
// Streaming response
|
|
4083
|
+
// #3089 — some "reasoning" openai-compatible upstreams ignore a stream:true
|
|
4084
|
+
// request and return a complete application/json chat-completion body instead
|
|
4085
|
+
// of an SSE stream. The readiness check below only recognizes SSE `data:`
|
|
4086
|
+
// frames, so that body produced a spurious STREAM_EARLY_EOF / HTTP 502 even
|
|
4087
|
+
// though it carried valid content/reasoning_content. Detect a JSON (non-SSE)
|
|
4088
|
+
// upstream body and synthesize an equivalent OpenAI SSE stream so the
|
|
4089
|
+
// streaming pipeline (and the client) get a valid stream.
|
|
4090
|
+
providerResponse = await maybeConvertJsonBodyToSse(providerResponse, { log, provider, model });
|
|
4091
|
+
const streamReadinessPolicy = resolveStreamReadinessTimeout({
|
|
4092
|
+
baseTimeoutMs: STREAM_READINESS_TIMEOUT_MS,
|
|
4093
|
+
provider,
|
|
4094
|
+
model,
|
|
4095
|
+
body: (finalBody || translatedBody) as Record<string, unknown> | null | undefined,
|
|
4096
|
+
maxTimeoutMs: agentGoalPolicy.detected
|
|
4097
|
+
? Math.max(STREAM_READINESS_MAX_TIMEOUT_MS, agentGoalPolicy.readinessMaxTimeoutMs)
|
|
4098
|
+
: STREAM_READINESS_MAX_TIMEOUT_MS,
|
|
4099
|
+
});
|
|
4100
|
+
if (streamReadinessPolicy.timeoutMs !== streamReadinessPolicy.baseTimeoutMs) {
|
|
4101
|
+
log?.debug?.(
|
|
4102
|
+
"STREAM",
|
|
4103
|
+
`adaptive readiness timeout=${streamReadinessPolicy.timeoutMs}ms base=${streamReadinessPolicy.baseTimeoutMs}ms reason=${streamReadinessPolicy.reasons.join(",")}`
|
|
4104
|
+
);
|
|
4105
|
+
}
|
|
4106
|
+
|
|
4107
|
+
const streamReadiness = await ensureStreamReadiness(providerResponse, {
|
|
4108
|
+
timeoutMs: streamReadinessPolicy.timeoutMs,
|
|
4109
|
+
provider,
|
|
4110
|
+
model,
|
|
4111
|
+
log,
|
|
4112
|
+
});
|
|
4113
|
+
if (streamReadiness.ok === false) {
|
|
4114
|
+
const { response: failureResponse, reason } = streamReadiness;
|
|
4115
|
+
const failure = {
|
|
4116
|
+
status: failureResponse.status,
|
|
4117
|
+
message: reason,
|
|
4118
|
+
code: streamReadiness.code,
|
|
4119
|
+
type: streamReadiness.type,
|
|
4120
|
+
};
|
|
4121
|
+
trackPendingRequest(model, provider, connectionId, false);
|
|
4122
|
+
appendRequestLog({
|
|
4123
|
+
model,
|
|
4124
|
+
provider,
|
|
4125
|
+
connectionId,
|
|
4126
|
+
status: `FAILED ${failureResponse.status}`,
|
|
4127
|
+
}).catch(() => {});
|
|
4128
|
+
persistAttemptLogs({
|
|
4129
|
+
status: failureResponse.status,
|
|
4130
|
+
error: reason,
|
|
4131
|
+
providerRequest: finalBody || translatedBody,
|
|
4132
|
+
clientResponse: buildErrorBody(failureResponse.status, reason),
|
|
4133
|
+
claudeCacheMeta: claudePromptCacheLogMeta,
|
|
4134
|
+
cacheSource: "upstream",
|
|
4135
|
+
});
|
|
4136
|
+
persistFailureUsage(failureResponse.status, streamReadiness.code);
|
|
4137
|
+
// Do NOT call onStreamFailure — a stream stall is an upstream issue,
|
|
4138
|
+
// not an account/quota failure. Marking the account unavailable here
|
|
4139
|
+
// would lock out legitimate accounts when the upstream hangs.
|
|
4140
|
+
return {
|
|
4141
|
+
success: false,
|
|
4142
|
+
status: failureResponse.status,
|
|
4143
|
+
error: reason,
|
|
4144
|
+
errorType: streamReadiness.type,
|
|
4145
|
+
errorCode: streamReadiness.code,
|
|
4146
|
+
response: failureResponse,
|
|
4147
|
+
};
|
|
4148
|
+
}
|
|
4149
|
+
providerResponse = streamReadiness.response;
|
|
4150
|
+
|
|
4151
|
+
// Notify success - caller can clear error status if needed
|
|
4152
|
+
if (onRequestSuccess) {
|
|
4153
|
+
await onRequestSuccess();
|
|
4154
|
+
}
|
|
4155
|
+
|
|
4156
|
+
const responseHeaders = assembleStreamingResponseHeaders({
|
|
4157
|
+
providerHeaders: providerResponse.headers,
|
|
4158
|
+
provider,
|
|
4159
|
+
model,
|
|
4160
|
+
pendingRequestId,
|
|
4161
|
+
compressionResponseMeta,
|
|
4162
|
+
});
|
|
4163
|
+
|
|
4164
|
+
// Create transform stream with logger for streaming response
|
|
4165
|
+
let transformStream;
|
|
4166
|
+
const responseToolNameMap = mergeResponseToolNameMap(
|
|
4167
|
+
toolNameMap,
|
|
4168
|
+
(finalBody as Record<string, unknown> | null | undefined) ?? null
|
|
4169
|
+
);
|
|
4170
|
+
|
|
4171
|
+
let streamCompletionRecorded = false;
|
|
4172
|
+
let streamFailureCompletionRecorded = false;
|
|
4173
|
+
|
|
4174
|
+
// Callback to save call log when stream completes (include responseBody when provided by stream)
|
|
4175
|
+
const onStreamComplete = ({
|
|
4176
|
+
status: streamStatus,
|
|
4177
|
+
usage: streamUsage,
|
|
4178
|
+
responseBody: streamResponseBody,
|
|
4179
|
+
providerPayload,
|
|
4180
|
+
clientPayload,
|
|
4181
|
+
error: streamError,
|
|
4182
|
+
errorCode: streamErrorCode,
|
|
4183
|
+
ttft,
|
|
4184
|
+
}) => {
|
|
4185
|
+
const normalizedStreamStatus = streamStatus || 200;
|
|
4186
|
+
if (streamCompletionRecorded) return;
|
|
4187
|
+
streamCompletionRecorded = true;
|
|
4188
|
+
if (normalizedStreamStatus !== 200) {
|
|
4189
|
+
if (streamFailureCompletionRecorded) return;
|
|
4190
|
+
streamFailureCompletionRecorded = true;
|
|
4191
|
+
}
|
|
4192
|
+
const cacheUsageLogMeta = buildCacheUsageLogMeta(streamUsage);
|
|
4193
|
+
const streamConnectionId = getCurrentConnectionId();
|
|
4194
|
+
|
|
4195
|
+
if (normalizedStreamStatus === 200) {
|
|
4196
|
+
void maybeSyncClaudeExtraUsageState({
|
|
4197
|
+
provider,
|
|
4198
|
+
connectionId: streamConnectionId,
|
|
4199
|
+
providerSpecificData: credentials?.providerSpecificData,
|
|
4200
|
+
log,
|
|
4201
|
+
});
|
|
4202
|
+
}
|
|
4203
|
+
|
|
4204
|
+
// Reasoning Replay Cache (#1628): Capture reasoning_content from streaming responses
|
|
4205
|
+
// with tool_calls so it can be replayed on subsequent turns (DeepSeek V4, Kimi K2, etc.)
|
|
4206
|
+
if (normalizedStreamStatus === 200 && streamResponseBody) {
|
|
4207
|
+
try {
|
|
4208
|
+
const body = streamResponseBody as Record<string, unknown>;
|
|
4209
|
+
const choices = body.choices as { message?: Record<string, unknown> }[] | undefined;
|
|
4210
|
+
const msg = choices?.[0]?.message;
|
|
4211
|
+
cacheReasoningFromAssistantMessage(msg, provider, model, {
|
|
4212
|
+
requestId: skillRequestId,
|
|
4213
|
+
messageIndex: 0,
|
|
4214
|
+
});
|
|
4215
|
+
} catch {
|
|
4216
|
+
// Cache capture is non-critical — never block the stream
|
|
4217
|
+
}
|
|
4218
|
+
}
|
|
4219
|
+
effectiveServiceTier = resolveReportedServiceTier(streamResponseBody) ?? effectiveServiceTier;
|
|
4220
|
+
|
|
4221
|
+
// Context Editing telemetry (streaming): the reconstructed stream body now carries
|
|
4222
|
+
// context_management.applied_edits from the final message_delta snapshot. Mirror the
|
|
4223
|
+
// non-streaming hook so streaming context-clear savings also surface under engine
|
|
4224
|
+
// "context-editing" in compression analytics. Best-effort, Claude-only.
|
|
4225
|
+
if (normalizedStreamStatus === 200) {
|
|
4226
|
+
recordContextEditingTelemetryHook({
|
|
4227
|
+
contextEditingEnabled,
|
|
4228
|
+
provider,
|
|
4229
|
+
responseBody: streamResponseBody,
|
|
4230
|
+
skillRequestId,
|
|
4231
|
+
log,
|
|
4232
|
+
});
|
|
4233
|
+
}
|
|
4234
|
+
|
|
4235
|
+
streamFailure.finalizeStreamRequestLog({
|
|
4236
|
+
pendingRequestId,
|
|
4237
|
+
model,
|
|
4238
|
+
provider,
|
|
4239
|
+
connectionId: streamConnectionId,
|
|
4240
|
+
providerResponse: providerPayload ?? streamResponseBody ?? undefined,
|
|
4241
|
+
clientResponse: clientPayload ?? streamResponseBody ?? undefined,
|
|
4242
|
+
status: normalizedStreamStatus,
|
|
4243
|
+
error: streamError,
|
|
4244
|
+
errorCode: streamErrorCode,
|
|
4245
|
+
});
|
|
4246
|
+
|
|
4247
|
+
// Track cache token metrics for streaming responses
|
|
4248
|
+
if (streamUsage && typeof streamUsage === "object") {
|
|
4249
|
+
attachCompressionUsageReceiptAfterAnalytics(streamUsage as Record<string, unknown>, "stream");
|
|
4250
|
+
}
|
|
4251
|
+
recordStreamingUsageStats(streamUsage, {
|
|
4252
|
+
provider,
|
|
4253
|
+
model,
|
|
4254
|
+
streamStatus: normalizedStreamStatus,
|
|
4255
|
+
startTime,
|
|
4256
|
+
ttft,
|
|
4257
|
+
streamErrorCode,
|
|
4258
|
+
connectionId: streamConnectionId,
|
|
4259
|
+
apiKeyInfo,
|
|
4260
|
+
effectiveServiceTier,
|
|
4261
|
+
isCombo,
|
|
4262
|
+
comboStrategy,
|
|
4263
|
+
endpoint: endpointPath,
|
|
4264
|
+
});
|
|
4265
|
+
|
|
4266
|
+
persistAttemptLogs({
|
|
4267
|
+
status: normalizedStreamStatus,
|
|
4268
|
+
error: streamError || undefined,
|
|
4269
|
+
tokens: streamUsage || {},
|
|
4270
|
+
responseBody: streamResponseBody ?? undefined,
|
|
4271
|
+
providerRequest: finalBody || translatedBody,
|
|
4272
|
+
providerResponse: providerPayload,
|
|
4273
|
+
clientResponse: clientPayload ?? streamResponseBody ?? undefined,
|
|
4274
|
+
claudeCacheMeta: claudePromptCacheLogMeta,
|
|
4275
|
+
claudeCacheUsageMeta: cacheUsageLogMeta,
|
|
4276
|
+
cacheSource: "upstream",
|
|
4277
|
+
});
|
|
4278
|
+
|
|
4279
|
+
recordStreamingCost({
|
|
4280
|
+
apiKeyId: apiKeyInfo?.id,
|
|
4281
|
+
provider,
|
|
4282
|
+
model,
|
|
4283
|
+
streamUsage,
|
|
4284
|
+
serviceTier: effectiveServiceTier,
|
|
4285
|
+
calculateCost,
|
|
4286
|
+
recordCost,
|
|
4287
|
+
});
|
|
4288
|
+
|
|
4289
|
+
// === Quota Share POST-hook streaming (B/F7) — fire-and-forget, fail-open ===
|
|
4290
|
+
// Resolve the real per-request cost (calculateCost) so USD-unit pools accrue
|
|
4291
|
+
// on streaming traffic too; this previously recorded usd:0 hardcoded, which
|
|
4292
|
+
// meant DeepSeek-style `usd/monthly` shared pools never blocked on streams.
|
|
4293
|
+
scheduleStreamingQuotaShareConsumption({
|
|
4294
|
+
apiKeyId: apiKeyInfo?.id,
|
|
4295
|
+
connectionId: credentials?.connectionId,
|
|
4296
|
+
provider,
|
|
4297
|
+
model,
|
|
4298
|
+
streamUsage,
|
|
4299
|
+
streamStatus: normalizedStreamStatus,
|
|
4300
|
+
serviceTier: effectiveServiceTier,
|
|
4301
|
+
calculateCost,
|
|
4302
|
+
log,
|
|
4303
|
+
});
|
|
4304
|
+
// === /Quota Share POST-hook streaming ===
|
|
4305
|
+
|
|
4306
|
+
if (
|
|
4307
|
+
memoryOwnerId &&
|
|
4308
|
+
memorySettings?.enabled &&
|
|
4309
|
+
memorySettings.maxTokens > 0 &&
|
|
4310
|
+
streamStatus === 200
|
|
4311
|
+
) {
|
|
4312
|
+
const requestMemoryText = extractMemoryTextFromRequestBody(body as Record<string, unknown>);
|
|
4313
|
+
if (requestMemoryText) {
|
|
4314
|
+
extractFacts(requestMemoryText, memoryOwnerId, pipelineSessionId);
|
|
4315
|
+
}
|
|
4316
|
+
|
|
4317
|
+
const streamedMemoryText = extractMemoryTextFromResponse(
|
|
4318
|
+
(streamResponseBody ?? null) as Record<string, unknown> | null
|
|
4319
|
+
);
|
|
4320
|
+
if (streamedMemoryText) {
|
|
4321
|
+
extractFacts(streamedMemoryText, memoryOwnerId, pipelineSessionId);
|
|
4322
|
+
}
|
|
4323
|
+
}
|
|
4324
|
+
|
|
4325
|
+
// Semantic cache: store assembled streaming response for future cache hits
|
|
4326
|
+
storeStreamingSemanticCacheResponse({
|
|
4327
|
+
enabled: semanticCacheEnabled,
|
|
4328
|
+
streamStatus,
|
|
4329
|
+
streamResponseBody,
|
|
4330
|
+
body,
|
|
4331
|
+
headers: clientRawRequest?.headers,
|
|
4332
|
+
model,
|
|
4333
|
+
apiKeyId: apiKeyInfo?.id ?? undefined,
|
|
4334
|
+
streamUsage,
|
|
4335
|
+
log,
|
|
4336
|
+
});
|
|
4337
|
+
};
|
|
4338
|
+
|
|
4339
|
+
const streamFailureFinalizers = streamFailure.createStreamFailureFinalizers({
|
|
4340
|
+
isFailureCompletionRecorded: () => streamFailureCompletionRecorded,
|
|
4341
|
+
isStreamCompletionRecorded: () => streamCompletionRecorded,
|
|
4342
|
+
onStreamComplete,
|
|
4343
|
+
persistFailureUsage,
|
|
4344
|
+
onStreamFailure,
|
|
4345
|
+
});
|
|
4346
|
+
const handleStreamFailure = streamFailureFinalizers.handleStreamFailure;
|
|
4347
|
+
onPipelineStreamError = streamFailureFinalizers.onPipelineStreamError;
|
|
4348
|
+
onClientDisconnectFinalize = (event) =>
|
|
4349
|
+
handleStreamFailure({
|
|
4350
|
+
status: 499,
|
|
4351
|
+
message: `Client disconnected: ${event.reason}`,
|
|
4352
|
+
code: "client_disconnected",
|
|
4353
|
+
type: "client_disconnected",
|
|
4354
|
+
});
|
|
4355
|
+
|
|
4356
|
+
// For providers using Responses API format, translate stream back to openai (Chat Completions) format
|
|
4357
|
+
// UNLESS client is Droid CLI which expects openai-responses format back
|
|
4358
|
+
const needsResponsesTranslation =
|
|
4359
|
+
targetFormat === FORMATS.OPENAI_RESPONSES &&
|
|
4360
|
+
clientResponseFormat === FORMATS.OPENAI &&
|
|
4361
|
+
!isResponsesEndpoint &&
|
|
4362
|
+
!isDroidCLI;
|
|
4363
|
+
const streamStateBody = finalBody || body;
|
|
4364
|
+
|
|
4365
|
+
if (needsResponsesTranslation) {
|
|
4366
|
+
// Provider returns openai-responses, translate to openai (Chat Completions) that clients expect
|
|
4367
|
+
log?.debug?.("STREAM", `Responses translation mode: openai-responses → openai`);
|
|
4368
|
+
transformStream = createSSETransformStreamWithLogger(
|
|
4369
|
+
"openai-responses",
|
|
4370
|
+
"openai",
|
|
4371
|
+
provider,
|
|
4372
|
+
reqLogger,
|
|
4373
|
+
responseToolNameMap,
|
|
4374
|
+
model,
|
|
4375
|
+
connectionId,
|
|
4376
|
+
streamStateBody,
|
|
4377
|
+
onStreamComplete,
|
|
4378
|
+
apiKeyInfo,
|
|
4379
|
+
handleStreamFailure,
|
|
4380
|
+
copilotCompatibleReasoning
|
|
4381
|
+
);
|
|
4382
|
+
} else if (needsTranslation(targetFormat, clientResponseFormat)) {
|
|
4383
|
+
// Standard translation for other providers
|
|
4384
|
+
log?.debug?.("STREAM", `Translation mode: ${targetFormat} → ${clientResponseFormat}`);
|
|
4385
|
+
transformStream = createSSETransformStreamWithLogger(
|
|
4386
|
+
targetFormat,
|
|
4387
|
+
clientResponseFormat,
|
|
4388
|
+
provider,
|
|
4389
|
+
reqLogger,
|
|
4390
|
+
responseToolNameMap,
|
|
4391
|
+
model,
|
|
4392
|
+
connectionId,
|
|
4393
|
+
streamStateBody,
|
|
4394
|
+
onStreamComplete,
|
|
4395
|
+
apiKeyInfo,
|
|
4396
|
+
handleStreamFailure,
|
|
4397
|
+
copilotCompatibleReasoning,
|
|
4398
|
+
// Suppress the `</think>` close marker for clients that render it verbatim
|
|
4399
|
+
// (e.g. OpenCode by UA; any client via `x-omniroute-thinking-marker: off`);
|
|
4400
|
+
// preserved for Claude Code / Cursor and unknown clients by default (#5245 /
|
|
4401
|
+
// #5312). The header wins over the UA allowlist.
|
|
4402
|
+
resolveSuppressThinkClose({
|
|
4403
|
+
userAgent: streamUserAgent,
|
|
4404
|
+
thinkingMarkerHeader,
|
|
4405
|
+
})
|
|
4406
|
+
);
|
|
4407
|
+
} else {
|
|
4408
|
+
log?.debug?.("STREAM", `Standard passthrough mode`);
|
|
4409
|
+
transformStream = createPassthroughStreamWithLogger(
|
|
4410
|
+
provider,
|
|
4411
|
+
reqLogger,
|
|
4412
|
+
responseToolNameMap,
|
|
4413
|
+
model,
|
|
4414
|
+
connectionId,
|
|
4415
|
+
streamStateBody,
|
|
4416
|
+
onStreamComplete,
|
|
4417
|
+
apiKeyInfo,
|
|
4418
|
+
handleStreamFailure,
|
|
4419
|
+
clientResponseFormat
|
|
4420
|
+
);
|
|
4421
|
+
}
|
|
4422
|
+
|
|
4423
|
+
const finalStream = assembleStreamingPipeline({
|
|
4424
|
+
providerResponse,
|
|
4425
|
+
transformStream,
|
|
4426
|
+
streamController,
|
|
4427
|
+
createPiiTransform,
|
|
4428
|
+
clientRawRequestHeaders: clientRawRequest?.headers,
|
|
4429
|
+
clientResponseFormat,
|
|
4430
|
+
echoModel,
|
|
4431
|
+
responseHeaders,
|
|
4432
|
+
});
|
|
4433
|
+
|
|
4434
|
+
// ── Gamification event (fire-and-forget) ──
|
|
4435
|
+
await emitRequestGamificationEvent({ apiKeyId: apiKeyInfo?.id, model, provider });
|
|
4436
|
+
|
|
4437
|
+
// ── Plugin onResponse hook (fire-and-forget) ──
|
|
4438
|
+
await runPluginOnResponseHook({ requestId: traceId, body, model, provider, apiKeyInfo });
|
|
4439
|
+
|
|
4440
|
+
return {
|
|
4441
|
+
success: true,
|
|
4442
|
+
response: new Response(finalStream, {
|
|
4443
|
+
headers: responseHeaders,
|
|
4444
|
+
}),
|
|
4445
|
+
};
|
|
4446
|
+
}
|
|
4447
|
+
|
|
4448
|
+
export function isTokenExpiringSoon(expiresAt, bufferMs = 5 * 60 * 1000) {
|
|
4449
|
+
if (!expiresAt) return false;
|
|
4450
|
+
const expiresAtMs = new Date(expiresAt).getTime();
|
|
4451
|
+
return expiresAtMs - Date.now() < bufferMs;
|
|
4452
|
+
}
|