cartethyia 2.7.4 → 2.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +5 -0
- package/README.md +18 -6
- package/dist/dashboard/assets/BannedPage-B1N603cM.js +1 -0
- package/dist/dashboard/assets/BannedPage-B1N603cM.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-9q4_4NoK.js +3 -0
- package/dist/dashboard/assets/CliToolDetailPage-9q4_4NoK.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-DwshGtK-.js +1 -0
- package/dist/dashboard/assets/CliToolsPage-DwshGtK-.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-D762MIkp.js +1 -0
- package/dist/dashboard/assets/CombosPage-D762MIkp.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-KRYjHZsX.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-KRYjHZsX.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-DXsxk5uM.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-DXsxk5uM.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BEjAjD4F.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-BEjAjD4F.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-d5v3K_Yc.js +899 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-d5v3K_Yc.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-Ccv4tjNS.js +1 -0
- package/dist/dashboard/assets/LoginPage-Ccv4tjNS.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BMS2t8BA.js → ModelPicker-C6WlpU5E.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-C6WlpU5E.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-DN9fYNWA.js +1 -0
- package/dist/dashboard/assets/OverviewPage-DN9fYNWA.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-BY0ieUgz.js +5 -0
- package/dist/dashboard/assets/ProviderDetailPage-BY0ieUgz.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DNsbfea2.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-DNsbfea2.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-lsrx7NYg.js → ProxyPage-DxjvUzSD.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-DxjvUzSD.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-OaHScjpX.js +1 -0
- package/dist/dashboard/assets/QuotaPage-OaHScjpX.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BW4AKj2H.js +1 -0
- package/dist/dashboard/assets/SettingsPage-BW4AKj2H.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-44BZrxFC.js +1 -0
- package/dist/dashboard/assets/SetupPage-44BZrxFC.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-Cq5tW7hW.js → StudioPage-DRPavCdU.js} +4 -4
- package/dist/dashboard/assets/StudioPage-DRPavCdU.js.gz +0 -0
- package/dist/dashboard/assets/ToolIcon-CfeLA-Z_.js +1 -0
- package/dist/dashboard/assets/ToolIcon-CfeLA-Z_.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-B7M7LvMM.js +56 -0
- package/dist/dashboard/assets/UsagePage-B7M7LvMM.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-KvX83mRe.js +1 -0
- package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
- package/dist/dashboard/assets/app-DRnQLhsc.js +9 -0
- package/dist/dashboard/assets/app-DRnQLhsc.js.gz +0 -0
- package/dist/dashboard/assets/app-mweRcsKp.css +2 -0
- package/dist/dashboard/assets/app-mweRcsKp.css.gz +0 -0
- package/dist/dashboard/assets/assert-CidQVoNo.js +1 -0
- package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
- package/dist/dashboard/assets/{badge-D9CS6dZk.js → badge-BaX2GTrn.js} +1 -1
- package/dist/dashboard/assets/badge-BaX2GTrn.js.gz +0 -0
- package/dist/dashboard/assets/{banner-DympBNq4.js → banner-DFH4Bkte.js} +1 -1
- package/dist/dashboard/assets/banner-DFH4Bkte.js.gz +0 -0
- package/dist/dashboard/assets/brand/login_register.jpg +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
- package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/og_image.jpg +0 -0
- package/dist/dashboard/assets/{card-ibRWU_uT.js → card-CxDw0lK6.js} +1 -1
- package/dist/dashboard/assets/card-CxDw0lK6.js.gz +0 -0
- package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js +1 -0
- package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js.gz +0 -0
- package/dist/dashboard/assets/circle-question-mark-Cz4un8sH.js +1 -0
- package/dist/dashboard/assets/circle-question-mark-Cz4un8sH.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js +1 -0
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-DJm-9pz7.js → clipboard-button-DfS1ntiq.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-DfS1ntiq.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BK-NRr5u.js +1 -0
- package/dist/dashboard/assets/dialog-BK-NRr5u.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-CqGYeOrd.js → drawer-QqPul6sC.js} +1 -1
- package/dist/dashboard/assets/drawer-QqPul6sC.js.gz +0 -0
- package/dist/dashboard/assets/entry-BMlYJVFj.js +1 -0
- package/dist/dashboard/assets/entry-BMlYJVFj.js.gz +0 -0
- package/dist/dashboard/assets/entry-D6WvojeD.js +4 -0
- package/dist/dashboard/assets/entry-D6WvojeD.js.gz +0 -0
- package/dist/dashboard/assets/entry-Dmx7gBiN.js +1 -0
- package/dist/dashboard/assets/entry-Dmx7gBiN.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-D-MTJxMr.js → formatters-u7JPB5BX.js} +1 -1
- package/dist/dashboard/assets/formatters-u7JPB5BX.js.gz +0 -0
- package/dist/dashboard/assets/house-h9AZlJuT.js +1 -0
- package/dist/dashboard/assets/house-h9AZlJuT.js.gz +0 -0
- package/dist/dashboard/assets/{index-DBNPOfhV.js → index-Ko4dLRnV.js} +2 -2
- package/dist/dashboard/assets/index-Ko4dLRnV.js.gz +0 -0
- package/dist/dashboard/assets/{inline-BT0zntEr.js → inline-CHJQwxLa.js} +1 -1
- package/dist/dashboard/assets/inline-CHJQwxLa.js.gz +0 -0
- package/dist/dashboard/assets/{input-Da2ZViWg.js → input-BlmlizVV.js} +1 -1
- package/dist/dashboard/assets/input-BlmlizVV.js.gz +0 -0
- package/dist/dashboard/assets/{layout-BXNeLw6M.js → layout-Bz93I-yE.js} +1 -1
- package/dist/dashboard/assets/layout-Bz93I-yE.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-BycOrrIV.js → list-row-Ml16KCfu.js} +1 -1
- package/dist/dashboard/assets/list-row-Ml16KCfu.js.gz +0 -0
- package/dist/dashboard/assets/live-7_QpnYGu.js +1 -0
- package/dist/dashboard/assets/live-7_QpnYGu.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-Rr0TIvcj.js → mutation-GU7XdEjz.js} +1 -1
- package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
- package/dist/dashboard/assets/network-CuMDaTzT.js +1 -0
- package/dist/dashboard/assets/network-CuMDaTzT.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CoqpaDMx.js +2 -0
- package/dist/dashboard/assets/numbers-CoqpaDMx.js.gz +0 -0
- package/dist/dashboard/assets/pagination-B5aOoweZ.js +1 -0
- package/dist/dashboard/assets/pagination-B5aOoweZ.js.gz +0 -0
- package/dist/dashboard/assets/providers/zai.webp +0 -0
- package/dist/dashboard/assets/providers/zcode.webp +0 -0
- package/dist/dashboard/assets/{query-keys-BloKtaXJ.js → query-keys-YSoQOUh6.js} +1 -1
- package/dist/dashboard/assets/{query-keys-BloKtaXJ.js.gz → query-keys-YSoQOUh6.js.gz} +0 -0
- package/dist/dashboard/assets/quota-DJ1QlAOq.js +1 -0
- package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
- package/dist/dashboard/assets/routing-fnCsxgNO.js +1 -0
- package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
- package/dist/dashboard/assets/{select-BIYIBkMU.js → select-BHqpd-bg.js} +2 -2
- package/dist/dashboard/assets/select-BHqpd-bg.js.gz +0 -0
- package/dist/dashboard/assets/settings-BxslsErn.js +1 -0
- package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
- package/dist/dashboard/assets/shield-BMQNxORh.js +1 -0
- package/dist/dashboard/assets/shield-BMQNxORh.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DfgnFDWY.js → stack-j7cXjgYX.js} +1 -1
- package/dist/dashboard/assets/stack-j7cXjgYX.js.gz +0 -0
- package/dist/dashboard/assets/{state-CkfdYWel.js → state-DHmEnLa2.js} +1 -1
- package/dist/dashboard/assets/state-DHmEnLa2.js.gz +0 -0
- package/dist/dashboard/assets/{switch-SYm237HG.js → switch-DmZcyTIC.js} +1 -1
- package/dist/dashboard/assets/switch-DmZcyTIC.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BQqCRNIi.js → useMutation-CbCP4w1o.js} +1 -1
- package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.jpg +0 -0
- package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +60 -59
- package/migrations/full-postgres/0036_system_prompt_presets_refs.sql +10 -0
- package/migrations/full-postgres/0037_share_concurrency_scope.sql +3 -0
- package/migrations/full-postgres/0038_add_accounts_quota_snapshot.sql +7 -0
- package/migrations/full-postgres/0039_add_telemetry_provider_model_indexes.sql +13 -0
- package/migrations/full-postgres/0040_add_models_dialect.sql +3 -0
- package/migrations/full-postgres/0041_add_daily_checkin_ledger_account_fk.sql +7 -0
- package/migrations/full-postgres/0042_add_telemetry_degenerate_stop.sql +14 -0
- package/migrations/lite-sqlite3/0036_system_prompt_presets_refs.sql +14 -0
- package/migrations/lite-sqlite3/0037_share_concurrency_scope.sql +3 -0
- package/migrations/lite-sqlite3/0038_add_accounts_quota_snapshot.sql +4 -0
- package/migrations/lite-sqlite3/0039_add_telemetry_provider_model_indexes.sql +6 -0
- package/migrations/lite-sqlite3/0040_add_models_dialect.sql +3 -0
- package/migrations/lite-sqlite3/0041_add_daily_checkin_ledger_account_fk.sql +26 -0
- package/migrations/lite-sqlite3/0042_add_telemetry_degenerate_stop.sql +12 -0
- package/package.json +4 -6
- package/scripts/cli/update.ts +15 -41
- package/src/boot/app.ts +58 -2
- package/src/boot/dependencies.ts +794 -732
- package/src/boot/lifecycle.ts +11 -12
- package/src/config/cooldowns.ts +34 -55
- package/src/config/network.ts +1 -1
- package/src/config/package-paths.ts +1 -1
- package/src/config/providers.ts +19 -14
- package/src/config/resources.ts +0 -5
- package/src/config/runtime.ts +2 -5
- package/src/config/spec.ts +33 -6
- package/src/config/usage.ts +27 -0
- package/src/console/api-keys/ban-constants.ts +38 -0
- package/src/console/api-keys/contracts.ts +71 -62
- package/src/console/api-keys/routes.ts +56 -24
- package/src/console/auth/access.ts +4 -4
- package/src/console/auth/account-reset.ts +1 -1
- package/src/console/auth/audit-recorder.ts +1 -1
- package/src/console/auth/lockout.ts +7 -3
- package/src/console/auth/row-coercion.ts +57 -12
- package/src/console/auth/session-resolver.ts +1 -1
- package/src/console/auth/session-service.ts +8 -2
- package/src/console/auth/session.ts +37 -31
- package/src/console/auth/setup-service.ts +4 -3
- package/src/console/auth/user-store.ts +1 -1
- package/src/console/backup/9router-map.ts +0 -3
- package/src/console/backup/contracts.ts +9 -9
- package/src/console/backup/export-endpoint.ts +5 -6
- package/src/console/backup/nine-router.ts +1 -7
- package/src/console/backup/routes.ts +29 -33
- package/src/console/backup/service.ts +468 -468
- package/src/console/backup/store.ts +47 -29
- package/src/console/backup/validate.ts +11 -1
- package/src/console/cli-tools/contracts.ts +43 -299
- package/src/console/cli-tools/fs-ops.ts +29 -0
- package/src/console/cli-tools/injectors/claude.ts +3 -3
- package/src/console/cli-tools/injectors/codex.ts +181 -181
- package/src/console/cli-tools/injectors/driver.ts +150 -247
- package/src/console/cli-tools/injectors/kilo.ts +146 -21
- package/src/console/cli-tools/injectors/opencode.ts +176 -153
- package/src/console/cli-tools/routes.ts +7 -1
- package/src/console/cli-tools/service.ts +8 -7
- package/src/console/cli-tools/store.ts +2 -2
- package/src/console/console-router.ts +32 -0
- package/src/console/dashboard-assets.ts +9 -9
- package/src/console/domain-registration.ts +16 -2
- package/src/console/inferhub/client.ts +0 -7
- package/src/console/inferhub/key-store.ts +1 -1
- package/src/console/inferhub/normalize.ts +2 -2
- package/src/console/inferhub/routes.ts +1 -1
- package/src/console/inferhub/types.ts +1 -1
- package/src/console/observability/contracts.ts +7 -1
- package/src/console/observability/live.ts +31 -18
- package/src/console/observability/logs.ts +1 -1
- package/src/console/observability/sse.ts +62 -8
- package/src/console/observability/store.ts +23 -4
- package/src/console/providers/catalog/catalog-projections.ts +1 -1
- package/src/console/providers/catalog/contracts.ts +39 -0
- package/src/console/providers/catalog/model-operations.ts +1 -1
- package/src/console/providers/catalog/provider-operations.ts +95 -17
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/routes.ts +27 -3
- package/src/console/providers/catalog/store.ts +147 -8
- package/src/console/providers/detail/routes.ts +1 -1
- package/src/console/providers/oauth/callback-listener.ts +100 -18
- package/src/console/providers/oauth/routes.ts +92 -13
- package/src/console/quota/account-quota-global.ts +41 -3
- package/src/console/quota/account-quota-shared.ts +10 -3
- package/src/console/quota/account-quota-tenant.ts +259 -66
- package/src/console/quota/daily-growth-job.ts +308 -0
- package/src/console/quota/native-identity.ts +2 -2
- package/src/console/quota/quota-view.ts +1 -1
- package/src/console/remote-support/routes.ts +1 -1
- package/src/console/remote-support/service.ts +2 -2
- package/src/console/routing/model/contracts.ts +1 -1
- package/src/console/routing/pools/contracts.ts +2 -2
- package/src/console/routing/pools/import-parse.ts +3 -3
- package/src/console/routing/pools/relay-deploy.ts +3 -3
- package/src/console/settings/contracts.ts +29 -2
- package/src/console/settings/store.ts +20 -0
- package/src/console/share/share-router.ts +100 -32
- package/src/console/share/share-stats.ts +11 -13
- package/src/console/share/share-usage.ts +1 -1
- package/src/console/shared/errors.ts +2 -2
- package/src/console/shared/object.ts +18 -0
- package/src/console/shared/read-through-cache.ts +4 -4
- package/src/console/shared/transaction.ts +14 -0
- package/src/console/system-prompt/contracts.ts +89 -12
- package/src/console/system-prompt/routes.ts +98 -45
- package/src/console/updates/apply-update.ts +244 -0
- package/src/console/updates/routes.ts +252 -97
- package/src/main.ts +11 -5
- package/src/network/http2-bidi.ts +1 -1
- package/src/network/ip-utils.ts +298 -0
- package/src/network/outbound-fetch.ts +1 -1
- package/src/network/pool/agent.ts +7 -6
- package/src/network/pool/byte-accounting.ts +1 -1
- package/src/network/pool/resolver.ts +16 -12
- package/src/network/pool/selector.ts +150 -136
- package/src/network/pool-health-machine.ts +1 -1
- package/src/network/rate-limit-verdict.ts +1 -1
- package/src/network/retry.ts +1 -1
- package/src/network/ssrf.ts +20 -71
- package/src/observability/event-log.ts +9 -2
- package/src/observability/event-query.ts +61 -291
- package/src/observability/log-sanitize.ts +1 -1
- package/src/observability/metrics.ts +4 -4
- package/src/observability/payload-capture.ts +6 -5
- package/src/observability/payload-store.ts +1 -1
- package/src/observability/runtime-metrics.ts +1 -1
- package/src/observability/telemetry-buffer.ts +19 -3
- package/src/observability/telemetry-reconciliation.ts +3 -3
- package/src/observability/token-speed.ts +1 -1
- package/src/persistence/connection-url.ts +1 -1
- package/src/persistence/db-handle.ts +1 -1
- package/src/persistence/db-pg.ts +2 -2
- package/src/persistence/db-sqlite.ts +3 -3
- package/src/persistence/dialect.ts +5 -1
- package/src/persistence/readiness.ts +1 -1
- package/src/persistence/redis.ts +1 -1
- package/src/persistence/schema.ts +121 -40
- package/src/persistence/shared-cache.ts +2 -2
- package/src/persistence/sort-order.ts +1 -1
- package/src/persistence/stores/api-key-store.ts +29 -2
- package/src/persistence/stores/share-store.ts +20 -8
- package/src/persistence/system-prompt-store.ts +63 -18
- package/src/persistence/tenant-preferences.ts +1 -1
- package/src/persistence/ttl-cache.ts +3 -3
- package/src/persistence/usage-flush.ts +7 -2
- package/src/protocol/canonical-model.ts +1 -1
- package/src/protocol/codecs.ts +6 -10
- package/src/protocol/dsml.ts +8 -125
- package/src/protocol/image-clamp.ts +2 -2
- package/src/protocol/reasoning-text.ts +1 -1
- package/src/protocol/reasoning.ts +7 -52
- package/src/protocol/registry.ts +50 -15
- package/src/protocol/request/chat.ts +46 -19
- package/src/protocol/request/codex.ts +1 -1
- package/src/protocol/request/messages.ts +46 -14
- package/src/protocol/request/responses.ts +19 -10
- package/src/protocol/response/chat.ts +25 -10
- package/src/protocol/response/codex.ts +1 -1
- package/src/protocol/response/messages.ts +1 -1
- package/src/protocol/response/omp-tool-call-markup.ts +1 -1
- package/src/protocol/response/responses.ts +1 -1
- package/src/protocol/sse-decoder.ts +50 -1
- package/src/protocol/stop-reason.ts +2 -2
- package/src/protocol/think-split.ts +347 -0
- package/src/protocol/translation/attachment-integrity.ts +3 -3
- package/src/protocol/translation/cache-controls.ts +8 -20
- package/src/protocol/translation/capabilities.ts +6 -6
- package/src/protocol/translation/dialect/deepseek.ts +10 -0
- package/src/protocol/translation/dialect/inband.ts +39 -0
- package/src/protocol/translation/dialect/model-thinking.ts +89 -0
- package/src/protocol/translation/dialect/qwen.ts +114 -0
- package/src/protocol/translation/dialect/resolver.ts +83 -0
- package/src/protocol/translation/dialect/rules.ts +93 -0
- package/src/protocol/translation/dialect/thinking-ladder.ts +245 -0
- package/src/protocol/translation/dialect/types.ts +58 -0
- package/src/protocol/translation/quirks.ts +18 -4
- package/src/protocol/translation/thinking.ts +12 -252
- package/src/protocol/translation/tool-id.ts +3 -3
- package/src/protocol/translation/tool-repair.ts +1 -1
- package/src/protocol/upstream/post-json.ts +6 -1
- package/src/providers/adapters/compatible-adapter.ts +71 -4
- package/src/providers/authentication/jwt-validator.ts +0 -5
- package/src/providers/authentication/oauth-client.ts +254 -253
- package/src/providers/authentication/oauth-flow-store.ts +42 -7
- package/src/providers/bundled-discovery-providers.ts +6 -30
- package/src/providers/configured-openai-providers.ts +82 -97
- package/src/providers/default-registry.ts +35 -37
- package/src/providers/discovery/catalog/catalog-format.ts +13 -2
- package/src/providers/discovery/discovery-types.ts +0 -1
- package/src/providers/discovery/openai-model-discovery.ts +2 -2
- package/src/providers/discovery/probe-phases.ts +48 -13
- package/src/providers/discovery/probing-service.ts +6 -8
- package/src/providers/integrations/agentrouter/agentrouter.ts +12 -2
- package/src/providers/integrations/antigravity/antigravity-oauth.ts +0 -1
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +88 -28
- package/src/providers/integrations/antigravity/antigravity-quota.ts +18 -6
- package/src/providers/integrations/antigravity/antigravity.ts +22 -13
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -1
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +20 -4
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +19 -52
- package/src/providers/integrations/buddy/buddy-identity.ts +86 -0
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +1 -1
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +1 -2
- package/src/providers/integrations/buddy/codebuddy/provider.ts +4 -15
- package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
- package/src/providers/integrations/buddy/workbuddy/provider.ts +5 -9
- package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
- package/src/providers/integrations/claude/claude-cch.ts +148 -148
- package/src/providers/integrations/claude/fallback-credit.ts +3 -7
- package/src/providers/integrations/cline/cline.ts +1 -1
- package/src/providers/integrations/codex/codex-device-code.ts +101 -101
- package/src/providers/integrations/codex/codex-oauth.ts +0 -1
- package/src/providers/integrations/commandcode/commandcode.ts +75 -115
- package/src/providers/integrations/commandcode/quota.ts +131 -51
- package/src/providers/integrations/cursor/cursor-http2.ts +0 -3
- package/src/providers/integrations/cursor/cursor-quota.ts +2 -6
- package/src/providers/integrations/cursor/cursor-request.ts +0 -87
- package/src/providers/integrations/cursor/cursor-shared.ts +0 -4
- package/src/providers/integrations/cursor/cursor.ts +35 -3
- package/src/providers/integrations/cursor/protocol/framing.ts +0 -56
- package/src/providers/integrations/cursor/protocol/messages.ts +0 -96
- package/src/providers/integrations/factory/factory.ts +9 -9
- package/src/providers/integrations/github/github-discovery.ts +70 -12
- package/src/providers/integrations/github/github-oauth.ts +184 -14
- package/src/providers/integrations/github/github.ts +134 -26
- package/src/providers/integrations/grok/grok-oauth.ts +261 -261
- package/src/providers/integrations/grok/grok-quota-frame.ts +1 -1
- package/src/providers/integrations/grok/grok-quota.ts +16 -7
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +0 -5
- package/src/providers/integrations/grok/grok-ratelimit.ts +3 -0
- package/src/providers/integrations/grok/grok.ts +86 -36
- package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
- package/src/providers/integrations/kiro/kiro-cache.ts +31 -0
- package/src/providers/integrations/kiro/kiro-catalog.ts +31 -5
- package/src/providers/integrations/kiro/kiro-discovery.ts +47 -43
- package/src/providers/integrations/kiro/kiro-machine-id.ts +6 -3
- package/src/providers/integrations/kiro/kiro-oauth.ts +523 -255
- package/src/providers/integrations/kiro/kiro-quota.ts +35 -47
- package/src/providers/integrations/kiro/kiro-request.ts +8 -11
- package/src/providers/integrations/kiro/kiro-stream.ts +13 -0
- package/src/providers/integrations/kiro/kiro.ts +80 -68
- package/src/providers/integrations/muse/muse-oauth.ts +0 -1
- package/src/providers/integrations/muse/muse-quota.ts +0 -4
- package/src/providers/integrations/opencode/fingerprint.ts +7 -5
- package/src/providers/integrations/opencode/provider.ts +17 -24
- package/src/providers/integrations/qoder/qoder-catalog.ts +94 -11
- package/src/providers/integrations/qoder/qoder-oauth.ts +165 -25
- package/src/providers/integrations/qoder/qoder-quota.ts +48 -6
- package/src/providers/integrations/qoder/qoder-realm.ts +10 -31
- package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
- package/src/providers/integrations/qoder/qoder-umid.ts +21 -25
- package/src/providers/integrations/qoder/qoder-virtual.ts +12 -0
- package/src/providers/integrations/qoder/qoder.ts +422 -71
- package/src/providers/integrations/shared/connect.ts +0 -54
- package/src/providers/integrations/shared/hash.ts +15 -0
- package/src/providers/integrations/shared/session-cache.ts +136 -0
- package/src/providers/integrations/trae/trae-cache.ts +7 -87
- package/src/providers/integrations/trae/trae-catalog.ts +1 -6
- package/src/providers/integrations/trae/trae-protocol.ts +17 -8
- package/src/providers/integrations/trae/trae-realm.ts +0 -7
- package/src/providers/integrations/trae/trae.ts +51 -8
- package/src/providers/integrations/xai/xai-oauth.ts +5 -5
- package/src/providers/integrations/zcode-start-plan/gifts.ts +265 -0
- package/src/providers/integrations/zcode-start-plan/oauth.ts +154 -0
- package/src/providers/integrations/zcode-start-plan/provider.ts +131 -0
- package/src/providers/integrations/zcode-start-plan/quota.ts +152 -0
- package/src/providers/integrations/zcode-start-plan/zcode_prompt.json +10 -0
- package/src/providers/operations/account-error-classification.ts +198 -133
- package/src/providers/operations/account-health-service.ts +31 -39
- package/src/providers/operations/account-reset-service.ts +58 -1
- package/src/providers/operations/byok-wire-profile.ts +19 -15
- package/src/providers/operations/cli-platform.ts +31 -5
- package/src/providers/operations/client-version-resolver.ts +4 -6
- package/src/providers/operations/client-versions.ts +10 -7
- package/src/providers/operations/provider-catalog-seeder.ts +2 -0
- package/src/providers/operations/provider-catalog-service.ts +18 -2
- package/src/providers/operations/provider-credential-service.ts +19 -2
- package/src/providers/provider-contracts.ts +18 -7
- package/src/providers/provider-metadata.ts +17 -2
- package/src/providers/provider-model-definition.ts +22 -1
- package/src/providers/provider-probe.ts +4 -2
- package/src/providers/provider-usage.ts +50 -19
- package/src/providers/quota/cache.ts +29 -0
- package/src/providers/quota/quota-contracts.ts +4 -0
- package/src/providers/quota/refresh.ts +27 -4
- package/src/providers/quota/snapshot.ts +94 -0
- package/src/providers/search/search-provider.ts +1 -3
- package/src/providers/search/search-providers.ts +2 -1
- package/src/security/abuse.ts +4 -4
- package/src/security/admission/service.ts +13 -6
- package/src/security/admission/ttl.ts +61 -61
- package/src/security/api-key-auth.ts +17 -3
- package/src/security/client-router-fingerprint.ts +4 -4
- package/src/security/credential-codec.ts +3 -3
- package/src/security/csrf.ts +2 -2
- package/src/security/ip-boundary.ts +16 -189
- package/src/security/model-access-rule.ts +1 -1
- package/src/security/outbound-headers.ts +18 -0
- package/src/shared/invoke-guard.ts +47 -0
- package/src/transport/contracts.ts +13 -4
- package/src/transport/dispatch/attempt-finalize.ts +70 -22
- package/src/transport/dispatch/attempt-loop.ts +22 -0
- package/src/transport/dispatch/capture-policy.ts +34 -37
- package/src/transport/dispatch/empty-stream.ts +23 -20
- package/src/transport/dispatch/generation.ts +25 -45
- package/src/transport/dispatch/native-shared.ts +127 -0
- package/src/transport/dispatch/proxy-request.ts +3 -1
- package/src/transport/dispatch/responses-compact.ts +25 -46
- package/src/transport/dispatch/streaming/encoder.ts +18 -72
- package/src/transport/dispatch/streaming/finalize.ts +2 -2
- package/src/transport/dispatch/streaming/stream.ts +7 -3
- package/src/transport/dispatch/streaming/thinking-loop.ts +1 -1
- package/src/transport/dispatch/systemone.ts +20 -48
- package/src/transport/dispatch/token-count.ts +41 -38
- package/src/transport/dispatch/websearch.ts +19 -45
- package/src/transport/drain-endpoint.ts +1 -1
- package/src/transport/failure-policy.ts +21 -1
- package/src/transport/guards/auth-guard.ts +0 -6
- package/src/transport/guards/body-policy.ts +3 -3
- package/src/transport/guards/error-lifecycle.ts +4 -0
- package/src/transport/guards/headroom-guard.ts +1 -1
- package/src/transport/guards/pipeline.ts +1 -1
- package/src/transport/guards/request-context.ts +12 -17
- package/src/transport/request/preparer.ts +286 -7
- package/src/transport/request/rtk/compress-request.ts +3 -3
- package/src/transport/request/rtk/rtk-constants.ts +2 -2
- package/src/transport/request/rtk/rtk-filters.ts +8 -8
- package/src/transport/request/state.ts +7 -13
- package/src/transport/request/system-prompt-injection.ts +74 -11
- package/src/transport/request/tenant-preferences.ts +22 -0
- package/src/transport/routing/candidate-index.ts +2 -2
- package/src/transport/routing/round-robin.ts +1 -1
- package/src/transport/routing/route-catalog.ts +17 -18
- package/src/transport/routing/route-model.ts +126 -33
- package/src/transport/routing/router.ts +159 -37
- package/src/transport/shutdown-notice.ts +2 -2
- package/src/transport/surface/adapters.ts +3 -3
- package/src/transport/surface/chat/encode.ts +7 -7
- package/src/transport/surface/chat/parse.ts +7 -7
- package/src/transport/surface/completion.ts +2 -2
- package/src/transport/surface/content-parts.ts +1 -1
- package/src/transport/surface/dialects.ts +2 -2
- package/src/transport/surface/messages/parse.ts +3 -3
- package/src/transport/surface/reasoning-summary.ts +9 -14
- package/src/transport/surface/responses/adapter.ts +1 -1
- package/src/transport/surface/responses/errors.ts +0 -9
- package/src/transport/vision-handoff-cache.ts +58 -0
- package/src/transport/vision-handoff.ts +431 -0
- package/src/workers/account-identity-sweep.ts +2 -2
- package/src/workers/buddy-growth-tasks.ts +392 -346
- package/src/workers/daily-checkin.ts +102 -49
- package/src/workers/maintenance-lease.ts +2 -35
- package/src/workers/oauth-refresh-sweep.ts +8 -1
- package/src/workers/quota-refresh-sweep.ts +120 -82
- package/src/workers/scheduler.ts +67 -4
- package/src/workers/sweep-runner.ts +24 -5
- package/src/workers/zcode-gift-claim.ts +108 -0
- package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js +0 -1
- package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
- package/dist/dashboard/assets/BannedPage-ChG5svFb.js +0 -1
- package/dist/dashboard/assets/BannedPage-ChG5svFb.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js +0 -1
- package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DCgm5g6g.js +0 -1
- package/dist/dashboard/assets/CombosPage-DCgm5g6g.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-U6AKaJcF.js +0 -1
- package/dist/dashboard/assets/LoginPage-U6AKaJcF.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BMS2t8BA.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js +0 -1
- package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js +0 -5
- package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-L0skzML8.js +0 -1
- package/dist/dashboard/assets/QuotaPage-L0skzML8.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js +0 -1
- package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-f0DE1h0-.js +0 -1
- package/dist/dashboard/assets/SetupPage-f0DE1h0-.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-ClzpwyET.js +0 -1
- package/dist/dashboard/assets/SortableList-ClzpwyET.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-Cq5tW7hW.js.gz +0 -0
- package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js +0 -1
- package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js +0 -56
- package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-C38iXoO4.js +0 -1
- package/dist/dashboard/assets/api-keys-C38iXoO4.js.gz +0 -0
- package/dist/dashboard/assets/app-BayTTy01.js +0 -9
- package/dist/dashboard/assets/app-BayTTy01.js.gz +0 -0
- package/dist/dashboard/assets/app-a159k0ub.css +0 -2
- package/dist/dashboard/assets/app-a159k0ub.css.gz +0 -0
- package/dist/dashboard/assets/assert-DbRA8EG2.js +0 -15
- package/dist/dashboard/assets/assert-DbRA8EG2.js.gz +0 -0
- package/dist/dashboard/assets/badge-D9CS6dZk.js.gz +0 -0
- package/dist/dashboard/assets/banner-DympBNq4.js.gz +0 -0
- package/dist/dashboard/assets/card-ibRWU_uT.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-63qBGaRw.js +0 -1
- package/dist/dashboard/assets/cli-tools-63qBGaRw.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DJm-9pz7.js.gz +0 -0
- package/dist/dashboard/assets/dialog-8fiE6tec.js +0 -1
- package/dist/dashboard/assets/dialog-8fiE6tec.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CqGYeOrd.js.gz +0 -0
- package/dist/dashboard/assets/entry-BSAsYsmV.js +0 -1
- package/dist/dashboard/assets/entry-BSAsYsmV.js.gz +0 -0
- package/dist/dashboard/assets/entry-CiIQULZD.js +0 -4
- package/dist/dashboard/assets/entry-CiIQULZD.js.gz +0 -0
- package/dist/dashboard/assets/entry-Daz0lJ-W.js +0 -1
- package/dist/dashboard/assets/entry-Daz0lJ-W.js.gz +0 -0
- package/dist/dashboard/assets/formatters-D-MTJxMr.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-CPK47xQx.js +0 -1
- package/dist/dashboard/assets/github-badge-CPK47xQx.js.gz +0 -0
- package/dist/dashboard/assets/index-DBNPOfhV.js.gz +0 -0
- package/dist/dashboard/assets/inline-BT0zntEr.js.gz +0 -0
- package/dist/dashboard/assets/input-Da2ZViWg.js.gz +0 -0
- package/dist/dashboard/assets/layout-BXNeLw6M.js.gz +0 -0
- package/dist/dashboard/assets/list-row-BycOrrIV.js.gz +0 -0
- package/dist/dashboard/assets/live-DCQNZpF7.js +0 -1
- package/dist/dashboard/assets/live-DCQNZpF7.js.gz +0 -0
- package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
- package/dist/dashboard/assets/network-9Yj2ZyLo.js +0 -1
- package/dist/dashboard/assets/network-9Yj2ZyLo.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CEagj6ZZ.js +0 -2
- package/dist/dashboard/assets/numbers-CEagj6ZZ.js.gz +0 -0
- package/dist/dashboard/assets/providers/zai-coding-plan.svg +0 -6
- package/dist/dashboard/assets/providers/zai-coding-plan.svg.gz +0 -0
- package/dist/dashboard/assets/quota-Caw7wqci.js +0 -1
- package/dist/dashboard/assets/quota-Caw7wqci.js.gz +0 -0
- package/dist/dashboard/assets/routing-CO9zCUus.js +0 -1
- package/dist/dashboard/assets/routing-CO9zCUus.js.gz +0 -0
- package/dist/dashboard/assets/select-BIYIBkMU.js.gz +0 -0
- package/dist/dashboard/assets/settings-jS3-PNSA.js +0 -1
- package/dist/dashboard/assets/settings-jS3-PNSA.js.gz +0 -0
- package/dist/dashboard/assets/stack-DfgnFDWY.js.gz +0 -0
- package/dist/dashboard/assets/state-CkfdYWel.js.gz +0 -0
- package/dist/dashboard/assets/stream-retry-5QnVuSSo.js +0 -1
- package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
- package/dist/dashboard/assets/switch-SYm237HG.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BQqCRNIi.js.gz +0 -0
- package/src/console/cli-tools/injectors/cline.ts +0 -110
- package/src/console/cli-tools/injectors/copilot.ts +0 -108
- package/src/console/cli-tools/injectors/cowork.ts +0 -64
- package/src/console/cli-tools/injectors/deepseek-tui.ts +0 -85
- package/src/console/cli-tools/injectors/droid.ts +0 -96
- package/src/console/cli-tools/injectors/grok-build.ts +0 -70
- package/src/console/cli-tools/injectors/hermes.ts +0 -101
- package/src/console/cli-tools/injectors/jcode.ts +0 -115
- package/src/console/cli-tools/injectors/openclaw.ts +0 -161
- package/src/network/response-headers.ts +0 -39
- package/src/protocol/response/dsml.ts +0 -38
- package/src/providers/integrations/xiaomi-mimo/think-stream.ts +0 -229
- /package/dist/dashboard/assets/brand/{og_bansos.webp → og_bansos_initial.webp} +0 -0
- /package/dist/dashboard/assets/brand/{og_image.webp → og_image_initial.webp} +0 -0
|
@@ -57,7 +57,7 @@ import type {
|
|
|
57
57
|
/** Success rate below which a model is not routed to, when one is reported. */
|
|
58
58
|
const MIN_SUCCESS_RATE_PCT = 80;
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
interface InferhubRoutesConfig {
|
|
61
61
|
readonly accessResolver: (request: Request) => AccessDecision | undefined;
|
|
62
62
|
/** Resolves the `config_read` key. Absent means the catalog cannot be read. */
|
|
63
63
|
readonly resolveConfigKey?: () => Promise<string | undefined> | string | undefined;
|
|
@@ -34,7 +34,7 @@ export interface ModelHealth {
|
|
|
34
34
|
readonly familyState: FamilyState | null;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
interface ModelCapabilities {
|
|
38
38
|
readonly contextWindow: number | null;
|
|
39
39
|
readonly maxOutputTokens: number | null;
|
|
40
40
|
readonly modalities: readonly string[];
|
|
@@ -173,7 +173,7 @@ export interface UsageCacheResponse {
|
|
|
173
173
|
cacheWriteTokens: number;
|
|
174
174
|
hitRate: number;
|
|
175
175
|
}
|
|
176
|
-
|
|
176
|
+
type UsageRequestMode = "stream" | "non_stream";
|
|
177
177
|
export interface UsageRequestItem {
|
|
178
178
|
requestId: string;
|
|
179
179
|
surface?: string;
|
|
@@ -232,6 +232,12 @@ export interface UsageRequestItem {
|
|
|
232
232
|
* written before the column existed.
|
|
233
233
|
*/
|
|
234
234
|
payloadCaptureState?: string;
|
|
235
|
+
/**
|
|
236
|
+
* True for a completed request whose output was far smaller than the model's
|
|
237
|
+
* ceiling and which ended with an ordinary `stop` (a "degenerate stop").
|
|
238
|
+
* Absent for everything else — including rows written before the column.
|
|
239
|
+
*/
|
|
240
|
+
degenerateStop?: boolean;
|
|
235
241
|
/**
|
|
236
242
|
* Bitmask of the payload stages captured for this request: 1 request,
|
|
237
243
|
* 2 provider request, 4 provider response, 8 client response. Read from the
|
|
@@ -34,7 +34,7 @@ import { poolByteSnapshot } from "../../network/pool/byte-accounting";
|
|
|
34
34
|
import { consoleSseResponse, createConsoleSseStream } from "./sse";
|
|
35
35
|
import { subscribeRequestCompleted } from "../../observability/request-notify";
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
interface LiveConfig {
|
|
38
38
|
readonly accessResolver: ConsoleAccessResolver;
|
|
39
39
|
readonly poolSelector?: NetworkPoolSelector;
|
|
40
40
|
readonly db?: CartethyiaDatabase;
|
|
@@ -94,10 +94,9 @@ export function createLiveRoutes(config: LiveConfig): AnyElysia {
|
|
|
94
94
|
requireScope(access, "dashboard:read");
|
|
95
95
|
const tenantId = access?.tenantId;
|
|
96
96
|
if (!tenantId) return errorResponse(new Error("Tenant required"), set, "Live operation failed");
|
|
97
|
-
set.headers
|
|
98
|
-
set
|
|
99
|
-
|
|
100
|
-
set.headers["x-accel-buffering"] = "no";
|
|
97
|
+
// No `set.headers` writes here: this handler returns a `Response`, so
|
|
98
|
+
// Elysia discards `set` and `consoleSseResponse` below owns the SSE
|
|
99
|
+
// headers (content-type, cache-control, connection, buffering).
|
|
101
100
|
return consoleSseResponse(
|
|
102
101
|
createConsoleSseStream(request.signal, ({ send }) => {
|
|
103
102
|
return subscribeRequestCompleted((notice) => {
|
|
@@ -119,22 +118,36 @@ export function createLiveRoutes(config: LiveConfig): AnyElysia {
|
|
|
119
118
|
})
|
|
120
119
|
.get("/live/in-flight/stream", ({ request, set }) => {
|
|
121
120
|
try {
|
|
122
|
-
requireScope(config.accessResolver(request), "dashboard:read");
|
|
121
|
+
const access = requireScope(config.accessResolver(request), "dashboard:read");
|
|
122
|
+
const tenantId = access?.tenantId;
|
|
123
|
+
return consoleSseResponse(
|
|
124
|
+
createConsoleSseStream(request.signal, ({ send }) => {
|
|
125
|
+
const store = config.stateStore;
|
|
126
|
+
if (!store) {
|
|
127
|
+
send("count", EMPTY_IN_FLIGHT);
|
|
128
|
+
return () => {};
|
|
129
|
+
}
|
|
130
|
+
// The Usage page keeps one stream open for both the in-flight gauge
|
|
131
|
+
// and request-table invalidation, so this route also fans out
|
|
132
|
+
// `request_completed` events (tagged by kind) for the tenant,
|
|
133
|
+
// mirroring `/live/requests/stream`.
|
|
134
|
+
const unsubscribe = store.subscribeInFlight((snapshot) => send("count", snapshot));
|
|
135
|
+
let unsubscribeCompleted: (() => void) | undefined;
|
|
136
|
+
if (tenantId) {
|
|
137
|
+
unsubscribeCompleted = subscribeRequestCompleted((notice) => {
|
|
138
|
+
if (notice.tenantId === tenantId) send("request_completed", notice);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
send("count", store.inFlightSnapshot());
|
|
142
|
+
return () => {
|
|
143
|
+
unsubscribe();
|
|
144
|
+
unsubscribeCompleted?.();
|
|
145
|
+
};
|
|
146
|
+
}),
|
|
147
|
+
);
|
|
123
148
|
} catch (e) {
|
|
124
149
|
return errorResponse(e, set, "Live operation failed");
|
|
125
150
|
}
|
|
126
|
-
return consoleSseResponse(
|
|
127
|
-
createConsoleSseStream(request.signal, ({ send }) => {
|
|
128
|
-
const store = config.stateStore;
|
|
129
|
-
if (!store) {
|
|
130
|
-
send("count", EMPTY_IN_FLIGHT);
|
|
131
|
-
return () => {};
|
|
132
|
-
}
|
|
133
|
-
const unsubscribe = store.subscribeInFlight((snapshot) => send("count", snapshot));
|
|
134
|
-
send("count", store.inFlightSnapshot());
|
|
135
|
-
return unsubscribe;
|
|
136
|
-
}),
|
|
137
|
-
);
|
|
138
151
|
})
|
|
139
152
|
.get("/live/pools", async ({ request, set }) => {
|
|
140
153
|
try {
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
} from "../../observability/log-ring";
|
|
24
24
|
import { consoleSseResponse, createConsoleSseStream } from "./sse";
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
interface LogsConfig {
|
|
27
27
|
readonly accessResolver: ConsoleAccessResolver;
|
|
28
28
|
readonly auditSink?: AuditSink;
|
|
29
29
|
}
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
* the shared `buildSseEventFrame` core so the two layers cannot drift.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
interface SseSender {
|
|
9
9
|
send(event: string, data: unknown): void;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/** Register endpoint-specific SSE subscriptions; return their teardown callback. */
|
|
13
|
-
|
|
13
|
+
type SseSetup = (sender: SseSender) => () => void;
|
|
14
14
|
|
|
15
|
-
import { buildSseEventFrame as formatSseFrame } from "../../protocol/sse-frame";
|
|
15
|
+
import { TEXT_ENCODER, buildSseEventFrame as formatSseFrame } from "../../protocol/sse-frame";
|
|
16
16
|
|
|
17
17
|
export { formatSseFrame };
|
|
18
18
|
|
|
@@ -22,38 +22,82 @@ export { formatSseFrame };
|
|
|
22
22
|
*/
|
|
23
23
|
const SSE_HEARTBEAT_MS = 25_000;
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Hard cap on one stream's lifetime. A tab that is abandoned but never
|
|
27
|
+
* unloads (a backgrounded browser window, a parked laptop) would otherwise
|
|
28
|
+
* hold its subscription and timers forever; the client reconnects on close,
|
|
29
|
+
* so this only forces a refresh. 45 minutes is far longer than any live view
|
|
30
|
+
* needs in one stretch.
|
|
31
|
+
*/
|
|
32
|
+
const SSE_MAX_LIFETIME_MS = 45 * 60_000;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Idle timeout: if no *data* frame is sent for this long, the stream sends a
|
|
36
|
+
* terminal `close` event and closes. Heartbeats deliberately do not count —
|
|
37
|
+
* they are there to keep proxies alive, not to prove someone is reading, so
|
|
38
|
+
* they must not defeat the abandoned-tab cutoff.
|
|
39
|
+
*/
|
|
40
|
+
const SSE_IDLE_TIMEOUT_MS = 5 * 60_000;
|
|
41
|
+
|
|
42
|
+
/** Watchdog cadence; the two cutoffs above are checked on this tick. */
|
|
43
|
+
const SSE_WATCHDOG_MS = 30_000;
|
|
44
|
+
|
|
45
|
+
/** Frame sent when the server cuts the stream (idle or max lifetime). */
|
|
46
|
+
function closeFrame(reason: string): Uint8Array {
|
|
47
|
+
return TEXT_ENCODER.encode(formatSseFrame("close", JSON.stringify({ reason })));
|
|
48
|
+
}
|
|
49
|
+
|
|
25
50
|
/**
|
|
26
51
|
* Console SSE response stream with uniform JSON event encoding, heartbeat,
|
|
27
|
-
* and abort/cancel cleanup. The setup callback runs
|
|
28
|
-
* start so the initial snapshot is enqueued before the
|
|
52
|
+
* idle/max-lifetime cutoff, and abort/cancel cleanup. The setup callback runs
|
|
53
|
+
* synchronously at stream start so the initial snapshot is enqueued before the
|
|
54
|
+
* first read.
|
|
55
|
+
*
|
|
56
|
+
* The encoder is the module-level shared `TextEncoder` from the frame module:
|
|
57
|
+
* allocating one per stream (let alone per send) is unnecessary garbage for a
|
|
58
|
+
* hot path that encodes a frame every few hundred milliseconds per connection.
|
|
29
59
|
*/
|
|
30
60
|
export function createConsoleSseStream(
|
|
31
61
|
signal: AbortSignal,
|
|
32
62
|
setup: SseSetup,
|
|
33
63
|
): ReadableStream<Uint8Array> {
|
|
34
|
-
const encoder = new TextEncoder();
|
|
35
64
|
let cleanupSubscription: (() => void) | undefined;
|
|
36
65
|
let heartbeat: ReturnType<typeof setInterval> | undefined;
|
|
66
|
+
let watchdog: ReturnType<typeof setInterval> | undefined;
|
|
37
67
|
let close: (() => void) | undefined;
|
|
38
68
|
|
|
39
69
|
return new ReadableStream<Uint8Array>({
|
|
40
70
|
start(controller) {
|
|
41
71
|
let closed = false;
|
|
72
|
+
const startedAt = Date.now();
|
|
73
|
+
let lastDataAt = startedAt;
|
|
42
74
|
const sender: SseSender = {
|
|
43
75
|
send(event, data) {
|
|
44
76
|
if (closed) return;
|
|
45
77
|
try {
|
|
46
|
-
|
|
78
|
+
lastDataAt = Date.now();
|
|
79
|
+
controller.enqueue(TEXT_ENCODER.encode(formatSseFrame(event, JSON.stringify(data))));
|
|
47
80
|
} catch {
|
|
48
81
|
close?.();
|
|
49
82
|
}
|
|
50
83
|
},
|
|
51
84
|
};
|
|
52
85
|
|
|
86
|
+
const terminate = (reason: string): void => {
|
|
87
|
+
if (closed) return;
|
|
88
|
+
try {
|
|
89
|
+
controller.enqueue(closeFrame(reason));
|
|
90
|
+
} catch {
|
|
91
|
+
// Fall through to close regardless.
|
|
92
|
+
}
|
|
93
|
+
close?.();
|
|
94
|
+
};
|
|
95
|
+
|
|
53
96
|
close = () => {
|
|
54
97
|
if (closed) return;
|
|
55
98
|
closed = true;
|
|
56
99
|
clearInterval(heartbeat);
|
|
100
|
+
clearInterval(watchdog);
|
|
57
101
|
cleanupSubscription?.();
|
|
58
102
|
try {
|
|
59
103
|
controller.close();
|
|
@@ -70,12 +114,22 @@ export function createConsoleSseStream(
|
|
|
70
114
|
heartbeat = setInterval(() => {
|
|
71
115
|
if (closed) return;
|
|
72
116
|
try {
|
|
73
|
-
controller.enqueue(
|
|
117
|
+
controller.enqueue(TEXT_ENCODER.encode(": ping\n\n"));
|
|
74
118
|
} catch {
|
|
75
119
|
close?.();
|
|
76
120
|
}
|
|
77
121
|
}, SSE_HEARTBEAT_MS);
|
|
78
122
|
|
|
123
|
+
watchdog = setInterval(() => {
|
|
124
|
+
if (closed) return;
|
|
125
|
+
const now = Date.now();
|
|
126
|
+
if (now - lastDataAt > SSE_IDLE_TIMEOUT_MS) {
|
|
127
|
+
terminate("idle");
|
|
128
|
+
} else if (now - startedAt > SSE_MAX_LIFETIME_MS) {
|
|
129
|
+
terminate("max_lifetime");
|
|
130
|
+
}
|
|
131
|
+
}, SSE_WATCHDOG_MS);
|
|
132
|
+
|
|
79
133
|
signal.addEventListener("abort", close, { once: true });
|
|
80
134
|
},
|
|
81
135
|
cancel() {
|
|
@@ -165,6 +165,9 @@ function mapUsageRequestItem(
|
|
|
165
165
|
// Why the drawer is empty for this request — see the contract field. This is
|
|
166
166
|
// what makes the blank panels diagnosable without server logs.
|
|
167
167
|
...(event.payloadCaptureState ? { payloadCaptureState: event.payloadCaptureState } : {}),
|
|
168
|
+
// Degenerate stop (short output ended by an ordinary `stop`) — carried so
|
|
169
|
+
// the request detail can flag the pattern.
|
|
170
|
+
...(event.degenerateStop ? { degenerateStop: true } : {}),
|
|
168
171
|
};
|
|
169
172
|
}
|
|
170
173
|
async function resolveProxyLabel(
|
|
@@ -184,6 +187,14 @@ async function resolveProxyLabel(
|
|
|
184
187
|
}
|
|
185
188
|
const HEALTH_WINDOW_MS = 24 * 3_600_000;
|
|
186
189
|
const TOP_N = 50;
|
|
190
|
+
/**
|
|
191
|
+
* TTL for the usage analytics read-through (summary, chart, breakdown, totals).
|
|
192
|
+
* The dashboard polls these every 5–10s; a 30s TTL means the heavy telemetry
|
|
193
|
+
* window scans run once per 30s and the polls in between are answered from
|
|
194
|
+
* Redis (or the in-process fallback) with a `304`. Health stays on the
|
|
195
|
+
* default 10s — it feeds the live status tile.
|
|
196
|
+
*/
|
|
197
|
+
const USAGE_READ_THROUGH_TTL_SECONDS = 30;
|
|
187
198
|
|
|
188
199
|
/**
|
|
189
200
|
* Nearest-rank percentile over a sorted array — kept for the JS-side
|
|
@@ -197,7 +208,7 @@ function percentile(sorted: readonly number[], fraction: number): number {
|
|
|
197
208
|
return Math.round(sorted[index] ?? 0);
|
|
198
209
|
}
|
|
199
210
|
|
|
200
|
-
|
|
211
|
+
interface UsagePerformanceSummary {
|
|
201
212
|
avgResolveMs: number;
|
|
202
213
|
avgDurationMs: number;
|
|
203
214
|
latencyP50Ms: number;
|
|
@@ -389,6 +400,7 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
|
|
|
389
400
|
async usage(tenantId: string, period: string): Promise<CachedResult<UsageResponse>> {
|
|
390
401
|
return this.readThroughWithEtag(this.usageCacheKey(tenantId, period, "usage"), () =>
|
|
391
402
|
this.computeUsage(tenantId, period),
|
|
403
|
+
USAGE_READ_THROUGH_TTL_SECONDS,
|
|
392
404
|
);
|
|
393
405
|
}
|
|
394
406
|
|
|
@@ -405,8 +417,12 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
|
|
|
405
417
|
* configured — and returns the strong validator alongside it, so a repeated
|
|
406
418
|
* poll can be answered with a `304` instead of the whole payload.
|
|
407
419
|
*/
|
|
408
|
-
private readThroughWithEtag<T>(
|
|
409
|
-
|
|
420
|
+
private readThroughWithEtag<T>(
|
|
421
|
+
key: string,
|
|
422
|
+
compute: () => Promise<T>,
|
|
423
|
+
ttlSeconds?: number,
|
|
424
|
+
): Promise<CachedResult<T>> {
|
|
425
|
+
return cachedReadWithEtag(this.redis, key, compute, ttlSeconds);
|
|
410
426
|
}
|
|
411
427
|
|
|
412
428
|
private async computeUsage(tenantId: string, period: string): Promise<UsageResponse> {
|
|
@@ -459,6 +475,7 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
|
|
|
459
475
|
async usageSummary(tenantId: string, period: string): Promise<CachedResult<UsageSummaryResponse>> {
|
|
460
476
|
return this.readThroughWithEtag(this.usageCacheKey(tenantId, period, "summary"), () =>
|
|
461
477
|
this.computeSummary(tenantId, period),
|
|
478
|
+
USAGE_READ_THROUGH_TTL_SECONDS,
|
|
462
479
|
);
|
|
463
480
|
}
|
|
464
481
|
|
|
@@ -499,6 +516,7 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
|
|
|
499
516
|
async usageChart(tenantId: string, period: string): Promise<CachedResult<UsageChartResponse>> {
|
|
500
517
|
return this.readThroughWithEtag(this.usageCacheKey(tenantId, period, "chart"), () =>
|
|
501
518
|
this.computeChart(tenantId, period),
|
|
519
|
+
USAGE_READ_THROUGH_TTL_SECONDS,
|
|
502
520
|
);
|
|
503
521
|
}
|
|
504
522
|
|
|
@@ -522,7 +540,7 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
|
|
|
522
540
|
input: bucket.inputTokens,
|
|
523
541
|
// `cached` is the cached share of the prompt: the SQL reader sums
|
|
524
542
|
// cached_input_tokens, which is the same number the old JS loop used.
|
|
525
|
-
cached: bucket.
|
|
543
|
+
cached: bucket.cachedInputTokens,
|
|
526
544
|
output: bucket.outputTokens,
|
|
527
545
|
});
|
|
528
546
|
}
|
|
@@ -546,6 +564,7 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
|
|
|
546
564
|
): Promise<CachedResult<UsageByResponse>> {
|
|
547
565
|
return this.readThroughWithEtag(this.usageCacheKey(tenantId, `${dimension}:${period}`, "by"), () =>
|
|
548
566
|
this.computeBy(tenantId, dimension, period),
|
|
567
|
+
USAGE_READ_THROUGH_TTL_SECONDS,
|
|
549
568
|
);
|
|
550
569
|
}
|
|
551
570
|
|
|
@@ -23,7 +23,7 @@ import type { ProviderRecord } from "./contracts";
|
|
|
23
23
|
* purpose: the projection should not be able to reach a loader, so the type
|
|
24
24
|
* cannot drift into "call something expensive to answer a boolean".
|
|
25
25
|
*/
|
|
26
|
-
|
|
26
|
+
interface ProviderDiscoveryLookup {
|
|
27
27
|
hasModelDiscovery(providerId: string): boolean;
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -508,6 +508,13 @@ export interface ProviderAccountResponse {
|
|
|
508
508
|
label: string;
|
|
509
509
|
credentialKind: CredentialKind;
|
|
510
510
|
credentialMode?: CredentialMode;
|
|
511
|
+
/**
|
|
512
|
+
* Which sign-in family issued this credential, read from the account's
|
|
513
|
+
* `auth_state` (e.g. `social`/`oidc`/`api_key` for Kiro). Surfaced so the
|
|
514
|
+
* console can badge an account by how it logged in without shipping the
|
|
515
|
+
* whole auth state to the browser.
|
|
516
|
+
*/
|
|
517
|
+
authMethod?: string;
|
|
511
518
|
tokenExpiresAt?: string;
|
|
512
519
|
/**
|
|
513
520
|
* Who issued this credential, read from the JWT `iss` claim at registration
|
|
@@ -653,6 +660,18 @@ export interface ProviderResponse {
|
|
|
653
660
|
isBuiltIn: boolean;
|
|
654
661
|
/** True when this tenant has any persisted account for the provider. */
|
|
655
662
|
configured?: boolean;
|
|
663
|
+
/**
|
|
664
|
+
* Number of provider accounts this tenant has for the provider. Present on
|
|
665
|
+
* list items (the Providers page overview); absent elsewhere.
|
|
666
|
+
*/
|
|
667
|
+
accountCount?: number;
|
|
668
|
+
/**
|
|
669
|
+
* Accounts whose status is not disabled/revoked — an account parked by the
|
|
670
|
+
* health machine (`cooldown`) still counts. Present on list items.
|
|
671
|
+
*/
|
|
672
|
+
activeAccountCount?: number;
|
|
673
|
+
/** Enabled model rows the tenant can see for the provider. Present on list items. */
|
|
674
|
+
modelCount?: number;
|
|
656
675
|
/** `false` only for a genuinely credential-less provider (OpenCode Free) —
|
|
657
676
|
* the dashboard hides account creation for it since none is usable. */
|
|
658
677
|
requiresAccount: boolean;
|
|
@@ -800,6 +819,26 @@ export interface ProviderCatalogStore {
|
|
|
800
819
|
syncModels(tenantId: string, providerId: string): Promise<{ synced: number }>;
|
|
801
820
|
listAccounts(tenantId: string, providerId: string): Promise<readonly ProviderAccountResponse[]>;
|
|
802
821
|
listAllAccounts(tenantId: string): Promise<readonly ProviderAccountResponse[]>;
|
|
822
|
+
/**
|
|
823
|
+
* Provider-id + status only, for every account the tenant can see. Much
|
|
824
|
+
* cheaper than `listAllAccounts` (which also fetches today/lifetime usage,
|
|
825
|
+
* inflight, pool rows): used by the provider list's "configured" marker and
|
|
826
|
+
* the flat model picker's active-account counts, neither of which reads
|
|
827
|
+
* usage.
|
|
828
|
+
*/
|
|
829
|
+
listAllAccountSummaries(tenantId: string): Promise<readonly { providerId: string; status: string }[]>;
|
|
830
|
+
/**
|
|
831
|
+
* Per-provider account totals for the tenant, from one grouped read. Used by
|
|
832
|
+
* the provider list overview (`accountCount` / `activeAccountCount`).
|
|
833
|
+
*/
|
|
834
|
+
providerAccountCounts(
|
|
835
|
+
tenantId: string,
|
|
836
|
+
): Promise<readonly { providerId: string; total: number; active: number }[]>;
|
|
837
|
+
/**
|
|
838
|
+
* Per-provider enabled model-row counts for the tenant, from one grouped read.
|
|
839
|
+
* Used by the provider list overview (`modelCount`).
|
|
840
|
+
*/
|
|
841
|
+
providerModelCounts(tenantId: string): Promise<readonly { providerId: string; count: number }[]>;
|
|
803
842
|
/**
|
|
804
843
|
* Non-secret upstream auth configuration for the given owned accounts,
|
|
805
844
|
* keyed by account id. Used by the account-export route so a re-import can
|
|
@@ -62,7 +62,7 @@ export function createModelCatalogOperations(config: ProviderCatalogConfig) {
|
|
|
62
62
|
const a = requireTenantAnyScope(access, MODEL_READ_SCOPES);
|
|
63
63
|
const [providers, allAccounts] = await Promise.all([
|
|
64
64
|
config.store.list(a.tenantId),
|
|
65
|
-
config.store.
|
|
65
|
+
config.store.listAllAccountSummaries(a.tenantId),
|
|
66
66
|
]);
|
|
67
67
|
const accountsByProvider = new Map<string, number>();
|
|
68
68
|
for (const account of allAccounts) {
|
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
requireGlobalAdmin,
|
|
17
17
|
requireTenantAnyScope,
|
|
18
18
|
} from "../../shared/errors";
|
|
19
|
+
import { cachedReadWithEtag, conditionalResponse } from "../../shared/read-through-cache";
|
|
20
|
+
import type { RedisClient } from "../../../persistence/redis";
|
|
19
21
|
import type { AccessDecision } from "../../../security/access-control";
|
|
20
22
|
import type { ProviderUsageDayRow } from "../../../observability/event-query";
|
|
21
23
|
import { isBundledProviderId } from "../../../providers/provider-contracts";
|
|
@@ -25,7 +27,6 @@ import type { OAuthLoginField } from "../../../providers/authentication/oauth-fl
|
|
|
25
27
|
import { providerCredentialHint, providerCredentialUrl, providerDisplayName, providerHasAdapterUserAgent, providerOperatorNotice, providerServiceKinds, type CompatibilityProfile } from "../../../providers/provider-metadata";
|
|
26
28
|
import type { AccountHealthEventRecord } from "../../../providers/operations/account-health-service";
|
|
27
29
|
import { healthEventBuffer } from "../../../persistence/health-event-flush";
|
|
28
|
-
import { materializeBuddyDeviceBinding } from "../../../providers/integrations/buddy/buddy-identity";
|
|
29
30
|
import { CREDENTIAL_MODES, PROVIDER_READ_SCOPES, PROVIDER_WRITE_SCOPES, type BuddyGrowthTaskLedgerEntry } from "./contracts";
|
|
30
31
|
import { enqueueCheckin } from "../../../workers/daily-checkin";
|
|
31
32
|
import {
|
|
@@ -129,7 +130,22 @@ export interface ProviderCatalogConfig {
|
|
|
129
130
|
readonly auditSink?: AuditSink;
|
|
130
131
|
/** Registry-backed OAuth login clients; used to compute `oauthFlows` on responses. */
|
|
131
132
|
readonly providerRegistry: ProviderRegistry;
|
|
132
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Shared Redis client for the provider-list read-through cache. Omitted in
|
|
135
|
+
* lite mode / tests, where `cachedReadWithEtag` falls back to its bounded
|
|
136
|
+
* in-process TTL store; the ETag/304 semantics are identical either way.
|
|
137
|
+
*/
|
|
138
|
+
readonly redis?: RedisClient | undefined;
|
|
139
|
+
/**
|
|
140
|
+
* Drops the cached route snapshot after a catalog write. `currentRevision`
|
|
141
|
+
* (when the host exposes it) is folded into the provider-list cache key, so
|
|
142
|
+
* every mutation that already bumps the snapshot — provider, account, and
|
|
143
|
+
* model writes — rotates the cached overview without a per-path delete.
|
|
144
|
+
*/
|
|
145
|
+
readonly snapshotInvalidator?: {
|
|
146
|
+
invalidate(): Promise<number>;
|
|
147
|
+
currentRevision?(): number;
|
|
148
|
+
};
|
|
133
149
|
/**
|
|
134
150
|
* Re-applies one provider's BYOK adapter registration after a create/update
|
|
135
151
|
* so a custom provider is dispatchable without a restart. Optional: tests
|
|
@@ -233,23 +249,81 @@ function toProviderLoginField(field: OAuthLoginField): ProviderLoginField {
|
|
|
233
249
|
: { options: field.options.map((option) => ({ value: option.value, label: option.label })) }),
|
|
234
250
|
};
|
|
235
251
|
}
|
|
252
|
+
/** Provider-list cache TTL: short enough that a 60s poll re-checks the DB
|
|
253
|
+
* occasionally, long enough that a warm page does zero per-card work. */
|
|
254
|
+
const PROVIDER_LIST_CACHE_TTL_SECONDS = 20;
|
|
255
|
+
|
|
256
|
+
/** Cache key namespace + shape version for the provider-list overview. */
|
|
257
|
+
const PROVIDER_LIST_CACHE_SHAPE = "providers:v1:list";
|
|
258
|
+
|
|
259
|
+
/** Version of the read-through cache backend, so a bump is a key rotation. */
|
|
260
|
+
const PROVIDER_LIST_CACHE_REVISION = 1;
|
|
261
|
+
|
|
262
|
+
/** The list projection shared by `listProviders` and its cached wrapper. */
|
|
263
|
+
async function computeProviderList(
|
|
264
|
+
config: ProviderCatalogConfig,
|
|
265
|
+
tenantId: string,
|
|
266
|
+
): Promise<ProviderResponse[]> {
|
|
267
|
+
const [records, accountCounts, modelCounts] = await Promise.all([
|
|
268
|
+
config.store.list(tenantId),
|
|
269
|
+
config.store.providerAccountCounts(tenantId),
|
|
270
|
+
config.store.providerModelCounts(tenantId),
|
|
271
|
+
]);
|
|
272
|
+
const accountsByProvider = new Map(accountCounts.map((row) => [row.providerId, row]));
|
|
273
|
+
const modelsByProvider = new Map(modelCounts.map((row) => [row.providerId, row.count]));
|
|
274
|
+
return Promise.all(
|
|
275
|
+
records.map((r) => {
|
|
276
|
+
const counts = accountsByProvider.get(r.providerId);
|
|
277
|
+
return attachProviderCapabilities(
|
|
278
|
+
{
|
|
279
|
+
...sanitizeProviderResponse(r),
|
|
280
|
+
// A provider with no persisted accounts is not "configured"; the
|
|
281
|
+
// count fields are still present (zero) so the card renders the
|
|
282
|
+
// empty state instead of a loading placeholder.
|
|
283
|
+
configured: counts !== undefined,
|
|
284
|
+
accountCount: counts?.total ?? 0,
|
|
285
|
+
activeAccountCount: counts?.active ?? 0,
|
|
286
|
+
modelCount: modelsByProvider.get(r.providerId) ?? 0,
|
|
287
|
+
},
|
|
288
|
+
config.providerRegistry,
|
|
289
|
+
);
|
|
290
|
+
}),
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
236
294
|
export function createProviderCatalogOperations(config: ProviderCatalogConfig) {
|
|
237
295
|
const operations = {
|
|
238
296
|
async listProviders(access: AccessDecision | undefined): Promise<ProviderResponse[]> {
|
|
239
297
|
const a = requireTenantAnyScope(access, PROVIDER_READ_SCOPES);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
298
|
+
return computeProviderList(config, a.tenantId);
|
|
299
|
+
},
|
|
300
|
+
/**
|
|
301
|
+
* The full provider list with overview counts, served through the
|
|
302
|
+
* read-through cache (P-04). A repeated poll answers `304` with the ETag
|
|
303
|
+
* instead of recomputing the two grouped count queries.
|
|
304
|
+
*
|
|
305
|
+
* Returning a `Response` rather than a plain array is what makes the ETag
|
|
306
|
+
* conditional branch possible: a `304` has no body to serialize. The cache
|
|
307
|
+
* key is tenant-scoped and carries the routing revision, so a provider /
|
|
308
|
+
* account / model mutation that bumps the snapshot rotates the key and the
|
|
309
|
+
* next read recomputes fresh counts.
|
|
310
|
+
*/
|
|
311
|
+
async listProvidersCached(
|
|
312
|
+
access: AccessDecision | undefined,
|
|
313
|
+
ifNoneMatch: string | null,
|
|
314
|
+
): Promise<Response> {
|
|
315
|
+
const a = requireTenantAnyScope(access, PROVIDER_READ_SCOPES);
|
|
316
|
+
const revision = config.snapshotInvalidator?.currentRevision?.() ?? 0;
|
|
317
|
+
const key =
|
|
318
|
+
`cartethyia:${PROVIDER_LIST_CACHE_SHAPE}:v${PROVIDER_LIST_CACHE_REVISION}:` +
|
|
319
|
+
`${a.tenantId}:${revision}`;
|
|
320
|
+
const result = await cachedReadWithEtag(
|
|
321
|
+
config.redis,
|
|
322
|
+
key,
|
|
323
|
+
() => computeProviderList(config, a.tenantId),
|
|
324
|
+
PROVIDER_LIST_CACHE_TTL_SECONDS,
|
|
252
325
|
);
|
|
326
|
+
return conditionalResponse(ifNoneMatch, result, result.value);
|
|
253
327
|
},
|
|
254
328
|
async getProviderDetail(
|
|
255
329
|
access: AccessDecision | undefined,
|
|
@@ -694,7 +768,7 @@ export function createProviderCatalogOperations(config: ProviderCatalogConfig) {
|
|
|
694
768
|
// Non-secret auth state is part of the credential export: without
|
|
695
769
|
// it a re-import loses the upstream user id and region that Trae
|
|
696
770
|
// refresh and dispatch are addressed by.
|
|
697
|
-
...(() => {
|
|
771
|
+
...(await (async () => {
|
|
698
772
|
const authState = authStates[account.id];
|
|
699
773
|
// Buddy providers bind a per-account device identity
|
|
700
774
|
// (qimei36) that is normally derived from the INTERNAL account
|
|
@@ -707,13 +781,17 @@ export function createProviderCatalogOperations(config: ProviderCatalogConfig) {
|
|
|
707
781
|
account.providerId === "cbcn" ||
|
|
708
782
|
account.providerId === "workbuddy" ||
|
|
709
783
|
account.providerId === "workbuddycn"
|
|
710
|
-
|
|
784
|
+
// dynamic-import: B-02 lazy graph — buddy device-binding materializer, buddy-family only.
|
|
785
|
+
// The buddy-identity graph is lazy-loaded: the device-binding
|
|
786
|
+
// materializer only runs for buddy-family accounts (B-02).
|
|
787
|
+
? (await import("../../../providers/integrations/buddy/buddy-identity"))
|
|
788
|
+
.materializeBuddyDeviceBinding(account.id, authState)
|
|
711
789
|
: undefined;
|
|
712
790
|
const effectiveAuthState = materialized ?? authState;
|
|
713
791
|
return effectiveAuthState !== undefined && Object.keys(effectiveAuthState).length > 0
|
|
714
792
|
? { authState: effectiveAuthState }
|
|
715
793
|
: {};
|
|
716
|
-
})(),
|
|
794
|
+
})()),
|
|
717
795
|
...(account.inflight === undefined ? {} : { inflight: account.inflight }),
|
|
718
796
|
createdAt: account.createdAt,
|
|
719
797
|
...(account.cooldownUntil ? { cooldownUntil: account.cooldownUntil } : {}),
|
|
Binary file
|
|
@@ -151,12 +151,36 @@ const usageMatrixQuery = t.Object({
|
|
|
151
151
|
days: t.Optional(t.Integer({ minimum: 1, maximum: 366 })),
|
|
152
152
|
});
|
|
153
153
|
export function createProviderCatalogRoutes(config: ProviderCatalogConfig): AnyElysia {
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
// One provider-list cache revision shared by both operation factories. Every
|
|
155
|
+
// provider/account/model mutation already funnels through the snapshot
|
|
156
|
+
// invalidator; wrapping it here means any of those writes bumps the revision
|
|
157
|
+
// folded into the provider-list cache key, so the cached overview (P-04)
|
|
158
|
+
// recomputes on the next read without a per-path cache delete. The revision
|
|
159
|
+
// stays per route-group, so a mutation handled by a peer process still
|
|
160
|
+
// converges when its own entry's TTL lapses.
|
|
161
|
+
const underlyingInvalidator = config.snapshotInvalidator;
|
|
162
|
+
let providerListCacheRevision = 0;
|
|
163
|
+
const catalogSnapshotInvalidator = underlyingInvalidator
|
|
164
|
+
? {
|
|
165
|
+
invalidate: async () => {
|
|
166
|
+
providerListCacheRevision += 1;
|
|
167
|
+
return underlyingInvalidator.invalidate();
|
|
168
|
+
},
|
|
169
|
+
currentRevision: () => providerListCacheRevision,
|
|
170
|
+
}
|
|
171
|
+
: undefined;
|
|
172
|
+
const effectiveConfig = catalogSnapshotInvalidator
|
|
173
|
+
? { ...config, snapshotInvalidator: catalogSnapshotInvalidator }
|
|
174
|
+
: config;
|
|
175
|
+
const factory = createProviderCatalogOperations(effectiveConfig);
|
|
176
|
+
const modelFactory = createModelCatalogOperations(effectiveConfig);
|
|
156
177
|
return new Elysia({ prefix: "/providers" })
|
|
157
178
|
.error(consoleErrorHandler("Provider catalog operation failed"))
|
|
158
179
|
.get("/", async ({ request }) => {
|
|
159
|
-
return await factory.
|
|
180
|
+
return await factory.listProvidersCached(
|
|
181
|
+
config.accessResolver(request),
|
|
182
|
+
request.headers.get("if-none-match"),
|
|
183
|
+
);
|
|
160
184
|
})
|
|
161
185
|
.patch(
|
|
162
186
|
"/platform/global/:providerId",
|