cartethyia 2.7.4 → 2.7.6
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 +5 -0
- package/README.md +18 -6
- package/dist/dashboard/assets/BannedPage-B1N603cM.js +1 -0
- package/dist/dashboard/assets/BannedPage-B1N603cM.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-9q4_4NoK.js +3 -0
- package/dist/dashboard/assets/CliToolDetailPage-9q4_4NoK.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-DwshGtK-.js +1 -0
- package/dist/dashboard/assets/CliToolsPage-DwshGtK-.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-D762MIkp.js +1 -0
- package/dist/dashboard/assets/CombosPage-D762MIkp.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-KRYjHZsX.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-KRYjHZsX.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-DXsxk5uM.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-DXsxk5uM.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BEjAjD4F.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-BEjAjD4F.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-d5v3K_Yc.js +899 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-d5v3K_Yc.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-Ccv4tjNS.js +1 -0
- package/dist/dashboard/assets/LoginPage-Ccv4tjNS.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BMS2t8BA.js → ModelPicker-C6WlpU5E.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-C6WlpU5E.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-DN9fYNWA.js +1 -0
- package/dist/dashboard/assets/OverviewPage-DN9fYNWA.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-BY0ieUgz.js +5 -0
- package/dist/dashboard/assets/ProviderDetailPage-BY0ieUgz.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DNsbfea2.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-DNsbfea2.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-lsrx7NYg.js → ProxyPage-DxjvUzSD.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-DxjvUzSD.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-OaHScjpX.js +1 -0
- package/dist/dashboard/assets/QuotaPage-OaHScjpX.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BW4AKj2H.js +1 -0
- package/dist/dashboard/assets/SettingsPage-BW4AKj2H.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-44BZrxFC.js +1 -0
- package/dist/dashboard/assets/SetupPage-44BZrxFC.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-Cq5tW7hW.js → StudioPage-DRPavCdU.js} +4 -4
- package/dist/dashboard/assets/StudioPage-DRPavCdU.js.gz +0 -0
- package/dist/dashboard/assets/ToolIcon-CfeLA-Z_.js +1 -0
- package/dist/dashboard/assets/ToolIcon-CfeLA-Z_.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-B7M7LvMM.js +56 -0
- package/dist/dashboard/assets/UsagePage-B7M7LvMM.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-KvX83mRe.js +1 -0
- package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
- package/dist/dashboard/assets/app-DRnQLhsc.js +9 -0
- package/dist/dashboard/assets/app-DRnQLhsc.js.gz +0 -0
- package/dist/dashboard/assets/app-mweRcsKp.css +2 -0
- package/dist/dashboard/assets/app-mweRcsKp.css.gz +0 -0
- package/dist/dashboard/assets/assert-CidQVoNo.js +1 -0
- package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
- package/dist/dashboard/assets/{badge-D9CS6dZk.js → badge-BaX2GTrn.js} +1 -1
- package/dist/dashboard/assets/badge-BaX2GTrn.js.gz +0 -0
- package/dist/dashboard/assets/{banner-DympBNq4.js → banner-DFH4Bkte.js} +1 -1
- package/dist/dashboard/assets/banner-DFH4Bkte.js.gz +0 -0
- package/dist/dashboard/assets/brand/login_register.jpg +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
- package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/og_image.jpg +0 -0
- package/dist/dashboard/assets/{card-ibRWU_uT.js → card-CxDw0lK6.js} +1 -1
- package/dist/dashboard/assets/card-CxDw0lK6.js.gz +0 -0
- package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js +1 -0
- package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js.gz +0 -0
- package/dist/dashboard/assets/circle-question-mark-Cz4un8sH.js +1 -0
- package/dist/dashboard/assets/circle-question-mark-Cz4un8sH.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js +1 -0
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-DJm-9pz7.js → clipboard-button-DfS1ntiq.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-DfS1ntiq.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BK-NRr5u.js +1 -0
- package/dist/dashboard/assets/dialog-BK-NRr5u.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-CqGYeOrd.js → drawer-QqPul6sC.js} +1 -1
- package/dist/dashboard/assets/drawer-QqPul6sC.js.gz +0 -0
- package/dist/dashboard/assets/entry-BMlYJVFj.js +1 -0
- package/dist/dashboard/assets/entry-BMlYJVFj.js.gz +0 -0
- package/dist/dashboard/assets/entry-D6WvojeD.js +4 -0
- package/dist/dashboard/assets/entry-D6WvojeD.js.gz +0 -0
- package/dist/dashboard/assets/entry-Dmx7gBiN.js +1 -0
- package/dist/dashboard/assets/entry-Dmx7gBiN.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-D-MTJxMr.js → formatters-u7JPB5BX.js} +1 -1
- package/dist/dashboard/assets/formatters-u7JPB5BX.js.gz +0 -0
- package/dist/dashboard/assets/house-h9AZlJuT.js +1 -0
- package/dist/dashboard/assets/house-h9AZlJuT.js.gz +0 -0
- package/dist/dashboard/assets/{index-DBNPOfhV.js → index-Ko4dLRnV.js} +2 -2
- package/dist/dashboard/assets/index-Ko4dLRnV.js.gz +0 -0
- package/dist/dashboard/assets/{inline-BT0zntEr.js → inline-CHJQwxLa.js} +1 -1
- package/dist/dashboard/assets/inline-CHJQwxLa.js.gz +0 -0
- package/dist/dashboard/assets/{input-Da2ZViWg.js → input-BlmlizVV.js} +1 -1
- package/dist/dashboard/assets/input-BlmlizVV.js.gz +0 -0
- package/dist/dashboard/assets/{layout-BXNeLw6M.js → layout-Bz93I-yE.js} +1 -1
- package/dist/dashboard/assets/layout-Bz93I-yE.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-BycOrrIV.js → list-row-Ml16KCfu.js} +1 -1
- package/dist/dashboard/assets/list-row-Ml16KCfu.js.gz +0 -0
- package/dist/dashboard/assets/live-7_QpnYGu.js +1 -0
- package/dist/dashboard/assets/live-7_QpnYGu.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-Rr0TIvcj.js → mutation-GU7XdEjz.js} +1 -1
- package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
- package/dist/dashboard/assets/network-CuMDaTzT.js +1 -0
- package/dist/dashboard/assets/network-CuMDaTzT.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CoqpaDMx.js +2 -0
- package/dist/dashboard/assets/numbers-CoqpaDMx.js.gz +0 -0
- package/dist/dashboard/assets/pagination-B5aOoweZ.js +1 -0
- package/dist/dashboard/assets/pagination-B5aOoweZ.js.gz +0 -0
- package/dist/dashboard/assets/providers/zai.webp +0 -0
- package/dist/dashboard/assets/providers/zcode.webp +0 -0
- package/dist/dashboard/assets/{query-keys-BloKtaXJ.js → query-keys-YSoQOUh6.js} +1 -1
- package/dist/dashboard/assets/{query-keys-BloKtaXJ.js.gz → query-keys-YSoQOUh6.js.gz} +0 -0
- package/dist/dashboard/assets/quota-DJ1QlAOq.js +1 -0
- package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
- package/dist/dashboard/assets/routing-fnCsxgNO.js +1 -0
- package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
- package/dist/dashboard/assets/{select-BIYIBkMU.js → select-BHqpd-bg.js} +2 -2
- package/dist/dashboard/assets/select-BHqpd-bg.js.gz +0 -0
- package/dist/dashboard/assets/settings-BxslsErn.js +1 -0
- package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
- package/dist/dashboard/assets/shield-BMQNxORh.js +1 -0
- package/dist/dashboard/assets/shield-BMQNxORh.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DfgnFDWY.js → stack-j7cXjgYX.js} +1 -1
- package/dist/dashboard/assets/stack-j7cXjgYX.js.gz +0 -0
- package/dist/dashboard/assets/{state-CkfdYWel.js → state-DHmEnLa2.js} +1 -1
- package/dist/dashboard/assets/state-DHmEnLa2.js.gz +0 -0
- package/dist/dashboard/assets/{switch-SYm237HG.js → switch-DmZcyTIC.js} +1 -1
- package/dist/dashboard/assets/switch-DmZcyTIC.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BQqCRNIi.js → useMutation-CbCP4w1o.js} +1 -1
- package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.jpg +0 -0
- package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +60 -59
- package/migrations/full-postgres/0036_system_prompt_presets_refs.sql +10 -0
- package/migrations/full-postgres/0037_share_concurrency_scope.sql +3 -0
- package/migrations/full-postgres/0038_add_accounts_quota_snapshot.sql +7 -0
- package/migrations/full-postgres/0039_add_telemetry_provider_model_indexes.sql +13 -0
- package/migrations/full-postgres/0040_add_models_dialect.sql +3 -0
- package/migrations/full-postgres/0041_add_daily_checkin_ledger_account_fk.sql +7 -0
- package/migrations/full-postgres/0042_add_telemetry_degenerate_stop.sql +14 -0
- package/migrations/lite-sqlite3/0036_system_prompt_presets_refs.sql +14 -0
- package/migrations/lite-sqlite3/0037_share_concurrency_scope.sql +3 -0
- package/migrations/lite-sqlite3/0038_add_accounts_quota_snapshot.sql +4 -0
- package/migrations/lite-sqlite3/0039_add_telemetry_provider_model_indexes.sql +6 -0
- package/migrations/lite-sqlite3/0040_add_models_dialect.sql +3 -0
- package/migrations/lite-sqlite3/0041_add_daily_checkin_ledger_account_fk.sql +26 -0
- package/migrations/lite-sqlite3/0042_add_telemetry_degenerate_stop.sql +12 -0
- package/package.json +4 -6
- package/scripts/cli/update.ts +15 -41
- package/src/boot/app.ts +58 -2
- package/src/boot/dependencies.ts +794 -732
- package/src/boot/lifecycle.ts +11 -12
- package/src/config/cooldowns.ts +34 -55
- package/src/config/network.ts +1 -1
- package/src/config/package-paths.ts +1 -1
- package/src/config/providers.ts +19 -14
- package/src/config/resources.ts +0 -5
- package/src/config/runtime.ts +2 -5
- package/src/config/spec.ts +33 -6
- package/src/config/usage.ts +27 -0
- package/src/console/api-keys/ban-constants.ts +38 -0
- package/src/console/api-keys/contracts.ts +71 -62
- package/src/console/api-keys/routes.ts +56 -24
- package/src/console/auth/access.ts +4 -4
- package/src/console/auth/account-reset.ts +1 -1
- package/src/console/auth/audit-recorder.ts +1 -1
- package/src/console/auth/lockout.ts +7 -3
- package/src/console/auth/row-coercion.ts +57 -12
- package/src/console/auth/session-resolver.ts +1 -1
- package/src/console/auth/session-service.ts +8 -2
- package/src/console/auth/session.ts +37 -31
- package/src/console/auth/setup-service.ts +4 -3
- package/src/console/auth/user-store.ts +1 -1
- package/src/console/backup/9router-map.ts +0 -3
- package/src/console/backup/contracts.ts +9 -9
- package/src/console/backup/export-endpoint.ts +5 -6
- package/src/console/backup/nine-router.ts +1 -7
- package/src/console/backup/routes.ts +29 -33
- package/src/console/backup/service.ts +468 -468
- package/src/console/backup/store.ts +47 -29
- package/src/console/backup/validate.ts +11 -1
- package/src/console/cli-tools/contracts.ts +43 -299
- package/src/console/cli-tools/fs-ops.ts +29 -0
- package/src/console/cli-tools/injectors/claude.ts +3 -3
- package/src/console/cli-tools/injectors/codex.ts +181 -181
- package/src/console/cli-tools/injectors/driver.ts +150 -247
- package/src/console/cli-tools/injectors/kilo.ts +146 -21
- package/src/console/cli-tools/injectors/opencode.ts +176 -153
- package/src/console/cli-tools/routes.ts +7 -1
- package/src/console/cli-tools/service.ts +8 -7
- package/src/console/cli-tools/store.ts +2 -2
- package/src/console/console-router.ts +32 -0
- package/src/console/dashboard-assets.ts +9 -9
- package/src/console/domain-registration.ts +16 -2
- package/src/console/inferhub/client.ts +0 -7
- package/src/console/inferhub/key-store.ts +1 -1
- package/src/console/inferhub/normalize.ts +2 -2
- package/src/console/inferhub/routes.ts +1 -1
- package/src/console/inferhub/types.ts +1 -1
- package/src/console/observability/contracts.ts +7 -1
- package/src/console/observability/live.ts +31 -18
- package/src/console/observability/logs.ts +1 -1
- package/src/console/observability/sse.ts +62 -8
- package/src/console/observability/store.ts +23 -4
- package/src/console/providers/catalog/catalog-projections.ts +1 -1
- package/src/console/providers/catalog/contracts.ts +39 -0
- package/src/console/providers/catalog/model-operations.ts +1 -1
- package/src/console/providers/catalog/provider-operations.ts +95 -17
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/routes.ts +27 -3
- package/src/console/providers/catalog/store.ts +147 -8
- package/src/console/providers/detail/routes.ts +1 -1
- package/src/console/providers/oauth/callback-listener.ts +100 -18
- package/src/console/providers/oauth/routes.ts +92 -13
- package/src/console/quota/account-quota-global.ts +41 -3
- package/src/console/quota/account-quota-shared.ts +10 -3
- package/src/console/quota/account-quota-tenant.ts +259 -66
- package/src/console/quota/daily-growth-job.ts +308 -0
- package/src/console/quota/native-identity.ts +2 -2
- package/src/console/quota/quota-view.ts +1 -1
- package/src/console/remote-support/routes.ts +1 -1
- package/src/console/remote-support/service.ts +2 -2
- package/src/console/routing/model/contracts.ts +1 -1
- package/src/console/routing/pools/contracts.ts +2 -2
- package/src/console/routing/pools/import-parse.ts +3 -3
- package/src/console/routing/pools/relay-deploy.ts +3 -3
- package/src/console/settings/contracts.ts +29 -2
- package/src/console/settings/store.ts +20 -0
- package/src/console/share/share-router.ts +100 -32
- package/src/console/share/share-stats.ts +11 -13
- package/src/console/share/share-usage.ts +1 -1
- package/src/console/shared/errors.ts +2 -2
- package/src/console/shared/object.ts +18 -0
- package/src/console/shared/read-through-cache.ts +4 -4
- package/src/console/shared/transaction.ts +14 -0
- package/src/console/system-prompt/contracts.ts +89 -12
- package/src/console/system-prompt/routes.ts +98 -45
- package/src/console/updates/apply-update.ts +244 -0
- package/src/console/updates/routes.ts +252 -97
- package/src/main.ts +11 -5
- package/src/network/http2-bidi.ts +1 -1
- package/src/network/ip-utils.ts +298 -0
- package/src/network/outbound-fetch.ts +1 -1
- package/src/network/pool/agent.ts +7 -6
- package/src/network/pool/byte-accounting.ts +1 -1
- package/src/network/pool/resolver.ts +16 -12
- package/src/network/pool/selector.ts +150 -136
- package/src/network/pool-health-machine.ts +1 -1
- package/src/network/rate-limit-verdict.ts +1 -1
- package/src/network/retry.ts +1 -1
- package/src/network/ssrf.ts +20 -71
- package/src/observability/event-log.ts +9 -2
- package/src/observability/event-query.ts +61 -291
- package/src/observability/log-sanitize.ts +1 -1
- package/src/observability/metrics.ts +4 -4
- package/src/observability/payload-capture.ts +6 -5
- package/src/observability/payload-store.ts +1 -1
- package/src/observability/runtime-metrics.ts +1 -1
- package/src/observability/telemetry-buffer.ts +19 -3
- package/src/observability/telemetry-reconciliation.ts +3 -3
- package/src/observability/token-speed.ts +1 -1
- package/src/persistence/connection-url.ts +1 -1
- package/src/persistence/db-handle.ts +1 -1
- package/src/persistence/db-pg.ts +2 -2
- package/src/persistence/db-sqlite.ts +3 -3
- package/src/persistence/dialect.ts +5 -1
- package/src/persistence/readiness.ts +1 -1
- package/src/persistence/redis.ts +1 -1
- package/src/persistence/schema.ts +121 -40
- package/src/persistence/shared-cache.ts +2 -2
- package/src/persistence/sort-order.ts +1 -1
- package/src/persistence/stores/api-key-store.ts +29 -2
- package/src/persistence/stores/share-store.ts +20 -8
- package/src/persistence/system-prompt-store.ts +63 -18
- package/src/persistence/tenant-preferences.ts +1 -1
- package/src/persistence/ttl-cache.ts +3 -3
- package/src/persistence/usage-flush.ts +7 -2
- package/src/protocol/canonical-model.ts +1 -1
- package/src/protocol/codecs.ts +6 -10
- package/src/protocol/dsml.ts +8 -125
- package/src/protocol/image-clamp.ts +2 -2
- package/src/protocol/reasoning-text.ts +1 -1
- package/src/protocol/reasoning.ts +7 -52
- package/src/protocol/registry.ts +50 -15
- package/src/protocol/request/chat.ts +46 -19
- package/src/protocol/request/codex.ts +1 -1
- package/src/protocol/request/messages.ts +46 -14
- package/src/protocol/request/responses.ts +19 -10
- package/src/protocol/response/chat.ts +25 -10
- package/src/protocol/response/codex.ts +1 -1
- package/src/protocol/response/messages.ts +1 -1
- package/src/protocol/response/omp-tool-call-markup.ts +1 -1
- package/src/protocol/response/responses.ts +1 -1
- package/src/protocol/sse-decoder.ts +50 -1
- package/src/protocol/stop-reason.ts +2 -2
- package/src/protocol/think-split.ts +347 -0
- package/src/protocol/translation/attachment-integrity.ts +3 -3
- package/src/protocol/translation/cache-controls.ts +8 -20
- package/src/protocol/translation/capabilities.ts +6 -6
- package/src/protocol/translation/dialect/deepseek.ts +10 -0
- package/src/protocol/translation/dialect/inband.ts +39 -0
- package/src/protocol/translation/dialect/model-thinking.ts +89 -0
- package/src/protocol/translation/dialect/qwen.ts +114 -0
- package/src/protocol/translation/dialect/resolver.ts +83 -0
- package/src/protocol/translation/dialect/rules.ts +93 -0
- package/src/protocol/translation/dialect/thinking-ladder.ts +245 -0
- package/src/protocol/translation/dialect/types.ts +58 -0
- package/src/protocol/translation/quirks.ts +18 -4
- package/src/protocol/translation/thinking.ts +12 -252
- package/src/protocol/translation/tool-id.ts +3 -3
- package/src/protocol/translation/tool-repair.ts +1 -1
- package/src/protocol/upstream/post-json.ts +6 -1
- package/src/providers/adapters/compatible-adapter.ts +71 -4
- package/src/providers/authentication/jwt-validator.ts +0 -5
- package/src/providers/authentication/oauth-client.ts +254 -253
- package/src/providers/authentication/oauth-flow-store.ts +42 -7
- package/src/providers/bundled-discovery-providers.ts +6 -30
- package/src/providers/configured-openai-providers.ts +82 -97
- package/src/providers/default-registry.ts +35 -37
- package/src/providers/discovery/catalog/catalog-format.ts +13 -2
- package/src/providers/discovery/discovery-types.ts +0 -1
- package/src/providers/discovery/openai-model-discovery.ts +2 -2
- package/src/providers/discovery/probe-phases.ts +48 -13
- package/src/providers/discovery/probing-service.ts +6 -8
- package/src/providers/integrations/agentrouter/agentrouter.ts +12 -2
- package/src/providers/integrations/antigravity/antigravity-oauth.ts +0 -1
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +88 -28
- package/src/providers/integrations/antigravity/antigravity-quota.ts +18 -6
- package/src/providers/integrations/antigravity/antigravity.ts +22 -13
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -1
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +20 -4
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +19 -52
- package/src/providers/integrations/buddy/buddy-identity.ts +86 -0
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +1 -1
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +1 -2
- package/src/providers/integrations/buddy/codebuddy/provider.ts +4 -15
- package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
- package/src/providers/integrations/buddy/workbuddy/provider.ts +5 -9
- package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
- package/src/providers/integrations/claude/claude-cch.ts +148 -148
- package/src/providers/integrations/claude/fallback-credit.ts +3 -7
- package/src/providers/integrations/cline/cline.ts +1 -1
- package/src/providers/integrations/codex/codex-device-code.ts +101 -101
- package/src/providers/integrations/codex/codex-oauth.ts +0 -1
- package/src/providers/integrations/commandcode/commandcode.ts +75 -115
- package/src/providers/integrations/commandcode/quota.ts +131 -51
- package/src/providers/integrations/cursor/cursor-http2.ts +0 -3
- package/src/providers/integrations/cursor/cursor-quota.ts +2 -6
- package/src/providers/integrations/cursor/cursor-request.ts +0 -87
- package/src/providers/integrations/cursor/cursor-shared.ts +0 -4
- package/src/providers/integrations/cursor/cursor.ts +35 -3
- package/src/providers/integrations/cursor/protocol/framing.ts +0 -56
- package/src/providers/integrations/cursor/protocol/messages.ts +0 -96
- package/src/providers/integrations/factory/factory.ts +9 -9
- package/src/providers/integrations/github/github-discovery.ts +70 -12
- package/src/providers/integrations/github/github-oauth.ts +184 -14
- package/src/providers/integrations/github/github.ts +134 -26
- package/src/providers/integrations/grok/grok-oauth.ts +261 -261
- package/src/providers/integrations/grok/grok-quota-frame.ts +1 -1
- package/src/providers/integrations/grok/grok-quota.ts +16 -7
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +0 -5
- package/src/providers/integrations/grok/grok-ratelimit.ts +3 -0
- package/src/providers/integrations/grok/grok.ts +86 -36
- package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
- package/src/providers/integrations/kiro/kiro-cache.ts +31 -0
- package/src/providers/integrations/kiro/kiro-catalog.ts +31 -5
- package/src/providers/integrations/kiro/kiro-discovery.ts +47 -43
- package/src/providers/integrations/kiro/kiro-machine-id.ts +6 -3
- package/src/providers/integrations/kiro/kiro-oauth.ts +523 -255
- package/src/providers/integrations/kiro/kiro-quota.ts +35 -47
- package/src/providers/integrations/kiro/kiro-request.ts +8 -11
- package/src/providers/integrations/kiro/kiro-stream.ts +13 -0
- package/src/providers/integrations/kiro/kiro.ts +80 -68
- package/src/providers/integrations/muse/muse-oauth.ts +0 -1
- package/src/providers/integrations/muse/muse-quota.ts +0 -4
- package/src/providers/integrations/opencode/fingerprint.ts +7 -5
- package/src/providers/integrations/opencode/provider.ts +17 -24
- package/src/providers/integrations/qoder/qoder-catalog.ts +94 -11
- package/src/providers/integrations/qoder/qoder-oauth.ts +165 -25
- package/src/providers/integrations/qoder/qoder-quota.ts +48 -6
- package/src/providers/integrations/qoder/qoder-realm.ts +10 -31
- package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
- package/src/providers/integrations/qoder/qoder-umid.ts +21 -25
- package/src/providers/integrations/qoder/qoder-virtual.ts +12 -0
- package/src/providers/integrations/qoder/qoder.ts +422 -71
- package/src/providers/integrations/shared/connect.ts +0 -54
- package/src/providers/integrations/shared/hash.ts +15 -0
- package/src/providers/integrations/shared/session-cache.ts +136 -0
- package/src/providers/integrations/trae/trae-cache.ts +7 -87
- package/src/providers/integrations/trae/trae-catalog.ts +1 -6
- package/src/providers/integrations/trae/trae-protocol.ts +17 -8
- package/src/providers/integrations/trae/trae-realm.ts +0 -7
- package/src/providers/integrations/trae/trae.ts +51 -8
- package/src/providers/integrations/xai/xai-oauth.ts +5 -5
- package/src/providers/integrations/zcode-start-plan/gifts.ts +265 -0
- package/src/providers/integrations/zcode-start-plan/oauth.ts +154 -0
- package/src/providers/integrations/zcode-start-plan/provider.ts +131 -0
- package/src/providers/integrations/zcode-start-plan/quota.ts +152 -0
- package/src/providers/integrations/zcode-start-plan/zcode_prompt.json +10 -0
- package/src/providers/operations/account-error-classification.ts +198 -133
- package/src/providers/operations/account-health-service.ts +31 -39
- package/src/providers/operations/account-reset-service.ts +58 -1
- package/src/providers/operations/byok-wire-profile.ts +19 -15
- package/src/providers/operations/cli-platform.ts +31 -5
- package/src/providers/operations/client-version-resolver.ts +4 -6
- package/src/providers/operations/client-versions.ts +10 -7
- package/src/providers/operations/provider-catalog-seeder.ts +2 -0
- package/src/providers/operations/provider-catalog-service.ts +18 -2
- package/src/providers/operations/provider-credential-service.ts +19 -2
- package/src/providers/provider-contracts.ts +18 -7
- package/src/providers/provider-metadata.ts +17 -2
- package/src/providers/provider-model-definition.ts +22 -1
- package/src/providers/provider-probe.ts +4 -2
- package/src/providers/provider-usage.ts +50 -19
- package/src/providers/quota/cache.ts +29 -0
- package/src/providers/quota/quota-contracts.ts +4 -0
- package/src/providers/quota/refresh.ts +27 -4
- package/src/providers/quota/snapshot.ts +94 -0
- package/src/providers/search/search-provider.ts +1 -3
- package/src/providers/search/search-providers.ts +2 -1
- package/src/security/abuse.ts +4 -4
- package/src/security/admission/service.ts +13 -6
- package/src/security/admission/ttl.ts +61 -61
- package/src/security/api-key-auth.ts +17 -3
- package/src/security/client-router-fingerprint.ts +4 -4
- package/src/security/credential-codec.ts +3 -3
- package/src/security/csrf.ts +2 -2
- package/src/security/ip-boundary.ts +16 -189
- package/src/security/model-access-rule.ts +1 -1
- package/src/security/outbound-headers.ts +18 -0
- package/src/shared/invoke-guard.ts +47 -0
- package/src/transport/contracts.ts +13 -4
- package/src/transport/dispatch/attempt-finalize.ts +70 -22
- package/src/transport/dispatch/attempt-loop.ts +22 -0
- package/src/transport/dispatch/capture-policy.ts +34 -37
- package/src/transport/dispatch/empty-stream.ts +23 -20
- package/src/transport/dispatch/generation.ts +25 -45
- package/src/transport/dispatch/native-shared.ts +127 -0
- package/src/transport/dispatch/proxy-request.ts +3 -1
- package/src/transport/dispatch/responses-compact.ts +25 -46
- package/src/transport/dispatch/streaming/encoder.ts +18 -72
- package/src/transport/dispatch/streaming/finalize.ts +2 -2
- package/src/transport/dispatch/streaming/stream.ts +7 -3
- package/src/transport/dispatch/streaming/thinking-loop.ts +1 -1
- package/src/transport/dispatch/systemone.ts +20 -48
- package/src/transport/dispatch/token-count.ts +41 -38
- package/src/transport/dispatch/websearch.ts +19 -45
- package/src/transport/drain-endpoint.ts +1 -1
- package/src/transport/failure-policy.ts +21 -1
- package/src/transport/guards/auth-guard.ts +0 -6
- package/src/transport/guards/body-policy.ts +3 -3
- package/src/transport/guards/error-lifecycle.ts +4 -0
- package/src/transport/guards/headroom-guard.ts +1 -1
- package/src/transport/guards/pipeline.ts +1 -1
- package/src/transport/guards/request-context.ts +12 -17
- package/src/transport/request/preparer.ts +286 -7
- package/src/transport/request/rtk/compress-request.ts +3 -3
- package/src/transport/request/rtk/rtk-constants.ts +2 -2
- package/src/transport/request/rtk/rtk-filters.ts +8 -8
- package/src/transport/request/state.ts +7 -13
- package/src/transport/request/system-prompt-injection.ts +74 -11
- package/src/transport/request/tenant-preferences.ts +22 -0
- package/src/transport/routing/candidate-index.ts +2 -2
- package/src/transport/routing/round-robin.ts +1 -1
- package/src/transport/routing/route-catalog.ts +17 -18
- package/src/transport/routing/route-model.ts +126 -33
- package/src/transport/routing/router.ts +159 -37
- package/src/transport/shutdown-notice.ts +2 -2
- package/src/transport/surface/adapters.ts +3 -3
- package/src/transport/surface/chat/encode.ts +7 -7
- package/src/transport/surface/chat/parse.ts +7 -7
- package/src/transport/surface/completion.ts +2 -2
- package/src/transport/surface/content-parts.ts +1 -1
- package/src/transport/surface/dialects.ts +2 -2
- package/src/transport/surface/messages/parse.ts +3 -3
- package/src/transport/surface/reasoning-summary.ts +9 -14
- package/src/transport/surface/responses/adapter.ts +1 -1
- package/src/transport/surface/responses/errors.ts +0 -9
- package/src/transport/vision-handoff-cache.ts +58 -0
- package/src/transport/vision-handoff.ts +431 -0
- package/src/workers/account-identity-sweep.ts +2 -2
- package/src/workers/buddy-growth-tasks.ts +392 -346
- package/src/workers/daily-checkin.ts +102 -49
- package/src/workers/maintenance-lease.ts +2 -35
- package/src/workers/oauth-refresh-sweep.ts +8 -1
- package/src/workers/quota-refresh-sweep.ts +120 -82
- package/src/workers/scheduler.ts +67 -4
- package/src/workers/sweep-runner.ts +24 -5
- package/src/workers/zcode-gift-claim.ts +108 -0
- package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js +0 -1
- package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
- package/dist/dashboard/assets/BannedPage-ChG5svFb.js +0 -1
- package/dist/dashboard/assets/BannedPage-ChG5svFb.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js +0 -1
- package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DCgm5g6g.js +0 -1
- package/dist/dashboard/assets/CombosPage-DCgm5g6g.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-U6AKaJcF.js +0 -1
- package/dist/dashboard/assets/LoginPage-U6AKaJcF.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BMS2t8BA.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js +0 -1
- package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js +0 -5
- package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-L0skzML8.js +0 -1
- package/dist/dashboard/assets/QuotaPage-L0skzML8.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js +0 -1
- package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-f0DE1h0-.js +0 -1
- package/dist/dashboard/assets/SetupPage-f0DE1h0-.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-ClzpwyET.js +0 -1
- package/dist/dashboard/assets/SortableList-ClzpwyET.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-Cq5tW7hW.js.gz +0 -0
- package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js +0 -1
- package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js +0 -56
- package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-C38iXoO4.js +0 -1
- package/dist/dashboard/assets/api-keys-C38iXoO4.js.gz +0 -0
- package/dist/dashboard/assets/app-BayTTy01.js +0 -9
- package/dist/dashboard/assets/app-BayTTy01.js.gz +0 -0
- package/dist/dashboard/assets/app-a159k0ub.css +0 -2
- package/dist/dashboard/assets/app-a159k0ub.css.gz +0 -0
- package/dist/dashboard/assets/assert-DbRA8EG2.js +0 -15
- package/dist/dashboard/assets/assert-DbRA8EG2.js.gz +0 -0
- package/dist/dashboard/assets/badge-D9CS6dZk.js.gz +0 -0
- package/dist/dashboard/assets/banner-DympBNq4.js.gz +0 -0
- package/dist/dashboard/assets/card-ibRWU_uT.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-63qBGaRw.js +0 -1
- package/dist/dashboard/assets/cli-tools-63qBGaRw.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DJm-9pz7.js.gz +0 -0
- package/dist/dashboard/assets/dialog-8fiE6tec.js +0 -1
- package/dist/dashboard/assets/dialog-8fiE6tec.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CqGYeOrd.js.gz +0 -0
- package/dist/dashboard/assets/entry-BSAsYsmV.js +0 -1
- package/dist/dashboard/assets/entry-BSAsYsmV.js.gz +0 -0
- package/dist/dashboard/assets/entry-CiIQULZD.js +0 -4
- package/dist/dashboard/assets/entry-CiIQULZD.js.gz +0 -0
- package/dist/dashboard/assets/entry-Daz0lJ-W.js +0 -1
- package/dist/dashboard/assets/entry-Daz0lJ-W.js.gz +0 -0
- package/dist/dashboard/assets/formatters-D-MTJxMr.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-CPK47xQx.js +0 -1
- package/dist/dashboard/assets/github-badge-CPK47xQx.js.gz +0 -0
- package/dist/dashboard/assets/index-DBNPOfhV.js.gz +0 -0
- package/dist/dashboard/assets/inline-BT0zntEr.js.gz +0 -0
- package/dist/dashboard/assets/input-Da2ZViWg.js.gz +0 -0
- package/dist/dashboard/assets/layout-BXNeLw6M.js.gz +0 -0
- package/dist/dashboard/assets/list-row-BycOrrIV.js.gz +0 -0
- package/dist/dashboard/assets/live-DCQNZpF7.js +0 -1
- package/dist/dashboard/assets/live-DCQNZpF7.js.gz +0 -0
- package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
- package/dist/dashboard/assets/network-9Yj2ZyLo.js +0 -1
- package/dist/dashboard/assets/network-9Yj2ZyLo.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CEagj6ZZ.js +0 -2
- package/dist/dashboard/assets/numbers-CEagj6ZZ.js.gz +0 -0
- package/dist/dashboard/assets/providers/zai-coding-plan.svg +0 -6
- package/dist/dashboard/assets/providers/zai-coding-plan.svg.gz +0 -0
- package/dist/dashboard/assets/quota-Caw7wqci.js +0 -1
- package/dist/dashboard/assets/quota-Caw7wqci.js.gz +0 -0
- package/dist/dashboard/assets/routing-CO9zCUus.js +0 -1
- package/dist/dashboard/assets/routing-CO9zCUus.js.gz +0 -0
- package/dist/dashboard/assets/select-BIYIBkMU.js.gz +0 -0
- package/dist/dashboard/assets/settings-jS3-PNSA.js +0 -1
- package/dist/dashboard/assets/settings-jS3-PNSA.js.gz +0 -0
- package/dist/dashboard/assets/stack-DfgnFDWY.js.gz +0 -0
- package/dist/dashboard/assets/state-CkfdYWel.js.gz +0 -0
- package/dist/dashboard/assets/stream-retry-5QnVuSSo.js +0 -1
- package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
- package/dist/dashboard/assets/switch-SYm237HG.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BQqCRNIi.js.gz +0 -0
- package/src/console/cli-tools/injectors/cline.ts +0 -110
- package/src/console/cli-tools/injectors/copilot.ts +0 -108
- package/src/console/cli-tools/injectors/cowork.ts +0 -64
- package/src/console/cli-tools/injectors/deepseek-tui.ts +0 -85
- package/src/console/cli-tools/injectors/droid.ts +0 -96
- package/src/console/cli-tools/injectors/grok-build.ts +0 -70
- package/src/console/cli-tools/injectors/hermes.ts +0 -101
- package/src/console/cli-tools/injectors/jcode.ts +0 -115
- package/src/console/cli-tools/injectors/openclaw.ts +0 -161
- package/src/network/response-headers.ts +0 -39
- package/src/protocol/response/dsml.ts +0 -38
- package/src/providers/integrations/xiaomi-mimo/think-stream.ts +0 -229
- /package/dist/dashboard/assets/brand/{og_bansos.webp → og_bansos_initial.webp} +0 -0
- /package/dist/dashboard/assets/brand/{og_image.webp → og_image_initial.webp} +0 -0
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
import type { CanonicalEvent, SourceSurface, UsageRecord } from "../../../protocol/canonical-model";
|
|
13
13
|
import { canonicalTerminal } from "../../../protocol/codecs";
|
|
14
14
|
import { reasoningTextOf } from "../../../protocol/reasoning-text";
|
|
15
|
-
import {
|
|
15
|
+
import { ThinkSplitter } from "../../../protocol/think-split";
|
|
16
16
|
import { createDispatchStreamEncoder, type DispatchStreamEncoder } from "../stream-bridge";
|
|
17
17
|
import { THINKING_LOOP_MARKER, ThinkingLoopDetector } from "./thinking-loop";
|
|
18
18
|
|
|
19
19
|
/** The terminal event shape every stream settles (or fails to settle) on. */
|
|
20
|
-
|
|
20
|
+
type StreamTerminalEvent = Extract<CanonicalEvent, { type: "terminal" }>;
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* SSE comment frame: valid framing on every streamed surface, ignored by
|
|
@@ -46,7 +46,7 @@ interface TruncatedEventTranscript {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/** Error-frame input in the shape the dispatch stream encoder accepts. */
|
|
49
|
-
|
|
49
|
+
type StreamErrorFrameInput = Parameters<DispatchStreamEncoder["encodeError"]>[0];
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Per-event state and framing for one stream. Client framing and protocol
|
|
@@ -93,10 +93,11 @@ export class StreamEventPipeline {
|
|
|
93
93
|
*/
|
|
94
94
|
private readonly diagnosticsCaptureMode: "none" | "metadata" | "full";
|
|
95
95
|
private readonly streamEncoder: DispatchStreamEncoder;
|
|
96
|
-
// One splitter per stream
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
|
|
96
|
+
// One splitter per stream, kept purely as a feeder for the loop guard: it
|
|
97
|
+
// reports the content currently inside an open `<think>` span so a runaway
|
|
98
|
+
// chain-of-thought is caught even when the provider wrote it into the text
|
|
99
|
+
// stream. It never re-routes the emitted events — text is forwarded verbatim.
|
|
100
|
+
private readonly thinkSplitter = new ThinkSplitter();
|
|
100
101
|
/**
|
|
101
102
|
* Content-level loop guard. A model that degenerates into a repeated
|
|
102
103
|
* chain-of-thought keeps sending bytes, so the stall timeout — which fires
|
|
@@ -259,26 +260,17 @@ export class StreamEventPipeline {
|
|
|
259
260
|
// `loopDetected`, so this only matters for an event already in flight;
|
|
260
261
|
// dropping it is the alternative to throwing mid-stream.
|
|
261
262
|
if (this.loopDetector.detected) return false;
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
263
|
+
// Text is forwarded verbatim: inline thinking tags are a per-provider
|
|
264
|
+
// decoder concern, not a universal one. Splitting them here for every
|
|
265
|
+
// surface misclassified literal `<think>` in an answer as reasoning — the
|
|
266
|
+
// answer vanished into the thinking pane and the poisoned history stopped
|
|
267
|
+
// follow-up turns. The loop guard still watches reasoning a provider puts
|
|
268
|
+
// inside an open `<think>` span, without reclassifying the answer.
|
|
267
269
|
if (event.type === "content_delta" && event.content.kind === "text") {
|
|
270
|
+
const enqueued = this.enqueueOne(event, controller);
|
|
268
271
|
const split = this.thinkSplitter.push(event.content.text);
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const reasoningEvent: CanonicalEvent = {
|
|
272
|
-
...event,
|
|
273
|
-
content: { kind: "reasoning", payload: null, summary: split.reasoning },
|
|
274
|
-
};
|
|
275
|
-
enqueued = this.enqueueOne(reasoningEvent, controller) || enqueued;
|
|
276
|
-
if (this.guardReasoning(split.reasoning, controller)) return enqueued;
|
|
277
|
-
}
|
|
278
|
-
if (split.text.length > 0) {
|
|
279
|
-
enqueued =
|
|
280
|
-
this.enqueueOne({ ...event, content: { kind: "text", text: split.text } }, controller) ||
|
|
281
|
-
enqueued;
|
|
272
|
+
if (split.pendingReasoning.length > 0) {
|
|
273
|
+
if (this.guardReasoning(split.pendingReasoning, controller)) return true;
|
|
282
274
|
}
|
|
283
275
|
return enqueued;
|
|
284
276
|
}
|
|
@@ -290,31 +282,8 @@ export class StreamEventPipeline {
|
|
|
290
282
|
this.guardReasoning(reasoningTextOf(event.content) ?? "", controller);
|
|
291
283
|
return enqueued;
|
|
292
284
|
}
|
|
293
|
-
// Text held back for a tag that never completed must be released *before*
|
|
294
|
-
// the terminal: a content event after it is rejected by the surface
|
|
295
|
-
// encoders, and the tail of the answer would be lost instead.
|
|
296
285
|
if (event.type === "terminal") {
|
|
297
|
-
|
|
298
|
-
if (rest.reasoning.length > 0) {
|
|
299
|
-
this.enqueueOne(
|
|
300
|
-
{
|
|
301
|
-
type: "content_delta",
|
|
302
|
-
sequence_number: event.sequence_number,
|
|
303
|
-
content: { kind: "reasoning", payload: null, summary: rest.reasoning },
|
|
304
|
-
},
|
|
305
|
-
controller,
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
if (rest.text.length > 0) {
|
|
309
|
-
this.enqueueOne(
|
|
310
|
-
{
|
|
311
|
-
type: "content_delta",
|
|
312
|
-
sequence_number: event.sequence_number,
|
|
313
|
-
content: { kind: "text", text: rest.text },
|
|
314
|
-
},
|
|
315
|
-
controller,
|
|
316
|
-
);
|
|
317
|
-
}
|
|
286
|
+
return this.enqueueOne(event, controller);
|
|
318
287
|
}
|
|
319
288
|
return this.enqueueOne(event, controller);
|
|
320
289
|
}
|
|
@@ -335,29 +304,6 @@ export class StreamEventPipeline {
|
|
|
335
304
|
): boolean {
|
|
336
305
|
const reason = text.length > 0 ? this.loopDetector.push(text) : undefined;
|
|
337
306
|
if (reason === undefined) return false;
|
|
338
|
-
// Flush before the terminal: content after it is rejected by the surface
|
|
339
|
-
// encoders, and the held-back text would be lost.
|
|
340
|
-
const rest = this.thinkSplitter.flush();
|
|
341
|
-
if (rest.reasoning.length > 0) {
|
|
342
|
-
this.enqueueOne(
|
|
343
|
-
{
|
|
344
|
-
type: "content_delta",
|
|
345
|
-
sequence_number: this.lastSequenceNumber + 1,
|
|
346
|
-
content: { kind: "reasoning", payload: null, summary: rest.reasoning },
|
|
347
|
-
},
|
|
348
|
-
controller,
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
if (rest.text.length > 0) {
|
|
352
|
-
this.enqueueOne(
|
|
353
|
-
{
|
|
354
|
-
type: "content_delta",
|
|
355
|
-
sequence_number: this.lastSequenceNumber + 1,
|
|
356
|
-
content: { kind: "text", text: rest.text },
|
|
357
|
-
},
|
|
358
|
-
controller,
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
307
|
this.enqueueOne(
|
|
362
308
|
canonicalTerminal({
|
|
363
309
|
sequenceNumber: this.lastSequenceNumber + 1,
|
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
import { KEEPALIVE_COMMENT_BYTES, type StreamEventPipeline } from "./encoder";
|
|
40
40
|
|
|
41
41
|
/** Collaborators the stream tail reads from the dispatch handler. */
|
|
42
|
-
|
|
42
|
+
interface StreamFinalizeDeps {
|
|
43
43
|
readonly db: CartethyiaDatabase;
|
|
44
44
|
readonly poolSelector?: NetworkPoolSelector | undefined;
|
|
45
45
|
readonly telemetryBuffer?: TelemetryBatchBuffer | undefined;
|
|
@@ -56,7 +56,7 @@ export interface StreamCandidateIdentity {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/** What the stream tail needs from the attempt's input. */
|
|
59
|
-
|
|
59
|
+
interface StreamFinalizeInput {
|
|
60
60
|
readonly state: ProxyRequestState;
|
|
61
61
|
readonly deps: StreamFinalizeDeps;
|
|
62
62
|
readonly prepared: PreparedProxyRequest;
|
|
@@ -64,7 +64,7 @@ import { StreamEventPipeline } from "./encoder";
|
|
|
64
64
|
import { StreamFinalizer, type StreamCandidateIdentity } from "./finalize";
|
|
65
65
|
|
|
66
66
|
/** Collaborators the streaming branch reads from the dispatch handler. */
|
|
67
|
-
|
|
67
|
+
interface StreamingDispatchDeps {
|
|
68
68
|
readonly db: CartethyiaDatabase;
|
|
69
69
|
readonly networkBindingFactory?: ValidatedNetworkBindingFactory | undefined;
|
|
70
70
|
readonly poolSelector?: NetworkPoolSelector | undefined;
|
|
@@ -73,7 +73,7 @@ export interface StreamingDispatchDeps {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/** Everything one streamed attempt needs, all resolved by its caller. */
|
|
76
|
-
|
|
76
|
+
interface StreamingDispatchInput {
|
|
77
77
|
readonly state: ProxyRequestState;
|
|
78
78
|
readonly deps: StreamingDispatchDeps;
|
|
79
79
|
readonly prepared: PreparedProxyRequest;
|
|
@@ -228,7 +228,11 @@ export async function dispatchStreamingAttempt(input: StreamingDispatchInput): P
|
|
|
228
228
|
if (!isWrappedInjectionProvider(candidate.provider_id)) return {};
|
|
229
229
|
const text = resolveTenantSystemPrompt(
|
|
230
230
|
await readTenantSystemPromptsForDispatch(deps.db, prepared.authorization.snapshot.tenant_id),
|
|
231
|
-
{
|
|
231
|
+
{
|
|
232
|
+
providerId: candidate.provider_id,
|
|
233
|
+
modelId: candidate.model_id,
|
|
234
|
+
requestedModel: prepared.plan?.requested_model ?? candidate.model_id,
|
|
235
|
+
},
|
|
232
236
|
)?.text;
|
|
233
237
|
return text === undefined ? {} : { tenantSystemPrompt: text };
|
|
234
238
|
})()),
|
|
@@ -70,7 +70,7 @@ const SEGMENT_MIN_CLUSTER = 4;
|
|
|
70
70
|
* would hide every later prose paragraph from the heuristics. */
|
|
71
71
|
const CODE_LINE = /^(?:[ \t]*```.*|(?: {2,}|\t)(?![ \t]*(?:[-*+]|\d+[.)])[ \t]).*|.*[{}[\](;,>][ \t]*)$/gm;
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
interface ThinkingLoopConfig {
|
|
74
74
|
readonly enabled?: boolean;
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { ProviderAdapter, ProviderDispatchTarget } from "../../providers/provider-contracts";
|
|
2
2
|
import { GatewayError } from "../../protocol/gateway-error";
|
|
3
|
-
import { resolveCredentialForAccount } from "../../providers/operations/provider-credential-service";
|
|
4
3
|
import { metrics } from "../../observability/metrics";
|
|
5
|
-
import {
|
|
6
|
-
import { completeAttempt, estimatedUsage, nativeCompletionBase } from "./attempt-finalize";
|
|
7
|
-
import { repriceUsage, usageFromProvider } from "../../providers/provider-usage";
|
|
4
|
+
import { estimatedUsage, nativeCompletionBase, completeAttempt } from "./attempt-finalize";
|
|
8
5
|
import { runAttemptLoop } from "./attempt-loop";
|
|
6
|
+
import {
|
|
7
|
+
networkFetchContext,
|
|
8
|
+
requireRecordBody,
|
|
9
|
+
resolveAttemptCredential,
|
|
10
|
+
stripThinkingSuffix,
|
|
11
|
+
usageFromUpstreamClone,
|
|
12
|
+
} from "./native-shared";
|
|
9
13
|
|
|
10
14
|
/**
|
|
11
15
|
* The native System One route (`POST /v1/systemone`) and the dependency surface
|
|
@@ -20,7 +24,7 @@ import { runAttemptLoop } from "./attempt-loop";
|
|
|
20
24
|
*/
|
|
21
25
|
import type { GatewayHandlerDeps } from "./handler-deps";
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
type SystemoneHandlerDeps = GatewayHandlerDeps;
|
|
24
28
|
|
|
25
29
|
/** Adapter that can serve a System One decision request. */
|
|
26
30
|
type SystemoneAdapter = ProviderAdapter & {
|
|
@@ -31,10 +35,10 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
|
|
|
31
35
|
return async ({ request }: { request: Request }): Promise<Response> => {
|
|
32
36
|
const state = deps.stateStore.require(request);
|
|
33
37
|
const authorization = state.authorization;
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
const decision = requireRecordBody(
|
|
39
|
+
state,
|
|
40
|
+
"systemone request body must be a JSON object (request {requestId}). Check the shape of what you sent — then resend 🪞",
|
|
41
|
+
);
|
|
38
42
|
if (typeof decision.model !== "string" || decision.model.trim().length === 0)
|
|
39
43
|
throw new GatewayError("invalid_request", 400, "model is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
|
|
40
44
|
// A thinking suffix (`model(high)`) is stripped for the same reason the
|
|
@@ -42,9 +46,7 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
|
|
|
42
46
|
// allowlist and the route plan, and `(high)` is not part of any registered
|
|
43
47
|
// id. The level itself is dropped — a decision request carries no reasoning
|
|
44
48
|
// effort to shape, and the body is passed upstream verbatim.
|
|
45
|
-
const {
|
|
46
|
-
const decisionBody: Record<string, unknown> =
|
|
47
|
-
bareModel === decision.model ? decision : { ...decision, model: bareModel };
|
|
49
|
+
const { bareModel, body: decisionBody } = stripThinkingSuffix(decision.model, decision);
|
|
48
50
|
// Trust boundary only: the decision model's own question shapes are
|
|
49
51
|
// upstream's to validate. `state` and a `questions` object are what make a
|
|
50
52
|
// System One request a System One request at all.
|
|
@@ -60,7 +62,7 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
|
|
|
60
62
|
const prepared = await deps.proxyPreparer.prepareNativeService({
|
|
61
63
|
model: bareModel,
|
|
62
64
|
serviceKind: "systemone",
|
|
63
|
-
authorization
|
|
65
|
+
authorization: authorization!,
|
|
64
66
|
...(state.abortController.signal ? { signal: state.abortController.signal } : {}),
|
|
65
67
|
...(state.clientUserAgent === undefined ? {} : { clientUserAgent: state.clientUserAgent }),
|
|
66
68
|
});
|
|
@@ -85,19 +87,7 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
|
|
|
85
87
|
"no eligible System One route (request {requestId}). The bouncer stepped out — come back shortly 🚪",
|
|
86
88
|
),
|
|
87
89
|
prepare: async (candidate) => {
|
|
88
|
-
const credential = candidate
|
|
89
|
-
? await resolveCredentialForAccount(
|
|
90
|
-
deps.db,
|
|
91
|
-
candidate.provider_id,
|
|
92
|
-
candidate.provider_account_id,
|
|
93
|
-
deps.oauthRefreshService && deps.resolveOAuthRefresher
|
|
94
|
-
? { refreshService: deps.oauthRefreshService, resolveRefresher: deps.resolveOAuthRefresher }
|
|
95
|
-
: undefined,
|
|
96
|
-
)
|
|
97
|
-
: {
|
|
98
|
-
provider_id: candidate.provider_id as ProviderAdapter["provider_id"],
|
|
99
|
-
credential_kind: "none" as const,
|
|
100
|
-
};
|
|
90
|
+
const credential = await resolveAttemptCredential(deps, candidate);
|
|
101
91
|
const adapter = (await deps.resolveProviderAdapter?.(candidate.provider_id)) as SystemoneAdapter | undefined;
|
|
102
92
|
if (!adapter || typeof adapter.systemone !== "function")
|
|
103
93
|
throw new GatewayError(
|
|
@@ -131,14 +121,7 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
|
|
|
131
121
|
deadline: state.deadlineMs,
|
|
132
122
|
abort_signal: state.abortController.signal,
|
|
133
123
|
...(candidate.user_agent === undefined ? {} : { user_agent: candidate.user_agent }),
|
|
134
|
-
...(deps.
|
|
135
|
-
? {
|
|
136
|
-
outbound_fetch: deps.networkBindingFactory.fetch(
|
|
137
|
-
leases.proxySlot?.poolId,
|
|
138
|
-
authorization.tenantId,
|
|
139
|
-
),
|
|
140
|
-
}
|
|
141
|
-
: {}),
|
|
124
|
+
...networkFetchContext(deps, leases.proxySlot?.poolId, authorization!.tenantId),
|
|
142
125
|
});
|
|
143
126
|
if (!response.ok) {
|
|
144
127
|
// Surface the upstream decision error verbatim: it is the operator's
|
|
@@ -154,20 +137,9 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
|
|
|
154
137
|
// Prefer the usage the decision endpoint reported; fall back to the
|
|
155
138
|
// fixed native estimate. `response.clone()` leaves the body readable
|
|
156
139
|
// for the client.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
reportedUsage = usageFromProvider(
|
|
161
|
-
typeof parsed === "object" && parsed !== null
|
|
162
|
-
? (parsed as Record<string, unknown>)["usage"]
|
|
163
|
-
: undefined,
|
|
164
|
-
);
|
|
165
|
-
} catch {
|
|
166
|
-
reportedUsage = undefined;
|
|
167
|
-
}
|
|
168
|
-
const usage = repriceUsage(
|
|
169
|
-
reportedUsage ??
|
|
170
|
-
estimatedUsage(prepared.estimatedInputTokens, prepared.estimatedOutputTokens),
|
|
140
|
+
const usage = await usageFromUpstreamClone(
|
|
141
|
+
response,
|
|
142
|
+
estimatedUsage(prepared.estimatedInputTokens, prepared.estimatedOutputTokens),
|
|
171
143
|
candidate.provider_id,
|
|
172
144
|
candidate.model_id,
|
|
173
145
|
);
|
|
@@ -36,31 +36,33 @@
|
|
|
36
36
|
import type { ProviderAdapter, ProviderDispatchTarget } from "../../providers/provider-contracts";
|
|
37
37
|
import { GatewayError } from "../../protocol/gateway-error";
|
|
38
38
|
import { isRecord } from "../../protocol/codecs";
|
|
39
|
-
import { resolveCredentialForAccount } from "../../providers/operations/provider-credential-service";
|
|
40
39
|
import { metrics } from "../../observability/metrics";
|
|
40
|
+
import type { TelemetryBatchBuffer } from "../../observability/telemetry-buffer";
|
|
41
41
|
import type { ProxyRequestState } from "../request/state";
|
|
42
42
|
import type { PreparedNativeRequest } from "../request/preparer";
|
|
43
43
|
import { completeAttempt, estimatedUsage, nativeCompletionBase } from "./attempt-finalize";
|
|
44
|
+
import { finalizeRequestTelemetry } from "../guards/error-lifecycle";
|
|
44
45
|
import { runAttemptLoop } from "./attempt-loop";
|
|
46
|
+
import { hasScope, networkFetchContext, requireRecordBody, resolveAttemptCredential } from "./native-shared";
|
|
45
47
|
|
|
46
48
|
import type { GatewayHandlerDeps } from "./handler-deps";
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
type TokenCountHandlerDeps = GatewayHandlerDeps;
|
|
49
51
|
|
|
50
52
|
/** Builds the `POST /v1/messages/count_tokens` handler. */
|
|
51
53
|
export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
|
|
52
54
|
return async ({ request }: { request: Request }): Promise<Response> => {
|
|
53
55
|
const state = deps.stateStore.require(request);
|
|
54
56
|
const authorization = state.authorization;
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
const countBody = requireRecordBody(
|
|
58
|
+
state,
|
|
59
|
+
"The count_tokens body must be a JSON object (request {requestId}), the same shape POST /v1/messages accepts. Check the shape of what you sent — then resend 🪞",
|
|
60
|
+
);
|
|
61
|
+
// `requireRecordBody` already refused a missing authorization; this guard
|
|
62
|
+
// exists only to narrow the type for the code below.
|
|
63
|
+
if (!authorization) {
|
|
64
|
+
throw new GatewayError("invalid_request", 400, "authentication required (request {requestId})");
|
|
62
65
|
}
|
|
63
|
-
const countBody = body as Record<string, unknown>;
|
|
64
66
|
|
|
65
67
|
// `model` names the route and `messages` is what gets counted; both are the
|
|
66
68
|
// upstream's own required fields, so the gateway does not restate its own
|
|
@@ -71,7 +73,7 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
|
|
|
71
73
|
if (!Array.isArray(countBody.messages)) {
|
|
72
74
|
throw new GatewayError("invalid_request", 400, "messages must be an array (request {requestId}) — there is nothing to count without it. Check the shape of what you sent — then resend 🪞");
|
|
73
75
|
}
|
|
74
|
-
if (!authorization
|
|
76
|
+
if (!hasScope(authorization?.snapshot.scopes, "routing:invoke")) {
|
|
75
77
|
throw new GatewayError(
|
|
76
78
|
"policy_rejected",
|
|
77
79
|
403,
|
|
@@ -93,7 +95,7 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
|
|
|
93
95
|
// real 429/503 — a fabricated count would tell the client its context
|
|
94
96
|
// fits when nothing is there to serve the turn that follows.
|
|
95
97
|
if (!(error instanceof GatewayError) || error.code !== "capability_unsupported") throw error;
|
|
96
|
-
return estimatedCountResponse(state, countBody);
|
|
98
|
+
return estimatedCountResponse(state, countBody, deps.telemetryBuffer, deps.db);
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
return runAttemptLoop<Response, ProviderAdapter>({
|
|
@@ -116,19 +118,7 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
|
|
|
116
118
|
"no eligible route can count tokens for this model (request {requestId}). The bouncer stepped out — come back shortly 🚪",
|
|
117
119
|
),
|
|
118
120
|
prepare: async (candidate) => {
|
|
119
|
-
const credential = candidate
|
|
120
|
-
? await resolveCredentialForAccount(
|
|
121
|
-
deps.db,
|
|
122
|
-
candidate.provider_id,
|
|
123
|
-
candidate.provider_account_id,
|
|
124
|
-
deps.oauthRefreshService && deps.resolveOAuthRefresher
|
|
125
|
-
? { refreshService: deps.oauthRefreshService, resolveRefresher: deps.resolveOAuthRefresher }
|
|
126
|
-
: undefined,
|
|
127
|
-
)
|
|
128
|
-
: {
|
|
129
|
-
provider_id: candidate.provider_id as ProviderAdapter["provider_id"],
|
|
130
|
-
credential_kind: "none" as const,
|
|
131
|
-
};
|
|
121
|
+
const credential = await resolveAttemptCredential(deps, candidate);
|
|
132
122
|
const adapter = await deps.resolveProviderAdapter?.(candidate.provider_id);
|
|
133
123
|
if (!adapter) {
|
|
134
124
|
throw new GatewayError("capability_unsupported", 400, "token counting is unavailable for this provider (request {requestId}). Know your tools — this one just isn't in the box 🧰");
|
|
@@ -167,14 +157,7 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
|
|
|
167
157
|
deadline: state.deadlineMs,
|
|
168
158
|
abort_signal: state.abortController.signal,
|
|
169
159
|
...(candidate.user_agent === undefined ? {} : { user_agent: candidate.user_agent }),
|
|
170
|
-
...(deps.
|
|
171
|
-
? {
|
|
172
|
-
outbound_fetch: deps.networkBindingFactory.fetch(
|
|
173
|
-
leases.proxySlot?.poolId,
|
|
174
|
-
authorization.tenantId,
|
|
175
|
-
),
|
|
176
|
-
}
|
|
177
|
-
: {}),
|
|
160
|
+
...networkFetchContext(deps, leases.proxySlot?.poolId, authorization.tenantId),
|
|
178
161
|
});
|
|
179
162
|
|
|
180
163
|
// A count is free and generates nothing, so nothing is reserved or
|
|
@@ -221,20 +204,40 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
|
|
|
221
204
|
*
|
|
222
205
|
* No attempt is finalized: nothing was leased, nothing was sent upstream, and
|
|
223
206
|
* no provider produced this number, so recording it against an account would
|
|
224
|
-
* invent usage the account never served. The request still
|
|
225
|
-
*
|
|
226
|
-
*
|
|
207
|
+
* invent usage the account never served. The request is still finalized through
|
|
208
|
+
* `finalizeRequestTelemetry` (the early-rejection pattern) — without it the
|
|
209
|
+
* latency histogram, the `request_complete` console event, and the durable
|
|
210
|
+
* telemetry row all miss this request — and `state.completed` prevents the
|
|
211
|
+
* `afterResponse` fallback from finalizing it a second time.
|
|
227
212
|
*/
|
|
228
213
|
function estimatedCountResponse(
|
|
229
214
|
state: ProxyRequestState,
|
|
230
215
|
countBody: Record<string, unknown>,
|
|
216
|
+
telemetryBuffer: TelemetryBatchBuffer | undefined,
|
|
217
|
+
db: GatewayHandlerDeps["db"],
|
|
231
218
|
): Response {
|
|
232
219
|
const inputTokens = estimateMessagesInputTokens(countBody);
|
|
233
220
|
const payload = JSON.stringify({ input_tokens: inputTokens });
|
|
234
|
-
|
|
221
|
+
// The request still gets an outcome — without one the `afterResponse` hook
|
|
222
|
+
// would finalize it as a failure, and a count that answered 200 would show up
|
|
223
|
+
// in Console Log as an error. `usage` is a zero-priced estimate: nothing was
|
|
224
|
+
// leased and no provider produced this number, so recording a cost against an
|
|
225
|
+
// account would invent usage the account never served (`estimated_cost: null`).
|
|
226
|
+
state.outcome = {
|
|
227
|
+
status: "completed",
|
|
228
|
+
httpStatus: 200,
|
|
229
|
+
usage: estimatedUsage(inputTokens, 0),
|
|
230
|
+
};
|
|
235
231
|
state.completed = true;
|
|
236
232
|
state.clientResponseText = payload;
|
|
237
233
|
metrics.proxy_requests_total.inc(1, { status: "completed" });
|
|
234
|
+
// Finalize telemetry inline (the early-rejection pattern) instead of routing
|
|
235
|
+
// through `completeAttempt`: there was no lease and no provider — but the
|
|
236
|
+
// latency histogram, the `request_complete` console event, and the durable
|
|
237
|
+
// row must still happen, exactly once. `completed = true` also disables the
|
|
238
|
+
// `afterResponse` fallback, which is what used to make this request vanish
|
|
239
|
+
// from telemetry entirely.
|
|
240
|
+
if (telemetryBuffer) finalizeRequestTelemetry(state, telemetryBuffer, db);
|
|
238
241
|
return new Response(payload, {
|
|
239
242
|
headers: {
|
|
240
243
|
"content-type": "application/json",
|
|
@@ -255,7 +258,7 @@ function estimatedCountResponse(
|
|
|
255
258
|
* `/v1/messages?beta=true/count_tokens` — the counting segment lands inside the
|
|
256
259
|
* query, so the upstream sees the chat path with junk after it and never counts.
|
|
257
260
|
*/
|
|
258
|
-
|
|
261
|
+
function joinCountTokensPath(endpoint: string): string {
|
|
259
262
|
const queryAt = endpoint.indexOf("?");
|
|
260
263
|
const path = queryAt === -1 ? endpoint : endpoint.slice(0, queryAt);
|
|
261
264
|
const query = queryAt === -1 ? "" : endpoint.slice(queryAt);
|
|
@@ -16,51 +16,44 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { ProviderAdapter, ProviderDispatchTarget } from "../../providers/provider-contracts";
|
|
18
18
|
import { GatewayError } from "../../protocol/gateway-error";
|
|
19
|
-
import { resolveCredentialForAccount } from "../../providers/operations/provider-credential-service";
|
|
20
19
|
import { metrics } from "../../observability/metrics";
|
|
21
|
-
import { parseThinkingSuffix } from "../../protocol/translation/thinking";
|
|
22
20
|
import { completeAttempt, estimatedUsage, nativeCompletionBase } from "./attempt-finalize";
|
|
23
21
|
import { runAttemptLoop } from "./attempt-loop";
|
|
22
|
+
import {
|
|
23
|
+
hasScope,
|
|
24
|
+
jsonResponse,
|
|
25
|
+
networkFetchContext,
|
|
26
|
+
requireRecordBody,
|
|
27
|
+
resolveAttemptCredential,
|
|
28
|
+
stripThinkingSuffix,
|
|
29
|
+
} from "./native-shared";
|
|
24
30
|
|
|
25
31
|
import type { GatewayHandlerDeps } from "./handler-deps";
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
type WebsearchHandlerDeps = GatewayHandlerDeps;
|
|
30
34
|
|
|
31
35
|
/** Adapter that can serve a web-search request. */
|
|
32
36
|
type WebsearchAdapter = ProviderAdapter & {
|
|
33
37
|
websearch: NonNullable<ProviderAdapter["websearch"]>;
|
|
34
38
|
};
|
|
35
39
|
|
|
36
|
-
function jsonResponse(payload: unknown, status: number, requestId: string): Response {
|
|
37
|
-
return new Response(JSON.stringify(payload), {
|
|
38
|
-
status,
|
|
39
|
-
headers: {
|
|
40
|
-
"content-type": "application/json",
|
|
41
|
-
"cache-control": "no-store",
|
|
42
|
-
"x-request-id": requestId,
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
40
|
export function createWebsearchHandler(deps: WebsearchHandlerDeps) {
|
|
48
41
|
return async ({ request }: { request: Request }): Promise<Response> => {
|
|
49
42
|
const state = deps.stateStore.require(request);
|
|
50
43
|
const authorization = state.authorization;
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
44
|
+
const search = requireRecordBody(state, "The search body must be a JSON object (request {requestId}). Send one with at least a model and a query. Check the shape of what you sent — then resend 🪞");
|
|
45
|
+
// `requireRecordBody` already refused a missing authorization; this guard
|
|
46
|
+
// exists only to narrow the type for the code below.
|
|
47
|
+
if (!authorization) {
|
|
48
|
+
throw new GatewayError("invalid_request", 400, "authentication required (request {requestId})");
|
|
49
|
+
}
|
|
55
50
|
if (typeof search.model !== "string" || search.model.trim().length === 0)
|
|
56
51
|
throw new GatewayError("invalid_request", 400, "A model is required (request {requestId}): it names the search provider to use. Check the shape of what you sent — then resend 🪞");
|
|
57
52
|
// The search "model" names the provider (or an alias/combo of one). A
|
|
58
53
|
// thinking suffix is stripped for the same reason the canonical preparer
|
|
59
54
|
// strips it: it is not part of any registered id.
|
|
60
|
-
const {
|
|
61
|
-
|
|
62
|
-
bareModel === search.model ? search : { ...search, model: bareModel };
|
|
63
|
-
if (!authorization.snapshot.scopes?.includes("search:invoke" as never)) {
|
|
55
|
+
const { bareModel, body: searchBody } = stripThinkingSuffix(search.model, search);
|
|
56
|
+
if (!hasScope(authorization?.snapshot.scopes, "search:invoke")) {
|
|
64
57
|
throw new GatewayError("policy_rejected", 403, "search not allowed for this key (request {requestId}) — enable search:invoke on the API key. Not allowed by policy — no hard feelings 🫂");
|
|
65
58
|
}
|
|
66
59
|
// Trust boundary only: the query must be a non-empty string. The adapter
|
|
@@ -91,19 +84,7 @@ export function createWebsearchHandler(deps: WebsearchHandlerDeps) {
|
|
|
91
84
|
strictPoolSelection: false,
|
|
92
85
|
exhaustedError: new GatewayError("route_unavailable", 503, "no eligible search route (request {requestId}). The bouncer stepped out — come back shortly 🚪"),
|
|
93
86
|
prepare: async (candidate) => {
|
|
94
|
-
const credential = candidate
|
|
95
|
-
? await resolveCredentialForAccount(
|
|
96
|
-
deps.db,
|
|
97
|
-
candidate.provider_id,
|
|
98
|
-
candidate.provider_account_id,
|
|
99
|
-
deps.oauthRefreshService && deps.resolveOAuthRefresher
|
|
100
|
-
? { refreshService: deps.oauthRefreshService, resolveRefresher: deps.resolveOAuthRefresher }
|
|
101
|
-
: undefined,
|
|
102
|
-
)
|
|
103
|
-
: {
|
|
104
|
-
provider_id: candidate.provider_id as ProviderAdapter["provider_id"],
|
|
105
|
-
credential_kind: "none" as const,
|
|
106
|
-
};
|
|
87
|
+
const credential = await resolveAttemptCredential(deps, candidate);
|
|
107
88
|
const adapter = (await deps.resolveProviderAdapter?.(candidate.provider_id)) as WebsearchAdapter | undefined;
|
|
108
89
|
if (!adapter || typeof adapter.websearch !== "function")
|
|
109
90
|
throw new GatewayError(
|
|
@@ -132,14 +113,7 @@ export function createWebsearchHandler(deps: WebsearchHandlerDeps) {
|
|
|
132
113
|
deadline: state.deadlineMs,
|
|
133
114
|
abort_signal: state.abortController.signal,
|
|
134
115
|
...(candidate.user_agent === undefined ? {} : { user_agent: candidate.user_agent }),
|
|
135
|
-
...(deps.
|
|
136
|
-
? {
|
|
137
|
-
outbound_fetch: deps.networkBindingFactory.fetch(
|
|
138
|
-
leases.proxySlot?.poolId,
|
|
139
|
-
authorization.tenantId,
|
|
140
|
-
),
|
|
141
|
-
}
|
|
142
|
-
: {}),
|
|
116
|
+
...networkFetchContext(deps, leases.proxySlot?.poolId, authorization.tenantId),
|
|
143
117
|
});
|
|
144
118
|
const payload = {
|
|
145
119
|
provider: candidate.provider_id,
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import { timingSafeEqual } from "node:crypto";
|
|
21
21
|
import { GatewayError } from "../protocol/gateway-error";
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
interface DrainEndpointDeps {
|
|
24
24
|
/** Shared secret; the route is registered only when this is set. */
|
|
25
25
|
readonly token: string;
|
|
26
26
|
/** Begins the graceful drain (the process's own shutdown path). */
|
|
@@ -656,6 +656,12 @@ const QUOTA_ERROR_IDENTIFIERS: ReadonlySet<string> = new Set([
|
|
|
656
656
|
// it is a spend/limit verdict, never an auth one, so it must classify as
|
|
657
657
|
// quota (a cooldown) instead of `auth_invalidated`.
|
|
658
658
|
"110",
|
|
659
|
+
// Qoder also reports quota exhaustion as `{"code":116,"error":"quota
|
|
660
|
+
// exceeded"}` (seen on the IDE model-server surface with a 499/stream
|
|
661
|
+
// close). The 499 special-case below already treats qoder 499 as quota;
|
|
662
|
+
// naming the code here makes any status that carries it classify as quota
|
|
663
|
+
// instead of `invalid_request`/`authentication_failed`.
|
|
664
|
+
"116",
|
|
659
665
|
// InferHub publishes its own vocabulary in `error.code`. The two 403s below
|
|
660
666
|
// are deliberate absences: `account_blocked` means the account was blocked
|
|
661
667
|
// (an auth/entitlement verdict, not a spend limit) and
|
|
@@ -914,7 +920,21 @@ export async function mapUpstreamHttpError(response: Response, providerId: strin
|
|
|
914
920
|
? { code: "context_length_exceeded" as const, status: 413, origin: "upstream" as const }
|
|
915
921
|
: classifyUpstreamError(response.status, identifier);
|
|
916
922
|
const extracted = extractUpstreamMessage(parsedBody ?? text);
|
|
917
|
-
|
|
923
|
+
let message =
|
|
924
|
+
extracted.length > 0
|
|
925
|
+
? extracted
|
|
926
|
+
: text.trim() || `Provider returned HTTP ${response.status}.`;
|
|
927
|
+
// A bare "Provider returned HTTP <5xx>" tells the operator nothing actionable;
|
|
928
|
+
// platform-unavailable fallbacks get the same human sign-off every other
|
|
929
|
+
// gateway error carries. The raw upstream body stays in `details.raw` for
|
|
930
|
+
// diagnostics either way.
|
|
931
|
+
if (
|
|
932
|
+
classification.code === "platform_unavailable" &&
|
|
933
|
+
message === `Provider returned HTTP ${response.status}.`
|
|
934
|
+
) {
|
|
935
|
+
message = `The provider's service is temporarily unavailable (HTTP ${response.status}) (request {requestId}). Your request did not go through — wait a moment and try again. Hold tight ☁️`;
|
|
936
|
+
}
|
|
937
|
+
message = message.slice(0, MAX_UPSTREAM_ERROR_BYTES).replace(/[\r\n]+/g, " ");
|
|
918
938
|
const headerWaitMs = parseUpstreamBackoff(response.headers);
|
|
919
939
|
// A queue carries its own interval, which is the one the caller must honour:
|
|
920
940
|
// it is what the upstream promised, while `Retry-After` may be absent or may
|
|
@@ -20,12 +20,6 @@ import { createAccessDecision } from "../../security/access-control";
|
|
|
20
20
|
export function createApiKeyAuthenticationMiddleware(deps: {
|
|
21
21
|
readonly db: CartethyiaDatabase;
|
|
22
22
|
readonly stateStore: ProxyRequestStateStore;
|
|
23
|
-
/**
|
|
24
|
-
* Model-abuse ban gate. A banned IP or key is refused here, before the
|
|
25
|
-
* canonical parse and route preparation, and before `state.authorization` is
|
|
26
|
-
* assigned — so a banned caller's attempts produce no telemetry row and no
|
|
27
|
-
* console error, which is exactly the noise this layer exists to stop.
|
|
28
|
-
*/
|
|
29
23
|
/**
|
|
30
24
|
* Tenant invoke kill switch. Checked after the key resolves, because the
|
|
31
25
|
* tenant is a property of the key: the same gateway serves several tenants
|