cartethyia 2.7.4 → 2.7.5
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/README.md +17 -5
- package/dist/dashboard/assets/{BannedPage-ChG5svFb.js → BannedPage-Cj15GsMp.js} +1 -1
- package/dist/dashboard/assets/BannedPage-Cj15GsMp.js.gz +0 -0
- package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js +1 -0
- package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-BIcWQun7.js → CliToolDetailPage-DZKmVuJN.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-Bf2VelDe.js → CliToolsPage-BIccZmeL.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-BIccZmeL.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DEEKkYD-.js +1 -0
- package/dist/dashboard/assets/CombosPage-DEEKkYD-.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-ggeMSjte.js → CustomizationPage-BKj_r0rp.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js +656 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-U6AKaJcF.js → LoginPage-DkXb6dx-.js} +1 -1
- package/dist/dashboard/assets/LoginPage-DkXb6dx-.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BMS2t8BA.js → ModelPicker-DnQPSIA8.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-DnQPSIA8.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js +1 -0
- package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js +5 -0
- package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-lsrx7NYg.js → ProxyPage-5SnUSy3j.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js +1 -0
- package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js +1 -0
- package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-f0DE1h0-.js → SetupPage-WTa0sktC.js} +1 -1
- package/dist/dashboard/assets/SetupPage-WTa0sktC.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-Cq5tW7hW.js → StudioPage-BfAb5-o_.js} +3 -3
- package/dist/dashboard/assets/StudioPage-BfAb5-o_.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js +56 -0
- package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-l29JROPv.js +1 -0
- package/dist/dashboard/assets/api-keys-l29JROPv.js.gz +0 -0
- package/dist/dashboard/assets/app--PJWq8_C.css +2 -0
- package/dist/dashboard/assets/app--PJWq8_C.css.gz +0 -0
- package/dist/dashboard/assets/assert-C0K-c4eo.js +1 -0
- package/dist/dashboard/assets/assert-C0K-c4eo.js.gz +0 -0
- package/dist/dashboard/assets/{badge-D9CS6dZk.js → badge-DfbhQU4k.js} +1 -1
- package/dist/dashboard/assets/badge-DfbhQU4k.js.gz +0 -0
- package/dist/dashboard/assets/{banner-DympBNq4.js → banner-BllT4LcF.js} +1 -1
- package/dist/dashboard/assets/banner-BllT4LcF.js.gz +0 -0
- package/dist/dashboard/assets/brand/login_register.jpg +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
- package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/og_image.jpg +0 -0
- package/dist/dashboard/assets/{card-ibRWU_uT.js → card-CtkX66Wd.js} +1 -1
- package/dist/dashboard/assets/card-CtkX66Wd.js.gz +0 -0
- package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js +1 -0
- package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-CQXVMn4P.js +1 -0
- package/dist/dashboard/assets/cli-tools-CQXVMn4P.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-DJm-9pz7.js → clipboard-button-BQqBQd2n.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-BQqBQd2n.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-8fiE6tec.js → dialog-BEK7KP0H.js} +1 -1
- package/dist/dashboard/assets/dialog-BEK7KP0H.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-CqGYeOrd.js → drawer-BrTEZ8zu.js} +1 -1
- package/dist/dashboard/assets/drawer-BrTEZ8zu.js.gz +0 -0
- package/dist/dashboard/assets/entry-CHWxbS56.js +1 -0
- package/dist/dashboard/assets/entry-CHWxbS56.js.gz +0 -0
- package/dist/dashboard/assets/{entry-BSAsYsmV.js → entry-EBHJ3DrE.js} +1 -1
- package/dist/dashboard/assets/entry-EBHJ3DrE.js.gz +0 -0
- package/dist/dashboard/assets/entry-LCS85hiq.js +4 -0
- package/dist/dashboard/assets/entry-LCS85hiq.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-D-MTJxMr.js → formatters-BAACA727.js} +1 -1
- package/dist/dashboard/assets/formatters-BAACA727.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-CPK47xQx.js → github-badge-YSCE4UC2.js} +1 -1
- package/dist/dashboard/assets/github-badge-YSCE4UC2.js.gz +0 -0
- package/dist/dashboard/assets/{index-DBNPOfhV.js → index-FQlj0HUT.js} +2 -2
- package/dist/dashboard/assets/index-FQlj0HUT.js.gz +0 -0
- package/dist/dashboard/assets/{inline-BT0zntEr.js → inline-C4gU5iOL.js} +1 -1
- package/dist/dashboard/assets/inline-C4gU5iOL.js.gz +0 -0
- package/dist/dashboard/assets/{input-Da2ZViWg.js → input-BHHX_Tmm.js} +1 -1
- package/dist/dashboard/assets/input-BHHX_Tmm.js.gz +0 -0
- package/dist/dashboard/assets/{layout-BXNeLw6M.js → layout-CajrYUnD.js} +1 -1
- package/dist/dashboard/assets/layout-CajrYUnD.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-BycOrrIV.js → list-row-Bx2I7XUe.js} +1 -1
- package/dist/dashboard/assets/list-row-Bx2I7XUe.js.gz +0 -0
- package/dist/dashboard/assets/live-DNNqRbj2.js +1 -0
- package/dist/dashboard/assets/live-DNNqRbj2.js.gz +0 -0
- package/dist/dashboard/assets/network-C5UBACrJ.js +1 -0
- package/dist/dashboard/assets/network-C5UBACrJ.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CUWAZK4J.js +2 -0
- package/dist/dashboard/assets/numbers-CUWAZK4J.js.gz +0 -0
- package/dist/dashboard/assets/pagination-Xsi9Qx04.js +1 -0
- package/dist/dashboard/assets/pagination-Xsi9Qx04.js.gz +0 -0
- package/dist/dashboard/assets/quota-DvQbD_sr.js +1 -0
- package/dist/dashboard/assets/quota-DvQbD_sr.js.gz +0 -0
- package/dist/dashboard/assets/routing-in0f_IfK.js +1 -0
- package/dist/dashboard/assets/routing-in0f_IfK.js.gz +0 -0
- package/dist/dashboard/assets/{select-BIYIBkMU.js → select-BLULNqTp.js} +1 -1
- package/dist/dashboard/assets/select-BLULNqTp.js.gz +0 -0
- package/dist/dashboard/assets/settings-CcGczj6I.js +1 -0
- package/dist/dashboard/assets/settings-CcGczj6I.js.gz +0 -0
- package/dist/dashboard/assets/shield-BMQNxORh.js +1 -0
- package/dist/dashboard/assets/shield-BMQNxORh.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DfgnFDWY.js → stack-D2lIR0tg.js} +1 -1
- package/dist/dashboard/assets/stack-D2lIR0tg.js.gz +0 -0
- package/dist/dashboard/assets/{state-CkfdYWel.js → state-DTARjfiW.js} +1 -1
- package/dist/dashboard/assets/state-DTARjfiW.js.gz +0 -0
- package/dist/dashboard/assets/{switch-SYm237HG.js → switch-c0oPSnqW.js} +1 -1
- package/dist/dashboard/assets/switch-c0oPSnqW.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BQqCRNIi.js → useMutation-m8WGNPrz.js} +1 -1
- package/dist/dashboard/assets/useMutation-m8WGNPrz.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.jpg +0 -0
- package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +56 -55
- package/migrations/full-postgres/0036_system_prompt_presets_refs.sql +10 -0
- package/migrations/full-postgres/0037_share_concurrency_scope.sql +3 -0
- package/migrations/full-postgres/0038_add_accounts_quota_snapshot.sql +7 -0
- package/migrations/full-postgres/0039_add_telemetry_provider_model_indexes.sql +13 -0
- package/migrations/full-postgres/0040_add_models_dialect.sql +3 -0
- package/migrations/full-postgres/0041_add_daily_checkin_ledger_account_fk.sql +7 -0
- package/migrations/lite-sqlite3/0036_system_prompt_presets_refs.sql +14 -0
- package/migrations/lite-sqlite3/0037_share_concurrency_scope.sql +3 -0
- package/migrations/lite-sqlite3/0038_add_accounts_quota_snapshot.sql +4 -0
- package/migrations/lite-sqlite3/0039_add_telemetry_provider_model_indexes.sql +6 -0
- package/migrations/lite-sqlite3/0040_add_models_dialect.sql +3 -0
- package/migrations/lite-sqlite3/0041_add_daily_checkin_ledger_account_fk.sql +26 -0
- package/package.json +4 -6
- package/scripts/cli/update.ts +15 -41
- package/src/boot/app.ts +58 -2
- package/src/boot/dependencies.ts +786 -732
- package/src/boot/lifecycle.ts +11 -12
- package/src/config/cooldowns.ts +34 -55
- package/src/config/network.ts +1 -1
- package/src/config/package-paths.ts +1 -1
- package/src/config/providers.ts +13 -14
- package/src/config/resources.ts +0 -5
- package/src/config/runtime.ts +2 -5
- package/src/config/spec.ts +26 -6
- package/src/console/api-keys/ban-constants.ts +38 -0
- package/src/console/api-keys/contracts.ts +71 -62
- package/src/console/api-keys/routes.ts +48 -12
- package/src/console/auth/access.ts +4 -4
- package/src/console/auth/account-reset.ts +1 -1
- package/src/console/auth/audit-recorder.ts +1 -1
- package/src/console/auth/lockout.ts +7 -3
- package/src/console/auth/row-coercion.ts +57 -12
- package/src/console/auth/session-resolver.ts +1 -1
- package/src/console/auth/session-service.ts +8 -2
- package/src/console/auth/session.ts +37 -31
- package/src/console/auth/setup-service.ts +4 -3
- package/src/console/auth/user-store.ts +1 -1
- package/src/console/backup/9router-map.ts +0 -3
- package/src/console/backup/contracts.ts +9 -9
- package/src/console/backup/export-endpoint.ts +5 -6
- package/src/console/backup/nine-router.ts +1 -7
- package/src/console/backup/routes.ts +29 -33
- package/src/console/backup/service.ts +468 -468
- package/src/console/backup/store.ts +47 -29
- package/src/console/backup/validate.ts +11 -1
- package/src/console/cli-tools/contracts.ts +7 -7
- package/src/console/cli-tools/injectors/cline.ts +110 -110
- package/src/console/cli-tools/injectors/codex.ts +181 -181
- package/src/console/cli-tools/injectors/copilot.ts +108 -108
- package/src/console/cli-tools/injectors/driver.ts +247 -247
- package/src/console/cli-tools/injectors/jcode.ts +115 -115
- package/src/console/cli-tools/injectors/openclaw.ts +161 -161
- package/src/console/cli-tools/injectors/opencode.ts +153 -153
- package/src/console/cli-tools/routes.ts +1 -1
- package/src/console/cli-tools/service.ts +1 -1
- package/src/console/cli-tools/store.ts +2 -2
- package/src/console/console-router.ts +32 -0
- package/src/console/dashboard-assets.ts +9 -9
- package/src/console/domain-registration.ts +16 -2
- package/src/console/inferhub/client.ts +0 -7
- package/src/console/inferhub/key-store.ts +1 -1
- package/src/console/inferhub/normalize.ts +2 -2
- package/src/console/inferhub/routes.ts +1 -1
- package/src/console/inferhub/types.ts +1 -1
- package/src/console/observability/contracts.ts +1 -1
- package/src/console/observability/live.ts +31 -18
- package/src/console/observability/logs.ts +1 -1
- package/src/console/observability/sse.ts +62 -8
- package/src/console/observability/store.ts +20 -4
- package/src/console/providers/catalog/catalog-projections.ts +1 -1
- package/src/console/providers/catalog/contracts.ts +39 -0
- package/src/console/providers/catalog/model-operations.ts +1 -1
- package/src/console/providers/catalog/provider-operations.ts +95 -17
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/routes.ts +27 -3
- package/src/console/providers/catalog/store.ts +147 -8
- package/src/console/providers/detail/routes.ts +1 -1
- package/src/console/providers/oauth/callback-listener.ts +100 -18
- package/src/console/providers/oauth/routes.ts +92 -13
- package/src/console/quota/account-quota-global.ts +41 -3
- package/src/console/quota/account-quota-shared.ts +10 -3
- package/src/console/quota/account-quota-tenant.ts +259 -66
- package/src/console/quota/daily-growth-job.ts +308 -0
- package/src/console/quota/native-identity.ts +2 -2
- package/src/console/quota/quota-view.ts +1 -1
- package/src/console/remote-support/routes.ts +1 -1
- package/src/console/remote-support/service.ts +2 -2
- package/src/console/routing/model/contracts.ts +1 -1
- package/src/console/routing/pools/contracts.ts +2 -2
- package/src/console/routing/pools/import-parse.ts +3 -3
- package/src/console/routing/pools/relay-deploy.ts +3 -3
- package/src/console/settings/contracts.ts +29 -2
- package/src/console/settings/store.ts +20 -0
- package/src/console/share/share-router.ts +100 -32
- package/src/console/share/share-stats.ts +4 -4
- package/src/console/share/share-usage.ts +1 -1
- package/src/console/shared/errors.ts +2 -2
- package/src/console/shared/object.ts +18 -0
- package/src/console/shared/read-through-cache.ts +4 -4
- package/src/console/shared/transaction.ts +14 -0
- package/src/console/system-prompt/contracts.ts +89 -12
- package/src/console/system-prompt/routes.ts +98 -45
- package/src/console/updates/apply-update.ts +244 -0
- package/src/console/updates/routes.ts +252 -97
- package/src/main.ts +11 -5
- package/src/network/http2-bidi.ts +1 -1
- package/src/network/ip-utils.ts +298 -0
- package/src/network/outbound-fetch.ts +1 -1
- package/src/network/pool/agent.ts +7 -6
- package/src/network/pool/byte-accounting.ts +1 -1
- package/src/network/pool/resolver.ts +16 -12
- package/src/network/pool/selector.ts +150 -136
- package/src/network/pool-health-machine.ts +1 -1
- package/src/network/rate-limit-verdict.ts +1 -1
- package/src/network/retry.ts +1 -1
- package/src/network/ssrf.ts +20 -71
- package/src/observability/event-log.ts +2 -2
- package/src/observability/event-query.ts +59 -291
- package/src/observability/log-sanitize.ts +1 -1
- package/src/observability/metrics.ts +4 -4
- package/src/observability/payload-capture.ts +6 -5
- package/src/observability/payload-store.ts +1 -1
- package/src/observability/runtime-metrics.ts +1 -1
- package/src/observability/telemetry-buffer.ts +12 -3
- package/src/observability/telemetry-reconciliation.ts +3 -3
- package/src/observability/token-speed.ts +1 -1
- package/src/persistence/connection-url.ts +1 -1
- package/src/persistence/db-handle.ts +1 -1
- package/src/persistence/db-pg.ts +2 -2
- package/src/persistence/db-sqlite.ts +3 -3
- package/src/persistence/dialect.ts +5 -1
- package/src/persistence/readiness.ts +1 -1
- package/src/persistence/redis.ts +1 -1
- package/src/persistence/schema.ts +113 -40
- package/src/persistence/shared-cache.ts +2 -2
- package/src/persistence/sort-order.ts +1 -1
- package/src/persistence/stores/api-key-store.ts +10 -1
- package/src/persistence/stores/share-store.ts +20 -8
- package/src/persistence/system-prompt-store.ts +63 -18
- package/src/persistence/tenant-preferences.ts +1 -1
- package/src/persistence/ttl-cache.ts +3 -3
- package/src/persistence/usage-flush.ts +7 -2
- package/src/protocol/canonical-model.ts +1 -1
- package/src/protocol/codecs.ts +6 -10
- package/src/protocol/dsml.ts +3 -49
- package/src/protocol/image-clamp.ts +2 -2
- package/src/protocol/reasoning-text.ts +1 -1
- package/src/protocol/reasoning.ts +7 -52
- package/src/protocol/registry.ts +50 -15
- package/src/protocol/request/chat.ts +46 -19
- package/src/protocol/request/codex.ts +1 -1
- package/src/protocol/request/messages.ts +46 -14
- package/src/protocol/request/responses.ts +19 -10
- package/src/protocol/response/chat.ts +25 -10
- package/src/protocol/response/codex.ts +1 -1
- package/src/protocol/response/messages.ts +1 -1
- package/src/protocol/response/omp-tool-call-markup.ts +1 -1
- package/src/protocol/response/responses.ts +1 -1
- package/src/protocol/sse-decoder.ts +50 -1
- package/src/protocol/stop-reason.ts +2 -2
- package/src/protocol/think-split.ts +347 -0
- package/src/protocol/translation/attachment-integrity.ts +3 -3
- package/src/protocol/translation/cache-controls.ts +8 -20
- package/src/protocol/translation/capabilities.ts +6 -6
- package/src/protocol/translation/dialect/deepseek.ts +10 -0
- package/src/protocol/translation/dialect/inband.ts +39 -0
- package/src/protocol/translation/dialect/model-thinking.ts +89 -0
- package/src/protocol/translation/dialect/qwen.ts +114 -0
- package/src/protocol/translation/dialect/resolver.ts +83 -0
- package/src/protocol/translation/dialect/rules.ts +93 -0
- package/src/protocol/translation/dialect/thinking-ladder.ts +245 -0
- package/src/protocol/translation/dialect/types.ts +58 -0
- package/src/protocol/translation/quirks.ts +18 -4
- package/src/protocol/translation/thinking.ts +12 -252
- package/src/protocol/translation/tool-id.ts +3 -3
- package/src/protocol/translation/tool-repair.ts +1 -1
- package/src/protocol/upstream/post-json.ts +6 -1
- package/src/providers/adapters/compatible-adapter.ts +42 -3
- package/src/providers/authentication/jwt-validator.ts +0 -5
- package/src/providers/authentication/oauth-client.ts +254 -253
- package/src/providers/authentication/oauth-flow-store.ts +42 -7
- package/src/providers/bundled-discovery-providers.ts +6 -30
- package/src/providers/configured-openai-providers.ts +82 -97
- package/src/providers/default-registry.ts +23 -35
- package/src/providers/discovery/catalog/catalog-format.ts +13 -2
- package/src/providers/discovery/discovery-types.ts +0 -1
- package/src/providers/discovery/openai-model-discovery.ts +2 -2
- package/src/providers/discovery/probe-phases.ts +48 -13
- package/src/providers/discovery/probing-service.ts +6 -8
- package/src/providers/integrations/agentrouter/agentrouter.ts +12 -2
- package/src/providers/integrations/antigravity/antigravity-oauth.ts +0 -1
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +0 -14
- package/src/providers/integrations/antigravity/antigravity.ts +2 -2
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -1
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +20 -4
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +19 -52
- package/src/providers/integrations/buddy/buddy-identity.ts +86 -0
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +1 -1
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +1 -2
- package/src/providers/integrations/buddy/codebuddy/provider.ts +4 -15
- package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
- package/src/providers/integrations/buddy/workbuddy/provider.ts +5 -9
- package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
- package/src/providers/integrations/claude/claude-cch.ts +148 -148
- package/src/providers/integrations/claude/fallback-credit.ts +3 -7
- package/src/providers/integrations/cline/cline.ts +1 -1
- package/src/providers/integrations/codex/codex-device-code.ts +101 -101
- package/src/providers/integrations/codex/codex-oauth.ts +0 -1
- package/src/providers/integrations/commandcode/commandcode.ts +75 -115
- package/src/providers/integrations/commandcode/quota.ts +131 -51
- package/src/providers/integrations/cursor/cursor-http2.ts +0 -3
- package/src/providers/integrations/cursor/cursor-quota.ts +2 -6
- package/src/providers/integrations/cursor/cursor-request.ts +0 -87
- package/src/providers/integrations/cursor/cursor-shared.ts +0 -4
- package/src/providers/integrations/cursor/cursor.ts +38 -5
- package/src/providers/integrations/cursor/protocol/framing.ts +0 -56
- package/src/providers/integrations/cursor/protocol/messages.ts +0 -96
- package/src/providers/integrations/factory/factory.ts +9 -9
- package/src/providers/integrations/github/github-discovery.ts +55 -10
- package/src/providers/integrations/github/github-oauth.ts +6 -5
- package/src/providers/integrations/github/github.ts +41 -14
- package/src/providers/integrations/grok/grok-oauth.ts +261 -261
- package/src/providers/integrations/grok/grok-quota-frame.ts +1 -1
- package/src/providers/integrations/grok/grok-quota.ts +16 -7
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +0 -5
- package/src/providers/integrations/grok/grok-ratelimit.ts +3 -0
- package/src/providers/integrations/grok/grok.ts +86 -36
- package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
- package/src/providers/integrations/kiro/kiro-cache.ts +31 -0
- package/src/providers/integrations/kiro/kiro-catalog.ts +31 -5
- package/src/providers/integrations/kiro/kiro-discovery.ts +47 -43
- package/src/providers/integrations/kiro/kiro-machine-id.ts +6 -3
- package/src/providers/integrations/kiro/kiro-oauth.ts +523 -255
- package/src/providers/integrations/kiro/kiro-quota.ts +35 -47
- package/src/providers/integrations/kiro/kiro-request.ts +8 -11
- package/src/providers/integrations/kiro/kiro-stream.ts +13 -0
- package/src/providers/integrations/kiro/kiro.ts +81 -68
- package/src/providers/integrations/muse/muse-oauth.ts +0 -1
- package/src/providers/integrations/muse/muse-quota.ts +0 -4
- package/src/providers/integrations/opencode/fingerprint.ts +7 -5
- package/src/providers/integrations/opencode/provider.ts +17 -24
- package/src/providers/integrations/qoder/qoder-catalog.ts +85 -8
- package/src/providers/integrations/qoder/qoder-oauth.ts +165 -25
- package/src/providers/integrations/qoder/qoder-quota.ts +48 -6
- package/src/providers/integrations/qoder/qoder-realm.ts +10 -31
- package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
- package/src/providers/integrations/qoder/qoder-umid.ts +21 -25
- package/src/providers/integrations/qoder/qoder-virtual.ts +12 -0
- package/src/providers/integrations/qoder/qoder.ts +354 -62
- package/src/providers/integrations/shared/connect.ts +0 -54
- package/src/providers/integrations/shared/hash.ts +15 -0
- package/src/providers/integrations/shared/session-cache.ts +136 -0
- package/src/providers/integrations/trae/trae-cache.ts +7 -87
- package/src/providers/integrations/trae/trae-catalog.ts +1 -6
- package/src/providers/integrations/trae/trae-protocol.ts +17 -8
- package/src/providers/integrations/trae/trae-realm.ts +0 -7
- package/src/providers/integrations/trae/trae.ts +53 -10
- package/src/providers/integrations/xai/xai-oauth.ts +5 -5
- package/src/providers/integrations/zcode-start-plan/gifts.ts +265 -0
- package/src/providers/integrations/zcode-start-plan/oauth.ts +154 -0
- package/src/providers/integrations/zcode-start-plan/provider.ts +131 -0
- package/src/providers/integrations/zcode-start-plan/quota.ts +152 -0
- package/src/providers/integrations/zcode-start-plan/zcode_prompt.json +10 -0
- package/src/providers/operations/account-error-classification.ts +131 -116
- package/src/providers/operations/account-health-service.ts +31 -39
- package/src/providers/operations/account-reset-service.ts +58 -1
- package/src/providers/operations/cli-platform.ts +31 -5
- package/src/providers/operations/client-version-resolver.ts +4 -6
- package/src/providers/operations/client-versions.ts +10 -7
- package/src/providers/operations/provider-catalog-seeder.ts +2 -0
- package/src/providers/operations/provider-catalog-service.ts +18 -2
- package/src/providers/operations/provider-credential-service.ts +19 -2
- package/src/providers/provider-contracts.ts +18 -7
- package/src/providers/provider-metadata.ts +17 -2
- package/src/providers/provider-model-definition.ts +22 -1
- package/src/providers/provider-probe.ts +4 -2
- package/src/providers/provider-usage.ts +81 -16
- package/src/providers/quota/cache.ts +29 -0
- package/src/providers/quota/quota-contracts.ts +4 -0
- package/src/providers/quota/refresh.ts +27 -4
- package/src/providers/quota/snapshot.ts +94 -0
- package/src/providers/search/search-provider.ts +1 -3
- package/src/providers/search/search-providers.ts +2 -1
- package/src/security/abuse.ts +4 -4
- package/src/security/admission/service.ts +2 -2
- package/src/security/admission/ttl.ts +61 -61
- package/src/security/api-key-auth.ts +16 -4
- package/src/security/client-router-fingerprint.ts +4 -4
- package/src/security/credential-codec.ts +3 -3
- package/src/security/csrf.ts +2 -2
- package/src/security/ip-boundary.ts +16 -189
- package/src/security/model-access-rule.ts +1 -1
- package/src/security/outbound-headers.ts +18 -0
- package/src/shared/invoke-guard.ts +47 -0
- package/src/transport/contracts.ts +13 -4
- package/src/transport/dispatch/attempt-finalize.ts +1 -1
- package/src/transport/dispatch/capture-policy.ts +34 -37
- package/src/transport/dispatch/empty-stream.ts +23 -20
- package/src/transport/dispatch/generation.ts +25 -45
- package/src/transport/dispatch/native-shared.ts +127 -0
- package/src/transport/dispatch/proxy-request.ts +3 -1
- package/src/transport/dispatch/responses-compact.ts +25 -46
- package/src/transport/dispatch/streaming/encoder.ts +18 -72
- package/src/transport/dispatch/streaming/finalize.ts +2 -2
- package/src/transport/dispatch/streaming/stream.ts +7 -3
- package/src/transport/dispatch/streaming/thinking-loop.ts +1 -1
- package/src/transport/dispatch/systemone.ts +20 -48
- package/src/transport/dispatch/token-count.ts +41 -38
- package/src/transport/dispatch/websearch.ts +19 -45
- package/src/transport/drain-endpoint.ts +1 -1
- package/src/transport/failure-policy.ts +6 -0
- package/src/transport/guards/auth-guard.ts +0 -6
- package/src/transport/guards/body-policy.ts +3 -3
- package/src/transport/guards/headroom-guard.ts +1 -1
- package/src/transport/guards/pipeline.ts +1 -1
- package/src/transport/guards/request-context.ts +12 -17
- package/src/transport/request/preparer.ts +286 -7
- package/src/transport/request/rtk/compress-request.ts +3 -3
- package/src/transport/request/rtk/rtk-constants.ts +2 -2
- package/src/transport/request/rtk/rtk-filters.ts +8 -8
- package/src/transport/request/state.ts +1 -13
- package/src/transport/request/system-prompt-injection.ts +74 -11
- package/src/transport/request/tenant-preferences.ts +22 -0
- package/src/transport/routing/candidate-index.ts +2 -2
- package/src/transport/routing/round-robin.ts +1 -1
- package/src/transport/routing/route-catalog.ts +17 -18
- package/src/transport/routing/route-model.ts +126 -33
- package/src/transport/routing/router.ts +152 -37
- package/src/transport/shutdown-notice.ts +2 -2
- package/src/transport/surface/adapters.ts +3 -3
- package/src/transport/surface/chat/encode.ts +7 -7
- package/src/transport/surface/chat/parse.ts +7 -7
- package/src/transport/surface/completion.ts +2 -2
- package/src/transport/surface/content-parts.ts +1 -1
- package/src/transport/surface/dialects.ts +2 -2
- package/src/transport/surface/messages/parse.ts +3 -3
- package/src/transport/surface/responses/adapter.ts +1 -1
- package/src/transport/surface/responses/errors.ts +0 -9
- package/src/transport/vision-handoff-cache.ts +58 -0
- package/src/transport/vision-handoff.ts +376 -0
- package/src/workers/account-identity-sweep.ts +2 -2
- package/src/workers/buddy-growth-tasks.ts +392 -346
- package/src/workers/daily-checkin.ts +102 -49
- package/src/workers/maintenance-lease.ts +2 -35
- package/src/workers/oauth-refresh-sweep.ts +8 -1
- package/src/workers/quota-refresh-sweep.ts +120 -82
- package/src/workers/scheduler.ts +67 -4
- package/src/workers/sweep-runner.ts +24 -5
- package/src/workers/zcode-gift-claim.ts +108 -0
- package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js +0 -1
- package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
- package/dist/dashboard/assets/BannedPage-ChG5svFb.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DCgm5g6g.js +0 -1
- package/dist/dashboard/assets/CombosPage-DCgm5g6g.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-U6AKaJcF.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BMS2t8BA.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js +0 -1
- package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js +0 -5
- package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-L0skzML8.js +0 -1
- package/dist/dashboard/assets/QuotaPage-L0skzML8.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js +0 -1
- package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-f0DE1h0-.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-ClzpwyET.js +0 -1
- package/dist/dashboard/assets/SortableList-ClzpwyET.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-Cq5tW7hW.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js +0 -56
- package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-C38iXoO4.js +0 -1
- package/dist/dashboard/assets/api-keys-C38iXoO4.js.gz +0 -0
- package/dist/dashboard/assets/app-a159k0ub.css +0 -2
- package/dist/dashboard/assets/app-a159k0ub.css.gz +0 -0
- package/dist/dashboard/assets/assert-DbRA8EG2.js +0 -15
- package/dist/dashboard/assets/assert-DbRA8EG2.js.gz +0 -0
- package/dist/dashboard/assets/badge-D9CS6dZk.js.gz +0 -0
- package/dist/dashboard/assets/banner-DympBNq4.js.gz +0 -0
- package/dist/dashboard/assets/card-ibRWU_uT.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-63qBGaRw.js +0 -1
- package/dist/dashboard/assets/cli-tools-63qBGaRw.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DJm-9pz7.js.gz +0 -0
- package/dist/dashboard/assets/dialog-8fiE6tec.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CqGYeOrd.js.gz +0 -0
- package/dist/dashboard/assets/entry-BSAsYsmV.js.gz +0 -0
- package/dist/dashboard/assets/entry-CiIQULZD.js +0 -4
- package/dist/dashboard/assets/entry-CiIQULZD.js.gz +0 -0
- package/dist/dashboard/assets/entry-Daz0lJ-W.js +0 -1
- package/dist/dashboard/assets/entry-Daz0lJ-W.js.gz +0 -0
- package/dist/dashboard/assets/formatters-D-MTJxMr.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-CPK47xQx.js.gz +0 -0
- package/dist/dashboard/assets/index-DBNPOfhV.js.gz +0 -0
- package/dist/dashboard/assets/inline-BT0zntEr.js.gz +0 -0
- package/dist/dashboard/assets/input-Da2ZViWg.js.gz +0 -0
- package/dist/dashboard/assets/layout-BXNeLw6M.js.gz +0 -0
- package/dist/dashboard/assets/list-row-BycOrrIV.js.gz +0 -0
- package/dist/dashboard/assets/live-DCQNZpF7.js +0 -1
- package/dist/dashboard/assets/live-DCQNZpF7.js.gz +0 -0
- package/dist/dashboard/assets/network-9Yj2ZyLo.js +0 -1
- package/dist/dashboard/assets/network-9Yj2ZyLo.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CEagj6ZZ.js +0 -2
- package/dist/dashboard/assets/numbers-CEagj6ZZ.js.gz +0 -0
- package/dist/dashboard/assets/quota-Caw7wqci.js +0 -1
- package/dist/dashboard/assets/quota-Caw7wqci.js.gz +0 -0
- package/dist/dashboard/assets/routing-CO9zCUus.js +0 -1
- package/dist/dashboard/assets/routing-CO9zCUus.js.gz +0 -0
- package/dist/dashboard/assets/select-BIYIBkMU.js.gz +0 -0
- package/dist/dashboard/assets/settings-jS3-PNSA.js +0 -1
- package/dist/dashboard/assets/settings-jS3-PNSA.js.gz +0 -0
- package/dist/dashboard/assets/stack-DfgnFDWY.js.gz +0 -0
- package/dist/dashboard/assets/state-CkfdYWel.js.gz +0 -0
- package/dist/dashboard/assets/stream-retry-5QnVuSSo.js +0 -1
- package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
- package/dist/dashboard/assets/switch-SYm237HG.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BQqCRNIi.js.gz +0 -0
- package/src/network/response-headers.ts +0 -39
- package/src/protocol/response/dsml.ts +0 -38
- package/src/providers/integrations/xiaomi-mimo/think-stream.ts +0 -229
- /package/dist/dashboard/assets/{app-BayTTy01.js → app-Bgd3zANQ.js} +0 -0
- /package/dist/dashboard/assets/{app-BayTTy01.js.gz → app-Bgd3zANQ.js.gz} +0 -0
- /package/dist/dashboard/assets/brand/{og_bansos.webp → og_bansos_initial.webp} +0 -0
- /package/dist/dashboard/assets/brand/{og_image.webp → og_image_initial.webp} +0 -0
|
@@ -41,6 +41,77 @@ const MIN_TOKEN_LENGTH = 20;
|
|
|
41
41
|
*/
|
|
42
42
|
const SHARE_STATS_STREAM_INTERVAL_MS = 30_000;
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* A subscriber of the process-wide share-stats sampler. A recipient is keyed
|
|
46
|
+
* by its family (`tenantId` + template key) so one sampler tick can push to
|
|
47
|
+
* many links without a per-connection timer.
|
|
48
|
+
*/
|
|
49
|
+
type ShareStatsListener = (family: ShareFamilyStats) => void;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* One sampler per gateway for share-stats SSE delivery. Previously each
|
|
53
|
+
* `/share/:token/stats/stream` connection ran its own 30s timer and re-read
|
|
54
|
+
* the family rollup; with N open tabs that is N timers and N rollups all
|
|
55
|
+
* reading the same data. Now a single process-level sampler ticks on the
|
|
56
|
+
* shared cadence and fans each computed rollup out to the subscribers whose
|
|
57
|
+
* family it matches. Timers run only while at least one stream is open.
|
|
58
|
+
*/
|
|
59
|
+
function createShareStatsSampler(config: {
|
|
60
|
+
readonly db: CartethyiaDatabase;
|
|
61
|
+
readonly shareStore: ShareLinkStore;
|
|
62
|
+
readonly stats: ShareStatsPort;
|
|
63
|
+
}): { subscribe(token: string, listener: ShareStatsListener): () => void } {
|
|
64
|
+
const subscribers = new Map<string, Set<ShareStatsListener>>();
|
|
65
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
66
|
+
|
|
67
|
+
const tick = async (): Promise<void> => {
|
|
68
|
+
// One resolve per family token: a recipient with several tabs shares one
|
|
69
|
+
// rollup, and a family with no open stream is never computed.
|
|
70
|
+
const tokens = new Set(subscribers.keys());
|
|
71
|
+
for (const token of tokens) {
|
|
72
|
+
const listeners = subscribers.get(token);
|
|
73
|
+
if (listeners === undefined || listeners.size === 0) continue;
|
|
74
|
+
try {
|
|
75
|
+
const family = await resolveFamilyStats(config.db, config.shareStore, config.stats, token);
|
|
76
|
+
if (family === null) continue;
|
|
77
|
+
for (const listener of listeners) listener(family);
|
|
78
|
+
} catch {
|
|
79
|
+
// A transient read must not interrupt the sampler.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const start = (): void => {
|
|
85
|
+
if (timer !== undefined) return;
|
|
86
|
+
timer = setTimeout(async function run() {
|
|
87
|
+
timer = setTimeout(run, SHARE_STATS_STREAM_INTERVAL_MS);
|
|
88
|
+
await tick();
|
|
89
|
+
}, SHARE_STATS_STREAM_INTERVAL_MS);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const stop = (): void => {
|
|
93
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
94
|
+
timer = undefined;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
subscribe(token, listener) {
|
|
99
|
+
let listeners = subscribers.get(token);
|
|
100
|
+
if (listeners === undefined) {
|
|
101
|
+
listeners = new Set();
|
|
102
|
+
subscribers.set(token, listeners);
|
|
103
|
+
}
|
|
104
|
+
listeners.add(listener);
|
|
105
|
+
if (subscribers.size > 0) start();
|
|
106
|
+
return () => {
|
|
107
|
+
listeners.delete(listener);
|
|
108
|
+
if (listeners.size === 0) subscribers.delete(token);
|
|
109
|
+
if (subscribers.size === 0) stop();
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
44
115
|
/**
|
|
45
116
|
* Context and capabilities for one allowed model, sourced from the public
|
|
46
117
|
* catalog. Every field is nullable/optional so the page can show what is known
|
|
@@ -56,7 +127,7 @@ export interface ShareModelInfo {
|
|
|
56
127
|
readonly webSearch: boolean;
|
|
57
128
|
}
|
|
58
129
|
|
|
59
|
-
|
|
130
|
+
interface ShareRouterOptions {
|
|
60
131
|
readonly db: CartethyiaDatabase;
|
|
61
132
|
readonly shareStore: ShareLinkStore;
|
|
62
133
|
/** Resolves the normalized client IP through the trusted-proxy boundary. */
|
|
@@ -263,6 +334,12 @@ async function resolveFamilyStats(
|
|
|
263
334
|
/** Creates the public enrollment page and one-time shared-key issuance route. */
|
|
264
335
|
export function createShareRouter(options: ShareRouterOptions): AnyElysia {
|
|
265
336
|
const { db, shareStore } = options;
|
|
337
|
+
// The sampler is shared by every stats stream this router serves; it is a
|
|
338
|
+
// no-op when the composition omits `stats` (tests that do not exercise the
|
|
339
|
+
// stream), because `tick` never runs with no subscribers.
|
|
340
|
+
const statsSampler = options.stats === undefined
|
|
341
|
+
? undefined
|
|
342
|
+
: createShareStatsSampler({ db, shareStore, stats: options.stats });
|
|
266
343
|
|
|
267
344
|
return new Elysia()
|
|
268
345
|
/**
|
|
@@ -296,6 +373,7 @@ export function createShareRouter(options: ShareRouterOptions): AnyElysia {
|
|
|
296
373
|
oneTimeLimit: row.lifetimeTokenBudget,
|
|
297
374
|
requestsPerMinute: row.requestsPerMinute,
|
|
298
375
|
maxConcurrentRequests: row.maxConcurrentRequests,
|
|
376
|
+
concurrencyScope: row.concurrencyScope ?? "family",
|
|
299
377
|
modelPrefix: row.modelPrefix,
|
|
300
378
|
models,
|
|
301
379
|
modelInfo,
|
|
@@ -442,48 +520,38 @@ export function createShareRouter(options: ShareRouterOptions): AnyElysia {
|
|
|
442
520
|
/**
|
|
443
521
|
* Live family activity for the share page's stats section, pushed over SSE.
|
|
444
522
|
*
|
|
445
|
-
* The stream sends one snapshot immediately on connect, then
|
|
446
|
-
*
|
|
447
|
-
* refreshing. Telemetry is buffered server-side and drained in
|
|
448
|
-
* second, so
|
|
449
|
-
* be. Same bearer token and same masked-IP rollup as the one-shot
|
|
450
|
-
* token that stops resolving closes the stream rather than
|
|
523
|
+
* The stream sends one snapshot immediately on connect, then pushes on a
|
|
524
|
+
* shared sampler cadence so the recipient watches the shared quota move
|
|
525
|
+
* instead of refreshing. Telemetry is buffered server-side and drained in
|
|
526
|
+
* under a second, so the sampler's DB re-read is as fresh as an event bus
|
|
527
|
+
* would be. Same bearer token and same masked-IP rollup as the one-shot
|
|
528
|
+
* route; a token that stops resolving closes the stream rather than
|
|
529
|
+
* reporting zeros.
|
|
451
530
|
*/
|
|
452
531
|
.get("/share/:token/stats/stream", async ({ params, request }) => {
|
|
453
532
|
if (typeof params.token !== "string") return notFound();
|
|
454
533
|
const token = params.token;
|
|
455
534
|
if (token.length < MIN_TOKEN_LENGTH) return notFound();
|
|
456
535
|
const stats = options.stats;
|
|
457
|
-
if (!stats
|
|
536
|
+
if (!stats || statsSampler === undefined) {
|
|
537
|
+
return json({ error: { code: "stats_unavailable", message: "Stats are unavailable" } }, 503);
|
|
538
|
+
}
|
|
539
|
+
const sampler = statsSampler;
|
|
458
540
|
return consoleSseResponse(
|
|
459
541
|
createConsoleSseStream(request.signal, (sender) => {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
542
|
+
const unsubscribe = sampler.subscribe(token, (family) => sender.send("stats", family));
|
|
543
|
+
// Bootstrap: deliver the link's current rollup immediately, and close
|
|
544
|
+
// the stream if the token no longer resolves — the same terminal
|
|
545
|
+
// behavior the per-connection timer had on a dead link.
|
|
546
|
+
void resolveFamilyStats(db, shareStore, stats, token).then((family) => {
|
|
465
547
|
if (family === null) {
|
|
466
548
|
sender.send("error", { code: "link_not_found" });
|
|
467
|
-
|
|
468
|
-
|
|
549
|
+
unsubscribe();
|
|
550
|
+
} else {
|
|
551
|
+
sender.send("stats", family);
|
|
469
552
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
const schedule = () => {
|
|
473
|
-
timer = setTimeout(() => {
|
|
474
|
-
if (closed) return;
|
|
475
|
-
void push().finally(() => {
|
|
476
|
-
if (!closed) schedule();
|
|
477
|
-
});
|
|
478
|
-
}, SHARE_STATS_STREAM_INTERVAL_MS);
|
|
479
|
-
};
|
|
480
|
-
void push().finally(() => {
|
|
481
|
-
if (!closed) schedule();
|
|
482
|
-
});
|
|
483
|
-
return () => {
|
|
484
|
-
closed = true;
|
|
485
|
-
clearTimeout(timer);
|
|
486
|
-
};
|
|
553
|
+
}).catch(() => undefined);
|
|
554
|
+
return unsubscribe;
|
|
487
555
|
}),
|
|
488
556
|
);
|
|
489
557
|
})
|
|
@@ -30,7 +30,7 @@ import { cachedRead } from "../shared/read-through-cache";
|
|
|
30
30
|
import { utcDayStart, utcMonthStart } from "../shared/day";
|
|
31
31
|
|
|
32
32
|
/** Totals for the whole share family, all-time plus the windows the UI draws. */
|
|
33
|
-
|
|
33
|
+
interface ShareFamilyTotals {
|
|
34
34
|
readonly requests: number;
|
|
35
35
|
readonly errors: number;
|
|
36
36
|
readonly inputTokens: number;
|
|
@@ -45,7 +45,7 @@ export interface ShareFamilyTotals {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/** One hour bucket of the last 24 hours, oldest first. */
|
|
48
|
-
|
|
48
|
+
interface ShareHourlyBucket {
|
|
49
49
|
/** ISO instant at the top of the hour. */
|
|
50
50
|
readonly hour: string;
|
|
51
51
|
readonly requests: number;
|
|
@@ -53,7 +53,7 @@ export interface ShareHourlyBucket {
|
|
|
53
53
|
readonly tokens: number;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
interface ShareTopModel {
|
|
57
57
|
readonly modelId: string;
|
|
58
58
|
readonly requests: number;
|
|
59
59
|
readonly tokens: number;
|
|
@@ -63,7 +63,7 @@ export interface ShareTopModel {
|
|
|
63
63
|
readonly avgTtfbMs: number | null;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
interface ShareTopClientIp {
|
|
67
67
|
/** Always masked: this payload is served to the recipient, not the tenant. */
|
|
68
68
|
readonly ip: string;
|
|
69
69
|
readonly requests: number;
|
|
@@ -13,7 +13,7 @@ import { apiKeys, telemetryUsageTotals } from "../../persistence/schema";
|
|
|
13
13
|
import { shouldMaskClientIp } from "../shared/ip-privacy";
|
|
14
14
|
import { utcDayStart } from "../shared/day";
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
interface SharedKeyTokenUsage {
|
|
17
17
|
readonly requests: number;
|
|
18
18
|
readonly errors: number;
|
|
19
19
|
readonly inputTokens: number;
|
|
@@ -7,7 +7,7 @@ import { formatPublicErrorMessage } from "../../protocol/gateway-error";
|
|
|
7
7
|
// (or the storage-layer BackupError, same code/status/message shape) and
|
|
8
8
|
// shapes it through errorResponse; guards narrow to TenantAccessDecision.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
interface ErrorResponseOptions {
|
|
11
11
|
readonly detailsPolicy?: "omit" | "include-if-present" | "always-include";
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -105,7 +105,7 @@ export function requireAnyScope(
|
|
|
105
105
|
return access;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
type TenantAccessDecision = AccessDecision & { readonly tenantId: string };
|
|
109
109
|
|
|
110
110
|
export function requireTenantScope(
|
|
111
111
|
access: AccessDecision | undefined,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds an object from `source`, omitting absent values.
|
|
3
|
+
*
|
|
4
|
+
* The console domains hand-roll the same `...(x === undefined ? {} : { x })`
|
|
5
|
+
* field-omission spread many times (key create/update patches, response
|
|
6
|
+
* sanitizers). This is the single helper for that pattern; `omitNull` extends
|
|
7
|
+
* it to also drop `null` values (the `== null` variant), matching call sites
|
|
8
|
+
* that treat null as "clear this field" rather than "set it to null".
|
|
9
|
+
*/
|
|
10
|
+
export function pickDefined<T extends object>(source: T, omitNull = false): { [K in keyof T]?: Exclude<T[K], undefined> } {
|
|
11
|
+
const out: Record<string, unknown> = {};
|
|
12
|
+
for (const [key, value] of Object.entries(source)) {
|
|
13
|
+
if (value === undefined) continue;
|
|
14
|
+
if (omitNull && value === null) continue;
|
|
15
|
+
out[key] = value;
|
|
16
|
+
}
|
|
17
|
+
return out as { [K in keyof T]?: Exclude<T[K], undefined> };
|
|
18
|
+
}
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
import { createHash } from "node:crypto";
|
|
32
32
|
|
|
33
33
|
/** The slice of Redis this cache needs, so it can be tested without a server. */
|
|
34
|
-
|
|
34
|
+
interface ReadThroughCacheClient {
|
|
35
35
|
get(key: string): Promise<string | null>;
|
|
36
36
|
set(key: string, value: string, mode: "EX", ttlSeconds: number): Promise<unknown>;
|
|
37
37
|
}
|
|
@@ -41,7 +41,7 @@ export interface ReadThroughCacheClient {
|
|
|
41
41
|
* more often pass a shorter TTL; this value suits the dashboard's polling
|
|
42
42
|
* cadence and the append-only telemetry log.
|
|
43
43
|
*/
|
|
44
|
-
|
|
44
|
+
const READ_THROUGH_DEFAULT_TTL_SECONDS = 10;
|
|
45
45
|
|
|
46
46
|
/** The cached value plus the strong validator for the bytes it serializes to. */
|
|
47
47
|
export interface CachedResult<T> {
|
|
@@ -59,7 +59,7 @@ function etagFor(bytes: string): string {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/** The sibling key holding the validator, so a cached body is never re-hashed. */
|
|
62
|
-
|
|
62
|
+
function etagKey(key: string): string {
|
|
63
63
|
return `${key}:etag`;
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -196,7 +196,7 @@ export function conditionalResponse<T>(
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
/** True when an `If-None-Match` header names this validator. */
|
|
199
|
-
|
|
199
|
+
function matchesEtag(header: string | null, etag: string): boolean {
|
|
200
200
|
if (header === null) return false;
|
|
201
201
|
const trimmed = header.trim();
|
|
202
202
|
if (trimmed === "*") return true;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CartethyiaDatabase } from "../../persistence/db-handle";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Casts a Drizzle nested-transaction handle to the shared database type.
|
|
5
|
+
*
|
|
6
|
+
* Drizzle types a nested transaction callback with the dialect's own
|
|
7
|
+
* transaction handle, which differs from `CartethyiaDatabase`. The cast is
|
|
8
|
+
* required (both drivers implement the surface stores use), but it is a type
|
|
9
|
+
* hole every time it appears raw — so it is centralized here rather than
|
|
10
|
+
* scattered across call sites.
|
|
11
|
+
*/
|
|
12
|
+
export function asConsoleDb(tx: unknown): CartethyiaDatabase {
|
|
13
|
+
return tx as unknown as CartethyiaDatabase;
|
|
14
|
+
}
|
|
@@ -16,33 +16,70 @@ export interface PromptSetting {
|
|
|
16
16
|
export const PROMPT_MODES = ["append", "override"] as const;
|
|
17
17
|
export type PromptMode = (typeof PROMPT_MODES)[number];
|
|
18
18
|
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
/**
|
|
20
|
+
* A named, reusable prompt. Scopes reference presets by id instead of each
|
|
21
|
+
* carrying its own text, so one tone/ruleset can be applied to many scopes.
|
|
22
|
+
*/
|
|
23
|
+
export interface SystemPromptPreset {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly name: string;
|
|
26
|
+
readonly enabled: boolean;
|
|
27
|
+
readonly mode: PromptMode;
|
|
28
|
+
readonly text: string;
|
|
24
29
|
}
|
|
25
30
|
|
|
31
|
+
/**
|
|
32
|
+
* How one scope decides what to inject. Modes:
|
|
33
|
+
*
|
|
34
|
+
* - `off` — explicitly inject nothing for this scope; scope-specific stop,
|
|
35
|
+
* does NOT fall through to the global prompt.
|
|
36
|
+
* - `global` — follow the tenant's global prompt (the default posture).
|
|
37
|
+
* - `preset` — use the referenced named preset (disabled preset = no inject).
|
|
38
|
+
* - `custom` — carry this scope's own prompt inline.
|
|
39
|
+
*
|
|
40
|
+
* A scope with no entry at all behaves as `global` (resolution falls through
|
|
41
|
+
* to the global prompt), so omitting a key is the same as an explicit
|
|
42
|
+
* `{ kind: "global" }` reference. The global scope itself never uses
|
|
43
|
+
* `{ kind: "global" }` — it is `null` (off), a preset, or a custom setting.
|
|
44
|
+
*/
|
|
45
|
+
export type ScopePromptRef =
|
|
46
|
+
| { readonly kind: "off" }
|
|
47
|
+
| { readonly kind: "global" }
|
|
48
|
+
| { readonly kind: "preset"; readonly presetId: string }
|
|
49
|
+
| { readonly kind: "custom"; readonly setting: PromptSetting };
|
|
50
|
+
|
|
26
51
|
/** The full per-tenant configuration, one row in `tenant_system_prompts`. */
|
|
27
52
|
export interface TenantSystemPrompts {
|
|
28
|
-
/** `null` =
|
|
29
|
-
readonly global:
|
|
30
|
-
/**
|
|
31
|
-
readonly
|
|
32
|
-
/**
|
|
33
|
-
readonly
|
|
53
|
+
/** `null` = no global injection — distinct from an explicit disabled setting. */
|
|
54
|
+
readonly global: ScopePromptRef | null;
|
|
55
|
+
/** preset id → preset */
|
|
56
|
+
readonly presets: Readonly<Record<string, SystemPromptPreset>>;
|
|
57
|
+
/** providerId → ref */
|
|
58
|
+
readonly providers: Readonly<Record<string, ScopePromptRef>>;
|
|
59
|
+
/** `${providerId}::${modelId}` → ref */
|
|
60
|
+
readonly models: Readonly<Record<string, ScopePromptRef>>;
|
|
61
|
+
/** combo name → ref */
|
|
62
|
+
readonly combos: Readonly<Record<string, ScopePromptRef>>;
|
|
63
|
+
/** alias name → ref */
|
|
64
|
+
readonly aliases: Readonly<Record<string, ScopePromptRef>>;
|
|
34
65
|
}
|
|
35
66
|
|
|
36
67
|
/** Empty configuration: nothing configured anywhere. */
|
|
37
68
|
export const EMPTY_TENANT_SYSTEM_PROMPTS: TenantSystemPrompts = {
|
|
38
69
|
global: null,
|
|
70
|
+
presets: {},
|
|
39
71
|
providers: {},
|
|
40
72
|
models: {},
|
|
73
|
+
combos: {},
|
|
74
|
+
aliases: {},
|
|
41
75
|
};
|
|
42
76
|
|
|
43
77
|
/** Maximum stored text per scope (5 MB). Enforced at the console API. */
|
|
44
78
|
export const SYSTEM_PROMPT_TEXT_CAP_BYTES = 5_000_000;
|
|
45
79
|
|
|
80
|
+
/** Maximum stored preset-name length. Enforced at the console API. */
|
|
81
|
+
export const SYSTEM_PROMPT_PRESET_NAME_CAP = 200;
|
|
82
|
+
|
|
46
83
|
/**
|
|
47
84
|
* Marker wrapping tenant text injected into a provider wire whose upstream
|
|
48
85
|
* validates the system prompt as a channel identity (the buddy family). The
|
|
@@ -52,12 +89,21 @@ export const SYSTEM_PROMPT_TEXT_CAP_BYTES = 5_000_000;
|
|
|
52
89
|
export const SYSTEM_PROMPT_WRAPPER_PREFIX = "<cartethyia:system-prompt>";
|
|
53
90
|
export const SYSTEM_PROMPT_WRAPPER_SUFFIX = "</cartethyia:system-prompt>";
|
|
54
91
|
|
|
92
|
+
/** Maximum number of presets stored for one tenant. */
|
|
93
|
+
export const SYSTEM_PROMPT_PRESET_LIMIT = 64;
|
|
94
|
+
|
|
55
95
|
/** Maximum number of per-provider entries stored for one tenant. */
|
|
56
96
|
export const SYSTEM_PROMPT_PROVIDER_LIMIT = 256;
|
|
57
97
|
|
|
58
98
|
/** Maximum number of per-model entries stored for one tenant. */
|
|
59
99
|
export const SYSTEM_PROMPT_MODEL_LIMIT = 512;
|
|
60
100
|
|
|
101
|
+
/** Maximum number of per-combo entries stored for one tenant. */
|
|
102
|
+
export const SYSTEM_PROMPT_COMBO_LIMIT = 512;
|
|
103
|
+
|
|
104
|
+
/** Maximum number of per-alias entries stored for one tenant. */
|
|
105
|
+
export const SYSTEM_PROMPT_ALIAS_LIMIT = 512;
|
|
106
|
+
|
|
61
107
|
/**
|
|
62
108
|
* Providers whose upstream validates the fixed system prompt as a channel
|
|
63
109
|
* identity. Their system turn must keep the fixed prompt as the opener, so
|
|
@@ -66,7 +112,7 @@ export const SYSTEM_PROMPT_MODEL_LIMIT = 512;
|
|
|
66
112
|
* `override` mode is not honoured for these providers — it would strip the
|
|
67
113
|
* identity and the upstream would refuse the request.
|
|
68
114
|
*/
|
|
69
|
-
|
|
115
|
+
const WRAPPED_INJECTION_PROVIDERS: ReadonlySet<string> = new Set([
|
|
70
116
|
"cb", // CodeBuddy International
|
|
71
117
|
"cbcn", // CodeBuddy CN
|
|
72
118
|
"workbuddy",
|
|
@@ -100,3 +146,34 @@ export function isPromptSetting(value: unknown): value is PromptSetting {
|
|
|
100
146
|
typeof candidate.text === "string"
|
|
101
147
|
);
|
|
102
148
|
}
|
|
149
|
+
|
|
150
|
+
/** True when the value is a well-formed preset (shape only, not size). */
|
|
151
|
+
export function isSystemPromptPreset(value: unknown): value is SystemPromptPreset {
|
|
152
|
+
if (typeof value !== "object" || value === null) return false;
|
|
153
|
+
const candidate = value as Partial<SystemPromptPreset>;
|
|
154
|
+
return (
|
|
155
|
+
typeof candidate.id === "string" &&
|
|
156
|
+
candidate.id.length > 0 &&
|
|
157
|
+
typeof candidate.name === "string" &&
|
|
158
|
+
candidate.name.length > 0 &&
|
|
159
|
+
typeof candidate.enabled === "boolean" &&
|
|
160
|
+
typeof candidate.mode === "string" &&
|
|
161
|
+
(PROMPT_MODES as readonly string[]).includes(candidate.mode) &&
|
|
162
|
+
typeof candidate.text === "string"
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** True when the value is a well-formed scope reference (shape only). */
|
|
167
|
+
export function isScopePromptRef(value: unknown): value is ScopePromptRef {
|
|
168
|
+
if (typeof value !== "object" || value === null) return false;
|
|
169
|
+
const kind = (value as { kind?: unknown }).kind;
|
|
170
|
+
if (kind === "off" || kind === "global") return true;
|
|
171
|
+
if (kind === "preset") {
|
|
172
|
+
const presetId = (value as { presetId?: unknown }).presetId;
|
|
173
|
+
return typeof presetId === "string" && presetId.length > 0;
|
|
174
|
+
}
|
|
175
|
+
if (kind === "custom") {
|
|
176
|
+
return isPromptSetting((value as { setting?: unknown }).setting);
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
@@ -21,18 +21,25 @@ import {
|
|
|
21
21
|
import {
|
|
22
22
|
EMPTY_TENANT_SYSTEM_PROMPTS,
|
|
23
23
|
PROMPT_MODES,
|
|
24
|
+
SYSTEM_PROMPT_ALIAS_LIMIT,
|
|
25
|
+
SYSTEM_PROMPT_COMBO_LIMIT,
|
|
24
26
|
SYSTEM_PROMPT_MODEL_LIMIT,
|
|
27
|
+
SYSTEM_PROMPT_PRESET_LIMIT,
|
|
28
|
+
SYSTEM_PROMPT_PRESET_NAME_CAP,
|
|
25
29
|
SYSTEM_PROMPT_PROVIDER_LIMIT,
|
|
26
30
|
SYSTEM_PROMPT_TEXT_CAP_BYTES,
|
|
27
31
|
isPromptSetting,
|
|
32
|
+
isScopePromptRef,
|
|
33
|
+
isSystemPromptPreset,
|
|
28
34
|
parseSystemPromptModelKey,
|
|
29
35
|
type PromptSetting,
|
|
30
|
-
type
|
|
36
|
+
type ScopePromptRef,
|
|
37
|
+
type SystemPromptPreset,
|
|
31
38
|
type TenantSystemPrompts,
|
|
32
39
|
} from "./contracts";
|
|
33
40
|
|
|
34
41
|
/** Route-group context injected by the composition root. */
|
|
35
|
-
|
|
42
|
+
interface SystemPromptRoutesConfig {
|
|
36
43
|
readonly db: CartethyiaDatabase;
|
|
37
44
|
readonly accessResolver: ConsoleAccessResolver;
|
|
38
45
|
readonly auditSink?: AuditSink;
|
|
@@ -53,6 +60,46 @@ function validateSetting(value: unknown, scopeLabel: string): PromptSetting {
|
|
|
53
60
|
return value;
|
|
54
61
|
}
|
|
55
62
|
|
|
63
|
+
/** Validates one preset. */
|
|
64
|
+
function validatePreset(value: unknown, scopeLabel: string): SystemPromptPreset {
|
|
65
|
+
if (!isSystemPromptPreset(value)) {
|
|
66
|
+
throw new ConsoleDomainError("invalid_request", 400, `${scopeLabel} is not a valid preset`);
|
|
67
|
+
}
|
|
68
|
+
if (value.name.length > SYSTEM_PROMPT_PRESET_NAME_CAP) {
|
|
69
|
+
throw new ConsoleDomainError(
|
|
70
|
+
"invalid_request",
|
|
71
|
+
400,
|
|
72
|
+
`${scopeLabel} name exceeds the ${SYSTEM_PROMPT_PRESET_NAME_CAP} character limit`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
validateSetting(value, scopeLabel);
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Validates one scope reference; a preset id that does not exist is
|
|
80
|
+
* neutralized to `off` rather than rejected, so deleting a preset does not
|
|
81
|
+
* block saving a configuration that still references it. */
|
|
82
|
+
function validateScopeRef(
|
|
83
|
+
value: unknown,
|
|
84
|
+
scopeLabel: string,
|
|
85
|
+
presetIds: ReadonlySet<string>,
|
|
86
|
+
): ScopePromptRef {
|
|
87
|
+
if (!isScopePromptRef(value)) {
|
|
88
|
+
throw new ConsoleDomainError("invalid_request", 400, `${scopeLabel} is not a valid scope reference`);
|
|
89
|
+
}
|
|
90
|
+
if (value.kind === "preset") {
|
|
91
|
+
if (!presetIds.has(value.presetId)) {
|
|
92
|
+
return { kind: "off" };
|
|
93
|
+
}
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
if (value.kind === "custom") {
|
|
97
|
+
validateSetting(value.setting, scopeLabel);
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
|
|
56
103
|
/** Validates the full PUT body into the stored shape. */
|
|
57
104
|
function validateBody(value: unknown): TenantSystemPrompts {
|
|
58
105
|
if (typeof value !== "object" || value === null) {
|
|
@@ -60,68 +107,71 @@ function validateBody(value: unknown): TenantSystemPrompts {
|
|
|
60
107
|
}
|
|
61
108
|
const body = value as {
|
|
62
109
|
global?: unknown;
|
|
110
|
+
presets?: unknown;
|
|
63
111
|
providers?: unknown;
|
|
64
112
|
models?: unknown;
|
|
113
|
+
combos?: unknown;
|
|
114
|
+
aliases?: unknown;
|
|
65
115
|
};
|
|
66
116
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
? null
|
|
70
|
-
: validateSetting(body.global, "global");
|
|
71
|
-
|
|
72
|
-
if (body.providers !== undefined && typeof body.providers !== "object") {
|
|
73
|
-
throw new ConsoleDomainError("invalid_request", 400, "providers must be an object");
|
|
117
|
+
if (body.presets !== undefined && typeof body.presets !== "object") {
|
|
118
|
+
throw new ConsoleDomainError("invalid_request", 400, "presets must be an object");
|
|
74
119
|
}
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
if (
|
|
120
|
+
const rawPresets = (body.presets ?? {}) as Record<string, unknown>;
|
|
121
|
+
const presetIds = Object.keys(rawPresets);
|
|
122
|
+
if (presetIds.length > SYSTEM_PROMPT_PRESET_LIMIT) {
|
|
78
123
|
throw new ConsoleDomainError(
|
|
79
124
|
"invalid_request",
|
|
80
125
|
400,
|
|
81
|
-
`
|
|
126
|
+
`presets exceeds the ${SYSTEM_PROMPT_PRESET_LIMIT} entry limit`,
|
|
82
127
|
);
|
|
83
128
|
}
|
|
84
|
-
const
|
|
85
|
-
for (const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
129
|
+
const presets: Record<string, SystemPromptPreset> = {};
|
|
130
|
+
for (const id of presetIds) {
|
|
131
|
+
presets[id] = validatePreset(rawPresets[id], `preset ${id}`);
|
|
132
|
+
}
|
|
133
|
+
const presetIdSet = new Set(presetIds);
|
|
134
|
+
|
|
135
|
+
const global =
|
|
136
|
+
body.global === null || body.global === undefined
|
|
137
|
+
? null
|
|
138
|
+
: validateScopeRef(body.global, "global", presetIdSet);
|
|
139
|
+
|
|
140
|
+
const readRefMap = (
|
|
141
|
+
raw: unknown,
|
|
142
|
+
label: string,
|
|
143
|
+
limit: number,
|
|
144
|
+
): Record<string, ScopePromptRef> => {
|
|
145
|
+
if (raw !== undefined && typeof raw !== "object") {
|
|
146
|
+
throw new ConsoleDomainError("invalid_request", 400, `${label} must be an object`);
|
|
89
147
|
}
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
148
|
+
const entries = (raw ?? {}) as Record<string, unknown>;
|
|
149
|
+
const keys = Object.keys(entries);
|
|
150
|
+
if (keys.length > limit) {
|
|
151
|
+
throw new ConsoleDomainError(
|
|
152
|
+
"invalid_request",
|
|
153
|
+
400,
|
|
154
|
+
`${label} exceeds the ${limit} entry limit`,
|
|
155
|
+
);
|
|
93
156
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
providers[providerId] = {
|
|
98
|
-
followGlobal: false,
|
|
99
|
-
setting: validateSetting(candidate.setting, `provider ${providerId}`),
|
|
100
|
-
};
|
|
157
|
+
const out: Record<string, ScopePromptRef> = {};
|
|
158
|
+
for (const key of keys) {
|
|
159
|
+
out[key] = validateScopeRef(entries[key], `${label} ${key}`, presetIdSet);
|
|
101
160
|
}
|
|
102
|
-
|
|
161
|
+
return out;
|
|
162
|
+
};
|
|
103
163
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const rawModels = (body.models ?? {}) as Record<string, unknown>;
|
|
108
|
-
const modelKeys = Object.keys(rawModels);
|
|
109
|
-
if (modelKeys.length > SYSTEM_PROMPT_MODEL_LIMIT) {
|
|
110
|
-
throw new ConsoleDomainError(
|
|
111
|
-
"invalid_request",
|
|
112
|
-
400,
|
|
113
|
-
`models exceeds the ${SYSTEM_PROMPT_MODEL_LIMIT} entry limit`,
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
const models: Record<string, PromptSetting> = {};
|
|
117
|
-
for (const key of modelKeys) {
|
|
164
|
+
const providers = readRefMap(body.providers, "providers", SYSTEM_PROMPT_PROVIDER_LIMIT);
|
|
165
|
+
const models = readRefMap(body.models, "models", SYSTEM_PROMPT_MODEL_LIMIT);
|
|
166
|
+
for (const key of Object.keys(models)) {
|
|
118
167
|
if (parseSystemPromptModelKey(key) === null) {
|
|
119
168
|
throw new ConsoleDomainError("invalid_request", 400, `model key ${key} is malformed`);
|
|
120
169
|
}
|
|
121
|
-
models[key] = validateSetting(rawModels[key], `model ${key}`);
|
|
122
170
|
}
|
|
171
|
+
const combos = readRefMap(body.combos, "combos", SYSTEM_PROMPT_COMBO_LIMIT);
|
|
172
|
+
const aliases = readRefMap(body.aliases, "aliases", SYSTEM_PROMPT_ALIAS_LIMIT);
|
|
123
173
|
|
|
124
|
-
return { global, providers, models };
|
|
174
|
+
return { global, presets, providers, models, combos, aliases };
|
|
125
175
|
}
|
|
126
176
|
|
|
127
177
|
/**
|
|
@@ -150,8 +200,11 @@ export function createSystemPromptRoutes(config: SystemPromptRoutesConfig) {
|
|
|
150
200
|
target: "tenant_system_prompts",
|
|
151
201
|
detail: {
|
|
152
202
|
global: next.global !== null,
|
|
203
|
+
presets: Object.keys(next.presets).length,
|
|
153
204
|
providers: Object.keys(next.providers).length,
|
|
154
205
|
models: Object.keys(next.models).length,
|
|
206
|
+
combos: Object.keys(next.combos).length,
|
|
207
|
+
aliases: Object.keys(next.aliases).length,
|
|
155
208
|
},
|
|
156
209
|
});
|
|
157
210
|
return next;
|