cartethyia 2.7.3 → 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-2TJD37Hb.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-xefJNRmJ.js → CliToolDetailPage-DZKmVuJN.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-Rj9iHQ3F.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-BKj_r0rp.js +1 -0
- 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-C4w7vea-.js → LoginPage-DkXb6dx-.js} +1 -1
- package/dist/dashboard/assets/LoginPage-DkXb6dx-.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BtPcAfot.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-5SnUSy3j.js +9 -0
- 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-BQS0Rx_u.js → SetupPage-WTa0sktC.js} +1 -1
- package/dist/dashboard/assets/SetupPage-WTa0sktC.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-TP7voAQ0.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-C0YYuZJ4.js → assert-C0K-c4eo.js} +1 -1
- package/dist/dashboard/assets/assert-C0K-c4eo.js.gz +0 -0
- package/dist/dashboard/assets/{badge-B_DMpMJN.js → badge-DfbhQU4k.js} +1 -1
- package/dist/dashboard/assets/badge-DfbhQU4k.js.gz +0 -0
- package/dist/dashboard/assets/{banner-DMrMPmth.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-Be5Uzamx.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-muFRphDu.js → cli-tools-CQXVMn4P.js} +1 -1
- package/dist/dashboard/assets/cli-tools-CQXVMn4P.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-DOfrVmsi.js → clipboard-button-BQqBQd2n.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-BQqBQd2n.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-C9omdjQt.js → dialog-BEK7KP0H.js} +1 -1
- package/dist/dashboard/assets/dialog-BEK7KP0H.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-Bm8WKaGY.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-q5HwZ8nN.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-BTScrQpe.js → formatters-BAACA727.js} +1 -1
- package/dist/dashboard/assets/formatters-BAACA727.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-Orjuzemr.js → github-badge-YSCE4UC2.js} +1 -1
- package/dist/dashboard/assets/github-badge-YSCE4UC2.js.gz +0 -0
- package/dist/dashboard/assets/{index-cPrX5vn6.js → index-FQlj0HUT.js} +2 -2
- package/dist/dashboard/assets/index-FQlj0HUT.js.gz +0 -0
- package/dist/dashboard/assets/{inline-Dvux8w-0.js → inline-C4gU5iOL.js} +1 -1
- package/dist/dashboard/assets/inline-C4gU5iOL.js.gz +0 -0
- package/dist/dashboard/assets/{input-CU1Rgf3p.js → input-BHHX_Tmm.js} +1 -1
- package/dist/dashboard/assets/input-BHHX_Tmm.js.gz +0 -0
- package/dist/dashboard/assets/{layout-C_7A6H55.js → layout-CajrYUnD.js} +1 -1
- package/dist/dashboard/assets/{layout-C_7A6H55.js.gz → layout-CajrYUnD.js.gz} +0 -0
- package/dist/dashboard/assets/{list-row-CzFvogbj.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/minimize-2-BTmMTK4o.js +1 -0
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-JJGQm8TD.js → mutation-Rr0TIvcj.js} +1 -1
- package/dist/dashboard/assets/mutation-Rr0TIvcj.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/{query-keys-CLaNz8Xz.js → query-keys-BloKtaXJ.js} +1 -1
- package/dist/dashboard/assets/query-keys-BloKtaXJ.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-BEH32QOC.js → routing-in0f_IfK.js} +1 -1
- package/dist/dashboard/assets/routing-in0f_IfK.js.gz +0 -0
- package/dist/dashboard/assets/{select-B3iTetHN.js → select-BLULNqTp.js} +2 -2
- 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-KrOrm7JZ.js → stack-D2lIR0tg.js} +1 -1
- package/dist/dashboard/assets/stack-D2lIR0tg.js.gz +0 -0
- package/dist/dashboard/assets/{state-DLS7IDMy.js → state-DTARjfiW.js} +1 -1
- package/dist/dashboard/assets/state-DTARjfiW.js.gz +0 -0
- package/dist/dashboard/assets/{switch-42ItvbAs.js → switch-c0oPSnqW.js} +1 -1
- package/dist/dashboard/assets/switch-c0oPSnqW.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-Dmnfh1Ue.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 +59 -57
- package/migrations/full-postgres/0027_qoder_identity_genuine_seed.sql +9 -10
- package/migrations/full-postgres/0032_add_provider_pool_accounts.sql +53 -0
- package/migrations/full-postgres/0033_add_api_key_ban.sql +9 -0
- package/migrations/full-postgres/0034_add_api_key_ban_until.sql +7 -0
- package/migrations/full-postgres/0035_align_pool_account_status_enum.sql +21 -0
- package/migrations/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/0027_qoder_identity_genuine_seed.sql +9 -10
- package/migrations/lite-sqlite3/0032_add_provider_pool_accounts.sql +44 -0
- package/migrations/lite-sqlite3/0033_add_api_key_ban.sql +8 -0
- package/migrations/lite-sqlite3/0034_add_api_key_ban_until.sql +5 -0
- package/migrations/lite-sqlite3/0035_align_pool_account_status_enum.sql +6 -0
- package/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 -5
- package/scripts/cli/update.ts +15 -41
- package/src/boot/app.ts +67 -10
- 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 +8 -4
- 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 +92 -31
- package/src/console/api-keys/routes.ts +131 -6
- package/src/console/audit/routes.ts +3 -3
- package/src/console/auth/access.ts +4 -4
- package/src/console/auth/account-reset.ts +5 -5
- 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 +40 -34
- 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 +11 -9
- package/src/console/backup/export-endpoint.ts +11 -12
- package/src/console/backup/nine-router.ts +1 -7
- package/src/console/backup/routes.ts +32 -36
- 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 +4 -4
- package/src/console/cli-tools/service.ts +1 -1
- package/src/console/cli-tools/store.ts +2 -2
- package/src/console/console-router-guards.ts +2 -2
- package/src/console/console-router.ts +36 -4
- package/src/console/dashboard-assets.ts +9 -9
- package/src/console/data-management/routes.ts +3 -3
- package/src/console/domain-registration.ts +18 -4
- 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 +5 -1
- package/src/console/observability/live.ts +34 -21
- package/src/console/observability/logs.ts +4 -4
- package/src/console/observability/performance.ts +3 -3
- package/src/console/observability/routes.ts +3 -3
- package/src/console/observability/sse.ts +62 -8
- package/src/console/observability/store.ts +36 -20
- package/src/console/providers/catalog/catalog-projections.ts +1 -1
- package/src/console/providers/catalog/contracts.ts +82 -22
- package/src/console/providers/catalog/model-operations.ts +1 -1
- package/src/console/providers/catalog/provider-operations.ts +110 -17
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/routes.ts +30 -6
- package/src/console/providers/catalog/store-helpers.ts +349 -326
- package/src/console/providers/catalog/store.ts +1662 -1292
- package/src/console/providers/detail/routes.ts +4 -4
- package/src/console/providers/oauth/callback-listener.ts +100 -18
- package/src/console/providers/oauth/routes.ts +95 -16
- 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 +285 -67
- package/src/console/quota/account-test-egress.ts +4 -4
- 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/model/routes.ts +3 -3
- package/src/console/routing/pools/contracts.ts +13 -10
- package/src/console/routing/pools/import-parse.ts +3 -3
- package/src/console/routing/pools/operations.ts +14 -7
- package/src/console/routing/pools/probe-verdict.ts +8 -3
- package/src/console/routing/pools/relay-deploy.ts +12 -12
- package/src/console/routing/pools/routes.ts +3 -3
- package/src/console/routing/pools/store.ts +21 -1
- package/src/console/settings/contracts.ts +29 -2
- package/src/console/settings/routes.ts +3 -3
- package/src/console/settings/store.ts +20 -0
- package/src/console/share/share-router.ts +112 -44
- package/src/console/share/share-stats.ts +109 -39
- 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 +42 -6
- 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 +256 -98
- 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 +76 -5
- package/src/network/pool/agent.ts +7 -6
- package/src/network/pool/byte-accounting.ts +1 -1
- package/src/network/pool/resolver.ts +55 -51
- package/src/network/pool/selector.ts +150 -136
- package/src/network/pool-cooldown-store.ts +71 -16
- package/src/network/pool-health-machine.ts +22 -2
- package/src/network/rate-limit-verdict.ts +68 -0
- package/src/network/retry.ts +1 -1
- package/src/network/ssrf.ts +24 -75
- package/src/observability/event-log.ts +2 -2
- package/src/observability/event-query.ts +286 -214
- 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/telemetry-status.ts +1 -1
- 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 +10 -5
- package/src/persistence/dialect.ts +5 -1
- package/src/persistence/readiness.ts +19 -7
- package/src/persistence/redis.ts +1 -1
- package/src/persistence/schema.ts +177 -45
- package/src/persistence/shared-cache.ts +2 -2
- package/src/persistence/sort-order.ts +1 -1
- package/src/persistence/stores/api-key-store.ts +109 -4
- package/src/persistence/stores/share-store.ts +20 -8
- package/src/persistence/stores/telemetry-store.ts +1 -1
- 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 +12 -16
- package/src/protocol/dsml.ts +3 -49
- package/src/protocol/gateway-error.ts +6 -3
- package/src/protocol/image-clamp.ts +2 -2
- package/src/protocol/image-generation.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 +27 -12
- package/src/protocol/response/codex.ts +2 -2
- package/src/protocol/response/gemini.ts +1 -1
- package/src/protocol/response/messages.ts +6 -6
- package/src/protocol/response/omp-tool-call-markup.ts +1 -1
- package/src/protocol/response/responses.ts +3 -3
- package/src/protocol/sse-decoder.ts +64 -4
- 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 +8 -8
- 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/protocol/upstream/send-claude-messages.ts +3 -3
- package/src/providers/adapters/base-adapter.ts +1 -1
- package/src/providers/adapters/compatible-adapter.ts +44 -5
- 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/authentication/oauth-refresh-service.ts +2 -2
- package/src/providers/bundled-discovery-providers.ts +6 -30
- package/src/providers/configured-openai-providers.ts +82 -97
- package/src/providers/configured-provider.ts +1 -1
- 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 +50 -15
- package/src/providers/discovery/probing-service.ts +18 -20
- package/src/providers/integrations/agentrouter/agentrouter.ts +14 -5
- package/src/providers/integrations/alibabacp/alibabacp.ts +2 -2
- package/src/providers/integrations/anthropic.ts +4 -11
- 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 +15 -26
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +2 -2
- 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-discovery-shared.ts +37 -6
- package/src/providers/integrations/buddy/buddy-identity.ts +108 -1
- package/src/providers/integrations/buddy/buddy-media.ts +16 -16
- 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 +5 -16
- package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
- package/src/providers/integrations/buddy/workbuddy/provider.ts +6 -10
- package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
- package/src/providers/integrations/claude/claude-betas.ts +1 -1
- package/src/providers/integrations/claude/claude-cch.ts +148 -148
- package/src/providers/integrations/claude/claude-credentials.ts +1 -1
- package/src/providers/integrations/claude/claude-slow-lane-retry.ts +1 -1
- package/src/providers/integrations/claude/claude.ts +3 -10
- package/src/providers/integrations/claude/fallback-credit.ts +3 -7
- package/src/providers/integrations/cline/cline.ts +2 -2
- package/src/providers/integrations/cloudflareai/cloudflareai.ts +4 -7
- package/src/providers/integrations/codex/codex-device-code.ts +101 -101
- package/src/providers/integrations/codex/codex-errors.ts +1 -1
- package/src/providers/integrations/codex/codex-oauth.ts +0 -1
- package/src/providers/integrations/codex/codex.ts +14 -15
- package/src/providers/integrations/commandcode/commandcode.ts +76 -116
- package/src/providers/integrations/commandcode/quota.ts +132 -48
- package/src/providers/integrations/cursor/cursor-http2.ts +11 -14
- 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 +53 -20
- 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 +13 -13
- package/src/providers/integrations/gemini.ts +11 -11
- 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 +47 -20
- 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 +88 -42
- package/src/providers/integrations/inferhub.ts +4 -11
- package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
- package/src/providers/integrations/kimi/kimi.ts +4 -11
- 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 +91 -78
- package/src/providers/integrations/muse/muse-oauth.ts +0 -1
- package/src/providers/integrations/muse/muse-quota.ts +0 -4
- package/src/providers/integrations/muse/muse.ts +6 -9
- package/src/providers/integrations/opencode/fingerprint.ts +7 -5
- package/src/providers/integrations/opencode/provider.ts +17 -24
- package/src/providers/integrations/perplexity.ts +2 -2
- package/src/providers/integrations/qoder/qoder-catalog.ts +132 -17
- package/src/providers/integrations/qoder/qoder-checkin.ts +26 -8
- package/src/providers/integrations/qoder/qoder-native-identity-store.ts +9 -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 +22 -28
- package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
- package/src/providers/integrations/qoder/qoder-umid.ts +33 -38
- package/src/providers/integrations/qoder/qoder-virtual.ts +14 -2
- package/src/providers/integrations/qoder/qoder.ts +521 -63
- 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/tokenharbor.ts +2 -2
- 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 +59 -19
- package/src/providers/integrations/xai/xai-oauth.ts +5 -5
- package/src/providers/integrations/zcode/oauth.ts +1 -1
- 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 +22 -6
- package/src/providers/operations/provider-credential-service.ts +35 -6
- package/src/providers/operations/upstream-deadline.ts +3 -3
- package/src/providers/provider-contracts.ts +65 -10
- package/src/providers/provider-credential-resolver.ts +1 -1
- 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-registry.ts +4 -4
- package/src/providers/provider-usage.ts +81 -16
- package/src/providers/quota/cache.ts +29 -0
- package/src/providers/quota/quota-contracts.ts +28 -10
- package/src/providers/quota/refresh.ts +29 -6
- package/src/providers/quota/snapshot.ts +94 -0
- package/src/providers/search/search-provider.ts +6 -8
- package/src/providers/search/search-providers.ts +2 -1
- package/src/security/abuse.ts +8 -8
- package/src/security/admission/reasons.ts +17 -17
- package/src/security/admission/service.ts +2 -2
- package/src/security/admission/ttl.ts +61 -61
- package/src/security/api-key-auth.ts +20 -8
- 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/coerce.ts +19 -0
- package/src/shared/invoke-guard.ts +47 -0
- package/src/transport/contracts.ts +13 -4
- package/src/transport/dispatch/attempt-finalize.ts +5 -4
- package/src/transport/dispatch/attempt-loop.ts +10 -7
- package/src/transport/dispatch/capture-policy.ts +34 -37
- package/src/transport/dispatch/empty-stream.ts +28 -25
- package/src/transport/dispatch/generation.ts +33 -53
- package/src/transport/dispatch/leases.ts +8 -8
- package/src/transport/dispatch/native-shared.ts +127 -0
- package/src/transport/dispatch/proxy-request.ts +10 -8
- package/src/transport/dispatch/responses-compact.ts +35 -56
- package/src/transport/dispatch/retry-policy.ts +4 -67
- package/src/transport/dispatch/stream-bridge.ts +1 -1
- package/src/transport/dispatch/streaming/encoder.ts +18 -72
- package/src/transport/dispatch/streaming/finalize.ts +11 -10
- 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 +27 -55
- package/src/transport/dispatch/token-count.ts +49 -46
- package/src/transport/dispatch/websearch.ts +24 -50
- package/src/transport/drain-endpoint.ts +3 -3
- package/src/transport/failure-policy.ts +19 -6
- package/src/transport/guards/auth-guard.ts +6 -11
- package/src/transport/guards/body-policy.ts +7 -7
- package/src/transport/guards/error-lifecycle.ts +18 -3
- package/src/transport/guards/guard-errors.ts +7 -3
- package/src/transport/guards/headroom-guard.ts +2 -2
- package/src/transport/guards/pipeline.ts +7 -7
- package/src/transport/guards/readiness-guard.ts +16 -2
- package/src/transport/guards/request-context.ts +15 -20
- package/src/transport/request/preparer.ts +306 -27
- 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 +5 -20
- 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 +113 -19
- package/src/transport/routing/route-model.ts +131 -38
- package/src/transport/routing/router.ts +154 -39
- package/src/transport/shutdown-notice.ts +21 -9
- 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 +3 -3
- package/src/transport/surface/content-parts.ts +1 -1
- package/src/transport/surface/dialects.ts +2 -2
- package/src/transport/surface/messages/encode.ts +1 -1
- 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-2TJD37Hb.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-xefJNRmJ.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-Rj9iHQ3F.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-JEbYJFxw.js +0 -1
- package/dist/dashboard/assets/CombosPage-JEbYJFxw.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-C4w7vea-.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BtPcAfot.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js +0 -1
- package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js +0 -4
- package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.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-0OsXRGXe.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-DdPc8slb.js +0 -9
- package/dist/dashboard/assets/ProxyPage-DdPc8slb.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js +0 -1
- package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CDZjTt87.js +0 -1
- package/dist/dashboard/assets/SettingsPage-CDZjTt87.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-BQS0Rx_u.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-BAL5npBC.js +0 -1
- package/dist/dashboard/assets/SortableList-BAL5npBC.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-TP7voAQ0.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js +0 -56
- package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-MbI7e7rt.js +0 -1
- package/dist/dashboard/assets/api-keys-MbI7e7rt.js.gz +0 -0
- package/dist/dashboard/assets/app-B2vyPoII.css +0 -2
- package/dist/dashboard/assets/app-B2vyPoII.css.gz +0 -0
- package/dist/dashboard/assets/assert-C0YYuZJ4.js.gz +0 -0
- package/dist/dashboard/assets/badge-B_DMpMJN.js.gz +0 -0
- package/dist/dashboard/assets/banner-DMrMPmth.js.gz +0 -0
- package/dist/dashboard/assets/card-Be5Uzamx.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-muFRphDu.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DOfrVmsi.js.gz +0 -0
- package/dist/dashboard/assets/dialog-C9omdjQt.js.gz +0 -0
- package/dist/dashboard/assets/drawer-Bm8WKaGY.js.gz +0 -0
- package/dist/dashboard/assets/entry-DS90gUks.js +0 -1
- package/dist/dashboard/assets/entry-DS90gUks.js.gz +0 -0
- package/dist/dashboard/assets/entry-j40LJotO.js +0 -4
- package/dist/dashboard/assets/entry-j40LJotO.js.gz +0 -0
- package/dist/dashboard/assets/entry-q5HwZ8nN.js.gz +0 -0
- package/dist/dashboard/assets/formatters-BTScrQpe.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-Orjuzemr.js.gz +0 -0
- package/dist/dashboard/assets/index-cPrX5vn6.js.gz +0 -0
- package/dist/dashboard/assets/inline-Dvux8w-0.js.gz +0 -0
- package/dist/dashboard/assets/input-CU1Rgf3p.js.gz +0 -0
- package/dist/dashboard/assets/list-row-CzFvogbj.js.gz +0 -0
- package/dist/dashboard/assets/live-KaG15HLQ.js +0 -1
- package/dist/dashboard/assets/live-KaG15HLQ.js.gz +0 -0
- package/dist/dashboard/assets/minimize-2-D-MN8W8a.js +0 -1
- package/dist/dashboard/assets/minimize-2-D-MN8W8a.js.gz +0 -0
- package/dist/dashboard/assets/mutation-JJGQm8TD.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BNDZnsUE.js +0 -2
- package/dist/dashboard/assets/numbers-BNDZnsUE.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-CLaNz8Xz.js.gz +0 -0
- package/dist/dashboard/assets/quota-DZGyp0N5.js +0 -1
- package/dist/dashboard/assets/quota-DZGyp0N5.js.gz +0 -0
- package/dist/dashboard/assets/routing-BEH32QOC.js.gz +0 -0
- package/dist/dashboard/assets/select-B3iTetHN.js.gz +0 -0
- package/dist/dashboard/assets/settings-Dt3Eqbnq.js +0 -1
- package/dist/dashboard/assets/settings-Dt3Eqbnq.js.gz +0 -0
- package/dist/dashboard/assets/stack-KrOrm7JZ.js.gz +0 -0
- package/dist/dashboard/assets/state-DLS7IDMy.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-42ItvbAs.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-Dmnfh1Ue.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-wvwCi4Eh.js → app-Bgd3zANQ.js} +0 -0
- /package/dist/dashboard/assets/{app-wvwCi4Eh.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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Elysia, t } from "elysia";
|
|
1
|
+
import { Elysia, t, type AnyElysia } from "elysia";
|
|
2
2
|
import { ConsoleDomainError, errorResponse, requireTenantScope } from "../../shared/errors";
|
|
3
3
|
import { literalUnion } from "../../shared/elysia-schema";
|
|
4
4
|
import type { AccessDecision } from "../../../security/access-control";
|
|
@@ -30,7 +30,7 @@ const ROUTING_BOUNDS = {
|
|
|
30
30
|
maxInflightPerProxy: PROVIDER_ROUTING_MAX_INFLIGHT_BOUNDS,
|
|
31
31
|
} as const;
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
function createProviderDetailOperations(deps: ProviderDetailConfig) {
|
|
34
34
|
const operations = {
|
|
35
35
|
async getRouting(
|
|
36
36
|
access: AccessDecision | undefined,
|
|
@@ -160,7 +160,7 @@ const updateRoutingBody = t.Object({
|
|
|
160
160
|
});
|
|
161
161
|
|
|
162
162
|
|
|
163
|
-
export function createProviderDetailRoutes(config: ProviderDetailConfig):
|
|
163
|
+
export function createProviderDetailRoutes(config: ProviderDetailConfig): AnyElysia {
|
|
164
164
|
const factory = createProviderDetailOperations(config);
|
|
165
165
|
return new Elysia({ prefix: "/providers" })
|
|
166
166
|
.get("/:providerId/routing", async ({ request, params, set }) => {
|
|
@@ -191,5 +191,5 @@ export function createProviderDetailRoutes(config: ProviderDetailConfig): Elysia
|
|
|
191
191
|
return providerDetailErrorResponse(e, set);
|
|
192
192
|
}
|
|
193
193
|
},
|
|
194
|
-
)
|
|
194
|
+
)
|
|
195
195
|
}
|
|
@@ -28,7 +28,7 @@ import { log } from "../../../observability/logger";
|
|
|
28
28
|
import { ConsoleDomainError } from "../../shared/errors";
|
|
29
29
|
|
|
30
30
|
/** A loopback endpoint a callback can actually be delivered to. */
|
|
31
|
-
|
|
31
|
+
interface LoopbackCallbackEndpoint {
|
|
32
32
|
readonly port: number;
|
|
33
33
|
readonly path: string;
|
|
34
34
|
}
|
|
@@ -44,7 +44,7 @@ const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
|
|
|
44
44
|
* installed app and a remote host is delivered over the network; neither is
|
|
45
45
|
* something this process can bind, and both keep the manual path.
|
|
46
46
|
*/
|
|
47
|
-
|
|
47
|
+
function loopbackCallbackEndpoint(redirectUri: string): LoopbackCallbackEndpoint | undefined {
|
|
48
48
|
let url: URL;
|
|
49
49
|
try {
|
|
50
50
|
url = new URL(redirectUri);
|
|
@@ -59,12 +59,19 @@ export function loopbackCallbackEndpoint(redirectUri: string): LoopbackCallbackE
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/** Completes one callback once its code and state have been read. */
|
|
62
|
-
|
|
62
|
+
interface OAuthCallbackCompleter {
|
|
63
63
|
complete(
|
|
64
64
|
providerId: string,
|
|
65
65
|
code: string,
|
|
66
66
|
state: string,
|
|
67
|
-
|
|
67
|
+
/** Extra query params the callback carried (e.g. Kiro's `login_option`). */
|
|
68
|
+
callbackParams?: Readonly<Record<string, string>>,
|
|
69
|
+
): Promise<{
|
|
70
|
+
readonly ok: boolean;
|
|
71
|
+
readonly message: string;
|
|
72
|
+
readonly redirectUrl?: string;
|
|
73
|
+
readonly continueState?: string;
|
|
74
|
+
}>;
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
/** Minimal `Bun.serve` surface this module depends on. */
|
|
@@ -79,7 +86,7 @@ type ServeFn = (options: {
|
|
|
79
86
|
readonly fetch: (request: Request) => Response | Promise<Response>;
|
|
80
87
|
}) => LoopbackServer;
|
|
81
88
|
|
|
82
|
-
|
|
89
|
+
interface CallbackListenerOptions {
|
|
83
90
|
readonly completer: OAuthCallbackCompleter;
|
|
84
91
|
/** Injected in tests; defaults to `Bun.serve`. */
|
|
85
92
|
readonly serve?: ServeFn;
|
|
@@ -92,7 +99,10 @@ const DEFAULT_TTL_MS = 900_000;
|
|
|
92
99
|
|
|
93
100
|
interface RegisteredFlow {
|
|
94
101
|
readonly providerId: string;
|
|
102
|
+
/** Paths this flow's callback may arrive on (e.g. Kiro serves both `/oauth/callback` and `/signin/callback`). */
|
|
103
|
+
readonly paths: readonly string[];
|
|
95
104
|
readonly timer: ReturnType<typeof setTimeout>;
|
|
105
|
+
inFlight: boolean;
|
|
96
106
|
}
|
|
97
107
|
|
|
98
108
|
interface PortListener {
|
|
@@ -134,17 +144,30 @@ export class OAuthCallbackListener {
|
|
|
134
144
|
if (endpoint === undefined) return false;
|
|
135
145
|
const existing = this.#ports.get(endpoint.port);
|
|
136
146
|
if (existing !== undefined) {
|
|
137
|
-
this.#remember(existing, state, providerId);
|
|
147
|
+
this.#remember(existing, state, providerId, this.#callbackPaths(endpoint.path));
|
|
138
148
|
return true;
|
|
139
149
|
}
|
|
140
150
|
const servers = this.#bind(endpoint);
|
|
141
151
|
const listener: PortListener = { port: endpoint.port, servers, flows: new Map() };
|
|
142
152
|
this.#ports.set(endpoint.port, listener);
|
|
143
|
-
this.#remember(listener, state, providerId);
|
|
153
|
+
this.#remember(listener, state, providerId, this.#callbackPaths(endpoint.path));
|
|
144
154
|
log.info(`[oauth] callback listener bound on 127.0.0.1:${endpoint.port}${endpoint.path}`);
|
|
145
155
|
return true;
|
|
146
156
|
}
|
|
147
157
|
|
|
158
|
+
/**
|
|
159
|
+
* The paths one login may deliver its callback on. Kiro's portal is observed
|
|
160
|
+
* (unpacked IDE 1.4.62) to redirect to both `/oauth/callback` and
|
|
161
|
+
* `/signin/callback`; binding only the advertised path left a redirect to the
|
|
162
|
+
* other one hitting a 404 and the login hanging. Other providers neither
|
|
163
|
+
* register nor expect `/signin/callback`, so adding it is harmless.
|
|
164
|
+
*/
|
|
165
|
+
#callbackPaths(advertised: string): readonly string[] {
|
|
166
|
+
return advertised === "/signin/callback"
|
|
167
|
+
? [advertised]
|
|
168
|
+
: [advertised, "/signin/callback"];
|
|
169
|
+
}
|
|
170
|
+
|
|
148
171
|
/** Drops a flow and frees its port when nothing else is waiting on it. */
|
|
149
172
|
release(redirectUri: string, state: string): void {
|
|
150
173
|
const endpoint = loopbackCallbackEndpoint(redirectUri);
|
|
@@ -154,6 +177,25 @@ export class OAuthCallbackListener {
|
|
|
154
177
|
this.#forget(endpoint.port, listener, state);
|
|
155
178
|
}
|
|
156
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Re-registers an existing flow state under a new callback state.
|
|
182
|
+
*
|
|
183
|
+
* Kiro's two-phase portal login starts a second authorization leg with a
|
|
184
|
+
* *fresh* state (the IDE mints a new UUID for the IdC authorize step), so
|
|
185
|
+
* the loopback listener must retain the pending flow under that second
|
|
186
|
+
* state too — otherwise the phase-2 callback answers "OAuth session not
|
|
187
|
+
* found or expired" even though the flow is still alive.
|
|
188
|
+
*/
|
|
189
|
+
track(redirectUri: string, state: string, newState: string): void {
|
|
190
|
+
const endpoint = loopbackCallbackEndpoint(redirectUri);
|
|
191
|
+
if (endpoint === undefined) return;
|
|
192
|
+
const listener = this.#ports.get(endpoint.port);
|
|
193
|
+
if (listener === undefined) return;
|
|
194
|
+
const flow = listener.flows.get(state);
|
|
195
|
+
if (flow === undefined) return;
|
|
196
|
+
this.#remember(listener, newState, flow.providerId, flow.paths);
|
|
197
|
+
}
|
|
198
|
+
|
|
157
199
|
/**
|
|
158
200
|
* Drops every flow for `providerId` on `redirectUri`'s port.
|
|
159
201
|
*
|
|
@@ -182,7 +224,7 @@ export class OAuthCallbackListener {
|
|
|
182
224
|
}
|
|
183
225
|
}
|
|
184
226
|
|
|
185
|
-
#remember(listener: PortListener, state: string, providerId: string): void {
|
|
227
|
+
#remember(listener: PortListener, state: string, providerId: string, paths: readonly string[]): void {
|
|
186
228
|
const previous = listener.flows.get(state);
|
|
187
229
|
if (previous !== undefined) clearTimeout(previous.timer);
|
|
188
230
|
const timer = setTimeout(() => {
|
|
@@ -190,7 +232,7 @@ export class OAuthCallbackListener {
|
|
|
190
232
|
}, this.#ttlMs);
|
|
191
233
|
// A pending login must not hold the process open on its own.
|
|
192
234
|
(timer as unknown as { unref?: () => void }).unref?.();
|
|
193
|
-
listener.flows.set(state, { providerId, timer });
|
|
235
|
+
listener.flows.set(state, { providerId, paths, timer, inFlight: false });
|
|
194
236
|
}
|
|
195
237
|
|
|
196
238
|
#forget(port: number, listener: PortListener, state: string): void {
|
|
@@ -250,9 +292,6 @@ export class OAuthCallbackListener {
|
|
|
250
292
|
endpoint: LoopbackCallbackEndpoint,
|
|
251
293
|
): Promise<Response> {
|
|
252
294
|
const url = new URL(request.url);
|
|
253
|
-
if (url.pathname !== endpoint.path) {
|
|
254
|
-
return new Response("Not found", { status: 404 });
|
|
255
|
-
}
|
|
256
295
|
const listener = this.#ports.get(endpoint.port);
|
|
257
296
|
if (listener === undefined) {
|
|
258
297
|
return new Response("OAuth session not found or expired.", { status: 400 });
|
|
@@ -278,7 +317,7 @@ export class OAuthCallbackListener {
|
|
|
278
317
|
{ status: 400, headers: { "content-type": "text/html; charset=utf-8" } },
|
|
279
318
|
);
|
|
280
319
|
}
|
|
281
|
-
if (code.length === 0) {
|
|
320
|
+
if (code.length === 0 && !url.searchParams.has("login_option")) {
|
|
282
321
|
return new Response("Missing authorization code.", { status: 400 });
|
|
283
322
|
}
|
|
284
323
|
// `state` is the correlation key and the replay guard. An authorization
|
|
@@ -297,14 +336,45 @@ export class OAuthCallbackListener {
|
|
|
297
336
|
if (matched === undefined) {
|
|
298
337
|
return new Response("OAuth session not found or expired.", { status: 400 });
|
|
299
338
|
}
|
|
300
|
-
//
|
|
301
|
-
//
|
|
339
|
+
// Some providers (Kiro's portal) can land the callback on any of several
|
|
340
|
+
// registered paths — `/oauth/callback` or `/signin/callback`. Accept the
|
|
341
|
+
// path the flow registered for, not only the advertised default.
|
|
342
|
+
if (!matched.paths.includes(url.pathname)) {
|
|
343
|
+
return new Response("Not found", { status: 404 });
|
|
344
|
+
}
|
|
345
|
+
if (matched.inFlight) {
|
|
346
|
+
return new Response("OAuth callback is already being processed.", { status: 409 });
|
|
347
|
+
}
|
|
348
|
+
// Claim while the exchange is in flight. A multi-step flow (Kiro OIDC) may
|
|
349
|
+
// return a redirect and reuse this listener/state for its next callback.
|
|
350
|
+
matched.inFlight = true;
|
|
302
351
|
const matchedState = state.length > 0 ? state : this.#stateOf(listener, matched);
|
|
303
|
-
|
|
304
|
-
|
|
352
|
+
let result: {
|
|
353
|
+
ok: boolean;
|
|
354
|
+
message: string;
|
|
355
|
+
redirectUrl?: string;
|
|
356
|
+
continueState?: string;
|
|
357
|
+
};
|
|
358
|
+
// Some authorization servers (Kiro's portal) append provider-specific query
|
|
359
|
+
// params — `login_option` names the social/enterprise family that approved.
|
|
360
|
+
// The exchange needs them to rebuild the exact redirect URI the portal saw,
|
|
361
|
+
// and the OIDC families carry `issuer_url`/`idc_region`/`client_id`/`scopes`
|
|
362
|
+
// that the continuation flow needs. Forward every param other than the ones
|
|
363
|
+
// this handler already consumed.
|
|
364
|
+
const callbackParams: Record<string, string> = {};
|
|
365
|
+
for (const [key, value] of url.searchParams) {
|
|
366
|
+
if (key === "code" || key === "state" || key === "error" || key === "error_description") continue;
|
|
367
|
+
callbackParams[key] = value;
|
|
368
|
+
}
|
|
305
369
|
try {
|
|
306
|
-
result = await this.#completer.complete(
|
|
370
|
+
result = await this.#completer.complete(
|
|
371
|
+
matched.providerId,
|
|
372
|
+
code,
|
|
373
|
+
state,
|
|
374
|
+
Object.keys(callbackParams).length > 0 ? callbackParams : undefined,
|
|
375
|
+
);
|
|
307
376
|
} catch (failure) {
|
|
377
|
+
if (matchedState !== undefined) this.#forget(endpoint.port, listener, matchedState);
|
|
308
378
|
const message = failure instanceof Error ? failure.message : "OAuth login failed";
|
|
309
379
|
log.error(`[oauth] callback completion failed for ${matched.providerId}: ${message}`);
|
|
310
380
|
return new Response(
|
|
@@ -312,6 +382,18 @@ export class OAuthCallbackListener {
|
|
|
312
382
|
{ status: 500, headers: { "content-type": "text/html; charset=utf-8" } },
|
|
313
383
|
);
|
|
314
384
|
}
|
|
385
|
+
if (result.redirectUrl !== undefined) {
|
|
386
|
+
// Phase 1 of a two-phase login (Kiro OIDC): the flow is still pending, so
|
|
387
|
+
// continue by sending the browser to the phase-2 authorize URL. The phase-2
|
|
388
|
+
// authorize URL carries a fresh state (`continueState`), which the completer
|
|
389
|
+
// already registered under — keep the current matcher alive for it.
|
|
390
|
+
matched.inFlight = false;
|
|
391
|
+
return new Response(null, {
|
|
392
|
+
status: 302,
|
|
393
|
+
headers: { location: result.redirectUrl },
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
if (matchedState !== undefined) this.#forget(endpoint.port, listener, matchedState);
|
|
315
397
|
if (!result.ok) {
|
|
316
398
|
return new Response(
|
|
317
399
|
`<!doctype html><html><body><p>OAuth login failed: ${escapeHtml(result.message)}</p></body></html>`,
|
|
@@ -9,7 +9,7 @@ import type { ProviderRegistry } from "../../../providers/provider-registry";
|
|
|
9
9
|
import { browserAuthorizeRedirectUri } from "../../../config/providers";
|
|
10
10
|
import { log } from "../../../observability/logger";
|
|
11
11
|
import { GatewayError } from "../../../protocol/gateway-error";
|
|
12
|
-
import { Elysia, t } from "elysia";
|
|
12
|
+
import { Elysia, t, type AnyElysia } from "elysia";
|
|
13
13
|
import { ConsoleDomainError, errorResponse, requireScope } from "../../shared/errors";
|
|
14
14
|
import { generateOAuthState, generatePkcePair } from "../../../providers/authentication/oauth-flow-store";
|
|
15
15
|
import type { OAuthFlowStorage } from "../../../providers/authentication/oauth-flow-store";
|
|
@@ -171,7 +171,7 @@ export class DrizzleOAuthAccountStore implements OAuthAccountStore {
|
|
|
171
171
|
* server the redirect arrives at; `callback-listener.ts` is what makes those
|
|
172
172
|
* the same endpoint.
|
|
173
173
|
*/
|
|
174
|
-
|
|
174
|
+
interface PersistedOAuthAccount {
|
|
175
175
|
readonly accountId: string;
|
|
176
176
|
}
|
|
177
177
|
|
|
@@ -212,9 +212,20 @@ async function requireClient(
|
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
/** The result of exchanging one authorization code, as the caller should report it. */
|
|
215
|
-
|
|
215
|
+
interface OAuthCompleteOutcome {
|
|
216
216
|
readonly ok: boolean;
|
|
217
217
|
readonly message: string;
|
|
218
|
+
/**
|
|
219
|
+
* When set, the exchange is not finished: the browser must be redirected to
|
|
220
|
+
* this URL to continue (Kiro's OIDC families need a second authorization
|
|
221
|
+
* leg after the portal callback).
|
|
222
|
+
*/
|
|
223
|
+
readonly redirectUrl?: string;
|
|
224
|
+
/**
|
|
225
|
+
* New callback state the phase-2 authorize URL carries (Kiro mints a fresh
|
|
226
|
+
* state for the IdC leg; the console retains the pending flow under it).
|
|
227
|
+
*/
|
|
228
|
+
readonly continueState?: string;
|
|
218
229
|
}
|
|
219
230
|
|
|
220
231
|
/**
|
|
@@ -231,29 +242,41 @@ export async function completeLogin(
|
|
|
231
242
|
providerId: string,
|
|
232
243
|
code: string,
|
|
233
244
|
state: string,
|
|
245
|
+
callbackParams?: Readonly<Record<string, string>>,
|
|
234
246
|
): Promise<OAuthCompleteOutcome> {
|
|
235
247
|
// An empty `state` means the authorization server does not echo one
|
|
236
248
|
// (OpenRouter omits it from the callback entirely); correlate by provider in
|
|
237
249
|
// that case, and by state otherwise so the stronger check stays in force
|
|
238
250
|
// wherever the server supports it.
|
|
251
|
+
//
|
|
252
|
+
// The flow is peeked, not consumed, until the exchange succeeds: consuming
|
|
253
|
+
// first means a failed exchange (a transient upstream blip, a paste that
|
|
254
|
+
// missed a parameter) burns the state and every retry answers "unknown or
|
|
255
|
+
// expired state". The state is deleted only after the account persists.
|
|
239
256
|
const flow = state
|
|
240
|
-
? await config.oauthFlowStore.
|
|
257
|
+
? await config.oauthFlowStore.peekPending(state)
|
|
241
258
|
: await config.oauthFlowStore.consumePendingByProvider(providerId);
|
|
242
259
|
if (!flow || flow.providerId !== providerId) {
|
|
243
260
|
return { ok: false, message: "unknown or expired state" };
|
|
244
261
|
}
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
|
|
250
|
-
else config.callbackListener.releaseProvider(flow.redirectUri, providerId);
|
|
262
|
+
// Release the listener registration for this flow. The pending state itself
|
|
263
|
+
// stays available so a manual re-paste can retry the exchange. The release is
|
|
264
|
+
// deferred until after the exchange for two-phase flows: Kiro's OIDC portal
|
|
265
|
+
// callback (phase 1) must keep the loopback listener alive so the second
|
|
266
|
+
// authorization leg can land on it.
|
|
251
267
|
const client = await requireClient(config.providerRegistry, providerId);
|
|
252
268
|
if (!client.exchangeCode) {
|
|
253
269
|
return { ok: false, message: "provider does not support browser authorization" };
|
|
254
270
|
}
|
|
271
|
+
const releaseListener = () => {
|
|
272
|
+
if (state.length > 0) config.callbackListener.release(flow.redirectUri, state);
|
|
273
|
+
else config.callbackListener.releaseProvider(flow.redirectUri, providerId);
|
|
274
|
+
};
|
|
255
275
|
try {
|
|
256
|
-
const result = await client.exchangeCode(code, flow.codeVerifier, flow.redirectUri, state
|
|
276
|
+
const result = await client.exchangeCode(code, flow.codeVerifier, flow.redirectUri, state, {
|
|
277
|
+
...(callbackParams === undefined ? {} : { parameters: callbackParams }),
|
|
278
|
+
...(flow.providerState === undefined ? {} : { providerState: flow.providerState }),
|
|
279
|
+
});
|
|
257
280
|
const tokenCheck = await validateIssuedAccessToken(
|
|
258
281
|
result.access,
|
|
259
282
|
providerJwtVerification(providerId),
|
|
@@ -270,9 +293,51 @@ export async function completeLogin(
|
|
|
270
293
|
label: result.accountLabel ?? flow.accountLabel,
|
|
271
294
|
...result,
|
|
272
295
|
});
|
|
296
|
+
// The exchange succeeded and the account is persisted; now the flow is
|
|
297
|
+
// spent. Consuming it here (peeked above) frees the state and the provider
|
|
298
|
+
// pointer so a replayed callback cannot re-create the account.
|
|
299
|
+
releaseListener();
|
|
300
|
+
if (state.length > 0) await config.oauthFlowStore.consumePending(state);
|
|
301
|
+
else await config.oauthFlowStore.consumePendingByProvider(providerId);
|
|
273
302
|
await config.snapshotInvalidator?.invalidate();
|
|
274
303
|
return { ok: true, message: `${providerId} account connected. You may close this tab.` };
|
|
275
304
|
} catch (error) {
|
|
305
|
+
// Kiro's OIDC families: the portal callback carries no code — only
|
|
306
|
+
// `login_option` + issuer/region. The client responded with the second-leg
|
|
307
|
+
// authorize URL plus a fresh PKCE verifier and the OIDC registration.
|
|
308
|
+
// Rewrite the pending flow so phase 2 exchanges against that verifier, keep
|
|
309
|
+
// the listener alive, and tell the caller to redirect the browser there.
|
|
310
|
+
// dynamic-import: B-02 lazy graph — 58KB kiro OIDC path, only for Kiro flows.
|
|
311
|
+
// The kiro-oauth graph is 58KB, so it is imported lazily here — only the
|
|
312
|
+
// Kiro OIDC path needs the error class — instead of on the console boot
|
|
313
|
+
// graph (B-02).
|
|
314
|
+
const { KiroOidcPendingError } = await import(
|
|
315
|
+
"../../../providers/integrations/kiro/kiro-oauth"
|
|
316
|
+
);
|
|
317
|
+
if (error instanceof KiroOidcPendingError) {
|
|
318
|
+
// Phase 1 of Kiro's OIDC portal login: the portal callback carried no
|
|
319
|
+
// code — only `login_option` + issuer/region. The client answered with the
|
|
320
|
+
// phase-2 authorize URL, a fresh PKCE verifier, and the OIDC client
|
|
321
|
+
// registration. Mirror the 1.4.62 IDE: the second leg uses a *new* state
|
|
322
|
+
// (not the portal's), so mint it here, keep the pending flow reachable
|
|
323
|
+
// under both states, and tell the caller which state to expect back.
|
|
324
|
+
const continueState = error.continueState;
|
|
325
|
+
if (state.length > 0 && continueState.length > 0) {
|
|
326
|
+
await config.oauthFlowStore.savePending(continueState, {
|
|
327
|
+
...flow,
|
|
328
|
+
codeVerifier: error.codeVerifier,
|
|
329
|
+
providerState: error.providerState,
|
|
330
|
+
});
|
|
331
|
+
config.callbackListener.track(flow.redirectUri, state, continueState);
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
ok: false,
|
|
335
|
+
message: error.message,
|
|
336
|
+
redirectUrl: error.authorizeUrl,
|
|
337
|
+
continueState,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
releaseListener();
|
|
276
341
|
// Upstream token-endpoint bodies can echo credentials or sensitive
|
|
277
342
|
// diagnostics — the browser gets a generic failure; the bounded detail
|
|
278
343
|
// stays server-side in the console log.
|
|
@@ -351,7 +416,7 @@ function failurePage(message: string, wantsJson: boolean): Response {
|
|
|
351
416
|
);
|
|
352
417
|
}
|
|
353
418
|
|
|
354
|
-
|
|
419
|
+
function createOAuthLoginOperations(config: OAuthLoginConfig) {
|
|
355
420
|
const operations = {
|
|
356
421
|
async beginAuthorize(
|
|
357
422
|
access: AccessDecision | undefined,
|
|
@@ -437,8 +502,15 @@ export function createOAuthLoginOperations(config: OAuthLoginConfig) {
|
|
|
437
502
|
code: string,
|
|
438
503
|
state: string,
|
|
439
504
|
wantsJson = false,
|
|
505
|
+
callbackParams?: Readonly<Record<string, string>>,
|
|
440
506
|
): Promise<Response> {
|
|
441
|
-
const outcome = await completeLogin(config, providerId, code, state);
|
|
507
|
+
const outcome = await completeLogin(config, providerId, code, state, callbackParams);
|
|
508
|
+
if (outcome.redirectUrl !== undefined) {
|
|
509
|
+
return new Response(null, {
|
|
510
|
+
status: 302,
|
|
511
|
+
headers: { location: outcome.redirectUrl },
|
|
512
|
+
});
|
|
513
|
+
}
|
|
442
514
|
return outcome.ok ? successPage(providerId, wantsJson) : failurePage(outcome.message, wantsJson);
|
|
443
515
|
},
|
|
444
516
|
async handleCallbackError(
|
|
@@ -639,7 +711,7 @@ const importBody = t.Object({
|
|
|
639
711
|
accountLabel: t.Optional(t.String()),
|
|
640
712
|
});
|
|
641
713
|
|
|
642
|
-
export function createOAuthLoginRoutes(config: OAuthLoginConfig):
|
|
714
|
+
export function createOAuthLoginRoutes(config: OAuthLoginConfig): AnyElysia {
|
|
643
715
|
const factory = createOAuthLoginOperations(config);
|
|
644
716
|
return new Elysia({ prefix: "/providers" })
|
|
645
717
|
.post(
|
|
@@ -670,7 +742,14 @@ export function createOAuthLoginRoutes(config: OAuthLoginConfig): Elysia {
|
|
|
670
742
|
// `state` is absent for authorization servers that do not echo it
|
|
671
743
|
// (OpenRouter). The flow is then correlated by provider instead — one
|
|
672
744
|
// browser login is in flight per provider at a time.
|
|
673
|
-
|
|
745
|
+
const loginOption = typeof query["login_option"] === "string" ? query["login_option"] : undefined;
|
|
746
|
+
return factory.handleCallback(
|
|
747
|
+
params.providerId,
|
|
748
|
+
code,
|
|
749
|
+
state,
|
|
750
|
+
wantsJson,
|
|
751
|
+
loginOption === undefined ? undefined : { login_option: loginOption },
|
|
752
|
+
);
|
|
674
753
|
})
|
|
675
754
|
.post(
|
|
676
755
|
"/:providerId/oauth/device/start",
|
|
@@ -721,6 +800,6 @@ export function createOAuthLoginRoutes(config: OAuthLoginConfig): Elysia {
|
|
|
721
800
|
return oauthErrorResponse(e, set);
|
|
722
801
|
}
|
|
723
802
|
},
|
|
724
|
-
)
|
|
803
|
+
)
|
|
725
804
|
}
|
|
726
805
|
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { t } from "elysia";
|
|
2
2
|
import type { Elysia } from "elysia";
|
|
3
|
-
import { and, eq, isNull } from "drizzle-orm";
|
|
3
|
+
import { and, eq, inArray, isNull } from "drizzle-orm";
|
|
4
4
|
import { adminAuditLog, providerAccounts } from "../../persistence/schema";
|
|
5
5
|
import { requireGlobalAdmin } from "../shared/errors";
|
|
6
|
-
import {
|
|
7
|
-
|
|
6
|
+
import {
|
|
7
|
+
GLOBAL_QUOTA_LENS,
|
|
8
|
+
invalidateQuotaCache,
|
|
9
|
+
invalidateQuotaCaches,
|
|
10
|
+
} from "../../providers/quota/cache";
|
|
11
|
+
import {
|
|
12
|
+
invalidateCredentialCache,
|
|
13
|
+
invalidateCredentialCaches,
|
|
14
|
+
} from "../../providers/operations/provider-credential-service";
|
|
8
15
|
import {
|
|
9
16
|
QUOTA_REFRESH_TIMEOUT_MS,
|
|
10
17
|
loadQuotaTarget,
|
|
@@ -119,6 +126,37 @@ export function registerAccountQuotaGlobalRoutes(
|
|
|
119
126
|
});
|
|
120
127
|
return { success: true };
|
|
121
128
|
})
|
|
129
|
+
.post("/global/accounts/batch-delete", {
|
|
130
|
+
body: t.Object({
|
|
131
|
+
accountIds: t.Array(t.String()),
|
|
132
|
+
}),
|
|
133
|
+
}, async ({ request, body }) => {
|
|
134
|
+
const access = requireGlobalAdmin(accessResolver(request));
|
|
135
|
+
const accountIds = [...new Set(body.accountIds)];
|
|
136
|
+
if (accountIds.length === 0) {
|
|
137
|
+
return { ok: true, deleted: [], missing: [] };
|
|
138
|
+
}
|
|
139
|
+
// One DELETE statement scoped to shared accounts only: a tenant-owned row
|
|
140
|
+
// (`tenant_id IS NOT NULL`) is never matchable by a global admin, so it
|
|
141
|
+
// lands in `missing`.
|
|
142
|
+
const deleted = await db
|
|
143
|
+
.delete(providerAccounts)
|
|
144
|
+
.where(and(inArray(providerAccounts.id, accountIds), isNull(providerAccounts.tenantId)))
|
|
145
|
+
.returning({ id: providerAccounts.id });
|
|
146
|
+
const deletedIds = deleted.map((row) => row.id);
|
|
147
|
+
const deletedSet = new Set(deletedIds);
|
|
148
|
+
const missing = accountIds.filter((id) => !deletedSet.has(id));
|
|
149
|
+
invalidateCredentialCaches(deletedIds);
|
|
150
|
+
await invalidateQuotaCaches(GLOBAL_QUOTA_LENS, deletedIds, redis);
|
|
151
|
+
await snapshotInvalidator?.invalidate();
|
|
152
|
+
await db.insert(adminAuditLog).values({
|
|
153
|
+
actor: access.admissionIdentity,
|
|
154
|
+
action: "provider_account.global.batch_deleted",
|
|
155
|
+
target: "global",
|
|
156
|
+
detail: { deleted: deletedIds, missing },
|
|
157
|
+
});
|
|
158
|
+
return { ok: true, deleted: deletedIds, missing };
|
|
159
|
+
})
|
|
122
160
|
.post("/global/accounts/:id/quota/refresh", async ({ request, params, set }) => {
|
|
123
161
|
const access = requireGlobalAdmin(accessResolver(request));
|
|
124
162
|
const target = await loadQuotaTarget(db, params.id, null);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { ConsoleAccessResolver } from "../auth/access";
|
|
2
2
|
import type { AuditRecorder } from "../auth/audit-recorder";
|
|
3
|
-
import { markClineApiKeyCredential } from "../../providers/integrations/cline/cline-quota";
|
|
4
3
|
import { type QuotaRefreshDeps } from "../../providers/quota/refresh";
|
|
5
4
|
import type { AccountTestEgressDeps } from "./account-test-egress";
|
|
6
5
|
|
|
@@ -31,8 +30,16 @@ export type AccountStatus = (typeof ACCOUNT_STATUSES)[number];
|
|
|
31
30
|
* omitted it, so a working account was cooled with `quota_fetch_failed` every
|
|
32
31
|
* minute while refreshing fine from the console.
|
|
33
32
|
*/
|
|
34
|
-
export function markProviderApiKeyCredential(providerId: string, credential: string): string {
|
|
35
|
-
|
|
33
|
+
export async function markProviderApiKeyCredential(providerId: string, credential: string): Promise<string> {
|
|
34
|
+
if (providerId !== "cline") return credential;
|
|
35
|
+
// dynamic-import: B-02 lazy graph — cline-only marker, kept off the boot graph.
|
|
36
|
+
// The cline-quota graph is loaded lazily: only `cline` accounts ever need
|
|
37
|
+
// the marker, so a deployment without cline must not pull it into the boot
|
|
38
|
+
// graph (B-02).
|
|
39
|
+
const { markClineApiKeyCredential } = await import(
|
|
40
|
+
"../../providers/integrations/cline/cline-quota"
|
|
41
|
+
);
|
|
42
|
+
return markClineApiKeyCredential(credential);
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
/**
|