cartethyia 2.7.2 → 2.7.4
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 +2 -0
- package/dist/dashboard/assets/{BannedPage-XaUmiBrC.js → BannedPage-ChG5svFb.js} +1 -1
- package/dist/dashboard/assets/BannedPage-ChG5svFb.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js +1 -0
- package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js +1 -0
- package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-CXGS_Ct-.js → CombosPage-DCgm5g6g.js} +1 -1
- package/dist/dashboard/assets/CombosPage-DCgm5g6g.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js.gz +0 -0
- package/dist/dashboard/assets/{HealthEventsModal-BKtAPYVF.js → HealthEventsModal-DzSWzE_j.js} +1 -1
- package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-BdmRLnq2.js → LoginPage-U6AKaJcF.js} +1 -1
- package/dist/dashboard/assets/LoginPage-U6AKaJcF.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BH8Tehdi.js → ModelPicker-BMS2t8BA.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-BMS2t8BA.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js +1 -0
- package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js +5 -0
- package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js +9 -0
- package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-L0skzML8.js +1 -0
- package/dist/dashboard/assets/QuotaPage-L0skzML8.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js +1 -0
- package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-f0DE1h0-.js +1 -0
- package/dist/dashboard/assets/SetupPage-f0DE1h0-.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-Dqf4zpS-.js → SortableList-ClzpwyET.js} +1 -1
- package/dist/dashboard/assets/SortableList-ClzpwyET.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-Byj2lAEm.js → StudioPage-Cq5tW7hW.js} +3 -3
- package/dist/dashboard/assets/StudioPage-Cq5tW7hW.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js +56 -0
- package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-C38iXoO4.js +1 -0
- package/dist/dashboard/assets/api-keys-C38iXoO4.js.gz +0 -0
- package/dist/dashboard/assets/app-a159k0ub.css +2 -0
- package/dist/dashboard/assets/app-a159k0ub.css.gz +0 -0
- package/dist/dashboard/assets/arrow-up-59ms1iiT.js +1 -0
- package/dist/dashboard/assets/arrow-up-59ms1iiT.js.gz +0 -0
- package/dist/dashboard/assets/assert-DbRA8EG2.js +15 -0
- package/dist/dashboard/assets/assert-DbRA8EG2.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DC2L43Mj.js → badge-D9CS6dZk.js} +1 -1
- package/dist/dashboard/assets/badge-D9CS6dZk.js.gz +0 -0
- package/dist/dashboard/assets/{banner-BBVcb0J3.js → banner-DympBNq4.js} +1 -1
- package/dist/dashboard/assets/banner-DympBNq4.js.gz +0 -0
- package/dist/dashboard/assets/{card-Cca1XC6G.js → card-ibRWU_uT.js} +1 -1
- package/dist/dashboard/assets/card-ibRWU_uT.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-63qBGaRw.js +1 -0
- package/dist/dashboard/assets/cli-tools-63qBGaRw.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-DUzEbynq.js → clipboard-button-DJm-9pz7.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-DJm-9pz7.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-DFvWhwTa.js → dialog-8fiE6tec.js} +1 -1
- package/dist/dashboard/assets/dialog-8fiE6tec.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CqGYeOrd.js +1 -0
- package/dist/dashboard/assets/drawer-CqGYeOrd.js.gz +0 -0
- package/dist/dashboard/assets/{entry-Bqy8RFvM.js → entry-BSAsYsmV.js} +1 -1
- package/dist/dashboard/assets/entry-BSAsYsmV.js.gz +0 -0
- package/dist/dashboard/assets/entry-CiIQULZD.js +4 -0
- package/dist/dashboard/assets/entry-CiIQULZD.js.gz +0 -0
- package/dist/dashboard/assets/entry-Daz0lJ-W.js +1 -0
- package/dist/dashboard/assets/entry-Daz0lJ-W.js.gz +0 -0
- package/dist/dashboard/assets/{concurrency-BDF0ok7y.js → fingerprint-pattern-BWuHtbW4.js} +1 -1
- package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-CWWZ-uCZ.js → formatters-D-MTJxMr.js} +1 -1
- package/dist/dashboard/assets/formatters-D-MTJxMr.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-C9e8Ts7e.js → github-badge-CPK47xQx.js} +1 -1
- package/dist/dashboard/assets/github-badge-CPK47xQx.js.gz +0 -0
- package/dist/dashboard/assets/{index-DFVFrnWW.js → index-DBNPOfhV.js} +2 -2
- package/dist/dashboard/assets/index-DBNPOfhV.js.gz +0 -0
- package/dist/dashboard/assets/{inline-BFUcvllj.js → inline-BT0zntEr.js} +1 -1
- package/dist/dashboard/assets/inline-BT0zntEr.js.gz +0 -0
- package/dist/dashboard/assets/{input-C_ILAvBb.js → input-Da2ZViWg.js} +1 -1
- package/dist/dashboard/assets/input-Da2ZViWg.js.gz +0 -0
- package/dist/dashboard/assets/{layout-CBIyT6Rt.js → layout-BXNeLw6M.js} +1 -1
- package/dist/dashboard/assets/layout-BXNeLw6M.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-ebJ_-Nsb.js → list-row-BycOrrIV.js} +1 -1
- package/dist/dashboard/assets/list-row-BycOrrIV.js.gz +0 -0
- package/dist/dashboard/assets/live-DCQNZpF7.js +1 -0
- package/dist/dashboard/assets/live-DCQNZpF7.js.gz +0 -0
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +1 -0
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-sMD965oO.js → mutation-Rr0TIvcj.js} +1 -1
- package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
- package/dist/dashboard/assets/network-9Yj2ZyLo.js +1 -0
- package/dist/dashboard/assets/network-9Yj2ZyLo.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BO9dKaFr.js → numbers-CEagj6ZZ.js} +2 -2
- package/dist/dashboard/assets/numbers-CEagj6ZZ.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-CfL_Zdoh.js → query-keys-BloKtaXJ.js} +1 -1
- package/dist/dashboard/assets/query-keys-BloKtaXJ.js.gz +0 -0
- package/dist/dashboard/assets/quota-Caw7wqci.js +1 -0
- package/dist/dashboard/assets/quota-Caw7wqci.js.gz +0 -0
- package/dist/dashboard/assets/routing-CO9zCUus.js +1 -0
- package/dist/dashboard/assets/routing-CO9zCUus.js.gz +0 -0
- package/dist/dashboard/assets/{select-DBNVLdUu.js → select-BIYIBkMU.js} +2 -2
- package/dist/dashboard/assets/select-BIYIBkMU.js.gz +0 -0
- package/dist/dashboard/assets/settings-2-Ds9DQp0t.js +1 -0
- package/dist/dashboard/assets/settings-2-Ds9DQp0t.js.gz +0 -0
- package/dist/dashboard/assets/settings-jS3-PNSA.js +1 -0
- package/dist/dashboard/assets/settings-jS3-PNSA.js.gz +0 -0
- package/dist/dashboard/assets/{stack-EMrOMt4I.js → stack-DfgnFDWY.js} +1 -1
- package/dist/dashboard/assets/stack-DfgnFDWY.js.gz +0 -0
- package/dist/dashboard/assets/{state-CWoo7ty5.js → state-CkfdYWel.js} +1 -1
- package/dist/dashboard/assets/state-CkfdYWel.js.gz +0 -0
- package/dist/dashboard/assets/{switch-CCjT6IkF.js → switch-SYm237HG.js} +1 -1
- package/dist/dashboard/assets/switch-SYm237HG.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-DjkLHUcE.js → useMutation-BQqCRNIi.js} +1 -1
- package/dist/dashboard/assets/useMutation-BQqCRNIi.js.gz +0 -0
- package/dist/dashboard/index.html +3 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +118 -115
- package/migrations/full-postgres/0026_add_qoder_native_identity.sql +20 -0
- package/migrations/full-postgres/0027_qoder_identity_genuine_seed.sql +21 -0
- package/migrations/full-postgres/0028_add_family_template_key_telemetry.sql +40 -0
- package/migrations/full-postgres/0029_null_share_child_policy_columns.sql +38 -0
- package/migrations/full-postgres/0030_add_api_key_max_children.sql +10 -0
- package/migrations/full-postgres/0031_tenant_system_prompts.sql +16 -0
- package/migrations/full-postgres/0032_add_provider_pool_accounts.sql +53 -0
- package/migrations/full-postgres/0033_add_api_key_ban.sql +9 -0
- package/migrations/full-postgres/0034_add_api_key_ban_until.sql +7 -0
- package/migrations/full-postgres/0035_align_pool_account_status_enum.sql +21 -0
- package/migrations/lite-sqlite3/0026_add_qoder_native_identity.sql +15 -0
- package/migrations/lite-sqlite3/0027_qoder_identity_genuine_seed.sql +21 -0
- package/migrations/lite-sqlite3/0028_add_family_template_key_telemetry.sql +40 -0
- package/migrations/lite-sqlite3/0029_null_share_child_policy_columns.sql +38 -0
- package/migrations/lite-sqlite3/0030_add_api_key_max_children.sql +10 -0
- package/migrations/lite-sqlite3/0031_tenant_system_prompts.sql +10 -0
- package/migrations/lite-sqlite3/0032_add_provider_pool_accounts.sql +44 -0
- package/migrations/lite-sqlite3/0033_add_api_key_ban.sql +8 -0
- package/migrations/lite-sqlite3/0034_add_api_key_ban_until.sql +5 -0
- package/migrations/lite-sqlite3/0035_align_pool_account_status_enum.sql +6 -0
- package/package.json +2 -1
- package/src/boot/app.ts +9 -8
- package/src/config/network.ts +7 -3
- package/src/console/api-keys/bucket-spend.ts +7 -7
- package/src/console/api-keys/contracts.ts +475 -410
- package/src/console/api-keys/routes.ts +211 -23
- package/src/console/audit/routes.ts +3 -3
- package/src/console/auth/account-reset.ts +4 -4
- package/src/console/auth/session.ts +3 -3
- package/src/console/backup/contracts.ts +2 -0
- package/src/console/backup/export-endpoint.ts +14 -17
- package/src/console/backup/routes.ts +43 -11
- package/src/console/backup/service.ts +11 -6
- package/src/console/backup/store.ts +384 -88
- package/src/console/cli-tools/routes.ts +3 -3
- package/src/console/console-router-guards.ts +2 -2
- package/src/console/console-router.ts +4 -4
- package/src/console/dashboard-assets.ts +16 -1
- package/src/console/data-management/routes.ts +3 -3
- package/src/console/domain-registration.ts +4 -2
- package/src/console/observability/contracts.ts +4 -0
- package/src/console/observability/live.ts +3 -3
- package/src/console/observability/logs.ts +3 -3
- package/src/console/observability/performance.ts +3 -3
- package/src/console/observability/routes.ts +3 -3
- package/src/console/observability/store.ts +16 -16
- package/src/console/providers/catalog/contracts.ts +43 -22
- package/src/console/providers/catalog/provider-operations.ts +17 -2
- package/src/console/providers/catalog/routes.ts +3 -3
- package/src/console/providers/catalog/store-helpers.ts +349 -326
- package/src/console/providers/catalog/store.ts +1523 -1292
- package/src/console/providers/detail/routes.ts +3 -3
- package/src/console/providers/oauth/routes.ts +3 -3
- package/src/console/quota/account-quota-tenant.ts +34 -2
- package/src/console/quota/account-quota.ts +4 -1
- package/src/console/quota/account-test-egress.ts +4 -4
- package/src/console/quota/native-identity.ts +189 -0
- package/src/console/remote-support/contracts.ts +12 -6
- package/src/console/remote-support/service.ts +2 -2
- package/src/console/routing/model/routes.ts +3 -3
- package/src/console/routing/pools/contracts.ts +11 -8
- package/src/console/routing/pools/operations.ts +14 -7
- package/src/console/routing/pools/probe-verdict.ts +8 -3
- package/src/console/routing/pools/relay-deploy.ts +9 -9
- package/src/console/routing/pools/routes.ts +3 -3
- package/src/console/routing/pools/store.ts +21 -1
- package/src/console/settings/routes.ts +3 -3
- package/src/console/share/share-router.ts +51 -18
- package/src/console/share/share-stats.ts +158 -49
- package/src/console/share/share-usage.ts +24 -7
- package/src/console/shared/read-through-cache.ts +38 -2
- package/src/console/system-prompt/contracts.ts +102 -0
- package/src/console/system-prompt/routes.ts +165 -0
- package/src/console/updates/routes.ts +7 -4
- package/src/main.ts +11 -0
- package/src/network/outbound-fetch.ts +75 -4
- package/src/network/pool/resolver.ts +39 -39
- package/src/network/pool-cooldown-store.ts +71 -16
- package/src/network/pool-health-machine.ts +21 -1
- package/src/network/rate-limit-verdict.ts +68 -0
- package/src/network/ssrf.ts +4 -4
- package/src/observability/event-query.ts +437 -22
- package/src/observability/telemetry-status.ts +1 -1
- package/src/persistence/db-pg.ts +42 -0
- package/src/persistence/db-sqlite.ts +7 -2
- package/src/persistence/readiness.ts +18 -6
- package/src/persistence/schema.ts +162 -1
- package/src/persistence/stores/api-key-store.ts +849 -581
- package/src/persistence/stores/share-store.ts +65 -13
- package/src/persistence/stores/telemetry-store.ts +58 -2
- package/src/persistence/system-prompt-store.ts +104 -0
- package/src/protocol/codecs.ts +6 -6
- package/src/protocol/gateway-error.ts +6 -3
- package/src/protocol/image-generation.ts +2 -2
- package/src/protocol/reasoning.ts +65 -1
- package/src/protocol/request/chat.ts +1 -0
- package/src/protocol/response/chat.ts +2 -2
- package/src/protocol/response/codex.ts +1 -1
- package/src/protocol/response/gemini.ts +1 -1
- package/src/protocol/response/messages.ts +5 -5
- package/src/protocol/response/responses.ts +2 -2
- package/src/protocol/sse-decoder.ts +14 -3
- package/src/protocol/translation/capabilities.ts +2 -2
- package/src/protocol/upstream/send-claude-messages.ts +3 -3
- package/src/providers/adapters/base-adapter.ts +1 -1
- package/src/providers/adapters/compatible-adapter.ts +2 -2
- package/src/providers/authentication/oauth-refresh-service.ts +2 -2
- package/src/providers/configured-provider.ts +1 -1
- package/src/providers/discovery/probe-phases.ts +2 -2
- package/src/providers/discovery/probing-service.ts +12 -12
- package/src/providers/integrations/agentrouter/agentrouter.ts +2 -3
- package/src/providers/integrations/alibabacp/alibabacp.ts +2 -2
- package/src/providers/integrations/anthropic.ts +4 -11
- package/src/providers/integrations/antigravity/antigravity.ts +33 -47
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -1
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +30 -2
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +37 -6
- package/src/providers/integrations/buddy/buddy-identity.ts +22 -1
- package/src/providers/integrations/buddy/buddy-media.ts +21 -17
- package/src/providers/integrations/buddy/codebuddy/provider.ts +6 -2
- package/src/providers/integrations/buddy/workbuddy/provider.ts +6 -2
- package/src/providers/integrations/claude/claude-betas.ts +1 -1
- package/src/providers/integrations/claude/claude-credentials.ts +1 -1
- package/src/providers/integrations/claude/claude-slow-lane-retry.ts +1 -1
- package/src/providers/integrations/claude/claude.ts +3 -10
- package/src/providers/integrations/cline/cline.ts +8 -1
- package/src/providers/integrations/cloudflareai/cloudflareai.ts +4 -7
- package/src/providers/integrations/codex/codex-errors.ts +1 -1
- package/src/providers/integrations/codex/codex.ts +14 -15
- package/src/providers/integrations/commandcode/commandcode.ts +4 -4
- package/src/providers/integrations/commandcode/quota.ts +5 -1
- package/src/providers/integrations/cursor/cursor-http2.ts +11 -11
- package/src/providers/integrations/cursor/cursor.ts +15 -15
- package/src/providers/integrations/factory/factory.ts +4 -4
- package/src/providers/integrations/gemini.ts +11 -11
- package/src/providers/integrations/github/github.ts +26 -7
- package/src/providers/integrations/grok/grok.ts +2 -6
- package/src/providers/integrations/hermes.ts +5 -1
- package/src/providers/integrations/inferhub.ts +4 -11
- package/src/providers/integrations/kilo/kilo.ts +4 -0
- package/src/providers/integrations/kimi/kimi.ts +4 -11
- package/src/providers/integrations/kiro/kiro.ts +10 -10
- package/src/providers/integrations/muse/muse.ts +6 -9
- package/src/providers/integrations/openrouter/provider.ts +4 -0
- package/src/providers/integrations/perplexity.ts +2 -2
- package/src/providers/integrations/qoder/qoder-catalog.ts +47 -9
- package/src/providers/integrations/qoder/qoder-checkin.ts +242 -51
- package/src/providers/integrations/qoder/qoder-native-identity-store.ts +157 -0
- package/src/providers/integrations/qoder/qoder-oauth.ts +12 -3
- package/src/providers/integrations/qoder/qoder-realm.ts +15 -0
- package/src/providers/integrations/qoder/qoder-umid.ts +287 -0
- package/src/providers/integrations/qoder/qoder-virtual.ts +2 -2
- package/src/providers/integrations/qoder/qoder.ts +192 -24
- package/src/providers/integrations/tokenharbor.ts +2 -2
- package/src/providers/integrations/trae/trae.ts +6 -9
- package/src/providers/integrations/zcode/oauth.ts +1 -1
- package/src/providers/operations/account-error-classification.ts +6 -8
- package/src/providers/operations/account-health-service.ts +14 -16
- package/src/providers/operations/client-versions.ts +37 -22
- package/src/providers/operations/provider-catalog-service.ts +4 -4
- package/src/providers/operations/provider-credential-service.ts +16 -4
- package/src/providers/operations/upstream-deadline.ts +3 -3
- package/src/providers/provider-contracts.ts +55 -3
- package/src/providers/provider-credential-resolver.ts +1 -1
- package/src/providers/provider-registry.ts +4 -4
- package/src/providers/quota/quota-contracts.ts +24 -10
- package/src/providers/quota/refresh.ts +2 -2
- package/src/providers/search/search-provider.ts +5 -5
- package/src/security/abuse.ts +4 -4
- package/src/security/access-control.ts +19 -0
- package/src/security/admission/reasons.ts +17 -17
- package/src/security/api-key-auth.ts +18 -5
- package/src/shared/coerce.ts +19 -0
- package/src/transport/dispatch/attempt-finalize.ts +4 -3
- package/src/transport/dispatch/attempt-loop.ts +10 -7
- package/src/transport/dispatch/empty-stream.ts +6 -6
- package/src/transport/dispatch/generation.ts +9 -9
- package/src/transport/dispatch/leases.ts +8 -8
- package/src/transport/dispatch/proxy-request.ts +38 -9
- package/src/transport/dispatch/responses-compact.ts +11 -11
- package/src/transport/dispatch/retry-policy.ts +4 -67
- package/src/transport/dispatch/stream-bridge.ts +1 -1
- package/src/transport/dispatch/streaming/finalize.ts +9 -8
- package/src/transport/dispatch/streaming/stream.ts +16 -1
- package/src/transport/dispatch/systemone.ts +8 -8
- package/src/transport/dispatch/token-count.ts +9 -9
- package/src/transport/dispatch/upstream.ts +10 -0
- package/src/transport/dispatch/websearch.ts +6 -6
- package/src/transport/drain-endpoint.ts +2 -2
- package/src/transport/failure-policy.ts +13 -6
- package/src/transport/guards/auth-guard.ts +6 -5
- package/src/transport/guards/body-policy.ts +4 -4
- package/src/transport/guards/error-lifecycle.ts +18 -3
- package/src/transport/guards/guard-errors.ts +7 -3
- package/src/transport/guards/headroom-guard.ts +1 -1
- package/src/transport/guards/pipeline.ts +6 -6
- package/src/transport/guards/readiness-guard.ts +16 -2
- package/src/transport/guards/request-context.ts +3 -3
- package/src/transport/request/preparer.ts +37 -21
- package/src/transport/request/state.ts +4 -7
- package/src/transport/request/system-prompt-injection.ts +89 -0
- package/src/transport/request/tenant-preferences.ts +43 -0
- package/src/transport/routing/admission.ts +13 -3
- package/src/transport/routing/route-catalog.ts +104 -2
- package/src/transport/routing/route-model.ts +60 -7
- package/src/transport/routing/router.ts +306 -256
- package/src/transport/shutdown-notice.ts +19 -7
- package/src/transport/surface/completion.ts +1 -1
- package/src/transport/surface/messages/encode.ts +1 -1
- package/src/workers/daily-checkin.ts +2 -0
- package/dist/dashboard/assets/BannedPage-XaUmiBrC.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-CqD7QiLM.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-CqD7QiLM.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-0XUo4UMn.js +0 -1
- package/dist/dashboard/assets/CliToolsPage-0XUo4UMn.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CXGS_Ct-.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DGnGNYnM.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-DGnGNYnM.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-DAG5J9EY.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-DAG5J9EY.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BKtAPYVF.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-BdmRLnq2.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BH8Tehdi.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CqMxibJB.js +0 -1
- package/dist/dashboard/assets/OverviewPage-CqMxibJB.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CqMh2NRX.js +0 -4
- package/dist/dashboard/assets/ProviderDetailPage-CqMh2NRX.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-B9uWu4Nz.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-B9uWu4Nz.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BoWOhpDO.js +0 -9
- package/dist/dashboard/assets/ProxyPage-BoWOhpDO.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-7P2n--Fv.js +0 -1
- package/dist/dashboard/assets/QuotaPage-7P2n--Fv.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CxZ1fTkN.js +0 -1
- package/dist/dashboard/assets/SettingsPage-CxZ1fTkN.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-Co12Zzaw.js +0 -1
- package/dist/dashboard/assets/SetupPage-Co12Zzaw.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-Dqf4zpS-.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-Byj2lAEm.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-CTkLAuRc.js +0 -56
- package/dist/dashboard/assets/UsagePage-CTkLAuRc.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-CUi6UXsR.js +0 -1
- package/dist/dashboard/assets/api-keys-CUi6UXsR.js.gz +0 -0
- package/dist/dashboard/assets/app-KvsUJUMF.css +0 -2
- package/dist/dashboard/assets/app-KvsUJUMF.css.gz +0 -0
- package/dist/dashboard/assets/assert-67xXH5rf.js +0 -1
- package/dist/dashboard/assets/assert-67xXH5rf.js.gz +0 -0
- package/dist/dashboard/assets/badge-DC2L43Mj.js.gz +0 -0
- package/dist/dashboard/assets/banner-BBVcb0J3.js.gz +0 -0
- package/dist/dashboard/assets/card-Cca1XC6G.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-KvsWa1Jl.js +0 -1
- package/dist/dashboard/assets/cli-tools-KvsWa1Jl.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DUzEbynq.js.gz +0 -0
- package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
- package/dist/dashboard/assets/dialog-DFvWhwTa.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CFM2sMX1.js +0 -1
- package/dist/dashboard/assets/drawer-CFM2sMX1.js.gz +0 -0
- package/dist/dashboard/assets/entry-Bqy8RFvM.js.gz +0 -0
- package/dist/dashboard/assets/entry-ClCMCWWQ.js +0 -4
- package/dist/dashboard/assets/entry-ClCMCWWQ.js.gz +0 -0
- package/dist/dashboard/assets/entry-_xHo5x6r.js +0 -1
- package/dist/dashboard/assets/entry-_xHo5x6r.js.gz +0 -0
- package/dist/dashboard/assets/formatters-CWWZ-uCZ.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-C9e8Ts7e.js.gz +0 -0
- package/dist/dashboard/assets/index-DFVFrnWW.js.gz +0 -0
- package/dist/dashboard/assets/inline-BFUcvllj.js.gz +0 -0
- package/dist/dashboard/assets/input-C_ILAvBb.js.gz +0 -0
- package/dist/dashboard/assets/layout-CBIyT6Rt.js.gz +0 -0
- package/dist/dashboard/assets/list-row-ebJ_-Nsb.js.gz +0 -0
- package/dist/dashboard/assets/live-D0mm--ME.js +0 -1
- package/dist/dashboard/assets/live-D0mm--ME.js.gz +0 -0
- package/dist/dashboard/assets/minimize-2-D-MN8W8a.js +0 -1
- package/dist/dashboard/assets/minimize-2-D-MN8W8a.js.gz +0 -0
- package/dist/dashboard/assets/mutation-sMD965oO.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BO9dKaFr.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-CfL_Zdoh.js.gz +0 -0
- package/dist/dashboard/assets/quota-DZGyp0N5.js +0 -1
- package/dist/dashboard/assets/quota-DZGyp0N5.js.gz +0 -0
- package/dist/dashboard/assets/routing-BnIpi1IJ.js +0 -1
- package/dist/dashboard/assets/routing-BnIpi1IJ.js.gz +0 -0
- package/dist/dashboard/assets/select-DBNVLdUu.js.gz +0 -0
- package/dist/dashboard/assets/settings-BAFhDgX8.js +0 -1
- package/dist/dashboard/assets/settings-BAFhDgX8.js.gz +0 -0
- package/dist/dashboard/assets/stack-EMrOMt4I.js.gz +0 -0
- package/dist/dashboard/assets/state-CWoo7ty5.js.gz +0 -0
- package/dist/dashboard/assets/switch-CCjT6IkF.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-DjkLHUcE.js.gz +0 -0
- /package/dist/dashboard/assets/{app-BaWd0puM.js → app-BayTTy01.js} +0 -0
- /package/dist/dashboard/assets/{app-BaWd0puM.js.gz → app-BayTTy01.js.gz} +0 -0
|
@@ -46,6 +46,17 @@ async function traeVersion(response: Response): Promise<string | null> {
|
|
|
46
46
|
/"insert":"((?:January|February|March|April|May|June|July|August|September|October|November|December)\s+\d{1,2},\s*\d{4}\s*\([^)]*\))\\n"/g;
|
|
47
47
|
for (const match of text.matchAll(heading)) {
|
|
48
48
|
const window = text.slice(match.index ?? 0, (match.index ?? 0) + 900);
|
|
49
|
+
// A release line may name a *range* ("TRAE v3.6.1 ~ 3.6.4 are released"),
|
|
50
|
+
// and the newest entry is the newest build in it. Taking the first number
|
|
51
|
+
// reported 3.6.1 while 3.6.4 was already shipping — and on an entry whose
|
|
52
|
+
// heading carried no version at all the old pattern matched nothing, so
|
|
53
|
+
// discovery fell through to the next (older) entry and the pin drifted
|
|
54
|
+
// backwards on every refresh.
|
|
55
|
+
const range = window.match(/(\d+\.\d+\.\d+)\s*[~–-]\s*(\d+\.\d+\.\d+)/);
|
|
56
|
+
if (range) {
|
|
57
|
+
const high = range[2] ?? range[1];
|
|
58
|
+
if (high && isSemverish(high)) return high.trim();
|
|
59
|
+
}
|
|
49
60
|
const found = window.match(/TraeCode\s+v?(\d+\.\d+\.\d+)|v?(\d+\.\d+\.\d+)\s+is\s+released/i);
|
|
50
61
|
const version = found?.[1] ?? found?.[2];
|
|
51
62
|
if (version && isSemverish(version)) return version.trim();
|
|
@@ -221,7 +232,7 @@ const KIRO_NODE_VERSION = "24.18.0";
|
|
|
221
232
|
export const VERSION_SOURCES = {
|
|
222
233
|
antigravity: {
|
|
223
234
|
key: "antigravity",
|
|
224
|
-
fallback: "2.
|
|
235
|
+
fallback: "2.28.2",
|
|
225
236
|
sources: [
|
|
226
237
|
{
|
|
227
238
|
url: "https://antigravity-hub-auto-updater-974169037036.us-central1.run.app/manifest/latest-arm64-mac.yml",
|
|
@@ -231,7 +242,7 @@ export const VERSION_SOURCES = {
|
|
|
231
242
|
},
|
|
232
243
|
qoder: {
|
|
233
244
|
key: "qoder",
|
|
234
|
-
fallback: "1.1.
|
|
245
|
+
fallback: "1.1.81",
|
|
235
246
|
sources: [
|
|
236
247
|
{
|
|
237
248
|
url: "https://registry.npmjs.org/@qoder-ai/qodercli/latest",
|
|
@@ -243,18 +254,21 @@ export const VERSION_SOURCES = {
|
|
|
243
254
|
key: "opencode",
|
|
244
255
|
// The v1 package (`opencode-ai`) stopped at 1.18. The v2 line publishes
|
|
245
256
|
// under `@opencode/cli`, and this feed is what its own installer reads.
|
|
246
|
-
fallback: "2.
|
|
257
|
+
fallback: "2.2.2",
|
|
247
258
|
minVersion: "2.0.0",
|
|
248
259
|
sources: [{ url: "https://opencode.ai/update/api/latest/cli/npm" }],
|
|
249
260
|
},
|
|
250
261
|
commandcode: {
|
|
251
262
|
key: "commandcode",
|
|
252
|
-
|
|
263
|
+
// npm `command-code` latest as of 2026-10-30. The CLI sends this as
|
|
264
|
+
// `x-command-code-version`, so a stale fallback presents a client build
|
|
265
|
+
// the upstream no longer ships — raise it whenever the package moves.
|
|
266
|
+
fallback: "1.88.2",
|
|
253
267
|
sources: [{ url: "https://registry.npmjs.org/command-code/latest" }],
|
|
254
268
|
},
|
|
255
269
|
grok: {
|
|
256
270
|
key: "grok",
|
|
257
|
-
fallback: "1.0.
|
|
271
|
+
fallback: "1.0.70",
|
|
258
272
|
sources: [
|
|
259
273
|
{ url: "https://storage.googleapis.com/grok-build-public-artifacts/cli/stable" },
|
|
260
274
|
{ url: "https://registry.npmjs.org/@xai-official/grok/latest" },
|
|
@@ -262,7 +276,7 @@ export const VERSION_SOURCES = {
|
|
|
262
276
|
},
|
|
263
277
|
clineClient: {
|
|
264
278
|
key: "cline-client",
|
|
265
|
-
fallback: "4.1.
|
|
279
|
+
fallback: "4.1.26",
|
|
266
280
|
minVersion: "4.1.23",
|
|
267
281
|
sources: [
|
|
268
282
|
{
|
|
@@ -273,22 +287,22 @@ export const VERSION_SOURCES = {
|
|
|
273
287
|
},
|
|
274
288
|
clineSdk: {
|
|
275
289
|
key: "cline-sdk",
|
|
276
|
-
fallback: "0.0
|
|
290
|
+
fallback: "0.2.0",
|
|
277
291
|
sources: [{ url: "https://registry.npmjs.org/@cline/sdk/latest", extract: clineSdkVersion }],
|
|
278
292
|
},
|
|
279
293
|
clineCli: {
|
|
280
294
|
key: "cline-cli",
|
|
281
|
-
fallback: "3.0.
|
|
295
|
+
fallback: "3.0.74",
|
|
282
296
|
sources: [{ url: "https://registry.npmjs.org/cline/latest", extract: clineSdkVersion }],
|
|
283
297
|
},
|
|
284
298
|
codex: {
|
|
285
299
|
key: "codex",
|
|
286
|
-
fallback: "0.
|
|
300
|
+
fallback: "0.166.0",
|
|
287
301
|
sources: [{ url: "https://registry.npmjs.org/@openai/codex/latest" }],
|
|
288
302
|
},
|
|
289
303
|
workbuddyClient: {
|
|
290
304
|
key: "workbuddy-client",
|
|
291
|
-
fallback: "5.
|
|
305
|
+
fallback: "5.9.4.42593613",
|
|
292
306
|
sources: [
|
|
293
307
|
{
|
|
294
308
|
url: "https://www.workbuddy.ai/v2/update?platform=workbuddy-win32-x64-user",
|
|
@@ -302,7 +316,7 @@ export const VERSION_SOURCES = {
|
|
|
302
316
|
},
|
|
303
317
|
workbuddyCnClient: {
|
|
304
318
|
key: "workbuddy-cn-client",
|
|
305
|
-
fallback: "5.
|
|
319
|
+
fallback: "5.9.3.42398134",
|
|
306
320
|
sources: [
|
|
307
321
|
{
|
|
308
322
|
url: "https://www.workbuddy.cn/v2/update?platform=workbuddy-win32-x64-user",
|
|
@@ -316,7 +330,7 @@ export const VERSION_SOURCES = {
|
|
|
316
330
|
},
|
|
317
331
|
workbuddyCli: {
|
|
318
332
|
key: "workbuddy",
|
|
319
|
-
fallback: "2.
|
|
333
|
+
fallback: "2.178.1",
|
|
320
334
|
sources: [
|
|
321
335
|
{ url: "https://registry.npmjs.org/@tencent-ai/codebuddy-code/latest" },
|
|
322
336
|
{ url: "https://registry.npmmirror.com/@tencent-ai/codebuddy-code/latest" },
|
|
@@ -329,7 +343,7 @@ export const VERSION_SOURCES = {
|
|
|
329
343
|
},
|
|
330
344
|
kiro: {
|
|
331
345
|
key: "kiro",
|
|
332
|
-
fallback: "1.
|
|
346
|
+
fallback: "1.4.62",
|
|
333
347
|
sources: [
|
|
334
348
|
{
|
|
335
349
|
// Kiro's downloads page exposes no stable version string. Homebrew
|
|
@@ -343,7 +357,7 @@ export const VERSION_SOURCES = {
|
|
|
343
357
|
},
|
|
344
358
|
codebuddy: {
|
|
345
359
|
key: "codebuddy",
|
|
346
|
-
fallback: "2.
|
|
360
|
+
fallback: "2.178.1",
|
|
347
361
|
sources: [
|
|
348
362
|
{ url: "https://registry.npmjs.org/@tencent-ai/codebuddy-code/latest" },
|
|
349
363
|
{ url: "https://registry.npmmirror.com/@tencent-ai/codebuddy-code/latest" },
|
|
@@ -366,7 +380,7 @@ export const VERSION_SOURCES = {
|
|
|
366
380
|
// Current `@anthropic-ai/claude-code` release, so the billing
|
|
367
381
|
// `cc_version=` suffix and the `claude-cli/` User-Agent stay on what
|
|
368
382
|
// upstream ships.
|
|
369
|
-
fallback: "2.1.
|
|
383
|
+
fallback: "2.1.314",
|
|
370
384
|
sources: [{ url: "https://registry.npmjs.org/@anthropic-ai/claude-code/latest" }],
|
|
371
385
|
},
|
|
372
386
|
claudeSdk: {
|
|
@@ -393,20 +407,21 @@ export const VERSION_SOURCES = {
|
|
|
393
407
|
},
|
|
394
408
|
kilo: {
|
|
395
409
|
key: "kilo-code",
|
|
396
|
-
fallback: "7.8.
|
|
410
|
+
fallback: "7.8.25",
|
|
397
411
|
sources: [{ url: "https://open-vsx.org/api/kilocode/kilo-code" }],
|
|
398
412
|
},
|
|
399
413
|
muse: {
|
|
400
414
|
key: "muse-code",
|
|
401
|
-
fallback: "1.4.
|
|
415
|
+
fallback: "1.4.14-R7309.1",
|
|
402
416
|
sources: [{ url: "https://api.meta.ai/muse-code/channels/muse-stable" }],
|
|
403
417
|
},
|
|
404
418
|
trae: {
|
|
405
419
|
key: "trae",
|
|
406
|
-
// Fallback = the
|
|
407
|
-
//
|
|
420
|
+
// Fallback = the newest release the changelog names, which is the *high*
|
|
421
|
+
// end of its newest line ("TRAE v3.6.1 ~ 3.6.4"). Discovery replaces it
|
|
422
|
+
// within one monitor tick; it is only what a cold process sends before
|
|
408
423
|
// the first successful fetch.
|
|
409
|
-
fallback: "3.6.
|
|
424
|
+
fallback: "3.6.4",
|
|
410
425
|
sources: [
|
|
411
426
|
{
|
|
412
427
|
url: "https://docs.trae.ai/ide/changelog?_lang=en",
|
|
@@ -419,7 +434,7 @@ export const VERSION_SOURCES = {
|
|
|
419
434
|
// Fallback = the last observed wire version. Discovery replaces it within
|
|
420
435
|
// one monitor tick; it is only what a cold process sends before the first
|
|
421
436
|
// successful fetch.
|
|
422
|
-
fallback: "cli-
|
|
437
|
+
fallback: "cli-3.27.22-b1aa50c",
|
|
423
438
|
sources: [
|
|
424
439
|
{
|
|
425
440
|
url: "https://cursor.com/api/download?platform=win32-x64-user&releaseTrack=stable",
|
|
@@ -433,7 +448,7 @@ export const VERSION_SOURCES = {
|
|
|
433
448
|
// comment this replaced claimed no semver existed and pinned a date tag
|
|
434
449
|
// (`v2026.9.24`) as the version — a tag, not a release, and not the
|
|
435
450
|
// identity the client sends.
|
|
436
|
-
fallback: "0.22.
|
|
451
|
+
fallback: "0.22.5",
|
|
437
452
|
sources: [
|
|
438
453
|
{
|
|
439
454
|
url: "https://api.github.com/repos/NousResearch/hermes-agent/releases/latest",
|
|
@@ -290,10 +290,10 @@ export async function registerByokProviders(
|
|
|
290
290
|
try {
|
|
291
291
|
providerId = parseProviderId(row.id);
|
|
292
292
|
} catch {
|
|
293
|
-
throw new GatewayError("invalid_request", 400, `invalid BYOK provider ID: ${row.id}
|
|
293
|
+
throw new GatewayError("invalid_request", 400, `invalid BYOK provider ID: ${row.id} (request {requestId}). Check the shape of what you sent — then resend 🪞`);
|
|
294
294
|
}
|
|
295
295
|
if (isBundledProviderId(providerId)) {
|
|
296
|
-
throw new GatewayError("invalid_request", 409, `BYOK provider ID collides with a built-in: ${row.id}
|
|
296
|
+
throw new GatewayError("invalid_request", 409, `BYOK provider ID collides with a built-in: ${row.id} (request {requestId}). This name's taken — pick another alias ✨`, {
|
|
297
297
|
provider_id: providerId,
|
|
298
298
|
});
|
|
299
299
|
}
|
|
@@ -309,14 +309,14 @@ export async function registerByokProviders(
|
|
|
309
309
|
}
|
|
310
310
|
} catch (error) {
|
|
311
311
|
if (error instanceof GatewayError) throw error;
|
|
312
|
-
throw new GatewayError("invalid_request", 400, `invalid BYOK base URL for provider ${row.id}
|
|
312
|
+
throw new GatewayError("invalid_request", 400, `invalid BYOK base URL for provider ${row.id} (request {requestId}). Check the shape of what you sent — then resend 🪞`);
|
|
313
313
|
}
|
|
314
314
|
// IP literals are rejected at registration; DNS names are revalidated at dispatch.
|
|
315
315
|
if (isIP(parsedUrl.hostname) !== 0 && !isAddressAllowed(parsedUrl.hostname, ssrfPolicy)) {
|
|
316
316
|
throw new GatewayError(
|
|
317
317
|
"invalid_request",
|
|
318
318
|
400,
|
|
319
|
-
`BYOK provider ${row.id} host is blocked (private/unsafe address)
|
|
319
|
+
`BYOK provider ${row.id} host is blocked (private/unsafe address) (request {requestId}). All roads blocked — time to find a new route 🗺️`,
|
|
320
320
|
);
|
|
321
321
|
}
|
|
322
322
|
const defaultPort = parsedUrl.protocol === "http:" ? 80 : 443;
|
|
@@ -9,8 +9,20 @@ import type { OAuthRefreshService, OAuthTokenRefresher } from "../authentication
|
|
|
9
9
|
import { CredentialResolver, type CredentialAlternative } from "../provider-credential-resolver";
|
|
10
10
|
import { parseProviderId, type CredentialKind } from "../provider-contracts";
|
|
11
11
|
import type { ResolvedCredential } from "../provider-contracts";
|
|
12
|
-
import { record } from "../authentication/oauth-flow-store";
|
|
13
12
|
import { refreshLeadMs } from "./oauth-refresh-lead";
|
|
13
|
+
import { asJsonbRecord } from "../../shared/coerce";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The `auth_state` JSONB column arrives as a POJO from Postgres (drizzle
|
|
17
|
+
* jsonb) but as a JSON STRING from SQLite (the native column stores text).
|
|
18
|
+
* The resolver's `record()` helper rejects strings, so a SQLite read dropped
|
|
19
|
+
* the auth state entirely — Qoder dispatch then saw no userId/machineId and
|
|
20
|
+
* failed with 401 "missing its user identity". Parse the string form once
|
|
21
|
+
* here so every backend hands the resolver the same POJO shape.
|
|
22
|
+
*/
|
|
23
|
+
function jsonbRecord(value: unknown): Record<string, unknown> | undefined {
|
|
24
|
+
return asJsonbRecord(value);
|
|
25
|
+
}
|
|
14
26
|
|
|
15
27
|
// ── Credential cache ─────────────────────────────────────────────────────────
|
|
16
28
|
/**
|
|
@@ -229,7 +241,7 @@ export async function resolveCredentialForAccount(
|
|
|
229
241
|
throw new GatewayError(
|
|
230
242
|
"authentication_failed",
|
|
231
243
|
401,
|
|
232
|
-
"provider credential cannot be decrypted; re-save the account credential",
|
|
244
|
+
"provider credential cannot be decrypted (request {requestId}); re-save the account credential. Wrong key, right door — check the credential 🔑",
|
|
233
245
|
{
|
|
234
246
|
providerId: row.providerId,
|
|
235
247
|
accountId: row.id,
|
|
@@ -253,7 +265,7 @@ export async function resolveCredentialForAccount(
|
|
|
253
265
|
}
|
|
254
266
|
}
|
|
255
267
|
|
|
256
|
-
const authState =
|
|
268
|
+
const authState = jsonbRecord(row.authState);
|
|
257
269
|
const alternative: CredentialAlternative = {
|
|
258
270
|
provider_id: parseProviderId(row.providerId),
|
|
259
271
|
account_id: row.id,
|
|
@@ -319,7 +331,7 @@ export async function resolveAccountCredentialsForExport(
|
|
|
319
331
|
await oauth.refreshService.ensureFreshAccessToken(accountId, refresher);
|
|
320
332
|
account = await loadAccountWithFreshness(db, accountId);
|
|
321
333
|
if (!account) {
|
|
322
|
-
throw new GatewayError("admission_unavailable", 503, "This provider account no longer exists, so its credential cannot be read. Re-select the account, or point the route at a live one.", {
|
|
334
|
+
throw new GatewayError("admission_unavailable", 503, "This provider account no longer exists, so its credential cannot be read (request {requestId}). Re-select the account, or point the route at a live one. The account went cold — reconnect or resend 🔌", {
|
|
323
335
|
provider_id: providerId,
|
|
324
336
|
account_id: accountId,
|
|
325
337
|
});
|
|
@@ -17,7 +17,7 @@ export function createUpstreamDeadlineLifecycle(
|
|
|
17
17
|
new GatewayError(
|
|
18
18
|
"deadline_exceeded",
|
|
19
19
|
504,
|
|
20
|
-
"upstream request deadline exceeded",
|
|
20
|
+
"upstream request deadline exceeded (request {requestId}). Some things just take longer 🐢",
|
|
21
21
|
{},
|
|
22
22
|
"upstream",
|
|
23
23
|
),
|
|
@@ -52,11 +52,11 @@ export function abortGatewayError(
|
|
|
52
52
|
const reason = lifecycle.signal.reason ?? requestSignal?.reason;
|
|
53
53
|
if (reason instanceof GatewayError) return reason;
|
|
54
54
|
if (reason instanceof DOMException && reason.name === "TimeoutError")
|
|
55
|
-
return new GatewayError("deadline_exceeded", 504, "request deadline exceeded", {}, "cartethyia");
|
|
55
|
+
return new GatewayError("deadline_exceeded", 504, "request deadline exceeded (request {requestId}). Some things just take longer 🐢", {}, "cartethyia");
|
|
56
56
|
const abortLike =
|
|
57
57
|
(error instanceof DOMException && error.name === "AbortError") ||
|
|
58
58
|
(error instanceof Error && error.name === "AbortError");
|
|
59
59
|
if (lifecycle.signal.aborted || requestSignal?.aborted || abortLike)
|
|
60
|
-
return new GatewayError("transport_closed", 499, "request was cancelled");
|
|
60
|
+
return new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
|
|
61
61
|
return undefined;
|
|
62
62
|
}
|
|
@@ -42,12 +42,56 @@ export function readCredentialSecret(credential: ResolvedCredential, errorMessag
|
|
|
42
42
|
throw new GatewayError(
|
|
43
43
|
"authentication_failed",
|
|
44
44
|
401,
|
|
45
|
-
errorMessage ?? "Provider credential secret is missing or empty",
|
|
45
|
+
`${errorMessage ?? "Provider credential secret is missing or empty"} (request {requestId}). Wrong key, right door — check the credential 🔑`,
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
48
|
return raw;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Asserts a credential is of the required kind, throwing a typed rejection
|
|
53
|
+
* otherwise. Providers disagree on the kind they accept (`oauth` vs
|
|
54
|
+
* `api_key` vs `scoped_access_token`), and each hand-rolled its own check +
|
|
55
|
+
* message. This centralizes the common pattern so a mis-bound credential reads
|
|
56
|
+
* the same everywhere, preserving the existing `invalid_request`/400 wire
|
|
57
|
+
* contract.
|
|
58
|
+
*/
|
|
59
|
+
export function assertCredentialKind(
|
|
60
|
+
credential: ResolvedCredential,
|
|
61
|
+
expected: CredentialKind | readonly CredentialKind[],
|
|
62
|
+
providerLabel: string,
|
|
63
|
+
): void {
|
|
64
|
+
const expectedKinds = Array.isArray(expected) ? expected : [expected];
|
|
65
|
+
if (expectedKinds.includes(credential.credential_kind)) return;
|
|
66
|
+
throw new GatewayError(
|
|
67
|
+
"invalid_request",
|
|
68
|
+
400,
|
|
69
|
+
`${providerLabel}: ${expectedKinds.join(" or ")} credential required (got credential_kind=${credential.credential_kind}) (request {requestId}). Wrong key, right door — check the credential 🔑`,
|
|
70
|
+
{ expected_credential_kind: expectedKinds },
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Asserts a candidate's wire family is one of the ones the provider serves,
|
|
76
|
+
* throwing `capability_unsupported` otherwise. Every provider hand-rolled this
|
|
77
|
+
* guard with the same shape; this centralizes the pattern so a mis-routed
|
|
78
|
+
* candidate reads the same everywhere.
|
|
79
|
+
*/
|
|
80
|
+
export function assertWireFamily(
|
|
81
|
+
candidate: ProviderDispatchTarget,
|
|
82
|
+
allowed: WireFamily | readonly WireFamily[],
|
|
83
|
+
providerLabel: string,
|
|
84
|
+
): void {
|
|
85
|
+
const allowedFamilies = Array.isArray(allowed) ? allowed : [allowed];
|
|
86
|
+
if (allowedFamilies.includes(candidate.wire_family)) return;
|
|
87
|
+
throw new GatewayError(
|
|
88
|
+
"capability_unsupported",
|
|
89
|
+
400,
|
|
90
|
+
`${providerLabel} supports ${allowedFamilies.join("/")} only, got ${candidate.wire_family} (request {requestId}). Know your tools — this one just isn't in the box 🧰`,
|
|
91
|
+
{ expected_wire_families: allowedFamilies, got_wire_family: candidate.wire_family },
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
51
95
|
/** Fetch capability that validates DNS answers and redirects before each request. */
|
|
52
96
|
export type ValidatedOutboundFetch = (
|
|
53
97
|
input: RequestInfo | URL,
|
|
@@ -155,6 +199,14 @@ export interface ProviderDispatchContext {
|
|
|
155
199
|
* and hold no database handle.
|
|
156
200
|
*/
|
|
157
201
|
readonly image_clamp_max_bytes?: number | undefined;
|
|
202
|
+
/**
|
|
203
|
+
* Tenant system-prompt text resolved once per request (append/override
|
|
204
|
+
* injection). The buddy adapters fold it into their fixed system turn after
|
|
205
|
+
* serialization, wrapped in the gateway's marker prefix/suffix, so the
|
|
206
|
+
* upstream's channel-identity validation still sees its prompt as the
|
|
207
|
+
* opener.
|
|
208
|
+
*/
|
|
209
|
+
readonly tenant_system_prompt?: string | undefined;
|
|
158
210
|
/** Opaque identity shared by retries of one logical gateway request. */
|
|
159
211
|
readonly request_identity?: object | undefined;
|
|
160
212
|
/**
|
|
@@ -344,7 +396,7 @@ export interface WebSearchOutcome {
|
|
|
344
396
|
function normalizeProviderSlug(id: string): string {
|
|
345
397
|
const trimmed = id.trim().toLowerCase();
|
|
346
398
|
if (!/^[a-z][a-z0-9-]{0,63}$/.test(trimmed)) {
|
|
347
|
-
throw new GatewayError("invalid_request", 400, "Provider ID must be a lowercase slug");
|
|
399
|
+
throw new GatewayError("invalid_request", 400, "Provider ID must be a lowercase slug (request {requestId}). Check the shape of what you sent — then resend 🪞");
|
|
348
400
|
}
|
|
349
401
|
return trimmed;
|
|
350
402
|
}
|
|
@@ -371,7 +423,7 @@ export function parseCustomProviderId(value: string): CustomProviderId {
|
|
|
371
423
|
throw new GatewayError(
|
|
372
424
|
"slug_reserved",
|
|
373
425
|
409,
|
|
374
|
-
"The provider ID is reserved for a builtin provider",
|
|
426
|
+
"The provider ID is reserved for a builtin provider (request {requestId}). This name's taken — pick another alias ✨",
|
|
375
427
|
{ provider_id: normalized },
|
|
376
428
|
);
|
|
377
429
|
}
|
|
@@ -68,7 +68,7 @@ export class CredentialResolver {
|
|
|
68
68
|
throw new GatewayError(
|
|
69
69
|
"invalid_request",
|
|
70
70
|
401,
|
|
71
|
-
"No usable upstream credential is configured for the selected provider",
|
|
71
|
+
"No usable upstream credential is configured for the selected provider (request {requestId}). Wrong key, right door — check the credential 🔑",
|
|
72
72
|
{ provider_id: providerId },
|
|
73
73
|
);
|
|
74
74
|
}
|
|
@@ -89,7 +89,7 @@ function assertAdapterIdentity(expected: ProviderId, adapter: ProviderAdapter):
|
|
|
89
89
|
// error: `invalid_request` would blame the caller and invite a byte-identical
|
|
90
90
|
// retry. `internal_error` (500) says what is true — the gateway is
|
|
91
91
|
// misconfigured and the request never had a chance.
|
|
92
|
-
throw new GatewayError("internal_error", 500, "Loaded adapter ID does not match registration", {
|
|
92
|
+
throw new GatewayError("internal_error", 500, "Loaded adapter ID does not match registration (request {requestId}). Even we have bad days — check the registry wiring 🐻", {
|
|
93
93
|
provider_id: expected,
|
|
94
94
|
adapter_id: adapter.provider_id,
|
|
95
95
|
});
|
|
@@ -122,12 +122,12 @@ export class ProviderRegistry {
|
|
|
122
122
|
register(registration: ProviderRegistration): void {
|
|
123
123
|
const providerId = parseProviderId(registration.provider_id);
|
|
124
124
|
if (this.#registrations.has(providerId)) {
|
|
125
|
-
throw new GatewayError("invalid_request", 409, "Provider is already registered", {
|
|
125
|
+
throw new GatewayError("invalid_request", 409, "Provider is already registered (request {requestId}). This name's taken — pick another alias ✨", {
|
|
126
126
|
provider_id: providerId,
|
|
127
127
|
});
|
|
128
128
|
}
|
|
129
129
|
if (providerId !== registration.provider_id) {
|
|
130
|
-
throw new GatewayError("internal_error", 500, "Provider registration ID is not normalized", {
|
|
130
|
+
throw new GatewayError("internal_error", 500, "Provider registration ID is not normalized (request {requestId}). Even we have bad days — check the registry wiring 🐻", {
|
|
131
131
|
provider_id: registration.provider_id,
|
|
132
132
|
});
|
|
133
133
|
}
|
|
@@ -158,7 +158,7 @@ export class ProviderRegistry {
|
|
|
158
158
|
upsert(registration: ProviderRegistration): boolean {
|
|
159
159
|
const providerId = parseProviderId(registration.provider_id);
|
|
160
160
|
if (providerId !== registration.provider_id) {
|
|
161
|
-
throw new GatewayError("internal_error", 500, "Provider registration ID is not normalized", {
|
|
161
|
+
throw new GatewayError("internal_error", 500, "Provider registration ID is not normalized (request {requestId}). Even we have bad days — check the registry wiring 🐻", {
|
|
162
162
|
provider_id: registration.provider_id,
|
|
163
163
|
});
|
|
164
164
|
}
|
|
@@ -58,20 +58,27 @@ export function totalRemainingCredit(
|
|
|
58
58
|
* independent quota windows, or `null` when neither is reported.
|
|
59
59
|
*
|
|
60
60
|
* Pooled credits are summed before converting to percent, so one exhausted
|
|
61
|
-
* bonus pack cannot hide balance in another.
|
|
62
|
-
*
|
|
61
|
+
* bonus pack cannot hide balance in another. A pooled window without a ceiling
|
|
62
|
+
* (Command Code credits) contributes its absolute remaining to the credit sum
|
|
63
|
+
* but cannot produce a percent, so it is not counted in the percent ratio.
|
|
64
|
+
* Independent windows keep their own remaining percent and the lowest one
|
|
65
|
+
* gates the account.
|
|
63
66
|
*/
|
|
64
67
|
export function totalRemainingPercent(
|
|
65
68
|
windows: readonly ProviderQuotaWindow[],
|
|
66
69
|
): number | null {
|
|
67
70
|
let creditLimit = 0;
|
|
68
71
|
let creditRemaining = 0;
|
|
72
|
+
let hasPooledLimit = false;
|
|
69
73
|
let independentPercent: number | null = null;
|
|
70
74
|
for (const window of windows) {
|
|
71
75
|
if (window.accounting === "pooled") {
|
|
72
76
|
const balance = creditWindowBalance(window);
|
|
73
77
|
if (balance !== null) {
|
|
74
|
-
|
|
78
|
+
if (balance.limit !== null) {
|
|
79
|
+
creditLimit += balance.limit;
|
|
80
|
+
hasPooledLimit = true;
|
|
81
|
+
}
|
|
75
82
|
creditRemaining += balance.remaining;
|
|
76
83
|
}
|
|
77
84
|
continue;
|
|
@@ -85,8 +92,13 @@ export function totalRemainingPercent(
|
|
|
85
92
|
independentPercent =
|
|
86
93
|
independentPercent === null ? clamped : Math.min(independentPercent, clamped);
|
|
87
94
|
}
|
|
88
|
-
|
|
89
|
-
|
|
95
|
+
// A pooled window with a reported ceiling (including a 0 ceiling — a spent
|
|
96
|
+
// balance) always defines a percent: 0 ceiling means 0% remaining.
|
|
97
|
+
const creditPercent = hasPooledLimit
|
|
98
|
+
? creditLimit > 0
|
|
99
|
+
? Math.min(100, Math.max(0, (creditRemaining / creditLimit) * 100))
|
|
100
|
+
: 0
|
|
101
|
+
: null;
|
|
90
102
|
if (creditPercent === null) return independentPercent;
|
|
91
103
|
if (independentPercent === null) return creditPercent;
|
|
92
104
|
return Math.min(creditPercent, independentPercent);
|
|
@@ -94,20 +106,22 @@ export function totalRemainingPercent(
|
|
|
94
106
|
|
|
95
107
|
function creditWindowBalance(
|
|
96
108
|
window: ProviderQuotaWindow,
|
|
97
|
-
): { readonly limit: number; readonly remaining: number } | null {
|
|
109
|
+
): { readonly limit: number | null; readonly remaining: number } | null {
|
|
110
|
+
// A ceiling of 0 is a spent balance, not an absence of data; only a window
|
|
111
|
+
// with no remaining figure at all is skipped. `limit` may be null (Command
|
|
112
|
+
// Code reports credits without a ceiling) — the remaining total still counts.
|
|
98
113
|
const limit =
|
|
99
|
-
typeof window.limit === "number" && Number.isFinite(window.limit) && window.limit
|
|
114
|
+
typeof window.limit === "number" && Number.isFinite(window.limit) && window.limit >= 0
|
|
100
115
|
? window.limit
|
|
101
116
|
: null;
|
|
102
|
-
if (limit === null) return null;
|
|
103
117
|
let remaining: number | null = null;
|
|
104
118
|
if (typeof window.remaining === "number" && Number.isFinite(window.remaining)) {
|
|
105
119
|
remaining = window.remaining;
|
|
106
|
-
} else if (typeof window.used === "number" && Number.isFinite(window.used)) {
|
|
120
|
+
} else if (typeof window.used === "number" && Number.isFinite(window.used) && limit !== null) {
|
|
107
121
|
remaining = limit - window.used;
|
|
108
122
|
}
|
|
109
123
|
if (remaining === null) return null;
|
|
110
|
-
return { limit, remaining: Math.
|
|
124
|
+
return { limit, remaining: Math.max(0, remaining) };
|
|
111
125
|
}
|
|
112
126
|
|
|
113
127
|
const TIMEOUT_MS = 15_000;
|
|
@@ -16,7 +16,7 @@ import { type FetchLike, type ProviderQuotaResult } from "./quota-contracts";
|
|
|
16
16
|
import type { ProviderId } from "../provider-contracts";
|
|
17
17
|
import type { ProviderRegistry } from "../provider-registry";
|
|
18
18
|
import { GLOBAL_QUOTA_LENS, setCachedQuota } from "./cache";
|
|
19
|
-
import {
|
|
19
|
+
import { asJsonbRecord } from "../../shared/coerce";
|
|
20
20
|
|
|
21
21
|
/** Why an account quota refresh failed; `null` means the fetch succeeded. */
|
|
22
22
|
export type QuotaRefreshFailureCode = "missing_credential" | "quota_fetch_failed";
|
|
@@ -355,7 +355,7 @@ async function runQuotaRefresh(
|
|
|
355
355
|
? (deps.markApiKeyCredential?.(target.providerId, credential) ?? credential)
|
|
356
356
|
: credential;
|
|
357
357
|
try {
|
|
358
|
-
const authState =
|
|
358
|
+
const authState = asJsonbRecord(target.authState);
|
|
359
359
|
quota = await fetchProviderQuota(deps.providerRegistry, target.providerId, marked, fetcher, {
|
|
360
360
|
// Carried so a collector can reuse a reading captured from real
|
|
361
361
|
// traffic rather than spending a request to rediscover it.
|
|
@@ -53,7 +53,7 @@ function stringField(record: Record<string, unknown> | undefined, key: string):
|
|
|
53
53
|
export function searchParamsFromBody(body: Record<string, unknown>): SearchParams {
|
|
54
54
|
const query = typeof body.query === "string" ? body.query.trim() : "";
|
|
55
55
|
if (query.length === 0)
|
|
56
|
-
throw new GatewayError("invalid_request", 400, "search query is required");
|
|
56
|
+
throw new GatewayError("invalid_request", 400, "search query is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
|
|
57
57
|
const rawMax = body.max_results;
|
|
58
58
|
const maxResults =
|
|
59
59
|
typeof rawMax === "number" && Number.isFinite(rawMax)
|
|
@@ -90,7 +90,7 @@ export function createSearchAdapter(spec: SearchProviderSpec): ProviderAdapter {
|
|
|
90
90
|
throw new GatewayError(
|
|
91
91
|
"capability_unsupported",
|
|
92
92
|
400,
|
|
93
|
-
`${spec.provider_id} serves web search, not a chat wire
|
|
93
|
+
`${spec.provider_id} serves web search, not a chat wire (request {requestId}). Know your tools — this one just isn't in the box 🧰`,
|
|
94
94
|
);
|
|
95
95
|
},
|
|
96
96
|
async websearch(
|
|
@@ -113,7 +113,7 @@ export function createSearchAdapter(spec: SearchProviderSpec): ProviderAdapter {
|
|
|
113
113
|
throw new GatewayError(
|
|
114
114
|
"platform_unavailable",
|
|
115
115
|
502,
|
|
116
|
-
`${spec.provider_id} search unreachable: ${error instanceof Error ? error.message : String(error)}
|
|
116
|
+
`${spec.provider_id} search unreachable (request {requestId}): ${error instanceof Error ? error.message : String(error)}. The connection ghosted us — come back in a moment 👻`,
|
|
117
117
|
);
|
|
118
118
|
}
|
|
119
119
|
if (!res.ok) {
|
|
@@ -121,7 +121,7 @@ export function createSearchAdapter(spec: SearchProviderSpec): ProviderAdapter {
|
|
|
121
121
|
throw new GatewayError(
|
|
122
122
|
res.status >= 500 ? "platform_unavailable" : "invalid_request",
|
|
123
123
|
res.status,
|
|
124
|
-
`${spec.provider_id} search error: ${detail.slice(0, 240)}
|
|
124
|
+
`${spec.provider_id} search error (request {requestId}): ${detail.slice(0, 240)}. The upstream came back empty-handed — retry 🌙`,
|
|
125
125
|
);
|
|
126
126
|
}
|
|
127
127
|
const raw = await res.text();
|
|
@@ -135,7 +135,7 @@ export function createSearchAdapter(spec: SearchProviderSpec): ProviderAdapter {
|
|
|
135
135
|
throw new GatewayError(
|
|
136
136
|
"platform_unavailable",
|
|
137
137
|
502,
|
|
138
|
-
`${spec.provider_id} search returned an unparseable body
|
|
138
|
+
`${spec.provider_id} search returned an unparseable body (request {requestId}). The upstream came back empty-handed — retry 🌙`,
|
|
139
139
|
);
|
|
140
140
|
}
|
|
141
141
|
const results = spec.normalize(data);
|
package/src/security/abuse.ts
CHANGED
|
@@ -399,7 +399,7 @@ export class IpAbuseProtectionService {
|
|
|
399
399
|
readonly signal?: AbortSignal;
|
|
400
400
|
}): Promise<void> {
|
|
401
401
|
if (input.signal?.aborted) {
|
|
402
|
-
throw new GatewayError("admission_unavailable", 503, "The abuse check never ran because the request was already cancelled.", {
|
|
402
|
+
throw new GatewayError("admission_unavailable", 503, "The abuse check never ran because the request was already cancelled (request {requestId}). Send it again whenever ready 🙃", {
|
|
403
403
|
reason: "admission-unavailable",
|
|
404
404
|
});
|
|
405
405
|
}
|
|
@@ -436,7 +436,7 @@ export class IpAbuseProtectionService {
|
|
|
436
436
|
|
|
437
437
|
if (outcome.banned) {
|
|
438
438
|
metrics.cartethyia_ip_abuse_refusals_total.inc(1, { reason: "ip-banned" });
|
|
439
|
-
throw new GatewayError("
|
|
439
|
+
throw new GatewayError("rate_limited", 429, "This address is temporarily banned for too many failed requests (request {requestId}). Wait for the ban to lift, then slow down. You've maxed this window — pace yourself ⏳", {
|
|
440
440
|
// Distinguishes this from the rate-limited 429 below and from an API
|
|
441
441
|
// key's own budget. It used to be "admission-unavailable", which is
|
|
442
442
|
// the label for an *unreachable* store — so every refusal from this
|
|
@@ -454,7 +454,7 @@ export class IpAbuseProtectionService {
|
|
|
454
454
|
|
|
455
455
|
if (outcome.count > this.maxRequestsPerWindow) {
|
|
456
456
|
metrics.cartethyia_ip_abuse_refusals_total.inc(1, { reason: "ip-rate-limited" });
|
|
457
|
-
throw new GatewayError("
|
|
457
|
+
throw new GatewayError("rate_limited", 429, "This address is sending requests faster than the abuse limit allows (request {requestId}). Back off and retry after the window rolls over. You've maxed this window — pace yourself ⏳", {
|
|
458
458
|
reason: "ip-rate-limited",
|
|
459
459
|
ip,
|
|
460
460
|
route: input.route,
|
|
@@ -464,7 +464,7 @@ export class IpAbuseProtectionService {
|
|
|
464
464
|
}
|
|
465
465
|
} catch (err) {
|
|
466
466
|
if (err instanceof GatewayError) throw err;
|
|
467
|
-
throw new GatewayError("admission_unavailable", 503, "The abuse-tracking store is unreachable, so requests from this address cannot be checked. Retry shortly.", {
|
|
467
|
+
throw new GatewayError("admission_unavailable", 503, "The abuse-tracking store is unreachable, so requests from this address cannot be checked (request {requestId}). Retry shortly. The bouncer stepped out — come back shortly 🚪", {
|
|
468
468
|
reason: "admission-unavailable",
|
|
469
469
|
cause: (err as Error).message,
|
|
470
470
|
});
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
// Shared authorization primitives: closed scope model and immutable access decisions.
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* The single identity of "who is speaking" — resolved once at auth and carried
|
|
5
|
+
* through planning, admission, pool selection, and telemetry so every layer
|
|
6
|
+
* reasons about the same caller.
|
|
7
|
+
*
|
|
8
|
+
* - `apiKeyId` — the credential that authenticated.
|
|
9
|
+
* - `admissionIdentity` — the quota/concurrency owner (the share parent for a
|
|
10
|
+
* child key; otherwise the key itself).
|
|
11
|
+
* - `tenantId` — the tenant the request runs as.
|
|
12
|
+
*
|
|
13
|
+
* Provider/account/model are added by the candidate, not here: the caller
|
|
14
|
+
* identity is stable regardless of what serves the request.
|
|
15
|
+
*/
|
|
16
|
+
export interface CallerIdentity {
|
|
17
|
+
readonly apiKeyId: string;
|
|
18
|
+
readonly admissionIdentity: string;
|
|
19
|
+
readonly tenantId: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
3
22
|
/**
|
|
4
23
|
* Closed set of authorization scopes.
|
|
5
24
|
* - `routing:invoke`: May call only /v1/* gateway routes within holder's tenant.
|