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
|
@@ -155,7 +155,18 @@ export type SharedApiKeyIssueResult =
|
|
|
155
155
|
readonly keyPrefix: string;
|
|
156
156
|
readonly createdAt: Date;
|
|
157
157
|
}
|
|
158
|
-
| { readonly kind: "link_unavailable" }
|
|
158
|
+
| { readonly kind: "link_unavailable" }
|
|
159
|
+
| {
|
|
160
|
+
readonly kind: "already_issued";
|
|
161
|
+
readonly parentKeyId: string;
|
|
162
|
+
readonly tenantId: string;
|
|
163
|
+
}
|
|
164
|
+
| {
|
|
165
|
+
readonly kind: "slot_full";
|
|
166
|
+
readonly parentKeyId: string;
|
|
167
|
+
readonly tenantId: string;
|
|
168
|
+
readonly limit: number;
|
|
169
|
+
};
|
|
159
170
|
|
|
160
171
|
type ApiKeyRow = typeof apiKeys.$inferSelect;
|
|
161
172
|
type ShareLinkRow = typeof shareLinks.$inferSelect;
|
|
@@ -485,6 +496,53 @@ export class DrizzleShareLinkStore implements ShareLinkStore {
|
|
|
485
496
|
);
|
|
486
497
|
if (!activeLink) return { kind: "link_unavailable" };
|
|
487
498
|
|
|
499
|
+
// One child key per canonical client IP. The public route mints from an
|
|
500
|
+
// enrollment link, and a bot replaying the same URL from one IP was
|
|
501
|
+
// creating an unbounded number of children (observed: 331k `api_keys`
|
|
502
|
+
// rows from two Google Cloud IPs in three days). If this (parent, IP)
|
|
503
|
+
// pair already has a live child, that is the same enrollment — return it
|
|
504
|
+
// as `already_issued` instead of minting another.
|
|
505
|
+
if (material.clientIpKey.length > 0) {
|
|
506
|
+
const existing = await tx
|
|
507
|
+
.select({ id: apiKeys.id })
|
|
508
|
+
.from(apiKeys)
|
|
509
|
+
.where(
|
|
510
|
+
and(
|
|
511
|
+
eq(apiKeys.parentKeyId, parent.id),
|
|
512
|
+
eq(apiKeys.issuedClientIpKey, material.clientIpKey),
|
|
513
|
+
isNull(apiKeys.revokedAt),
|
|
514
|
+
),
|
|
515
|
+
)
|
|
516
|
+
.limit(1);
|
|
517
|
+
if (existing[0] !== undefined) {
|
|
518
|
+
return {
|
|
519
|
+
kind: "already_issued",
|
|
520
|
+
parentKeyId: parent.id,
|
|
521
|
+
tenantId: parent.tenantId,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Slot cap: a template may bound how many concurrently-active children
|
|
527
|
+
// exist. The public link is mint-able by anyone, so without this cap a
|
|
528
|
+
// bot rotating egress IPs (CDN edges, cloud egress, proxies) could mint
|
|
529
|
+
// without bound — observed: 331k children in three days. The count is
|
|
530
|
+
// live children only; revoked children free their slot for reuse.
|
|
531
|
+
if (parent.maxChildren !== null && parent.maxChildren !== undefined) {
|
|
532
|
+
const liveCount = await tx
|
|
533
|
+
.select({ value: sql<number>`count(*) filter (where ${apiKeys.revokedAt} is null)` })
|
|
534
|
+
.from(apiKeys)
|
|
535
|
+
.where(eq(apiKeys.parentKeyId, parent.id));
|
|
536
|
+
if (Number(liveCount[0]?.value ?? 0) >= parent.maxChildren) {
|
|
537
|
+
return {
|
|
538
|
+
kind: "slot_full",
|
|
539
|
+
parentKeyId: parent.id,
|
|
540
|
+
tenantId: parent.tenantId,
|
|
541
|
+
limit: parent.maxChildren,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
488
546
|
// The label is `hint + random`, capped at 12 chars — never the
|
|
489
547
|
// parent's full label plus a suffix, which grew past every table
|
|
490
548
|
// column it rendered in. Built here (not imported from the console
|
|
@@ -493,6 +551,12 @@ export class DrizzleShareLinkStore implements ShareLinkStore {
|
|
|
493
551
|
const suffix = randomInt(1000, 10_000).toString();
|
|
494
552
|
const label =
|
|
495
553
|
hint.length === 0 ? `key-${suffix}` : `${hint}-${suffix}`.slice(0, 12);
|
|
554
|
+
// Child rows carry identity + scopes only. Auth resolves every policy
|
|
555
|
+
// field from the parent (`policy = parent ?? row`), so copying notes,
|
|
556
|
+
// limits, model_list, denylist, etc. was pure write-amplification.
|
|
557
|
+
// Nullable policy columns stay NULL; `model_access_mode` takes its
|
|
558
|
+
// NOT NULL default ('whitelist'). Keep in sync with migration 0029 and
|
|
559
|
+
// `normalizeShareChildPolicy` in `src/console/backup/store.ts`.
|
|
496
560
|
const [child] = await tx
|
|
497
561
|
.insert(apiKeys)
|
|
498
562
|
.values({
|
|
@@ -506,18 +570,6 @@ export class DrizzleShareLinkStore implements ShareLinkStore {
|
|
|
506
570
|
scopes: parent.scopes,
|
|
507
571
|
keyPrefix: material.keyPrefix,
|
|
508
572
|
keyEncrypted: null,
|
|
509
|
-
notesTitle: parent.notesTitle,
|
|
510
|
-
notesSubtitle: parent.notesSubtitle,
|
|
511
|
-
notesBody: parent.notesBody,
|
|
512
|
-
requestsPerMinute: parent.requestsPerMinute,
|
|
513
|
-
dailyTokenLimit: parent.dailyTokenLimit,
|
|
514
|
-
monthlyTokenLimit: parent.monthlyTokenLimit,
|
|
515
|
-
lifetimeTokenBudget: parent.lifetimeTokenBudget,
|
|
516
|
-
maxConcurrentRequests: parent.maxConcurrentRequests,
|
|
517
|
-
modelPrefix: parent.modelPrefix,
|
|
518
|
-
modelAccessMode: parent.modelAccessMode,
|
|
519
|
-
modelList: parent.modelList,
|
|
520
|
-
clientRouterDenylist: parent.clientRouterDenylist,
|
|
521
573
|
lifetimeTokensConsumed: 0,
|
|
522
574
|
})
|
|
523
575
|
.returning({ id: apiKeys.id, tenantId: apiKeys.tenantId, createdAt: apiKeys.createdAt });
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { readdir, rm } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { sql } from "drizzle-orm";
|
|
3
|
+
import { inArray, sql } from "drizzle-orm";
|
|
4
4
|
import { affectedRows, currentTimestamp, laterOf } from "../dialect";
|
|
5
5
|
import type { CartethyiaDatabase } from "../db-handle";
|
|
6
6
|
import {
|
|
7
|
+
apiKeys,
|
|
7
8
|
telemetryEvents,
|
|
8
9
|
telemetryPayloads,
|
|
9
10
|
telemetryUsageTotals,
|
|
@@ -54,7 +55,7 @@ function addUsageDelta(
|
|
|
54
55
|
delta.outputTokens += row.outputTokens ?? 0;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
function aggregateUsage(rows: readonly (typeof telemetryEvents.$inferInsert)[]) {
|
|
58
|
+
export function aggregateUsage(rows: readonly (typeof telemetryEvents.$inferInsert)[]) {
|
|
58
59
|
const deltas = new Map<string, UsageTotalDelta>();
|
|
59
60
|
for (const row of rows) {
|
|
60
61
|
addUsageDelta(deltas, row, "account", row.accountId);
|
|
@@ -117,6 +118,31 @@ export class DrizzleTelemetryStore {
|
|
|
117
118
|
this.eventWriter = eventWriter;
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Resolves the share-template key for each distinct api key in a batch.
|
|
123
|
+
*
|
|
124
|
+
* The template is the key itself when it is a top-level template, or its
|
|
125
|
+
* parent when it is a minted child. The column is denormalized onto the
|
|
126
|
+
* event so family rollups do not depend on the key row surviving.
|
|
127
|
+
*/
|
|
128
|
+
private async resolveFamilyTemplateIds(
|
|
129
|
+
rows: readonly (typeof telemetryEvents.$inferInsert)[],
|
|
130
|
+
): Promise<Map<string, string>> {
|
|
131
|
+
const ids = [
|
|
132
|
+
...new Set(
|
|
133
|
+
rows
|
|
134
|
+
.map((row) => row.apiKeyId)
|
|
135
|
+
.filter((id): id is string => typeof id === "string" && id.length > 0),
|
|
136
|
+
),
|
|
137
|
+
];
|
|
138
|
+
if (ids.length === 0) return new Map();
|
|
139
|
+
const resolved = await this.db
|
|
140
|
+
.select({ id: apiKeys.id, parentKeyId: apiKeys.parentKeyId })
|
|
141
|
+
.from(apiKeys)
|
|
142
|
+
.where(inArray(apiKeys.id, ids));
|
|
143
|
+
return new Map(resolved.map((row) => [row.id, row.parentKeyId ?? row.id]));
|
|
144
|
+
}
|
|
145
|
+
|
|
120
146
|
/**
|
|
121
147
|
* Applies usage deltas inside an enclosing transaction.
|
|
122
148
|
*
|
|
@@ -137,11 +163,34 @@ export class DrizzleTelemetryStore {
|
|
|
137
163
|
left.tenantId.localeCompare(right.tenantId)
|
|
138
164
|
|| left.identityType.localeCompare(right.identityType)
|
|
139
165
|
|| left.entityId.localeCompare(right.entityId));
|
|
166
|
+
// Resolve the share-template key for each api_key identity in the batch so
|
|
167
|
+
// the durable totals row carries it — family reads then survive the key
|
|
168
|
+
// row being deleted.
|
|
169
|
+
const apiKeyIds = [
|
|
170
|
+
...new Set(
|
|
171
|
+
ordered
|
|
172
|
+
.filter((delta) => delta.identityType === "api_key")
|
|
173
|
+
.map((delta) => delta.entityId),
|
|
174
|
+
),
|
|
175
|
+
];
|
|
176
|
+
const familyTemplates = new Map<string, string>();
|
|
177
|
+
if (apiKeyIds.length > 0) {
|
|
178
|
+
const keyRows = await tx
|
|
179
|
+
.select({ id: apiKeys.id, parentKeyId: apiKeys.parentKeyId })
|
|
180
|
+
.from(apiKeys)
|
|
181
|
+
.where(inArray(apiKeys.id, apiKeyIds));
|
|
182
|
+
for (const row of keyRows) {
|
|
183
|
+
familyTemplates.set(row.id, row.parentKeyId ?? row.id);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
140
186
|
const now = new Date();
|
|
141
187
|
await tx
|
|
142
188
|
.insert(telemetryUsageTotals)
|
|
143
189
|
.values(ordered.map((delta) => ({
|
|
144
190
|
...delta,
|
|
191
|
+
...(delta.identityType === "api_key" && familyTemplates.has(delta.entityId)
|
|
192
|
+
? { familyTemplateKeyId: familyTemplates.get(delta.entityId) }
|
|
193
|
+
: {}),
|
|
145
194
|
requests: delta.requests,
|
|
146
195
|
errors: delta.errors,
|
|
147
196
|
inputTokens: delta.inputTokens,
|
|
@@ -171,6 +220,13 @@ export class DrizzleTelemetryStore {
|
|
|
171
220
|
// The file accepts anything, so a malformed row would be written and then
|
|
172
221
|
// retried forever without ever failing. Rejecting it here keeps the buffer's
|
|
173
222
|
// drop-and-count contract: one bad row fails the batch, once.
|
|
223
|
+
const familyTemplates = await this.resolveFamilyTemplateIds(rows);
|
|
224
|
+
for (const row of rows) {
|
|
225
|
+
if (typeof row.apiKeyId === "string" && row.apiKeyId.length > 0) {
|
|
226
|
+
const template = familyTemplates.get(row.apiKeyId);
|
|
227
|
+
if (template !== undefined) row.familyTemplateKeyId = template;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
174
230
|
const unique: Array<typeof telemetryEvents.$inferInsert> = [];
|
|
175
231
|
const ids = new Set<string>();
|
|
176
232
|
for (const row of rows) {
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Persistence for tenant system-prompt injection settings.
|
|
2
|
+
//
|
|
3
|
+
// One row per tenant. Mutations bump the settings revision so the dispatch
|
|
4
|
+
// path's revision-keyed reader converges immediately (same mechanism as the
|
|
5
|
+
// runtime-preferences cache).
|
|
6
|
+
import { eq } from "drizzle-orm";
|
|
7
|
+
import type { CartethyiaDatabase } from "./db-handle";
|
|
8
|
+
import { tenantSystemPrompts } from "./schema";
|
|
9
|
+
import type {
|
|
10
|
+
ProviderPromptEntry,
|
|
11
|
+
PromptSetting,
|
|
12
|
+
TenantSystemPrompts,
|
|
13
|
+
} from "../console/system-prompt/contracts";
|
|
14
|
+
import {
|
|
15
|
+
EMPTY_TENANT_SYSTEM_PROMPTS,
|
|
16
|
+
SYSTEM_PROMPT_MODEL_LIMIT,
|
|
17
|
+
SYSTEM_PROMPT_PROVIDER_LIMIT,
|
|
18
|
+
isPromptSetting,
|
|
19
|
+
parseSystemPromptModelKey,
|
|
20
|
+
} from "../console/system-prompt/contracts";
|
|
21
|
+
import { bumpSettingsRevision } from "./settings-revision";
|
|
22
|
+
|
|
23
|
+
/** The stored row, normalized. Absent row reads as the empty configuration. */
|
|
24
|
+
export type StoredTenantSystemPrompts = TenantSystemPrompts;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Reads the tenant's stored configuration, defensively normalized: a
|
|
28
|
+
* hand-edited or legacy row must not poison the dispatch path, so malformed
|
|
29
|
+
* entries are dropped rather than trusted.
|
|
30
|
+
*/
|
|
31
|
+
export async function readTenantSystemPrompts(
|
|
32
|
+
db: CartethyiaDatabase,
|
|
33
|
+
tenantId: string,
|
|
34
|
+
): Promise<StoredTenantSystemPrompts> {
|
|
35
|
+
const rows = await db
|
|
36
|
+
.select({
|
|
37
|
+
global: tenantSystemPrompts.global,
|
|
38
|
+
providers: tenantSystemPrompts.providers,
|
|
39
|
+
models: tenantSystemPrompts.models,
|
|
40
|
+
})
|
|
41
|
+
.from(tenantSystemPrompts)
|
|
42
|
+
.where(eq(tenantSystemPrompts.tenantId, tenantId))
|
|
43
|
+
.limit(1);
|
|
44
|
+
const row = rows[0];
|
|
45
|
+
if (row === undefined) return EMPTY_TENANT_SYSTEM_PROMPTS;
|
|
46
|
+
|
|
47
|
+
const providers: Record<string, ProviderPromptEntry> = {};
|
|
48
|
+
for (const [providerId, entry] of Object.entries(row.providers ?? {})) {
|
|
49
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
50
|
+
const candidate = entry as Partial<ProviderPromptEntry>;
|
|
51
|
+
if (typeof candidate.followGlobal !== "boolean") continue;
|
|
52
|
+
if (candidate.followGlobal === false) {
|
|
53
|
+
if (!isPromptSetting(candidate.setting)) continue;
|
|
54
|
+
providers[providerId] = { followGlobal: false, setting: candidate.setting };
|
|
55
|
+
} else {
|
|
56
|
+
providers[providerId] = { followGlobal: true };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const models: Record<string, PromptSetting> = {};
|
|
61
|
+
for (const [key, setting] of Object.entries(row.models ?? {})) {
|
|
62
|
+
if (parseSystemPromptModelKey(key) === null) continue;
|
|
63
|
+
if (!isPromptSetting(setting)) continue;
|
|
64
|
+
models[key] = setting;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
global: row.global !== null && isPromptSetting(row.global) ? row.global : null,
|
|
69
|
+
providers,
|
|
70
|
+
models,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Upserts the tenant's configuration and bumps the settings revision. */
|
|
75
|
+
export async function writeTenantSystemPrompts(
|
|
76
|
+
db: CartethyiaDatabase,
|
|
77
|
+
tenantId: string,
|
|
78
|
+
next: TenantSystemPrompts,
|
|
79
|
+
): Promise<void> {
|
|
80
|
+
// Keep the row bounded even though the API already enforces the caps: a
|
|
81
|
+
// direct store caller must not be able to plant an unbounded map.
|
|
82
|
+
const providers = Object.entries(next.providers).slice(0, SYSTEM_PROMPT_PROVIDER_LIMIT);
|
|
83
|
+
const models = Object.entries(next.models).slice(0, SYSTEM_PROMPT_MODEL_LIMIT);
|
|
84
|
+
const values = {
|
|
85
|
+
global: next.global,
|
|
86
|
+
providers: Object.fromEntries(providers),
|
|
87
|
+
models: Object.fromEntries(models),
|
|
88
|
+
updatedAt: new Date(),
|
|
89
|
+
};
|
|
90
|
+
await db
|
|
91
|
+
.insert(tenantSystemPrompts)
|
|
92
|
+
.values({ tenantId, ...values })
|
|
93
|
+
.onConflictDoUpdate({ target: tenantSystemPrompts.tenantId, set: values });
|
|
94
|
+
bumpSettingsRevision();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Clears the tenant's configuration entirely (console "reset" path). */
|
|
98
|
+
export async function clearTenantSystemPrompts(
|
|
99
|
+
db: CartethyiaDatabase,
|
|
100
|
+
tenantId: string,
|
|
101
|
+
): Promise<void> {
|
|
102
|
+
await db.delete(tenantSystemPrompts).where(eq(tenantSystemPrompts.tenantId, tenantId));
|
|
103
|
+
bumpSettingsRevision();
|
|
104
|
+
}
|
package/src/protocol/codecs.ts
CHANGED
|
@@ -480,7 +480,7 @@ export function assertBoundedJsonDepth(value: unknown): void {
|
|
|
480
480
|
|
|
481
481
|
function assertBoundedDepthWalk(value: unknown, depth: number): void {
|
|
482
482
|
if (depth > MAX_JSON_DEPTH)
|
|
483
|
-
throw new GatewayError("invalid_request", 400, `The request body is nested deeper than this endpoint accepts (${MAX_JSON_DEPTH} levels). Flatten the payload
|
|
483
|
+
throw new GatewayError("invalid_request", 400, `The request body is nested deeper than this endpoint accepts (${MAX_JSON_DEPTH} levels) (request {requestId}). Flatten the payload. Check the shape of what you sent — then resend 🪞`);
|
|
484
484
|
if (Array.isArray(value)) {
|
|
485
485
|
for (const item of value) assertBoundedDepthWalk(item, depth + 1);
|
|
486
486
|
return;
|
|
@@ -1038,7 +1038,7 @@ export function filterProviderCustomHeaders(
|
|
|
1038
1038
|
for (const [rawName, rawValue] of Object.entries(input)) {
|
|
1039
1039
|
const name = rawName.toLowerCase();
|
|
1040
1040
|
if (!HEADER_TOKEN.test(name)) {
|
|
1041
|
-
throw new GatewayError("invalid_request", 400, `invalid custom header name: ${rawName}
|
|
1041
|
+
throw new GatewayError("invalid_request", 400, `invalid custom header name: ${rawName} (request {requestId}). Check the shape of what you sent — then resend 🪞`, {
|
|
1042
1042
|
header: rawName,
|
|
1043
1043
|
});
|
|
1044
1044
|
}
|
|
@@ -1047,7 +1047,7 @@ export function filterProviderCustomHeaders(
|
|
|
1047
1047
|
extraReserved.has(name) ||
|
|
1048
1048
|
name.startsWith("x-forwarded-")
|
|
1049
1049
|
) {
|
|
1050
|
-
throw new GatewayError("invalid_request", 400, `custom header is protected: ${rawName}
|
|
1050
|
+
throw new GatewayError("invalid_request", 400, `custom header is protected: ${rawName} (request {requestId}). Not allowed by policy — no hard feelings 🫂`, {
|
|
1051
1051
|
header: rawName,
|
|
1052
1052
|
});
|
|
1053
1053
|
}
|
|
@@ -1055,7 +1055,7 @@ export function filterProviderCustomHeaders(
|
|
|
1055
1055
|
throw new GatewayError(
|
|
1056
1056
|
"invalid_request",
|
|
1057
1057
|
400,
|
|
1058
|
-
`custom header value must be a string: ${rawName}
|
|
1058
|
+
`custom header value must be a string: ${rawName} (request {requestId}). Check the shape of what you sent — then resend 🪞`,
|
|
1059
1059
|
{ header: rawName },
|
|
1060
1060
|
);
|
|
1061
1061
|
}
|
|
@@ -1063,7 +1063,7 @@ export function filterProviderCustomHeaders(
|
|
|
1063
1063
|
throw new GatewayError(
|
|
1064
1064
|
"invalid_request",
|
|
1065
1065
|
400,
|
|
1066
|
-
`custom header value exceeds 4KiB: ${rawName}
|
|
1066
|
+
`custom header value exceeds 4KiB: ${rawName} (request {requestId}). We love long reads, but not that long 📚`,
|
|
1067
1067
|
{ header: rawName },
|
|
1068
1068
|
);
|
|
1069
1069
|
}
|
|
@@ -1071,7 +1071,7 @@ export function filterProviderCustomHeaders(
|
|
|
1071
1071
|
throw new GatewayError(
|
|
1072
1072
|
"invalid_request",
|
|
1073
1073
|
400,
|
|
1074
|
-
`custom header value contains control characters: ${rawName}
|
|
1074
|
+
`custom header value contains control characters: ${rawName} (request {requestId}). Check the shape of what you sent — then resend 🪞`,
|
|
1075
1075
|
{ header: rawName },
|
|
1076
1076
|
);
|
|
1077
1077
|
}
|
|
@@ -6,12 +6,14 @@ export type GatewayErrorCode =
|
|
|
6
6
|
| "capability_unsupported"
|
|
7
7
|
| "ambiguous_model"
|
|
8
8
|
| "model_not_found"
|
|
9
|
+
| "model_not_allowed"
|
|
9
10
|
| "upstream_not_found"
|
|
10
11
|
| "context_length_exceeded"
|
|
11
12
|
| "request_too_large"
|
|
12
13
|
| "accounts_unavailable"
|
|
13
14
|
| "accounts_rate_limited"
|
|
14
15
|
| "capacity_exhausted"
|
|
16
|
+
| "upstream_overloaded"
|
|
15
17
|
| "provider_capacity_exhausted"
|
|
16
18
|
| "upstream_queued"
|
|
17
19
|
| "proxy_pool_capacity_exceeded"
|
|
@@ -22,8 +24,10 @@ export type GatewayErrorCode =
|
|
|
22
24
|
| "proxy_pool_unhealthy"
|
|
23
25
|
| "invalid_pool_limits"
|
|
24
26
|
| "admission_unavailable"
|
|
27
|
+
| "route_unavailable"
|
|
25
28
|
| "slug_reserved"
|
|
26
29
|
| "quota_exceeded"
|
|
30
|
+
| "rate_limited"
|
|
27
31
|
| "authentication_failed"
|
|
28
32
|
| "policy_rejected"
|
|
29
33
|
| "invalid_request"
|
|
@@ -52,8 +56,7 @@ export type GatewayErrorCode =
|
|
|
52
56
|
| "proxy_unreachable"
|
|
53
57
|
| "tool_call_loop_detected"
|
|
54
58
|
| "client_router_denied"
|
|
55
|
-
| "shutting_down"
|
|
56
|
-
| "restart_for_update";
|
|
59
|
+
| "shutting_down";
|
|
57
60
|
|
|
58
61
|
/** Identifies which boundary produced a safe public error. */
|
|
59
62
|
export type GatewayErrorOrigin = "cartethyia" | "upstream" | "network";
|
|
@@ -226,7 +229,7 @@ export function capabilityUnsupported(
|
|
|
226
229
|
return new GatewayError(
|
|
227
230
|
"capability_unsupported",
|
|
228
231
|
400,
|
|
229
|
-
`The selected route does not support capability: ${capability}
|
|
232
|
+
`The selected route does not support capability: ${capability} (request {requestId}). Drop the ${capability} part of the request, or route to a model that accepts it. Know your tools — this one just isn't in the box 🧰`,
|
|
230
233
|
{ capability, ...details },
|
|
231
234
|
);
|
|
232
235
|
}
|
|
@@ -22,7 +22,7 @@ const OPENAI_ASPECT_RATIOS: Readonly<Record<string, string>> = {
|
|
|
22
22
|
export function imagePrompt(body: Record<string, unknown>): string {
|
|
23
23
|
if (typeof body.prompt === "string" && body.prompt.trim().length > 0) return body.prompt;
|
|
24
24
|
if (!Array.isArray(body.messages)) {
|
|
25
|
-
throw new GatewayError("invalid_request", 400, "prompt is required");
|
|
25
|
+
throw new GatewayError("invalid_request", 400, "prompt is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
|
|
26
26
|
}
|
|
27
27
|
const texts: string[] = [];
|
|
28
28
|
for (const message of body.messages) {
|
|
@@ -41,7 +41,7 @@ export function imagePrompt(body: Record<string, unknown>): string {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
const prompt = texts.join("\n").trim();
|
|
44
|
-
if (prompt.length === 0) throw new GatewayError("invalid_request", 400, "prompt is required");
|
|
44
|
+
if (prompt.length === 0) throw new GatewayError("invalid_request", 400, "prompt is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
|
|
45
45
|
return prompt;
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -50,19 +50,83 @@ export function applyDeepSeekReasoning(payload: Record<string, unknown>): void {
|
|
|
50
50
|
payload["reasoning_effort"] = "high";
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Model families whose Chat-compatible upstream enforces the DeepSeek replay
|
|
55
|
+
* rule: every prior assistant turn must carry `reasoning_content`, or the
|
|
56
|
+
* upstream refuses the whole request with
|
|
57
|
+
* `Missing required parameter: 'messages[N].reasoning_content'`. Seen on
|
|
58
|
+
* Cline's resold `reflection/beam-free` (2026-10-30) and on the buddy
|
|
59
|
+
* DeepSeek family. Cline, OpenRouter, Hermes, and Kilo share one chat wire,
|
|
60
|
+
* so the rule is stated once, keyed on the model family.
|
|
61
|
+
*/
|
|
62
|
+
const REASONING_REPLAY_MODEL_RE = /(?:deepseek|reflection\/beam)/i;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Ensures every assistant turn carries `reasoning_content` for model families
|
|
66
|
+
* whose upstream enforces the DeepSeek replay rule. The rule demands the field
|
|
67
|
+
* on *every* earlier assistant message — presence, not content — and the
|
|
68
|
+
* upstream rejects an absent field with
|
|
69
|
+
* `Missing required parameter: 'messages[N].reasoning_content'` even when the
|
|
70
|
+
* turn genuinely has no trace to replay. Clients that already learned the rule
|
|
71
|
+
* send `reasoning_content: ""` for exactly those turns (see the chat surface
|
|
72
|
+
* parser), so an empty string is the honest value for a turn with nothing to
|
|
73
|
+
* replay. Existing values are never overwritten.
|
|
74
|
+
*
|
|
75
|
+
* Unlike `backfillDeepSeekReasoningContent`, this runs only for the
|
|
76
|
+
* OpenAI-compatible resellers (Cline/OpenRouter/Hermes/Kilo), not the buddy
|
|
77
|
+
* family, whose upstream rejects empty reasoning as "stripped" and drops it.
|
|
78
|
+
*/
|
|
79
|
+
export function ensureChatReasoningReplayPresence(payload: Record<string, unknown>): void {
|
|
80
|
+
const model = typeof payload["model"] === "string" ? payload["model"] : "";
|
|
81
|
+
if (!REASONING_REPLAY_MODEL_RE.test(model)) return;
|
|
82
|
+
const messages = payload["messages"];
|
|
83
|
+
if (!Array.isArray(messages)) return;
|
|
84
|
+
const thinking = payload["thinking"];
|
|
85
|
+
const thinkingEnabled = isRecord(thinking) && thinking["type"] === "enabled";
|
|
86
|
+
const hasTrace = messages.some(
|
|
87
|
+
(message) =>
|
|
88
|
+
isRecord(message) &&
|
|
89
|
+
((typeof message["reasoning"] === "string" && message["reasoning"].length > 0) ||
|
|
90
|
+
"reasoning_content" in message),
|
|
91
|
+
);
|
|
92
|
+
// Same gate as the backfill: a request that never entered thinking mode and
|
|
93
|
+
// carries no reasoning anywhere has nothing the model could demand, so do
|
|
94
|
+
// not start marking its turns as thinking turns.
|
|
95
|
+
if (!thinkingEnabled && !hasTrace) return;
|
|
96
|
+
for (const message of messages) {
|
|
97
|
+
if (!isRecord(message) || message["role"] !== "assistant") continue;
|
|
98
|
+
if (typeof message["reasoning_content"] === "string") continue;
|
|
99
|
+
message["reasoning_content"] = "";
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
53
103
|
/** Backfills DeepSeek assistant reasoning_content required for replayed turns. */
|
|
54
104
|
export function backfillDeepSeekReasoningContent(payload: Record<string, unknown>): void {
|
|
105
|
+
const model = typeof payload["model"] === "string" ? payload["model"] : "";
|
|
55
106
|
const thinking = payload["thinking"];
|
|
56
107
|
const thinkingEnabled = isRecord(thinking) && thinking["type"] === "enabled";
|
|
57
108
|
const messages = payload["messages"];
|
|
58
109
|
if (!Array.isArray(messages)) return;
|
|
110
|
+
const requiresReplay = REASONING_REPLAY_MODEL_RE.test(model);
|
|
59
111
|
const hasTrace = messages.some(
|
|
60
112
|
(message) =>
|
|
61
113
|
isRecord(message) &&
|
|
62
114
|
((typeof message["reasoning"] === "string" && message["reasoning"].length > 0) ||
|
|
63
115
|
"reasoning_content" in message),
|
|
64
116
|
);
|
|
65
|
-
if (!thinkingEnabled && !hasTrace) return;
|
|
117
|
+
if (!thinkingEnabled && !requiresReplay && !hasTrace) return;
|
|
118
|
+
// As an *input* (a caller replays a prior turn), the model's own `reasoning`
|
|
119
|
+
// key is the canonical record of a thinking turn. Without it there is no
|
|
120
|
+
// trace to replay, so an assistant turn stays bare even under the hard rule;
|
|
121
|
+
// fabricating `""` would be read by the upstream as "thinking mode with the
|
|
122
|
+
// reasoning stripped" and reject the request anyway.
|
|
123
|
+
const hasModelReasoning = messages.some(
|
|
124
|
+
(message) =>
|
|
125
|
+
isRecord(message) &&
|
|
126
|
+
typeof message["reasoning"] === "string" &&
|
|
127
|
+
message["reasoning"].length > 0,
|
|
128
|
+
);
|
|
129
|
+
if (requiresReplay && !thinkingEnabled && !hasModelReasoning && !hasTrace) return;
|
|
66
130
|
for (const message of messages) {
|
|
67
131
|
if (!isRecord(message) || message["role"] !== "assistant") continue;
|
|
68
132
|
if (typeof message["reasoning_content"] === "string") continue;
|
|
@@ -549,6 +549,7 @@ export function canonicalToChatPayload(
|
|
|
549
549
|
...(options.maxOutputTokens === undefined ? {} : { maxOutputTokens: options.maxOutputTokens }),
|
|
550
550
|
});
|
|
551
551
|
delete payload["max_tokens"];
|
|
552
|
+
delete payload["max_completion_tokens"];
|
|
552
553
|
if (bounded !== undefined) payload[tokenLimitField] = bounded;
|
|
553
554
|
return payload;
|
|
554
555
|
}
|
|
@@ -472,8 +472,8 @@ export async function* decodeChatSseStream(
|
|
|
472
472
|
"platform_unavailable",
|
|
473
473
|
502,
|
|
474
474
|
error instanceof Error
|
|
475
|
-
? `Malformed SSE event: ${error.message}
|
|
476
|
-
:
|
|
475
|
+
? `Malformed SSE event: ${error.message} (request {requestId}). The upstream sent something we can't read — retry 🌙`
|
|
476
|
+
: `Malformed SSE event (request {requestId}). The upstream sent something we can't read — retry 🌙`,
|
|
477
477
|
{},
|
|
478
478
|
"upstream",
|
|
479
479
|
);
|
|
@@ -364,7 +364,7 @@ export class CodexStreamFrameProcessor {
|
|
|
364
364
|
throw new GatewayError(
|
|
365
365
|
"tool_call_loop_detected",
|
|
366
366
|
502,
|
|
367
|
-
"Codex whitespace-loop guard tripped: tool call emitted only whitespace argument deltas past the safety threshold",
|
|
367
|
+
"Codex whitespace-loop guard tripped (request {requestId}): tool call emitted only whitespace argument deltas past the safety threshold. Caught in a loop — breaking the cycle 🔁",
|
|
368
368
|
{ call_key: key, event_count: next.count, char_count: next.chars },
|
|
369
369
|
);
|
|
370
370
|
}
|
|
@@ -124,7 +124,7 @@ export function decodeGeminiStreamEvent(
|
|
|
124
124
|
throw new GatewayError(
|
|
125
125
|
"platform_unavailable",
|
|
126
126
|
502,
|
|
127
|
-
error instanceof Error ? `Malformed SSE event: ${error.message}
|
|
127
|
+
error instanceof Error ? `Malformed SSE event: ${error.message} (request {requestId}). The upstream sent something we can't read — retry 🌙` : `Malformed SSE event (request {requestId}). The upstream sent something we can't read — retry 🌙`,
|
|
128
128
|
{},
|
|
129
129
|
"upstream",
|
|
130
130
|
);
|
|
@@ -51,7 +51,7 @@ export function parseClaudeContent(value: unknown, isOAuth = false): ContentPart
|
|
|
51
51
|
throw new GatewayError(
|
|
52
52
|
"platform_unavailable",
|
|
53
53
|
502,
|
|
54
|
-
"Claude response tool_use is missing id or name",
|
|
54
|
+
"Claude response tool_use is missing id or name (request {requestId}). The upstream came back empty-handed — retry 🌙",
|
|
55
55
|
{},
|
|
56
56
|
"upstream",
|
|
57
57
|
);
|
|
@@ -69,7 +69,7 @@ export function parseClaudeContent(value: unknown, isOAuth = false): ContentPart
|
|
|
69
69
|
throw new GatewayError(
|
|
70
70
|
"platform_unavailable",
|
|
71
71
|
502,
|
|
72
|
-
"Claude response tool_result is missing tool_use_id",
|
|
72
|
+
"Claude response tool_result is missing tool_use_id (request {requestId}). The upstream came back empty-handed — retry 🌙",
|
|
73
73
|
{},
|
|
74
74
|
"upstream",
|
|
75
75
|
);
|
|
@@ -467,7 +467,7 @@ export async function* parseClaudeSseStream(
|
|
|
467
467
|
throw new GatewayError(
|
|
468
468
|
"platform_unavailable",
|
|
469
469
|
502,
|
|
470
|
-
"Malformed Claude SSE event",
|
|
470
|
+
"Malformed Claude SSE event (request {requestId}). The upstream sent something we can't read — retry 🌙",
|
|
471
471
|
{},
|
|
472
472
|
"upstream",
|
|
473
473
|
);
|
|
@@ -476,7 +476,7 @@ export async function* parseClaudeSseStream(
|
|
|
476
476
|
throw new GatewayError(
|
|
477
477
|
"platform_unavailable",
|
|
478
478
|
502,
|
|
479
|
-
"Invalid Claude SSE event",
|
|
479
|
+
"Invalid Claude SSE event (request {requestId}). The upstream sent something we can't read — retry 🌙",
|
|
480
480
|
{},
|
|
481
481
|
"upstream",
|
|
482
482
|
);
|
|
@@ -492,7 +492,7 @@ export async function* parseClaudeSseStream(
|
|
|
492
492
|
throw new GatewayError(
|
|
493
493
|
"platform_unavailable",
|
|
494
494
|
502,
|
|
495
|
-
"Claude stream ended before message_stop",
|
|
495
|
+
"Claude stream ended before message_stop (request {requestId}). The upstream came back empty-handed — retry 🌙",
|
|
496
496
|
{},
|
|
497
497
|
"upstream",
|
|
498
498
|
);
|
|
@@ -336,8 +336,8 @@ export async function* decodeResponsesSseStream(
|
|
|
336
336
|
"platform_unavailable",
|
|
337
337
|
502,
|
|
338
338
|
error instanceof Error
|
|
339
|
-
? `Malformed SSE event: ${error.message}
|
|
340
|
-
:
|
|
339
|
+
? `Malformed SSE event: ${error.message} (request {requestId}). The upstream sent something we can't read — retry 🌙`
|
|
340
|
+
: `Malformed SSE event (request {requestId}). The upstream sent something we can't read — retry 🌙`,
|
|
341
341
|
{},
|
|
342
342
|
"upstream",
|
|
343
343
|
);
|
|
@@ -57,14 +57,14 @@ const DEFAULT_MAX_EVENT_BYTES = 4 * 1024 * 1024;
|
|
|
57
57
|
// `upstream` origin keeps the public envelope from prefixing it
|
|
58
58
|
// a gateway-branded message, which blamed the gateway for the provider's bytes.
|
|
59
59
|
function oversized(what: string): GatewayError {
|
|
60
|
-
return new GatewayError("platform_unavailable", 502, `SSE ${what} exceeds size bound
|
|
60
|
+
return new GatewayError("platform_unavailable", 502, `SSE ${what} exceeds size bound (request {requestId}). The upstream sent more than we can hold — retry 🌙`, {}, "upstream");
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export async function* decodeSseEvents(
|
|
64
64
|
body: ReadableStream<Uint8Array> | null,
|
|
65
65
|
opts: SseDecodeOptions = {},
|
|
66
66
|
): AsyncIterable<SseEvent> {
|
|
67
|
-
if (body === null) throw new GatewayError("platform_unavailable", 502, "SSE response has no body", {}, "upstream");
|
|
67
|
+
if (body === null) throw new GatewayError("platform_unavailable", 502, "SSE response has no body (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
68
68
|
const maxEventBytes = opts.maxEventBytes ?? DEFAULT_MAX_EVENT_BYTES;
|
|
69
69
|
const maxLineBytes = opts.maxLineBytes ?? maxEventBytes;
|
|
70
70
|
const signal = opts.signal;
|
|
@@ -163,7 +163,18 @@ export async function* decodeSseEvents(
|
|
|
163
163
|
// not 502 the request: only `event:`/`data:` carry payload, the rest is
|
|
164
164
|
// heartbeat/metadata. Unknown colon-prefixed fields are ignored.
|
|
165
165
|
if (/^[a-zA-Z0-9_-]+:/.test(line)) return;
|
|
166
|
-
|
|
166
|
+
// Some vendors (Qoder IDE /model/v1) emit a single `data:` event whose JSON
|
|
167
|
+
// payload contains literal newlines, so the event arrives as several lines:
|
|
168
|
+
// the first has the `data:` prefix, the rest are bare continuations. Treat
|
|
169
|
+
// a bare line as a continuation of the open `data:` event rather than a
|
|
170
|
+
// malformed stream. A bare line with no open data is still an error.
|
|
171
|
+
if (dataLines.length > 0) {
|
|
172
|
+
dataLines.push(line);
|
|
173
|
+
eventBytes += line.length;
|
|
174
|
+
if (eventBytes > maxEventBytes) throw oversized("event");
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
throw new GatewayError("platform_unavailable", 502, "Malformed SSE line (request {requestId}). The upstream sent something we can't read — retry 🌙", {}, "upstream");
|
|
167
178
|
}
|
|
168
179
|
|
|
169
180
|
try {
|
|
@@ -259,7 +259,7 @@ export function ensureMaxTokensForThinking(
|
|
|
259
259
|
throw new GatewayError(
|
|
260
260
|
"invalid_request",
|
|
261
261
|
400,
|
|
262
|
-
`Anthropic thinking budget requires max_tokens greater than ${buffer}; got ${currentMax} with budget ${budget}
|
|
262
|
+
`Anthropic thinking budget requires max_tokens greater than ${buffer}; got ${currentMax} with budget ${budget} (request {requestId}). Check the shape of what you sent — then resend 🪞`,
|
|
263
263
|
{ budget_tokens: budget, max_tokens: currentMax },
|
|
264
264
|
);
|
|
265
265
|
}
|
|
@@ -364,7 +364,7 @@ function validateCacheBreakpoints(hint: CacheHint | undefined): void {
|
|
|
364
364
|
throw new GatewayError(
|
|
365
365
|
"invalid_request",
|
|
366
366
|
400,
|
|
367
|
-
`cache_hint declares ${hint.list.length} breakpoints; at most ${MAX_CACHE_BREAKPOINTS} are supported
|
|
367
|
+
`cache_hint declares ${hint.list.length} breakpoints; at most ${MAX_CACHE_BREAKPOINTS} are supported (request {requestId}). Check the shape of what you sent — then resend 🪞`,
|
|
368
368
|
{ breakpoint_count: hint.list.length, max_breakpoints: MAX_CACHE_BREAKPOINTS },
|
|
369
369
|
);
|
|
370
370
|
}
|