cartethyia 2.7.5 → 2.7.7
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 +8 -3
- package/README.md +26 -2
- package/dist/dashboard/assets/BannedPage-B05aF8Dg.js +1 -0
- package/dist/dashboard/assets/BannedPage-B05aF8Dg.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-CLUhmjUy.js +3 -0
- package/dist/dashboard/assets/CliToolDetailPage-CLUhmjUy.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-CQmrqqrk.js +1 -0
- package/dist/dashboard/assets/CliToolsPage-CQmrqqrk.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CHG_1Wr3.js +1 -0
- package/dist/dashboard/assets/CombosPage-CHG_1Wr3.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-D5rol2j2.js → ConsoleLogPage-DPgbklad.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-DPgbklad.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-BKj_r0rp.js → CustomizationPage-pnfAqr_-.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-pnfAqr_-.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-DGJuU2A6.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-DGJuU2A6.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-B5nZvmf0.js → InjectSystemPromptPanel-C-e2AMz6.js} +245 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-C-e2AMz6.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-J8wPbmfT.js +1 -0
- package/dist/dashboard/assets/LoginPage-J8wPbmfT.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-DnQPSIA8.js → ModelPicker-C6WlpU5E.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-C6WlpU5E.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BSQnF2Lp.js +1 -0
- package/dist/dashboard/assets/OverviewPage-BSQnF2Lp.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-DK_e0FJz.js +7 -0
- package/dist/dashboard/assets/ProviderDetailPage-DK_e0FJz.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-D_a3r6Gc.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-D_a3r6Gc.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-5SnUSy3j.js → ProxyPage-BBTRBAQ3.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-BBTRBAQ3.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-B2WqMSG1.js +1 -0
- package/dist/dashboard/assets/QuotaPage-B2WqMSG1.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-BJtRZ4qD.js → SettingsPage-D7xp5Ij_.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-D7xp5Ij_.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-LND6_Ss7.js +1 -0
- package/dist/dashboard/assets/SetupPage-LND6_Ss7.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-BfAb5-o_.js → StudioPage-BzNnm6Bi.js} +4 -4
- package/dist/dashboard/assets/StudioPage-BzNnm6Bi.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-BNzeZ2Vk.js → UsagePage-C1TbL24N.js} +13 -13
- package/dist/dashboard/assets/UsagePage-C1TbL24N.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-C0K-c4eo.js → assert-CidQVoNo.js} +1 -1
- package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DfbhQU4k.js → badge-BaX2GTrn.js} +1 -1
- package/dist/dashboard/assets/badge-BaX2GTrn.js.gz +0 -0
- package/dist/dashboard/assets/{banner-BllT4LcF.js → banner-DFH4Bkte.js} +1 -1
- package/dist/dashboard/assets/banner-DFH4Bkte.js.gz +0 -0
- package/dist/dashboard/assets/{card-CtkX66Wd.js → card-CxDw0lK6.js} +1 -1
- package/dist/dashboard/assets/card-CxDw0lK6.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-CQXVMn4P.js → cli-tools-Bwv5-zGZ.js} +1 -1
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-BQqBQd2n.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-BrTEZ8zu.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-Dmx7gBiN.js +1 -0
- package/dist/dashboard/assets/entry-Dmx7gBiN.js.gz +0 -0
- package/dist/dashboard/assets/entry-vqGb8yVz.js +4 -0
- package/dist/dashboard/assets/entry-vqGb8yVz.js.gz +0 -0
- package/dist/dashboard/assets/formatters-DP-5E01-.js +1 -0
- package/dist/dashboard/assets/formatters-DP-5E01-.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-FQlj0HUT.js → index-D36vCZht.js} +2 -2
- package/dist/dashboard/assets/index-D36vCZht.js.gz +0 -0
- package/dist/dashboard/assets/{inline-C4gU5iOL.js → inline-CHJQwxLa.js} +1 -1
- package/dist/dashboard/assets/inline-CHJQwxLa.js.gz +0 -0
- package/dist/dashboard/assets/{input-BHHX_Tmm.js → input-BlmlizVV.js} +1 -1
- package/dist/dashboard/assets/input-BlmlizVV.js.gz +0 -0
- package/dist/dashboard/assets/{layout-CajrYUnD.js → layout-Bz93I-yE.js} +1 -1
- package/dist/dashboard/assets/layout-Bz93I-yE.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-Bx2I7XUe.js → list-row-Ml16KCfu.js} +1 -1
- package/dist/dashboard/assets/list-row-Ml16KCfu.js.gz +0 -0
- package/dist/dashboard/assets/{live-DNNqRbj2.js → live-7_QpnYGu.js} +1 -1
- 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-C5UBACrJ.js → network-CuMDaTzT.js} +1 -1
- 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-Xsi9Qx04.js → pagination-B5aOoweZ.js} +1 -1
- 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-in0f_IfK.js → routing-fnCsxgNO.js} +1 -1
- package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
- package/dist/dashboard/assets/{select-BLULNqTp.js → select-C9Ssle9b.js} +2 -2
- package/dist/dashboard/assets/select-C9Ssle9b.js.gz +0 -0
- package/dist/dashboard/assets/{settings-CcGczj6I.js → settings-BxslsErn.js} +1 -1
- package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
- package/dist/dashboard/assets/{stack-D2lIR0tg.js → stack-j7cXjgYX.js} +1 -1
- package/dist/dashboard/assets/stack-j7cXjgYX.js.gz +0 -0
- package/dist/dashboard/assets/{state-DTARjfiW.js → state-DHmEnLa2.js} +1 -1
- package/dist/dashboard/assets/state-DHmEnLa2.js.gz +0 -0
- package/dist/dashboard/assets/{switch-c0oPSnqW.js → switch-DmZcyTIC.js} +1 -1
- package/dist/dashboard/assets/switch-DmZcyTIC.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-m8WGNPrz.js → useMutation-CbCP4w1o.js} +1 -1
- package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +57 -57
- package/migrations/full-postgres/0042_add_telemetry_degenerate_stop.sql +14 -0
- package/migrations/full-postgres/0043_add_provider_custom_user_agent.sql +14 -0
- package/migrations/lite-sqlite3/0042_add_telemetry_degenerate_stop.sql +12 -0
- package/migrations/lite-sqlite3/0043_add_provider_custom_user_agent.sql +10 -0
- package/package.json +3 -1
- package/src/boot/dependencies.ts +10 -2
- package/src/config/providers.ts +13 -7
- package/src/config/security.ts +1 -1
- package/src/config/spec.ts +31 -17
- package/src/config/usage.ts +27 -0
- package/src/console/api-keys/routes.ts +19 -23
- package/src/console/cli-tools/cartethyia-websearch-plugin.ts +186 -0
- package/src/console/cli-tools/contracts.ts +39 -295
- 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 +6 -6
- package/src/console/cli-tools/injectors/driver.ts +8 -105
- package/src/console/cli-tools/injectors/kilo.ts +146 -21
- package/src/console/cli-tools/injectors/opencode.ts +68 -17
- package/src/console/cli-tools/routes.ts +6 -0
- package/src/console/cli-tools/service.ts +7 -6
- package/src/console/observability/contracts.ts +6 -0
- package/src/console/observability/store.ts +3 -0
- package/src/console/providers/catalog/contracts.ts +4 -0
- package/src/console/providers/catalog/routes.ts +1 -0
- package/src/console/providers/detail/routes.ts +8 -0
- package/src/console/providers/detail/store.ts +4 -0
- package/src/console/quota/account-quota-tenant.ts +74 -0
- package/src/console/share/share-stats.ts +7 -9
- package/src/observability/event-log.ts +7 -0
- package/src/observability/event-query.ts +2 -0
- package/src/observability/telemetry-buffer.ts +9 -1
- package/src/persistence/boot.ts +13 -4
- package/src/persistence/schema.ts +15 -1
- package/src/persistence/stores/api-key-store.ts +19 -1
- package/src/protocol/canonical-model.ts +7 -0
- package/src/protocol/dsml.ts +5 -76
- package/src/protocol/request/gemini.ts +20 -1
- package/src/protocol/tool-identity.ts +15 -1
- package/src/providers/adapters/compatible-adapter.ts +29 -1
- package/src/providers/default-registry.ts +20 -4
- package/src/providers/discovery/discovery-types.ts +18 -0
- package/src/providers/discovery/probe-phases.ts +45 -0
- package/src/providers/discovery/probing-service.ts +41 -6
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +108 -16
- package/src/providers/integrations/antigravity/antigravity-quota.ts +18 -6
- package/src/providers/integrations/antigravity/antigravity.ts +44 -11
- package/src/providers/integrations/codex/codex.ts +28 -1
- package/src/providers/integrations/cursor/cursor.ts +2 -3
- package/src/providers/integrations/github/github-discovery.ts +15 -2
- package/src/providers/integrations/github/github-oauth.ts +178 -9
- package/src/providers/integrations/github/github.ts +95 -14
- package/src/providers/integrations/kiro/kiro.ts +1 -2
- package/src/providers/integrations/qoder/chat-proto.ts +328 -0
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +258 -0
- package/src/providers/integrations/qoder/qoder-adapter.ts +275 -0
- package/src/providers/integrations/qoder/qoder-auth.ts +201 -0
- package/src/providers/integrations/qoder/qoder-body.ts +78 -0
- package/src/providers/integrations/qoder/qoder-catalog.ts +14 -4
- package/src/providers/integrations/qoder/qoder-chat.ts +514 -0
- package/src/providers/integrations/qoder/qoder-cosy.ts +162 -0
- package/src/providers/integrations/qoder/qoder-grpc.ts +667 -0
- package/src/providers/integrations/qoder/qoder-ide.ts +584 -0
- package/src/providers/integrations/qoder/qoder-oauth.ts +4 -4
- package/src/providers/integrations/qoder/qoder-quota.ts +1 -1
- package/src/providers/integrations/qoder/qoder-realm.ts +40 -4
- package/src/providers/integrations/qoder/qoder-types.ts +51 -0
- package/src/providers/integrations/qoder/qoder-umid.ts +123 -8
- package/src/providers/integrations/qoder/qoder.ts +147 -1830
- package/src/providers/integrations/trae/trae.ts +2 -2
- package/src/providers/operations/account-error-classification.ts +67 -17
- package/src/providers/operations/byok-wire-profile.ts +19 -15
- package/src/providers/provider-contracts.ts +7 -0
- package/src/providers/provider-usage.ts +24 -55
- package/src/security/abuse.ts +5 -5
- package/src/security/admission/service.ts +11 -4
- package/src/security/api-key-auth.ts +5 -3
- package/src/transport/dispatch/attempt-finalize.ts +69 -21
- package/src/transport/dispatch/attempt-loop.ts +22 -0
- package/src/transport/dispatch/upstream.ts +4 -2
- package/src/transport/failure-policy.ts +15 -1
- package/src/transport/guards/error-lifecycle.ts +4 -0
- package/src/transport/request/state.ts +6 -0
- package/src/transport/routing/route-catalog.ts +8 -0
- package/src/transport/routing/route-model.ts +4 -0
- package/src/transport/routing/router.ts +8 -110
- package/src/transport/surface/reasoning-summary.ts +9 -14
- package/src/transport/vision-handoff.ts +83 -28
- package/dist/dashboard/assets/BannedPage-Cj15GsMp.js +0 -1
- package/dist/dashboard/assets/BannedPage-Cj15GsMp.js.gz +0 -0
- package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js +0 -1
- package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BIccZmeL.js +0 -1
- package/dist/dashboard/assets/CliToolsPage-BIccZmeL.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DEEKkYD-.js +0 -1
- package/dist/dashboard/assets/CombosPage-DEEKkYD-.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DkXb6dx-.js +0 -1
- package/dist/dashboard/assets/LoginPage-DkXb6dx-.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-DnQPSIA8.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js +0 -1
- package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js +0 -5
- package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js +0 -1
- package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-WTa0sktC.js +0 -1
- package/dist/dashboard/assets/SetupPage-WTa0sktC.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-BfAb5-o_.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-BNzeZ2Vk.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-l29JROPv.js +0 -1
- package/dist/dashboard/assets/api-keys-l29JROPv.js.gz +0 -0
- package/dist/dashboard/assets/app--PJWq8_C.css +0 -2
- package/dist/dashboard/assets/app--PJWq8_C.css.gz +0 -0
- package/dist/dashboard/assets/app-Bgd3zANQ.js +0 -9
- package/dist/dashboard/assets/app-Bgd3zANQ.js.gz +0 -0
- package/dist/dashboard/assets/assert-C0K-c4eo.js.gz +0 -0
- package/dist/dashboard/assets/badge-DfbhQU4k.js.gz +0 -0
- package/dist/dashboard/assets/banner-BllT4LcF.js.gz +0 -0
- package/dist/dashboard/assets/card-CtkX66Wd.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-CQXVMn4P.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-BQqBQd2n.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BEK7KP0H.js +0 -1
- package/dist/dashboard/assets/dialog-BEK7KP0H.js.gz +0 -0
- package/dist/dashboard/assets/drawer-BrTEZ8zu.js.gz +0 -0
- package/dist/dashboard/assets/entry-CHWxbS56.js +0 -1
- package/dist/dashboard/assets/entry-CHWxbS56.js.gz +0 -0
- package/dist/dashboard/assets/entry-EBHJ3DrE.js +0 -1
- package/dist/dashboard/assets/entry-EBHJ3DrE.js.gz +0 -0
- package/dist/dashboard/assets/entry-LCS85hiq.js +0 -4
- package/dist/dashboard/assets/entry-LCS85hiq.js.gz +0 -0
- package/dist/dashboard/assets/formatters-BAACA727.js +0 -1
- package/dist/dashboard/assets/formatters-BAACA727.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-YSCE4UC2.js +0 -1
- package/dist/dashboard/assets/github-badge-YSCE4UC2.js.gz +0 -0
- package/dist/dashboard/assets/index-FQlj0HUT.js.gz +0 -0
- package/dist/dashboard/assets/inline-C4gU5iOL.js.gz +0 -0
- package/dist/dashboard/assets/input-BHHX_Tmm.js.gz +0 -0
- package/dist/dashboard/assets/layout-CajrYUnD.js.gz +0 -0
- package/dist/dashboard/assets/list-row-Bx2I7XUe.js.gz +0 -0
- package/dist/dashboard/assets/live-DNNqRbj2.js.gz +0 -0
- package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
- package/dist/dashboard/assets/network-C5UBACrJ.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CUWAZK4J.js +0 -2
- package/dist/dashboard/assets/numbers-CUWAZK4J.js.gz +0 -0
- package/dist/dashboard/assets/pagination-Xsi9Qx04.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-DvQbD_sr.js +0 -1
- package/dist/dashboard/assets/quota-DvQbD_sr.js.gz +0 -0
- package/dist/dashboard/assets/routing-in0f_IfK.js.gz +0 -0
- package/dist/dashboard/assets/select-BLULNqTp.js.gz +0 -0
- package/dist/dashboard/assets/settings-CcGczj6I.js.gz +0 -0
- package/dist/dashboard/assets/stack-D2lIR0tg.js.gz +0 -0
- package/dist/dashboard/assets/state-DTARjfiW.js.gz +0 -0
- package/dist/dashboard/assets/switch-c0oPSnqW.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-m8WGNPrz.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/providers/integrations/qoder/qoder-text-tools.ts +0 -277
|
@@ -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(
|
|
@@ -935,6 +935,8 @@ export interface ProviderRoutingResponse {
|
|
|
935
935
|
readonly skipProxy: boolean;
|
|
936
936
|
/** Built-in API-key upstream User-Agent; OAuth and custom providers retain their own identity. */
|
|
937
937
|
readonly userAgent: string;
|
|
938
|
+
/** Operator-set custom User-Agent override; `null` = no override (native identity is used). */
|
|
939
|
+
readonly customUserAgent: string | null;
|
|
938
940
|
}
|
|
939
941
|
|
|
940
942
|
/**
|
|
@@ -966,4 +968,6 @@ export interface UpdateProviderRoutingRequest {
|
|
|
966
968
|
readonly skipProxy?: boolean;
|
|
967
969
|
/** Built-in API-key upstream User-Agent. */
|
|
968
970
|
readonly userAgent?: string;
|
|
971
|
+
/** Operator-set custom User-Agent override; `null` clears it back to native. */
|
|
972
|
+
readonly customUserAgent?: string | null;
|
|
969
973
|
}
|
|
@@ -126,6 +126,7 @@ const probeModelBody = t.Object({
|
|
|
126
126
|
reasoningEffort: t.Optional(literalUnion(PROBE_REASONING_EFFORTS)),
|
|
127
127
|
maxOutputTokens: t.Optional(t.Integer({ minimum: 1, maximum: 131072 })),
|
|
128
128
|
stream: t.Optional(t.Boolean()),
|
|
129
|
+
surface: t.Optional(literalUnion(["default", "cosy", "grpc", "ide"])),
|
|
129
130
|
});
|
|
130
131
|
const byokConnectionTestBody = t.Object({
|
|
131
132
|
baseUrl: t.String(),
|
|
@@ -100,6 +100,13 @@ function createProviderDetailOperations(deps: ProviderDetailConfig) {
|
|
|
100
100
|
) {
|
|
101
101
|
throw new ConsoleDomainError("invalid_request", 400, "userAgent must be a header value up to 4096 characters");
|
|
102
102
|
}
|
|
103
|
+
if (
|
|
104
|
+
patch.customUserAgent !== undefined &&
|
|
105
|
+
patch.customUserAgent !== null &&
|
|
106
|
+
(patch.customUserAgent.length > 4096 || /[\u0000-\u001f\u007f]/.test(patch.customUserAgent))
|
|
107
|
+
) {
|
|
108
|
+
throw new ConsoleDomainError("invalid_request", 400, "customUserAgent must be a header value up to 4096 characters, or null");
|
|
109
|
+
}
|
|
103
110
|
const updated = await deps.store.updateRouting(providerId, a.tenantId, patch);
|
|
104
111
|
await deps.auditSink?.record({
|
|
105
112
|
access: a,
|
|
@@ -157,6 +164,7 @@ const updateRoutingBody = t.Object({
|
|
|
157
164
|
usageFloorUnit: t.Optional(t.Union([t.Literal("credit"), t.Literal("percent")])),
|
|
158
165
|
skipProxy: t.Optional(t.Boolean()),
|
|
159
166
|
userAgent: t.Optional(t.String({ minLength: 0, maxLength: 4096 })),
|
|
167
|
+
customUserAgent: t.Optional(t.Nullable(t.String({ minLength: 0, maxLength: 4096 }))),
|
|
160
168
|
});
|
|
161
169
|
|
|
162
170
|
|
|
@@ -41,6 +41,7 @@ function mapRow(row: typeof providerRoutingSettings.$inferSelect): ProviderRouti
|
|
|
41
41
|
enabled: row.enabled,
|
|
42
42
|
skipProxy: row.skipProxy,
|
|
43
43
|
userAgent: row.userAgent,
|
|
44
|
+
customUserAgent: row.customUserAgent,
|
|
44
45
|
};
|
|
45
46
|
}
|
|
46
47
|
|
|
@@ -94,6 +95,7 @@ export class DrizzleProviderDetailStore implements ProviderDetailStore {
|
|
|
94
95
|
enabled: false,
|
|
95
96
|
skipProxy: defaultSkipProxy(providerId),
|
|
96
97
|
userAgent: buildClaudeCliUserAgent(),
|
|
98
|
+
customUserAgent: null,
|
|
97
99
|
};
|
|
98
100
|
}
|
|
99
101
|
async updateRouting(
|
|
@@ -116,6 +118,7 @@ export class DrizzleProviderDetailStore implements ProviderDetailStore {
|
|
|
116
118
|
enabled: patch.enabled ?? false,
|
|
117
119
|
skipProxy: patch.skipProxy ?? defaultSkipProxy(providerId),
|
|
118
120
|
userAgent: patch.userAgent ?? buildClaudeCliUserAgent(),
|
|
121
|
+
customUserAgent: patch.customUserAgent ?? null,
|
|
119
122
|
};
|
|
120
123
|
const setClause: Record<string, unknown> = {};
|
|
121
124
|
if (patch.strategy !== undefined) setClause.strategy = patch.strategy;
|
|
@@ -128,6 +131,7 @@ export class DrizzleProviderDetailStore implements ProviderDetailStore {
|
|
|
128
131
|
if (patch.enabled !== undefined) setClause.enabled = patch.enabled;
|
|
129
132
|
if (patch.skipProxy !== undefined) setClause.skipProxy = patch.skipProxy;
|
|
130
133
|
if (patch.userAgent !== undefined) setClause.userAgent = patch.userAgent;
|
|
134
|
+
if (patch.customUserAgent !== undefined) setClause.customUserAgent = patch.customUserAgent;
|
|
131
135
|
const upserted = await this.db
|
|
132
136
|
.insert(providerRoutingSettings)
|
|
133
137
|
.values(values)
|
|
@@ -48,6 +48,11 @@ import {
|
|
|
48
48
|
fetchQoderDailyCheckin,
|
|
49
49
|
isQoderCheckinProvider,
|
|
50
50
|
} from "../../providers/integrations/qoder/qoder-checkin";
|
|
51
|
+
import {
|
|
52
|
+
claimQoderCredits,
|
|
53
|
+
QODER_CREDIT_PROVIDER_IDS,
|
|
54
|
+
type QoderCreditClaimResult,
|
|
55
|
+
} from "../../providers/integrations/qoder/claim-qoder-credits";
|
|
51
56
|
import {
|
|
52
57
|
DrizzleQoderNativeIdentityStore,
|
|
53
58
|
type QoderNativeIdentityStore,
|
|
@@ -1136,6 +1141,75 @@ export function registerAccountQuotaTenantRoutes(
|
|
|
1136
1141
|
: (result.detail ?? "Claim it in the ZCode app"),
|
|
1137
1142
|
};
|
|
1138
1143
|
})
|
|
1144
|
+
// Qoder daily-credits claim is likewise an explicit operator action: the
|
|
1145
|
+
// CREDITS campaign only surfaces with the real UMID headers, so the claim
|
|
1146
|
+
// mints/binds a native identity first (same store the check-in worker uses).
|
|
1147
|
+
.post("/accounts/:id/claim-qoder-credits", async ({ request, params, set }) => {
|
|
1148
|
+
const access = requireTenantScope(accessResolver(request), "dashboard:write");
|
|
1149
|
+
const rows = await db
|
|
1150
|
+
.select()
|
|
1151
|
+
.from(providerAccounts)
|
|
1152
|
+
.where(
|
|
1153
|
+
and(eq(providerAccounts.id, params.id), eq(providerAccounts.tenantId, access.tenantId)),
|
|
1154
|
+
)
|
|
1155
|
+
.limit(1);
|
|
1156
|
+
const account = rows[0];
|
|
1157
|
+
if (!account) {
|
|
1158
|
+
set.status = 404;
|
|
1159
|
+
return { error: `Account ${params.id} not found`, code: "not_found" };
|
|
1160
|
+
}
|
|
1161
|
+
if (!QODER_CREDIT_PROVIDER_IDS.includes(account.providerId as (typeof QODER_CREDIT_PROVIDER_IDS)[number])) {
|
|
1162
|
+
set.status = 400;
|
|
1163
|
+
return {
|
|
1164
|
+
error: "Daily credits are only claimable for qoder accounts",
|
|
1165
|
+
code: "qoder_credits_unsupported",
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
let result: QoderCreditClaimResult;
|
|
1169
|
+
try {
|
|
1170
|
+
const credential = await refreshDeps.resolveCredential(account.providerId as ProviderId, account.id);
|
|
1171
|
+
const fetcher = networkBindingFactory
|
|
1172
|
+
? await dailyCheckinEgressForPass({ db, networkBindingFactory })(account.id, access.tenantId)
|
|
1173
|
+
: globalThis.fetch;
|
|
1174
|
+
result = await claimQoderCredits({ db, providerId: account.providerId, accountId: account.id, credential, fetcher });
|
|
1175
|
+
} catch {
|
|
1176
|
+
set.status = 502;
|
|
1177
|
+
return {
|
|
1178
|
+
ok: false, status: 502,
|
|
1179
|
+
data: { id: account.id, state: "error", amount: 0, detail: "Qoder credit claim request failed" },
|
|
1180
|
+
message: "Qoder credit claim request failed", code: "qoder_credit_claim_failed",
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
if (result.state === "claimed") {
|
|
1184
|
+
// Cold readers must not resurrect the balance from before the grant.
|
|
1185
|
+
await db.update(providerAccounts).set({ quotaSnapshot: null }).where(
|
|
1186
|
+
and(eq(providerAccounts.id, account.id), eq(providerAccounts.tenantId, access.tenantId)),
|
|
1187
|
+
);
|
|
1188
|
+
await invalidateQuotaCacheForAccount(account.id, redis);
|
|
1189
|
+
await snapshotInvalidator?.invalidate();
|
|
1190
|
+
}
|
|
1191
|
+
if (result.state === "error") {
|
|
1192
|
+
set.status = 502;
|
|
1193
|
+
return {
|
|
1194
|
+
ok: false,
|
|
1195
|
+
status: 502,
|
|
1196
|
+
data: { id: account.id, ...result },
|
|
1197
|
+
message: result.detail ?? "Qoder credit claim failed",
|
|
1198
|
+
code: "qoder_credit_claim_failed",
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
return {
|
|
1202
|
+
ok: true,
|
|
1203
|
+
status: 200,
|
|
1204
|
+
data: { id: account.id, ...result },
|
|
1205
|
+
message:
|
|
1206
|
+
result.state === "nothing"
|
|
1207
|
+
? "No CREDITS campaign to claim"
|
|
1208
|
+
: result.state === "claimed"
|
|
1209
|
+
? `Claimed ${result.amount} credits`
|
|
1210
|
+
: "CREDITS campaign already claimed",
|
|
1211
|
+
};
|
|
1212
|
+
})
|
|
1139
1213
|
.post("/accounts/:id/activity-report", async ({ request, params, set }) => {
|
|
1140
1214
|
const access = requireTenantScope(accessResolver(request), "dashboard:write");
|
|
1141
1215
|
const rows = await db
|
|
@@ -371,11 +371,9 @@ async function computeFamilyStatsBody(
|
|
|
371
371
|
.where(eq(apiKeys.id, templateKeyId))
|
|
372
372
|
.limit(1),
|
|
373
373
|
]);
|
|
374
|
-
// Cumulative family spend: the template's own counter
|
|
375
|
-
//
|
|
376
|
-
//
|
|
377
|
-
// likewise stay counted, so the running total is monotonic and consistent
|
|
378
|
-
// across the overview, the share dialog, and this page.
|
|
374
|
+
// Cumulative family spend: the template's own counter is the live
|
|
375
|
+
// accumulator (updated by every recipient commit), so it IS the total.
|
|
376
|
+
// Telemetry still drives the bounded daily/monthly windows below.
|
|
379
377
|
const templateLifetime = Number(template[0]?.lifetime ?? 0);
|
|
380
378
|
|
|
381
379
|
// The ranked tables need real events, bounded to the 24-hour window. The
|
|
@@ -398,10 +396,10 @@ async function computeFamilyStatsBody(
|
|
|
398
396
|
});
|
|
399
397
|
rollup.totals = {
|
|
400
398
|
...rollup.totals,
|
|
401
|
-
// The template counter
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
totalTokens: templateLifetime
|
|
399
|
+
// The template counter IS the running family total (live accumulator).
|
|
400
|
+
// Daily/monthly windows stay telemetry-only (the counter has no time
|
|
401
|
+
// axis), so those keep their own sums.
|
|
402
|
+
totalTokens: templateLifetime,
|
|
405
403
|
todayTokens,
|
|
406
404
|
monthTokens,
|
|
407
405
|
// Lifetime counts come from the durable rollup, not the page: a family
|
|
@@ -57,6 +57,12 @@ export interface TelemetryEventRecord {
|
|
|
57
57
|
readonly lastEventAtMs: number | null;
|
|
58
58
|
/** Why the drawer payloads are empty, or `captured`; absent when unrecorded. */
|
|
59
59
|
readonly payloadCaptureState?: string | null;
|
|
60
|
+
/**
|
|
61
|
+
* True for a completed request whose output was far smaller than the model's
|
|
62
|
+
* ceiling and which ended with an ordinary `stop` (a "degenerate stop").
|
|
63
|
+
* NULL/unset for everything else.
|
|
64
|
+
*/
|
|
65
|
+
readonly degenerateStop?: boolean;
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
function asString(value: unknown): string | null {
|
|
@@ -109,6 +115,7 @@ function normalize(raw: Record<string, unknown>): TelemetryEventRecord | undefin
|
|
|
109
115
|
tokensPerSec: asNumber(raw["tokensPerSec"]),
|
|
110
116
|
firstContentDeltaAtMs: asNumber(raw["firstContentDeltaAtMs"]),
|
|
111
117
|
lastEventAtMs: asNumber(raw["lastEventAtMs"]),
|
|
118
|
+
...(raw["degenerateStop"] === true ? { degenerateStop: true } : {}),
|
|
112
119
|
};
|
|
113
120
|
}
|
|
114
121
|
|
|
@@ -184,6 +184,7 @@ function eventRecordFromRow(row: {
|
|
|
184
184
|
readonly firstContentDeltaAtMs: number | null;
|
|
185
185
|
readonly lastEventAtMs: number | null;
|
|
186
186
|
readonly payloadCaptureState: string | null;
|
|
187
|
+
readonly degenerateStop: boolean | null;
|
|
187
188
|
}): TelemetryEventRecord {
|
|
188
189
|
return {
|
|
189
190
|
id: row.id ?? row.requestId,
|
|
@@ -219,6 +220,7 @@ function eventRecordFromRow(row: {
|
|
|
219
220
|
firstContentDeltaAtMs: row.firstContentDeltaAtMs,
|
|
220
221
|
lastEventAtMs: row.lastEventAtMs,
|
|
221
222
|
payloadCaptureState: row.payloadCaptureState,
|
|
223
|
+
...(row.degenerateStop === true ? { degenerateStop: true } : {}),
|
|
222
224
|
};
|
|
223
225
|
}
|
|
224
226
|
|
|
@@ -65,6 +65,12 @@ export interface TelemetryEventInput {
|
|
|
65
65
|
* list instead of needing the server's logs.
|
|
66
66
|
*/
|
|
67
67
|
readonly payloadCaptureState?: string;
|
|
68
|
+
/**
|
|
69
|
+
* True for a completed request whose output was far smaller than the model's
|
|
70
|
+
* ceiling and which ended with an ordinary `stop` (a "degenerate stop").
|
|
71
|
+
* Carried onto the telemetry row so this pattern can be measured.
|
|
72
|
+
*/
|
|
73
|
+
readonly degenerateStop?: boolean;
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
function tokenCountToInt(value: number | "unavailable" | undefined): number | undefined {
|
|
@@ -147,7 +153,8 @@ function telemetryEventRow(event: TelemetryEventInput) {
|
|
|
147
153
|
event.usage?.estimated_cost === null || event.usage === undefined
|
|
148
154
|
? null
|
|
149
155
|
: String(event.usage.estimated_cost),
|
|
150
|
-
//
|
|
156
|
+
// Provider-billed credit (Tencent buddy-meter `credit`, Qoder `credits`);
|
|
157
|
+
// NULL when the upstream reported no credit field.
|
|
151
158
|
// Numeric columns are string-typed in Drizzle, like `estimatedCostUsd`.
|
|
152
159
|
creditUsed: event.usage?.credit_used != null ? String(event.usage.credit_used) : null,
|
|
153
160
|
resolveMs: event.resolveMs ?? null,
|
|
@@ -155,6 +162,7 @@ function telemetryEventRow(event: TelemetryEventInput) {
|
|
|
155
162
|
firstContentDeltaAtMs: event.firstContentDeltaAtMs ?? null,
|
|
156
163
|
lastEventAtMs: event.lastEventAtMs ?? null,
|
|
157
164
|
payloadCaptureState: event.payloadCaptureState ?? null,
|
|
165
|
+
degenerateStop: event.degenerateStop ?? null,
|
|
158
166
|
};
|
|
159
167
|
}
|
|
160
168
|
|
package/src/persistence/boot.ts
CHANGED
|
@@ -11,18 +11,27 @@ import { applySqlMigrations, createPgHandle, getPool, isPgHandle } from "./db-pg
|
|
|
11
11
|
import { applySqliteMigrations, buildSqliteHandle, createSqliteDatabase } from "./db-sqlite";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* True when `error` is a
|
|
14
|
+
* True when `error` is a unique-violation from either supported dialect:
|
|
15
|
+
*
|
|
16
|
+
* - Postgres: SQLSTATE 23505 (`code: "23505"`).
|
|
17
|
+
* - SQLite: `UNIQUE constraint failed: <index>` (message text) or the
|
|
18
|
+
* driver's `SQLITE_CONSTRAINT_UNIQUE` code (19).
|
|
15
19
|
*
|
|
16
20
|
* Lives at the persistence boundary because it is a fact about the driver's
|
|
17
21
|
* error shape, not about any one domain. It walks a bounded `cause` chain
|
|
18
|
-
* because Drizzle wraps driver errors, so the
|
|
19
|
-
* error the caller catches.
|
|
22
|
+
* because Drizzle wraps driver errors, so the code/message is not always on
|
|
23
|
+
* the error the caller catches.
|
|
20
24
|
*/
|
|
21
25
|
export function isUniqueViolation(error: unknown): boolean {
|
|
22
26
|
let current: unknown = error;
|
|
23
27
|
for (let depth = 0; depth < 3; depth += 1) {
|
|
24
28
|
if (typeof current !== "object" || current === null) return false;
|
|
25
|
-
|
|
29
|
+
const record = current as { code?: unknown; message?: unknown };
|
|
30
|
+
const code = typeof record.code === "string" ? record.code : "";
|
|
31
|
+
const message = typeof record.message === "string" ? record.message : "";
|
|
32
|
+
if (code === "23505" || code === "SQLITE_CONSTRAINT_UNIQUE") return true;
|
|
33
|
+
if (message.includes("UNIQUE constraint failed:")) return true;
|
|
34
|
+
if (code === "19" && message.includes("constraint")) return true;
|
|
26
35
|
if (!("cause" in current)) return false;
|
|
27
36
|
current = current.cause;
|
|
28
37
|
}
|
|
@@ -793,6 +793,12 @@ export const providerRoutingSettings = pgTable(
|
|
|
793
793
|
// row that never set one is `buildClaudeCliUserAgent()` in
|
|
794
794
|
// providers/operations/custom-cli-headers.ts, which this mirrors.
|
|
795
795
|
userAgent: text("user_agent").notNull().default("claude-cli/2.1.293 (external, cli)"),
|
|
796
|
+
// Operator-set custom User-Agent, stored separately from the provider's
|
|
797
|
+
// native client identity (`user_agent`). NULL = no override — dispatch
|
|
798
|
+
// uses the native identity. A non-NULL value is an explicit override
|
|
799
|
+
// applied at the request edge, even when the provider would otherwise
|
|
800
|
+
// stamp its own identity.
|
|
801
|
+
customUserAgent: text("custom_user_agent"),
|
|
796
802
|
// When true, this provider's requests always dial direct. When false,
|
|
797
803
|
// dispatch automatically selects among every active network pool the
|
|
798
804
|
// tenant owns (least-loaded/non-cooldown) — never an admin-pinned pool.
|
|
@@ -1458,7 +1464,7 @@ export const telemetryEvents = pgTable(
|
|
|
1458
1464
|
outputTokens: integer("output_tokens"),
|
|
1459
1465
|
reasoningTokens: integer("reasoning_tokens"),
|
|
1460
1466
|
estimatedCostUsd: numeric("estimated_cost_usd", { precision: 12, scale: 6 }),
|
|
1461
|
-
/** Provider-billed credits (Tencent buddy-meter `credit`); NULL when unreported. */
|
|
1467
|
+
/** Provider-billed credits (Tencent buddy-meter `credit`, Qoder `credits`); NULL when unreported. */
|
|
1462
1468
|
creditUsed: numeric("credit_used", { precision: 12, scale: 4 }),
|
|
1463
1469
|
/**
|
|
1464
1470
|
* Gateway-side time before the upstream dispatch began: routing,
|
|
@@ -1483,6 +1489,14 @@ export const telemetryEvents = pgTable(
|
|
|
1483
1489
|
* tell "not configured" from "broken".
|
|
1484
1490
|
*/
|
|
1485
1491
|
payloadCaptureState: text("payload_capture_state"),
|
|
1492
|
+
/**
|
|
1493
|
+
* Set for a completed request whose output is far smaller than what the
|
|
1494
|
+
* model can produce AND which the model ended with an ordinary `stop`
|
|
1495
|
+
* (a "degenerate stop"). Introduced to measure how often a model
|
|
1496
|
+
* truncates itself on a cached prefix — the pattern that surfaced as
|
|
1497
|
+
* short, cut-off chat replies. NULL/unset for everything else.
|
|
1498
|
+
*/
|
|
1499
|
+
degenerateStop: boolean("degenerate_stop"),
|
|
1486
1500
|
},
|
|
1487
1501
|
(table) => [
|
|
1488
1502
|
index("telemetry_events_created_at_idx").on(table.createdAt),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Persisted API-key store: the console API-key domain and gateway authentication both consume this boundary.
|
|
2
2
|
|
|
3
|
-
import { and, asc, count, desc, eq, inArray, isNotNull, isNull, lte, or, sql, type SQL } from "drizzle-orm";
|
|
3
|
+
import { and, asc, count, desc, eq, gt, inArray, isNotNull, isNull, lte, or, sql, type SQL } from "drizzle-orm";
|
|
4
4
|
import { nextSortIndex, reorderSortIndex, type SortOrderDb } from "../sort-order";
|
|
5
5
|
import { getDbHandle } from "../boot";
|
|
6
6
|
import type { CartethyiaDatabase } from "../db-handle";
|
|
@@ -253,6 +253,8 @@ async function writeSharePopup(
|
|
|
253
253
|
/** Explicit persistence boundary. Production must provide this; there is no fallback store. */
|
|
254
254
|
export interface ApiKeyStore {
|
|
255
255
|
list(tenantId: string): Promise<readonly ApiKeyRecord[]>;
|
|
256
|
+
/** Active, non-revoked bans for one tenant, including share children. */
|
|
257
|
+
listActiveBanned(tenantId: string): Promise<readonly ApiKeyRecord[]>;
|
|
256
258
|
get(tenantId: string, keyId: string): Promise<ApiKeyRecord | undefined>;
|
|
257
259
|
listChildren(tenantId: string, parentKeyId: string): Promise<readonly ApiKeyRecord[]>;
|
|
258
260
|
revokeChildrenByScope(
|
|
@@ -382,6 +384,22 @@ export class DrizzleApiKeyStore implements ApiKeyStore {
|
|
|
382
384
|
.limit(1000);
|
|
383
385
|
return this.withPopups(this.db, rows);
|
|
384
386
|
}
|
|
387
|
+
async listActiveBanned(tenantId: string): Promise<readonly ApiKeyRecord[]> {
|
|
388
|
+
const now = new Date();
|
|
389
|
+
const rows = await this.db
|
|
390
|
+
.select()
|
|
391
|
+
.from(apiKeys)
|
|
392
|
+
.where(
|
|
393
|
+
and(
|
|
394
|
+
eq(apiKeys.tenantId, tenantId),
|
|
395
|
+
isNotNull(apiKeys.bannedAt),
|
|
396
|
+
isNull(apiKeys.revokedAt),
|
|
397
|
+
or(isNull(apiKeys.bannedUntil), gt(apiKeys.bannedUntil, now)),
|
|
398
|
+
),
|
|
399
|
+
)
|
|
400
|
+
.orderBy(desc(apiKeys.bannedAt), apiKeys.id);
|
|
401
|
+
return rows.map((row) => this.map(row));
|
|
402
|
+
}
|
|
385
403
|
|
|
386
404
|
/** Rewrites the list order for one tenant; `keyIds` is the full order. */
|
|
387
405
|
async reorder(tenantId: string, keyIds: readonly string[]): Promise<void> {
|
|
@@ -370,6 +370,13 @@ export interface CanonicalTerminalEvent {
|
|
|
370
370
|
readonly response_id?: string;
|
|
371
371
|
/** Event timestamp in milliseconds since epoch (optional for profiling). */
|
|
372
372
|
readonly timestamp?: number;
|
|
373
|
+
/**
|
|
374
|
+
* Provider-specific upstream metadata about the model that actually served
|
|
375
|
+
* the turn — its native model id, supply target, tier, etc. Not a client
|
|
376
|
+
* surface: probes read it to report what the upstream really ran when the
|
|
377
|
+
* transport's model echo is an opaque id.
|
|
378
|
+
*/
|
|
379
|
+
readonly provider_metadata?: Record<string, unknown>;
|
|
373
380
|
/**
|
|
374
381
|
* Set by an adapter whose turn is not finished: the stream ended because the
|
|
375
382
|
* model asked for a tool the client must answer, and the provider's
|
package/src/protocol/dsml.ts
CHANGED
|
@@ -3,21 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* DeepSeek-family models occasionally write their tool calls as XML-like DSML
|
|
5
5
|
* markup inside the plain-text content stream instead of the structured
|
|
6
|
-
* `tool_calls` field.
|
|
6
|
+
* `tool_calls` field. The gateway sees it from one upstream family:
|
|
7
7
|
*
|
|
8
8
|
* - CodeBuddy/WorkBuddy emit strict blocks (`<|DSML| calls>…</|DSML| calls>`)
|
|
9
9
|
* that must be parsed exactly, with declared-tool allowlisting.
|
|
10
|
-
* - Qoder emits looser blocks (`<[^>]*DSML[^>]*calls>…`) that tolerate any
|
|
11
|
-
* intermediate characters in the tag, and its parameter values are plain
|
|
12
|
-
* strings.
|
|
13
|
-
*
|
|
14
|
-
* The two share the same recovery idea, so the mechanics live here once and
|
|
15
|
-
* each caller picks its dialect through options. The defaults reproduce the
|
|
16
|
-
* CodeBuddy dialect exactly (`app/dsml.py` in codebuddy2api); Qoder passes
|
|
17
|
-
* `fuzzy: true` and its id factory, mirroring `qoder_proxy.py`.
|
|
18
10
|
*/
|
|
19
|
-
import { randomUUID } from "node:crypto";
|
|
20
|
-
|
|
21
11
|
export interface DsmlToolCall {
|
|
22
12
|
readonly id: string;
|
|
23
13
|
readonly name: string;
|
|
@@ -34,19 +24,15 @@ export interface DsmlParseOptions {
|
|
|
34
24
|
readonly separators?: readonly string[];
|
|
35
25
|
/** Block names a strict opener may name. */
|
|
36
26
|
readonly blockNames?: readonly string[];
|
|
37
|
-
/**
|
|
38
|
-
readonly fuzzy?: boolean;
|
|
39
|
-
/** How parameter values decode: `json` (CodeBuddy) or `string` (Qoder). */
|
|
27
|
+
/** How parameter values decode: `json` (CodeBuddy) or `string`. */
|
|
40
28
|
readonly params?: "json" | "string";
|
|
41
|
-
/** Call id factory. Defaults to sequential
|
|
29
|
+
/** Call id factory. Defaults to sequential. */
|
|
42
30
|
readonly id?: (call: { readonly name: string }, index: number, seq: number) => string;
|
|
43
31
|
/** Non-empty allowlist makes the strict dialect reject undeclared names. */
|
|
44
32
|
readonly allowedNames?: ReadonlySet<string>;
|
|
45
33
|
/**
|
|
46
34
|
* When true, JSON stringifies argument objects with recursively sorted keys,
|
|
47
|
-
* matching the reference bridge (`json.dumps(..., sort_keys=True)`).
|
|
48
|
-
* strict dialect defaults to true; the fuzzy dialect never sorts, matching
|
|
49
|
-
* its reference's `json.dumps(params)`.
|
|
35
|
+
* matching the reference bridge (`json.dumps(..., sort_keys=True)`).
|
|
50
36
|
*/
|
|
51
37
|
readonly sortKeys?: boolean;
|
|
52
38
|
/**
|
|
@@ -75,10 +61,6 @@ function sequentialId(_call: { readonly name: string }, _index: number, seq: num
|
|
|
75
61
|
return `call_dsml_${seq}_${_call.name}`;
|
|
76
62
|
}
|
|
77
63
|
|
|
78
|
-
function uuidId(_call: { readonly name: string }, _index: number, _seq: number): string {
|
|
79
|
-
return `call_${randomUUID().replace(/-/g, "").slice(0, 16)}`;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
64
|
interface DsmlParseResult {
|
|
83
65
|
readonly text: string;
|
|
84
66
|
readonly calls: readonly DsmlToolCall[];
|
|
@@ -257,63 +239,11 @@ function normalizeForSort(value: unknown): unknown {
|
|
|
257
239
|
return out;
|
|
258
240
|
}
|
|
259
241
|
|
|
260
|
-
/** The Qoder fuzzy block scanner, mirroring the reference bridge's regexes. */
|
|
261
|
-
const FUZZY_CALLS_RE = /<[^>]*DSML[^>]*calls>([\s\S]*?)<\/[^>]*DSML[^>]*calls>/;
|
|
262
|
-
const FUZZY_INVOKE_RE = /<[^>]*DSML[^>]*invoke\s+name=["']([^"']+)["']>([\s\S]*?)<\/[^>]*invoke>/g;
|
|
263
|
-
const FUZZY_PARAM_RE = /<[^>]*DSML[^>]*parameter\s+name=["']([^"']+)["'][^>]*>([\s\S]*?)<\/[^>]*parameter>/g;
|
|
264
|
-
|
|
265
|
-
function parseFuzzyDsml(text: string, options: DsmlParseOptions): DsmlRecovery {
|
|
266
|
-
const id = options.id ?? uuidId;
|
|
267
|
-
if (text.length === 0 || !text.includes("DSML")) return { calls: [], clean: text };
|
|
268
|
-
|
|
269
|
-
const callsMatch = FUZZY_CALLS_RE.exec(text);
|
|
270
|
-
let block: string;
|
|
271
|
-
let clean: string;
|
|
272
|
-
|
|
273
|
-
if (callsMatch) {
|
|
274
|
-
block = callsMatch[1] ?? "";
|
|
275
|
-
const before = text.slice(0, callsMatch.index).trimEnd();
|
|
276
|
-
const after = text.slice(callsMatch.index + callsMatch[0].length).trimStart();
|
|
277
|
-
clean = (before + " " + after).trim();
|
|
278
|
-
} else {
|
|
279
|
-
// No <calls> wrapper (issue #51): find bare <invoke> blocks in the whole
|
|
280
|
-
// text, then cut the prose off at the last `<` before the first invoke —
|
|
281
|
-
// exclusive of the invoke's own tag start, exactly like the reference's
|
|
282
|
-
// `text.rfind("<", 0, first_index)`.
|
|
283
|
-
FUZZY_INVOKE_RE.lastIndex = 0;
|
|
284
|
-
const first = FUZZY_INVOKE_RE.exec(text);
|
|
285
|
-
if (!first) return { calls: [], clean: text };
|
|
286
|
-
block = text;
|
|
287
|
-
const cut = text.lastIndexOf("<", first.index - 1);
|
|
288
|
-
clean = text.slice(0, cut >= 0 ? cut : first.index).trim();
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const calls: DsmlToolCall[] = [];
|
|
292
|
-
FUZZY_INVOKE_RE.lastIndex = 0;
|
|
293
|
-
let inv: RegExpExecArray | null;
|
|
294
|
-
let seq = 0;
|
|
295
|
-
while ((inv = FUZZY_INVOKE_RE.exec(block)) !== null) {
|
|
296
|
-
const params: Record<string, string> = {};
|
|
297
|
-
FUZZY_PARAM_RE.lastIndex = 0;
|
|
298
|
-
let par: RegExpExecArray | null;
|
|
299
|
-
while ((par = FUZZY_PARAM_RE.exec(inv[2] ?? "")) !== null) {
|
|
300
|
-
params[par[1] ?? ""] = (par[2] ?? "").trim();
|
|
301
|
-
}
|
|
302
|
-
const call = { name: inv[1] ?? "", arguments: JSON.stringify(params) };
|
|
303
|
-
calls.push({ ...call, id: id(call, calls.length, seq++) });
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return { calls, clean };
|
|
307
|
-
}
|
|
308
|
-
|
|
309
242
|
/**
|
|
310
|
-
* Recovers DSML tool calls from one plain-text string.
|
|
311
|
-
* by `options.fuzzy`; each dialect keeps its exact public shape:
|
|
243
|
+
* Recovers DSML tool calls from one plain-text string.
|
|
312
244
|
*
|
|
313
245
|
* - strict (CodeBuddy): returns `{ text, calls }`, with `text` the clean
|
|
314
246
|
* prose; `undefined` when nothing was recovered.
|
|
315
|
-
* - fuzzy (Qoder): returns `{ clean, calls }`, with no-DSML input returning
|
|
316
|
-
* `{ calls: [], clean: text }`.
|
|
317
247
|
*
|
|
318
248
|
* Callers that need the streaming strict parser use {@link DsmlStreamParser}.
|
|
319
249
|
*/
|
|
@@ -321,7 +251,6 @@ export function parseDsmlToolCalls(
|
|
|
321
251
|
text: string,
|
|
322
252
|
options: DsmlParseOptions = {},
|
|
323
253
|
): DsmlRecovery | DsmlParseResult | undefined {
|
|
324
|
-
if (options.fuzzy === true) return parseFuzzyDsml(text, options);
|
|
325
254
|
if (!text.includes("DSML")) return undefined;
|
|
326
255
|
const parser = new DsmlStreamParser(options);
|
|
327
256
|
const first = parser.push(text);
|
|
@@ -287,7 +287,26 @@ export function buildGeminiPayload(
|
|
|
287
287
|
});
|
|
288
288
|
const payload: Record<string, unknown> = { contents };
|
|
289
289
|
if (systemParts.length > 0) payload["systemInstruction"] = { role: "user", parts: systemParts };
|
|
290
|
-
|
|
290
|
+
// Gemini's native hosted tools are declared as bare object keys, not
|
|
291
|
+
// function declarations: `{ googleSearch: {} }` (and codeExecution/urlContext)
|
|
292
|
+
// enable Google-backed grounding. Accept both spellings the gateway's search
|
|
293
|
+
// adapters use (`web_search` tool_type and `googleSearch` name) and map them
|
|
294
|
+
// to the native declaration so the upstream actually grounds the query.
|
|
295
|
+
const HOSTED_TOOL_NAMES = new Set(["googleSearch", "codeExecution", "urlContext"]);
|
|
296
|
+
const hostedTools: Record<string, Record<string, unknown>>[] = [];
|
|
297
|
+
for (const tool of request.tools ?? []) {
|
|
298
|
+
if (tool.name === "googleSearch" || tool.tool_type === "web_search") {
|
|
299
|
+
hostedTools.push({ googleSearch: {} });
|
|
300
|
+
} else if (tool.name === "codeExecution") {
|
|
301
|
+
hostedTools.push({ codeExecution: {} });
|
|
302
|
+
} else if (tool.name === "urlContext") {
|
|
303
|
+
hostedTools.push({ urlContext: {} });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
const functionTools = (request.tools ?? []).filter(
|
|
307
|
+
(t) => t.name && !HOSTED_TOOL_NAMES.has(t.name) && t.tool_type !== "web_search",
|
|
308
|
+
);
|
|
309
|
+
if (hostedTools.length > 0) payload["tools"] = hostedTools;
|
|
291
310
|
if (functionTools.length > 0) {
|
|
292
311
|
payload["tools"] = [
|
|
293
312
|
{
|
|
@@ -73,9 +73,23 @@ export function trackToolCall(
|
|
|
73
73
|
fallbackIndex?: number,
|
|
74
74
|
): { index: number; isFirst: boolean; state: ToolCallState } {
|
|
75
75
|
const priorIndex = tracker.indexById.get(tool.id);
|
|
76
|
+
// A wire index is only honored when this id already owns it, or when it is
|
|
77
|
+
// not yet claimed by another call. Upstream Qoder parallel calls frequently
|
|
78
|
+
// repeat the same wire index (0,0) for different ids, which would otherwise
|
|
79
|
+
// collapse two calls into one at the encoder (`Map` keyed by index). When
|
|
80
|
+
// the wire index collides with another id, synthesize a fresh index so every
|
|
81
|
+
// logical call keeps a distinct wire position.
|
|
76
82
|
const index =
|
|
77
83
|
tool.index !== undefined && Number.isInteger(tool.index) && tool.index >= 0
|
|
78
|
-
?
|
|
84
|
+
? (() => {
|
|
85
|
+
if (priorIndex !== undefined) return priorIndex;
|
|
86
|
+
for (const [id, assigned] of tracker.indexById) {
|
|
87
|
+
if (id !== tool.id && assigned === tool.index) {
|
|
88
|
+
return tracker.nextIndex++;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return tool.index;
|
|
92
|
+
})()
|
|
79
93
|
: (priorIndex ?? fallbackIndex ?? tracker.nextIndex++);
|
|
80
94
|
tracker.indexById.set(tool.id, index);
|
|
81
95
|
tracker.nextIndex = Math.max(tracker.nextIndex, index + 1);
|
|
@@ -28,6 +28,19 @@ import {
|
|
|
28
28
|
} from "../../protocol/codecs";
|
|
29
29
|
const TEXT_DECODER = new TextDecoder("utf-8", { fatal: false });
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Provider ids that understand the OpenAI-style `service_tier` control. Only
|
|
33
|
+
* these upstreams accept the field; every other OpenAI-compatible provider
|
|
34
|
+
* either ignores it or rejects the whole request with `unsupported_value`
|
|
35
|
+
* (measured: GitHub Copilot returns 400). `service_tier` is advisory on the
|
|
36
|
+
* providers that do support it, so dropping it elsewhere is lossless.
|
|
37
|
+
*/
|
|
38
|
+
const SERVICE_TIER_SUPPORTED_PROVIDERS: ReadonlySet<string> = new Set([
|
|
39
|
+
"openai",
|
|
40
|
+
"codex",
|
|
41
|
+
"anthropic",
|
|
42
|
+
]);
|
|
43
|
+
|
|
31
44
|
/**
|
|
32
45
|
* The subset of a canonical request a native-service header hook may read.
|
|
33
46
|
*
|
|
@@ -279,6 +292,18 @@ export class OpenAICompatibleAdapter extends BaseProviderAdapter {
|
|
|
279
292
|
}
|
|
280
293
|
}
|
|
281
294
|
this.config.prePayload?.(payload, effectiveRequest, candidate, context);
|
|
295
|
+
// `service_tier` is an OpenAI-style control that only a small set of
|
|
296
|
+
// upstreams understand. Every other OpenAI-compatible provider either
|
|
297
|
+
// ignores it silently or rejects the whole request with
|
|
298
|
+
// `unsupported_value` (measured: GitHub Copilot returns 400). Since the
|
|
299
|
+
// field is purely advisory on the providers that support it, dropping it
|
|
300
|
+
// everywhere else is safe and prevents avoidable upstream 400s.
|
|
301
|
+
if (
|
|
302
|
+
payload["service_tier"] !== undefined &&
|
|
303
|
+
!SERVICE_TIER_SUPPORTED_PROVIDERS.has(candidate.provider_id)
|
|
304
|
+
) {
|
|
305
|
+
delete payload["service_tier"];
|
|
306
|
+
}
|
|
282
307
|
return payload;
|
|
283
308
|
}
|
|
284
309
|
|
|
@@ -429,12 +454,15 @@ export class OpenAICompatibleAdapter extends BaseProviderAdapter {
|
|
|
429
454
|
// the absence of every completion shape so a provider that merely
|
|
430
455
|
// echoes an `error` field alongside real output is not misread.
|
|
431
456
|
const envelopeError = json["error"];
|
|
457
|
+
const startPlanError = this.provider_id === "zcode-start" &&
|
|
458
|
+
(json["success"] === false || (json["code"] !== undefined &&
|
|
459
|
+
![0, 200, "0", "200"].includes(json["code"] as string | number)));
|
|
432
460
|
const hasCompletionShape =
|
|
433
461
|
json["choices"] !== undefined ||
|
|
434
462
|
json["output"] !== undefined ||
|
|
435
463
|
json["content"] !== undefined ||
|
|
436
464
|
json["candidates"] !== undefined;
|
|
437
|
-
if (envelopeError !== undefined && envelopeError !== null && !hasCompletionShape) {
|
|
465
|
+
if (((envelopeError !== undefined && envelopeError !== null) || startPlanError) && !hasCompletionShape) {
|
|
438
466
|
const errorObject = isRecord(envelopeError) ? envelopeError : json;
|
|
439
467
|
const identifier = upstreamErrorIdentifier(json);
|
|
440
468
|
const providerStatus = [
|