cartethyia 2.7.7 → 2.7.9
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 +0 -1
- package/README.md +29 -59
- package/dist/dashboard/assets/BannedPage-BxoG1MrL.js +1 -0
- package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-CLUhmjUy.js → CliToolDetailPage-ixwgKk53.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-CQmrqqrk.js → CliToolsPage-BmttNV3U.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BE3qwwfC.js +1 -0
- package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-C-e2AMz6.js → InjectSystemPromptPanel-BOoO8rpz.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-J8wPbmfT.js → LoginPage-ChJuNNVN.js} +1 -1
- package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-C6WlpU5E.js → ModelPicker-BZAJtPg1.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BE40s3u5.js +1 -0
- package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +8 -0
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-BBTRBAQ3.js → ProxyPage-BJ87Pp0U.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
- package/dist/dashboard/assets/{QuotaPage-B2WqMSG1.js → QuotaPage-gkSyRAKa.js} +1 -1
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js +1 -0
- package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-LND6_Ss7.js → SetupPage-CFNcATJJ.js} +1 -1
- package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-BzNnm6Bi.js → StudioPage-YoD4tY5l.js} +1 -1
- package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DxjaAhb_.js +56 -0
- package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BTlkq_Uk.js +1 -0
- package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
- package/dist/dashboard/assets/app-BX2nnKqs.css +2 -0
- package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CndthXFQ.js +1 -0
- package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
- package/dist/dashboard/assets/{badge-BaX2GTrn.js → badge-DdbT_7Am.js} +1 -1
- package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
- package/dist/dashboard/assets/ban-durations-B58FyRxC.js +1 -0
- package/dist/dashboard/assets/ban-durations-B58FyRxC.js.gz +0 -0
- package/dist/dashboard/assets/{banner-DFH4Bkte.js → banner-CsGN-nbm.js} +1 -1
- package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
- package/dist/dashboard/assets/brand/bansos_page.webp +0 -0
- package/dist/dashboard/assets/brand/denia_bansos.webp +0 -0
- package/dist/dashboard/assets/brand/favicon.webp +0 -0
- package/dist/dashboard/assets/brand/login_register.webp +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.webp +0 -0
- package/dist/dashboard/assets/brand/og_bansos.webp +0 -0
- package/dist/dashboard/assets/brand/og_image.webp +0 -0
- package/dist/dashboard/assets/brand/sidebar_ico.webp +0 -0
- package/dist/dashboard/assets/{card-CxDw0lK6.js → card-mgZ_TLLZ.js} +1 -1
- package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-A1-v8-03.js +1 -0
- package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-DfS1ntiq.js → clipboard-button-CJ4Zty89.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-BK-NRr5u.js → dialog-B5L2jPQ_.js} +1 -1
- package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-QqPul6sC.js → drawer-DkjUouAj.js} +1 -1
- package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
- package/dist/dashboard/assets/{entry-BMlYJVFj.js → entry-B-LVLV6o.js} +1 -1
- package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
- package/dist/dashboard/assets/entry-CAhdYl3B.js +1 -0
- package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
- package/dist/dashboard/assets/entry-D2xD0Tmd.js +4 -0
- package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-DP-5E01-.js → formatters-CrsdLVsn.js} +1 -1
- package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
- package/dist/dashboard/assets/{index-D36vCZht.js → index-BU64Lqvp.js} +2 -2
- package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
- package/dist/dashboard/assets/{inline-CHJQwxLa.js → inline-B2aDLyyg.js} +1 -1
- package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
- package/dist/dashboard/assets/{input-BlmlizVV.js → input-BCVkdR9d.js} +1 -1
- package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
- package/dist/dashboard/assets/{layout-Bz93I-yE.js → layout-DNcAdMK-.js} +1 -1
- package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-Ml16KCfu.js → list-row-DgrIH0hw.js} +1 -1
- package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
- package/dist/dashboard/assets/{live-7_QpnYGu.js → live-Cae01iu8.js} +1 -1
- package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-GU7XdEjz.js → mutation-BQ0ccH_A.js} +1 -1
- package/dist/dashboard/assets/mutation-BQ0ccH_A.js.gz +0 -0
- package/dist/dashboard/assets/{network-CuMDaTzT.js → network-CRib7H_2.js} +1 -1
- package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-CoqpaDMx.js → numbers-BKQE8Xpz.js} +1 -1
- package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-B5aOoweZ.js → pagination-ecPvG_8g.js} +1 -1
- package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
- package/dist/dashboard/assets/providers/devin.svg +9 -0
- package/dist/dashboard/assets/providers/devin.svg.gz +0 -0
- package/dist/dashboard/assets/providers/windsurf.svg +10 -0
- package/dist/dashboard/assets/providers/windsurf.svg.gz +0 -0
- package/dist/dashboard/assets/{query-keys-YSoQOUh6.js → query-keys-IKLh0YFd.js} +1 -1
- package/dist/dashboard/assets/query-keys-IKLh0YFd.js.gz +0 -0
- package/dist/dashboard/assets/routing-Ckm1UXuC.js +1 -0
- package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
- package/dist/dashboard/assets/{select-C9Ssle9b.js → select-ChoFWyKz.js} +1 -1
- package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
- package/dist/dashboard/assets/settings-CKjFPlKW.js +1 -0
- package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
- package/dist/dashboard/assets/{stack-j7cXjgYX.js → stack-DWCwU3fh.js} +1 -1
- package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
- package/dist/dashboard/assets/{state-DHmEnLa2.js → state-Cg3VL5J_.js} +1 -1
- package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
- package/dist/dashboard/assets/story/25817331.webp +0 -0
- package/dist/dashboard/assets/story/Shorekeeper.webp +0 -0
- package/dist/dashboard/assets/story/cartethyia-god.webp +0 -0
- package/dist/dashboard/assets/story/fleurdelys_plus.webp +0 -0
- package/dist/dashboard/assets/story/jinhsi-blossom.webp +0 -0
- package/dist/dashboard/assets/story/phrolova-a.webp +0 -0
- package/dist/dashboard/assets/story/requestdeniawokkjpg.webp +0 -0
- package/dist/dashboard/assets/story/wuthering-waves-hiyuki-aemeath-hiyuki-rover.webp +0 -0
- package/dist/dashboard/assets/{switch-DmZcyTIC.js → switch-Db70BMix.js} +1 -1
- package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-CbCP4w1o.js → useMutation-BqdE0LuM.js} +1 -1
- package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.webp +0 -0
- package/dist/dashboard/console-backgrounds_initial.webp +0 -0
- package/dist/dashboard/donation/qris-risun-marke.webp +0 -0
- package/dist/dashboard/index.html +3 -3
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +57 -55
- package/migrations/full-postgres/0044_add_models_warning_metadata.sql +10 -0
- package/migrations/full-postgres/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
- package/migrations/lite-sqlite3/0044_add_models_warning_metadata.sql +11 -0
- package/migrations/lite-sqlite3/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
- package/package.json +1 -1
- package/src/boot/dependencies.ts +11 -3
- package/src/config/cooldowns.ts +1 -1
- package/src/config/spec.ts +0 -6
- package/src/console/api-keys/routes.ts +11 -4
- package/src/console/console-router.ts +7 -0
- package/src/console/dashboard-assets.ts +5 -5
- package/src/console/domain-registration.ts +27 -0
- package/src/console/ip-bans/routes.ts +99 -0
- package/src/console/providers/catalog/catalog-projections.ts +5 -2
- package/src/console/providers/catalog/contracts.ts +123 -0
- package/src/console/providers/catalog/model-operations.ts +122 -10
- package/src/console/providers/catalog/provider-operations.ts +116 -8
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/routes.ts +64 -4
- package/src/console/providers/catalog/store-helpers.ts +30 -1
- package/src/console/providers/catalog/store.ts +243 -3
- package/src/console/quota/account-quota-global.ts +89 -1
- package/src/console/quota/native-identity.ts +2 -0
- package/src/console/settings/contracts.ts +18 -0
- package/src/console/settings/store.ts +32 -0
- package/src/console/system-prompt/contracts.ts +0 -1
- package/src/main.ts +1 -0
- package/src/observability/event-query.ts +18 -4
- package/src/persistence/dialect.ts +12 -0
- package/src/persistence/schema.ts +20 -2
- package/src/protocol/dsml.ts +1 -1
- package/src/protocol/gateway-error.ts +1 -0
- package/src/protocol/reasoning.ts +1 -1
- package/src/protocol/request/chat.ts +4 -4
- package/src/protocol/request/messages.ts +1 -1
- package/src/protocol/response/gemini.ts +6 -1
- package/src/protocol/sse-decoder.ts +25 -8
- package/src/protocol/stream-error-frames.ts +13 -3
- package/src/protocol/translation/quirks.ts +1 -1
- package/src/protocol/translation/tool-repair.ts +2 -2
- package/src/protocol/upstream/send-claude-messages.ts +15 -4
- package/src/protocol/upstream-parsing.ts +1 -1
- package/src/providers/adapters/compatible-adapter.ts +7 -5
- package/src/providers/authentication/oauth-refresh-service.ts +149 -36
- package/src/providers/default-registry.ts +68 -3
- package/src/providers/discovery/discovery-types.ts +12 -0
- package/src/providers/discovery/probe-phases.ts +61 -30
- package/src/providers/discovery/probing-service.ts +279 -164
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +117 -2
- package/src/providers/integrations/antigravity/antigravity.ts +13 -2
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +72 -34
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +18 -18
- package/src/providers/integrations/buddy/buddy-checkin.ts +17 -39
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +2 -2
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +5 -5
- package/src/providers/integrations/buddy/buddy-identity.ts +70 -0
- package/src/providers/integrations/buddy/buddy-media.ts +4 -5
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +2 -2
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +1 -1
- package/src/providers/integrations/buddy/buddy-usage-shared.ts +1 -3
- package/src/providers/integrations/buddy/capabilities.ts +4 -41
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +2 -1
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +20 -1
- package/src/providers/integrations/buddy/codebuddy/provider.ts +2 -2
- package/src/providers/integrations/buddy/codebuddy/shared.ts +6 -10
- package/src/providers/integrations/cursor/cursor-catalog.ts +2 -0
- package/src/providers/integrations/cursor/cursor-exec.ts +738 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +2 -0
- package/src/providers/integrations/cursor/cursor-request.ts +9 -8
- package/src/providers/integrations/cursor/cursor-shared.ts +19 -1
- package/src/providers/integrations/cursor/cursor.ts +50 -30
- package/src/providers/integrations/cursor/protocol/tool-canonical.ts +26 -100
- package/src/providers/integrations/devin/devin-auth.ts +353 -0
- package/src/providers/integrations/devin/devin-catalog.ts +207 -0
- package/src/providers/integrations/devin/devin-http.ts +91 -0
- package/src/providers/integrations/devin/devin-quota.ts +214 -0
- package/src/providers/integrations/devin/devin-shared.ts +111 -0
- package/src/providers/integrations/devin/devin.ts +539 -0
- package/src/providers/integrations/devin/protocol/devin-proto.ts +584 -0
- package/src/providers/integrations/devin/protocol/framing.ts +124 -0
- package/src/providers/integrations/factory/factory.ts +2 -2
- package/src/providers/integrations/freepi/freepi-catalog.ts +60 -0
- package/src/providers/integrations/freepi/freepi-discovery.ts +82 -0
- package/src/providers/integrations/freepi/freepi-headers.ts +21 -0
- package/src/providers/integrations/freepi/freepi-oauth.ts +142 -0
- package/src/providers/integrations/freepi/freepi-protocol.ts +242 -0
- package/src/providers/integrations/freepi/freepi-quota.ts +126 -0
- package/src/providers/integrations/freepi/freepi-realm.ts +35 -0
- package/src/providers/integrations/freepi/freepi.ts +292 -0
- package/src/providers/integrations/gemini.ts +2 -2
- package/src/providers/integrations/hermes/hermes-oauth.ts +94 -0
- package/src/providers/integrations/hermes.ts +4 -12
- package/src/providers/integrations/opencode/fingerprint.ts +1 -1
- package/src/providers/integrations/perch/perch-catalog.ts +105 -0
- package/src/providers/integrations/perch/perch-oauth.ts +170 -0
- package/src/providers/integrations/perch/perch-protocol.ts +211 -0
- package/src/providers/integrations/perch/perch-quota.ts +137 -0
- package/src/providers/integrations/perch/perch-realm.ts +82 -0
- package/src/providers/integrations/perch/perch-system-prompt.ts +13 -0
- package/src/providers/integrations/perch/perch.ts +315 -0
- package/src/providers/integrations/perch/saffron-system-prompt.txt +8 -0
- package/src/providers/integrations/qoder/chat-proto.ts +328 -328
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +1 -1
- package/src/providers/integrations/qoder/qoder-adapter.ts +89 -28
- package/src/providers/integrations/qoder/qoder-cache.ts +32 -0
- package/src/providers/integrations/qoder/qoder-catalog.ts +6 -1
- package/src/providers/integrations/qoder/qoder-checkin.ts +2 -2
- package/src/providers/integrations/qoder/qoder-grpc.ts +209 -5
- package/src/providers/integrations/qoder/qoder-ide.ts +5 -1
- package/src/providers/integrations/qoder/qoder-identity-cache.ts +184 -0
- package/src/providers/integrations/qoder/qoder-native-identity-store.ts +83 -18
- package/src/providers/integrations/qoder/qoder-realm.ts +9 -6
- package/src/providers/integrations/qoder/qoder-umid.ts +109 -342
- package/src/providers/integrations/qoder/qoder-virtual.ts +33 -1
- package/src/providers/integrations/qoder/qoder.ts +3 -6
- package/src/providers/integrations/trae/trae-catalog.ts +9 -14
- package/src/providers/integrations/trae/trae-headers.ts +36 -11
- package/src/providers/integrations/trae/trae-oauth.ts +14 -17
- package/src/providers/integrations/trae/trae-protocol.ts +3 -6
- package/src/providers/integrations/trae/trae-quota.ts +103 -21
- package/src/providers/integrations/trae/trae-realm.ts +36 -15
- package/src/providers/integrations/trae/trae.ts +97 -7
- package/src/providers/operations/account-error-classification.ts +66 -1
- package/src/providers/operations/account-health-service.ts +192 -26
- package/src/providers/operations/account-refresh-status.ts +284 -0
- package/src/providers/operations/account-tier.ts +67 -0
- package/src/providers/operations/byok-wire-profile.ts +9 -12
- package/src/providers/operations/client-versions.ts +37 -99
- package/src/providers/operations/cooldown-rules.ts +61 -0
- package/src/providers/operations/provider-catalog-seeder.ts +4 -0
- package/src/providers/provider-contracts.ts +29 -0
- package/src/providers/provider-metadata.ts +59 -4
- package/src/providers/provider-model-definition.ts +36 -0
- package/src/providers/quota/refresh.ts +21 -0
- package/src/security/abuse.ts +71 -0
- package/src/transport/dispatch/proxy-request.ts +1 -0
- package/src/transport/dispatch/streaming/stream.ts +1 -0
- package/src/transport/dispatch/upstream.ts +4 -1
- package/src/transport/failure-policy.ts +113 -7
- package/src/transport/routing/router.ts +25 -1
- package/src/transport/surface/messages/adapter.ts +2 -1
- package/src/transport/surface/responses/encode.ts +12 -1
- package/src/workers/daily-checkin.ts +1 -1
- package/src/workers/quota-refresh-sweep.ts +1 -1
- package/src/workers/scheduler.ts +22 -4
- package/dist/dashboard/assets/BannedPage-B05aF8Dg.js +0 -1
- package/dist/dashboard/assets/BannedPage-B05aF8Dg.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-CLUhmjUy.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-CQmrqqrk.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CHG_1Wr3.js +0 -1
- package/dist/dashboard/assets/CombosPage-CHG_1Wr3.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DPgbklad.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-DPgbklad.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-pnfAqr_-.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-pnfAqr_-.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-DGJuU2A6.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-DGJuU2A6.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-C-e2AMz6.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-J8wPbmfT.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-C6WlpU5E.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BSQnF2Lp.js +0 -1
- package/dist/dashboard/assets/OverviewPage-BSQnF2Lp.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-DK_e0FJz.js +0 -7
- package/dist/dashboard/assets/ProviderDetailPage-DK_e0FJz.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-D_a3r6Gc.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-D_a3r6Gc.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BBTRBAQ3.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-B2WqMSG1.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-D7xp5Ij_.js +0 -1
- package/dist/dashboard/assets/SettingsPage-D7xp5Ij_.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-LND6_Ss7.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-BzNnm6Bi.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-C1TbL24N.js +0 -56
- package/dist/dashboard/assets/UsagePage-C1TbL24N.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-KvX83mRe.js +0 -1
- package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
- package/dist/dashboard/assets/app-mweRcsKp.css +0 -2
- package/dist/dashboard/assets/app-mweRcsKp.css.gz +0 -0
- package/dist/dashboard/assets/assert-CidQVoNo.js +0 -1
- package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
- package/dist/dashboard/assets/badge-BaX2GTrn.js.gz +0 -0
- package/dist/dashboard/assets/banner-DFH4Bkte.js.gz +0 -0
- package/dist/dashboard/assets/brand/bansos_page.png +0 -0
- package/dist/dashboard/assets/brand/denia_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/favicon.jpg +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/brand/sidebar_ico.jpg +0 -0
- package/dist/dashboard/assets/card-CxDw0lK6.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js +0 -1
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DfS1ntiq.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BK-NRr5u.js.gz +0 -0
- package/dist/dashboard/assets/drawer-QqPul6sC.js.gz +0 -0
- package/dist/dashboard/assets/entry-BMlYJVFj.js.gz +0 -0
- package/dist/dashboard/assets/entry-Dmx7gBiN.js +0 -1
- package/dist/dashboard/assets/entry-Dmx7gBiN.js.gz +0 -0
- package/dist/dashboard/assets/entry-vqGb8yVz.js +0 -4
- package/dist/dashboard/assets/entry-vqGb8yVz.js.gz +0 -0
- package/dist/dashboard/assets/formatters-DP-5E01-.js.gz +0 -0
- package/dist/dashboard/assets/index-D36vCZht.js.gz +0 -0
- package/dist/dashboard/assets/inline-CHJQwxLa.js.gz +0 -0
- package/dist/dashboard/assets/input-BlmlizVV.js.gz +0 -0
- package/dist/dashboard/assets/layout-Bz93I-yE.js.gz +0 -0
- package/dist/dashboard/assets/list-row-Ml16KCfu.js.gz +0 -0
- package/dist/dashboard/assets/live-7_QpnYGu.js.gz +0 -0
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +0 -1
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
- package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
- package/dist/dashboard/assets/network-CuMDaTzT.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CoqpaDMx.js.gz +0 -0
- package/dist/dashboard/assets/pagination-B5aOoweZ.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-YSoQOUh6.js.gz +0 -0
- package/dist/dashboard/assets/routing-fnCsxgNO.js +0 -1
- package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
- package/dist/dashboard/assets/select-C9Ssle9b.js.gz +0 -0
- package/dist/dashboard/assets/settings-BxslsErn.js +0 -1
- package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
- package/dist/dashboard/assets/stack-j7cXjgYX.js.gz +0 -0
- package/dist/dashboard/assets/state-DHmEnLa2.js.gz +0 -0
- package/dist/dashboard/assets/switch-DmZcyTIC.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.jpg +0 -0
- package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
- package/dist/dashboard/donation/qris-risun-marke.png +0 -0
- package/src/providers/integrations/buddy/turing-device-token.ts +0 -262
- package/src/providers/integrations/buddy/workbuddy/cn-oauth.ts +0 -30
- package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +0 -58
- package/src/providers/integrations/buddy/workbuddy/oauth.ts +0 -31
- package/src/providers/integrations/buddy/workbuddy/provider.ts +0 -209
- package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -161
- package/src/providers/integrations/buddy/workbuddy/shared.ts +0 -285
- /package/dist/dashboard/assets/{app-DRnQLhsc.js → app-B7kIWFm5.js} +0 -0
- /package/dist/dashboard/assets/{app-DRnQLhsc.js.gz → app-B7kIWFm5.js.gz} +0 -0
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Qoder adapter — transport selection, credential resolution, dispatch.
|
|
3
3
|
*
|
|
4
|
-
* Surface contract (2026-11-
|
|
5
|
-
* - `
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* scheme (AES/RSA + alphabet-scrambled body).
|
|
4
|
+
* Surface contract (2026-11-07):
|
|
5
|
+
* - `cosy` (default, production): `/algo/.../agent_chat_generation` on
|
|
6
|
+
* `api1` → `api2` → `api3.qoder.sh` (legacy COSY) — the only surface
|
|
7
|
+
* verified alive. Accepts free-tier rows (`lite`).
|
|
8
|
+
* - `grpc` (`model.chat.ChatService/ChatCompletionStream`): **not the
|
|
9
|
+
* default anymore** — its host (`api2-v2.qoder.sh`) is NXDOMAIN since
|
|
10
|
+
* 2026-11-07. Kept as an explicit override for probes.
|
|
12
11
|
* - `ide` (`/model/v1/chat/completions`): **probe/testing only — never
|
|
13
12
|
* production.** It is the only surface that echoes strategy metadata
|
|
14
13
|
* (`raw_usage` → provider_model/target) and it streams immediately, but its
|
|
@@ -37,6 +36,7 @@ import { isQoderPat, qoderOAuthAuth, qoderSessionId, resolveQoderAuth } from "./
|
|
|
37
36
|
import { buildQoderRequest, callQoder } from "./qoder-chat";
|
|
38
37
|
import { buildQoderGrpcRequest, qoderGrpcMetadata, qoderGrpcToCanonicalEvents } from "./qoder-grpc";
|
|
39
38
|
import { buildQoderIdeRequest, qoderBodyToCanonicalEvents } from "./qoder-ide";
|
|
39
|
+
import { estimateQoderSessionCache } from "./qoder-cache";
|
|
40
40
|
import {
|
|
41
41
|
QODER_CHAT_PATH,
|
|
42
42
|
QODER_PROVIDER_ID,
|
|
@@ -55,17 +55,16 @@ interface QoderAdapterOptions {
|
|
|
55
55
|
/**
|
|
56
56
|
* Which chat transport to dispatch on.
|
|
57
57
|
*
|
|
58
|
-
* `
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* (`quota exceeded` 116) and must use the legacy COSY surface.
|
|
58
|
+
* `cosy` (default) = legacy COSY (`/algo/.../agent_chat_generation`) on
|
|
59
|
+
* `api1` → `api2` → `api3.qoder.sh` — the only surface verified alive
|
|
60
|
+
* (2026-11-07; the `api2-v2` model-server host is NXDOMAIN). This path also
|
|
61
|
+
* accepts free-tier rows (`lite`).
|
|
63
62
|
*
|
|
64
|
-
* `
|
|
65
|
-
* — the
|
|
63
|
+
* `grpc` forces the gRPC surface (`model.chat.ChatService/ChatCompletionStream`)
|
|
64
|
+
* — **no longer the default**; its host is not verified alive.
|
|
66
65
|
*
|
|
67
|
-
* `ide` forces
|
|
68
|
-
*
|
|
66
|
+
* `ide` forces `/model/v1` — **probe/testing only, never production**
|
|
67
|
+
* (see module doc).
|
|
69
68
|
*/
|
|
70
69
|
readonly surface?: "grpc" | "cosy" | "ide";
|
|
71
70
|
}
|
|
@@ -75,7 +74,7 @@ export function resolveQoderTransport(
|
|
|
75
74
|
surface: "grpc" | "cosy" | "ide" | undefined,
|
|
76
75
|
modelConfig: QoderModelConfig,
|
|
77
76
|
): "grpc" | "cosy" | "ide" {
|
|
78
|
-
const chosen = surface ?? "
|
|
77
|
+
const chosen = surface ?? "cosy";
|
|
79
78
|
// `ide` is an explicit testing override — never auto-selected, and it does
|
|
80
79
|
// not apply the free-tier rule (a forced IDE call is a test call).
|
|
81
80
|
if (chosen === "ide") return "ide";
|
|
@@ -104,7 +103,14 @@ class QoderAdapter implements ProviderAdapter {
|
|
|
104
103
|
constructor(options: QoderAdapterOptions = {}) {
|
|
105
104
|
this.provider_id = (options.providerId ?? QODER_PROVIDER_ID) as ProviderId;
|
|
106
105
|
this.realm = options.realm ?? "intl";
|
|
107
|
-
|
|
106
|
+
// The legacy COSY surface is the production default (2026-11-07): the
|
|
107
|
+
// gRPC host is NXDOMAIN and the IDE surface is probe-only. The constructor
|
|
108
|
+
// must default to it too, or every billed model dispatches to the dead
|
|
109
|
+
// gRPC path while free-tier rows get cosy via the is_free rule — the
|
|
110
|
+
// mismatch that made `deepseek-v4.1-flash` fail while `qwen3.8-flash`
|
|
111
|
+
// passed. This is the same default `resolveQoderTransport(undefined)`
|
|
112
|
+
// already returns.
|
|
113
|
+
this.surface = options.surface ?? "cosy";
|
|
108
114
|
this.fetchFn = options.fetch ?? globalThis.fetch;
|
|
109
115
|
}
|
|
110
116
|
|
|
@@ -121,6 +127,30 @@ class QoderAdapter implements ProviderAdapter {
|
|
|
121
127
|
>;
|
|
122
128
|
}
|
|
123
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Wraps an upstream event stream and applies the local session-cache
|
|
132
|
+
* estimate to every `usage` record (and terminal-carried usage).
|
|
133
|
+
*
|
|
134
|
+
* Qoder's upstream never reports prompt-cache hits (verified live on both
|
|
135
|
+
* gRPC and IDE HTTP surfaces), so the gap is closed the same way Trae and
|
|
136
|
+
* Kiro do: estimate the cached prefix from this session's request history.
|
|
137
|
+
*/
|
|
138
|
+
async *#withSessionCacheEstimate(
|
|
139
|
+
events: AsyncIterable<CanonicalEvent>,
|
|
140
|
+
request: CanonicalRequest,
|
|
141
|
+
sessionId: string | undefined,
|
|
142
|
+
): AsyncGenerator<CanonicalEvent> {
|
|
143
|
+
for await (const event of events) {
|
|
144
|
+
if (event.type === "usage") {
|
|
145
|
+
yield { ...event, usage: estimateQoderSessionCache(event.usage, sessionId, request.messages) };
|
|
146
|
+
} else if (event.type === "terminal" && event.usage !== undefined) {
|
|
147
|
+
yield { ...event, usage: estimateQoderSessionCache(event.usage, sessionId, request.messages) };
|
|
148
|
+
} else {
|
|
149
|
+
yield event;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
124
154
|
async *dispatch(
|
|
125
155
|
request: CanonicalRequest,
|
|
126
156
|
candidate: ProviderDispatchTarget,
|
|
@@ -207,7 +237,11 @@ class QoderAdapter implements ProviderAdapter {
|
|
|
207
237
|
if (!res.ok) throw await mapUpstreamHttpError(res, "qoder");
|
|
208
238
|
if (!res.body) throw new GatewayError("platform_unavailable", 502, "Qoder returned empty body (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
209
239
|
lifecycle.release();
|
|
210
|
-
yield*
|
|
240
|
+
yield* this.#withSessionCacheEstimate(
|
|
241
|
+
qoderBodyToCanonicalEvents(res.body as ReadableStream<Uint8Array>, lifecycle.signal, modelId),
|
|
242
|
+
request,
|
|
243
|
+
qoderSessionId(auth.userId, modelId),
|
|
244
|
+
);
|
|
211
245
|
return;
|
|
212
246
|
}
|
|
213
247
|
|
|
@@ -233,16 +267,39 @@ class QoderAdapter implements ProviderAdapter {
|
|
|
233
267
|
);
|
|
234
268
|
const grpcMetadata = qoderGrpcMetadata(auth);
|
|
235
269
|
lifecycle.release();
|
|
236
|
-
yield*
|
|
270
|
+
yield* this.#withSessionCacheEstimate(
|
|
271
|
+
qoderGrpcToCanonicalEvents(grpcRequest, grpcMetadata, this.realm, lifecycle.signal, context.provider_capture),
|
|
272
|
+
request,
|
|
273
|
+
qoderSessionId(auth.userId, modelId),
|
|
274
|
+
);
|
|
237
275
|
return;
|
|
238
276
|
}
|
|
239
277
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
278
|
+
// Legacy COSY hosts are tried in order (api1 → api2 → api3) with the
|
|
279
|
+
// endpoint path from discovery when present. A network-level failure on
|
|
280
|
+
// one host falls through to the next; an HTTP error response surfaces as-is.
|
|
281
|
+
const cosyCandidates = candidate.endpoint_path?.startsWith("http")
|
|
282
|
+
? [candidate.endpoint_path]
|
|
283
|
+
: qoderInferenceCandidates(this.realm, auth.securityOauthToken).map(
|
|
284
|
+
(host) => `${host}${QODER_CHAT_PATH}`,
|
|
285
|
+
);
|
|
286
|
+
let response: Response | undefined;
|
|
287
|
+
let lastError: unknown;
|
|
288
|
+
for (const cosyUrl of cosyCandidates) {
|
|
289
|
+
try {
|
|
290
|
+
const attempt = () =>
|
|
291
|
+
callQoder(cosyUrl, buildQoderRequest(modelId, request, modelConfig, auth, this.realm), modelId, auth, lifecycle.signal, fetcher, version);
|
|
292
|
+
response = await attempt();
|
|
293
|
+
break;
|
|
294
|
+
} catch (err: unknown) {
|
|
295
|
+
lastError = err;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (response === undefined) {
|
|
299
|
+
throw lastError instanceof Error
|
|
300
|
+
? lastError
|
|
301
|
+
: new GatewayError("transport_unavailable", 502, "Qoder legacy COSY hosts are unreachable (request {requestId}). The connection ghosted us — come back in a moment 👻", {}, "upstream");
|
|
302
|
+
}
|
|
246
303
|
if (!response.ok) throw await mapUpstreamHttpError(response, "qoder");
|
|
247
304
|
if (!response.body) throw new GatewayError("platform_unavailable", 502, "Qoder returned empty body (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
248
305
|
// Headers arrived: the pre-stream deadline has served its purpose.
|
|
@@ -250,7 +307,11 @@ class QoderAdapter implements ProviderAdapter {
|
|
|
250
307
|
// context.abort_signal) governs the body. Keeping this timer armed
|
|
251
308
|
// would kill healthy long streams at the stale pre-stream deadline.
|
|
252
309
|
lifecycle.release();
|
|
253
|
-
yield*
|
|
310
|
+
yield* this.#withSessionCacheEstimate(
|
|
311
|
+
qoderBodyToCanonicalEvents(response.body as ReadableStream<Uint8Array>, lifecycle.signal, request.model),
|
|
312
|
+
request,
|
|
313
|
+
qoderSessionId(auth.userId, modelId),
|
|
314
|
+
);
|
|
254
315
|
} catch (err: unknown) {
|
|
255
316
|
const abortError = abortGatewayError(lifecycle, err, context.abort_signal);
|
|
256
317
|
if (abortError) throw abortError;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Qoder session-cache estimation.
|
|
2
|
+
//
|
|
3
|
+
// The Qoder upstream never reports prompt-cache hits: neither the gRPC surface
|
|
4
|
+
// (used `prompt_tokens_details` absent on every chunk — verified live
|
|
5
|
+
// 2026-11-05, `scripts/provider-probing/qoder/direct/direct-cache-hit.ts`) nor
|
|
6
|
+
// the IDE HTTP surface (`usage: null` / `raw_usage` absent — `direct-cache-hit-ide.ts`)
|
|
7
|
+
// carries a cached-token counter. The underlying models do cache byte-identical
|
|
8
|
+
// prefixes, so for the same conversation we ESTIMATE the hit locally from the
|
|
9
|
+
// request history — exactly how the Trae and Kiro adapters bridge the same gap.
|
|
10
|
+
// This only fills the gap when upstream reports nothing (0 or unavailable); a
|
|
11
|
+
// genuine upstream report (>0) is trusted and left untouched.
|
|
12
|
+
//
|
|
13
|
+
// The state lives in a factory-created instance, so Qoder's sessions are fully
|
|
14
|
+
// isolated from Trae's/Kiro's (and any other consumer of the shared factory).
|
|
15
|
+
|
|
16
|
+
import type { CanonicalMessage, UsageRecord } from "../../../protocol/canonical-model";
|
|
17
|
+
import { createSessionCache, estimateSessionCache, resetSessionCacheForTesting } from "../shared/session-cache";
|
|
18
|
+
|
|
19
|
+
const cache = createSessionCache();
|
|
20
|
+
|
|
21
|
+
export function estimateQoderSessionCache(
|
|
22
|
+
record: UsageRecord,
|
|
23
|
+
sessionId: string | undefined,
|
|
24
|
+
messages: readonly CanonicalMessage[] | undefined,
|
|
25
|
+
): UsageRecord {
|
|
26
|
+
return estimateSessionCache(cache, record, sessionId, messages);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Drops all tracked sessions. Test-only. */
|
|
30
|
+
export function resetQoderSessionCacheForTesting(): void {
|
|
31
|
+
resetSessionCacheForTesting(cache);
|
|
32
|
+
}
|
|
@@ -12,7 +12,7 @@ import { GatewayError } from "../../../protocol/gateway-error";
|
|
|
12
12
|
import { defineModel } from "../../provider-model-definition";
|
|
13
13
|
import type { ModelDefinition } from "../../provider-model-definition";
|
|
14
14
|
import { QODER_CHAT_PATH, QODER_MODELS_PATH, qoderGatewayCandidates, type QoderRealm } from "./qoder-realm";
|
|
15
|
-
import { qoderVirtualIdFor, QODER_VIRTUAL_MODELS } from "./qoder-virtual";
|
|
15
|
+
import { qoderVirtualIdFor, QODER_PROVIDER_WARNINGS, QODER_VIRTUAL_MODELS } from "./qoder-virtual";
|
|
16
16
|
|
|
17
17
|
/** One upstream catalog row, as `/algo/api/v2/model/list` reports it. */
|
|
18
18
|
export interface QoderCatalogEntry {
|
|
@@ -199,6 +199,8 @@ function qoderModels(
|
|
|
199
199
|
// Only an explicit `true` marks the row free; an unstated flag is left
|
|
200
200
|
// unset so nothing claims a tier the roster did not state.
|
|
201
201
|
...(cfg.is_free === undefined ? {} : { free: cfg.is_free, freeTier: cfg.is_free }),
|
|
202
|
+
// Provider-level caveats, not per-model claims (D37).
|
|
203
|
+
warnings: QODER_PROVIDER_WARNINGS,
|
|
202
204
|
}),
|
|
203
205
|
);
|
|
204
206
|
}
|
|
@@ -255,6 +257,7 @@ export function qoderVirtualModels(
|
|
|
255
257
|
: target.freeTier === undefined
|
|
256
258
|
? {}
|
|
257
259
|
: { free: target.freeTier, freeTier: target.freeTier }),
|
|
260
|
+
warnings: QODER_PROVIDER_WARNINGS,
|
|
258
261
|
}),
|
|
259
262
|
);
|
|
260
263
|
}
|
|
@@ -349,6 +352,7 @@ export function qoderCatalogModel(
|
|
|
349
352
|
// The live row carries no output ceiling, so inherit the static roster's
|
|
350
353
|
// stated one when it exists (see `qoderRosterMaxOutputFor`).
|
|
351
354
|
...(rosterOutput === undefined ? {} : { out: rosterOutput }),
|
|
355
|
+
warnings: QODER_PROVIDER_WARNINGS,
|
|
352
356
|
});
|
|
353
357
|
}
|
|
354
358
|
|
|
@@ -542,6 +546,7 @@ export async function discoverQoderModels(input: {
|
|
|
542
546
|
: { out: rosterOutput }),
|
|
543
547
|
free: virtual.free ?? entry.isFree,
|
|
544
548
|
freeTier: virtual.free ?? entry.isFree,
|
|
549
|
+
warnings: QODER_PROVIDER_WARNINGS,
|
|
545
550
|
}),
|
|
546
551
|
);
|
|
547
552
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Qoder daily check-in (streak / 100 Credits per day), ported from the
|
|
3
|
-
* reference bridge (`qoder_accounts.py` in
|
|
3
|
+
* reference bridge (`qoder_accounts.py` in the donor hub).
|
|
4
4
|
*
|
|
5
5
|
* Two upstream surfaces, tried in order:
|
|
6
6
|
*
|
|
@@ -443,7 +443,7 @@ export async function fetchQoderDailyCheckin(args: {
|
|
|
443
443
|
}
|
|
444
444
|
}
|
|
445
445
|
if (!bound && store) {
|
|
446
|
-
const minted = await allocateQoderNativeIdentity(store, providerId, accountId);
|
|
446
|
+
const minted = await allocateQoderNativeIdentity(store, providerId, accountId, { realm });
|
|
447
447
|
if (minted) {
|
|
448
448
|
boundRecord = minted;
|
|
449
449
|
bound = {
|
|
@@ -20,7 +20,8 @@ import { join } from "node:path";
|
|
|
20
20
|
import * as grpc from "@grpc/grpc-js";
|
|
21
21
|
import * as protoLoader from "@grpc/proto-loader";
|
|
22
22
|
import { GatewayError } from "../../../protocol/gateway-error";
|
|
23
|
-
import type { CanonicalEvent, CanonicalRequest, CanonicalStopReason } from "../../../protocol/canonical-model";
|
|
23
|
+
import type { CanonicalEvent, CanonicalRequest, CanonicalStopReason, UsageRecord } from "../../../protocol/canonical-model";
|
|
24
|
+
import type { ProviderCaptureExchange } from "../../provider-contracts";
|
|
24
25
|
import { usageFromProvider } from "../../provider-usage";
|
|
25
26
|
import { getQoderVersion } from "../../operations/client-versions";
|
|
26
27
|
import { qoderMachineOs, qoderMachineHostname } from "../../operations/cli-platform";
|
|
@@ -440,6 +441,65 @@ export function qoderGrpcMetadata(auth: QoderAuth, modelId = ""): grpc.Metadata
|
|
|
440
441
|
return metadata;
|
|
441
442
|
}
|
|
442
443
|
|
|
444
|
+
/**
|
|
445
|
+
* gRPC metadata headers safe to persist in the provider-request panel.
|
|
446
|
+
*
|
|
447
|
+
* Mirrors the HTTP capture allowlist (`CAPTURED_REQUEST_HEADERS` in
|
|
448
|
+
* `transport/dispatch/attempt-finalize.ts`): session/identity framing is kept,
|
|
449
|
+
* but credentials are never stored. `authorization` (the Bearer job token) is
|
|
450
|
+
* deliberately excluded — the HTTP wrapper never captures it, and the gRPC
|
|
451
|
+
* capture must not either.
|
|
452
|
+
*/
|
|
453
|
+
const GRPC_CAPTURED_HEADERS = [
|
|
454
|
+
"x-request-id",
|
|
455
|
+
"x-session-id",
|
|
456
|
+
"cosy-version",
|
|
457
|
+
"cosy-clienttype",
|
|
458
|
+
"cosy-machineos",
|
|
459
|
+
"cosy-machinehostname",
|
|
460
|
+
"x-opencode-client",
|
|
461
|
+
"x-opencode-session",
|
|
462
|
+
"x-opencode-session-id",
|
|
463
|
+
"x-opencode-parent-session-id",
|
|
464
|
+
"x-opencode-request",
|
|
465
|
+
"x-opencode-project",
|
|
466
|
+
"x-conversation-id",
|
|
467
|
+
"x-conversation-request-id",
|
|
468
|
+
"x-conversation-message-id",
|
|
469
|
+
"x-root-request-id",
|
|
470
|
+
"x-trace-id",
|
|
471
|
+
"traceparent",
|
|
472
|
+
"b3",
|
|
473
|
+
] as const;
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* A gRPC exchange is not a `fetch`, so the HTTP capture wrapper never sees it.
|
|
477
|
+
* The adapter fills the sink directly with a faithful rendering of the proto
|
|
478
|
+
* call: the request line (grpc method, host, allowlisted metadata, and the
|
|
479
|
+
* translated body) and a deferred response that resolves to the collected
|
|
480
|
+
* stream summary.
|
|
481
|
+
*/
|
|
482
|
+
export function captureQoderGrpcRequest(
|
|
483
|
+
request: Record<string, unknown>,
|
|
484
|
+
metadata: grpc.Metadata,
|
|
485
|
+
realm: QoderRealm,
|
|
486
|
+
): Record<string, unknown> {
|
|
487
|
+
const host = qoderGrpcHost(realm);
|
|
488
|
+
const map = metadata.getMap() as Record<string, string | string[]>;
|
|
489
|
+
const headers: Record<string, string> = {};
|
|
490
|
+
for (const name of GRPC_CAPTURED_HEADERS) {
|
|
491
|
+
const value = map[name];
|
|
492
|
+
if (value === undefined) continue;
|
|
493
|
+
headers[name] = Array.isArray(value) ? value.join(", ") : String(value);
|
|
494
|
+
}
|
|
495
|
+
return {
|
|
496
|
+
method: "POST",
|
|
497
|
+
url: `grpc://${host}/model.chat.ChatService/ChatCompletionStream`,
|
|
498
|
+
headers,
|
|
499
|
+
body: request,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
443
503
|
/** Maps a proto `finish_reason` to the canonical stop reason. */
|
|
444
504
|
function mapFinishReason(raw: string | undefined): CanonicalStopReason | undefined {
|
|
445
505
|
switch (raw) {
|
|
@@ -450,6 +510,10 @@ function mapFinishReason(raw: string | undefined): CanonicalStopReason | undefin
|
|
|
450
510
|
return "tool_use";
|
|
451
511
|
case "length":
|
|
452
512
|
return "length";
|
|
513
|
+
case "error":
|
|
514
|
+
return "error";
|
|
515
|
+
case "refusal":
|
|
516
|
+
return "refusal";
|
|
453
517
|
default:
|
|
454
518
|
return undefined;
|
|
455
519
|
}
|
|
@@ -466,6 +530,7 @@ export async function* qoderGrpcToCanonicalEvents(
|
|
|
466
530
|
metadata: grpc.Metadata,
|
|
467
531
|
realm: QoderRealm,
|
|
468
532
|
signal: AbortSignal,
|
|
533
|
+
capture?: ProviderCaptureExchange,
|
|
469
534
|
): AsyncGenerator<CanonicalEvent> {
|
|
470
535
|
const ChatService = qoderGrpcClient();
|
|
471
536
|
const host = qoderGrpcHost(realm);
|
|
@@ -483,6 +548,122 @@ export async function* qoderGrpcToCanonicalEvents(
|
|
|
483
548
|
// clients reject content after the finish reason. A second terminal is
|
|
484
549
|
// skipped too (the upstream can re-emit the finish frame).
|
|
485
550
|
let finished = false;
|
|
551
|
+
// Last usage chunk seen; attached to the terminal event (the upstream
|
|
552
|
+
// streams usage after the finish-reason chunk, and a standalone `usage`
|
|
553
|
+
// event after terminal breaks strict Messages encoders).
|
|
554
|
+
let pendingUsage: UsageRecord | undefined;
|
|
555
|
+
/** Whether the terminal already carried the pending usage. */
|
|
556
|
+
let terminalEmitted = false;
|
|
557
|
+
// Capture sinks fed from the same `for await` loop that drives production
|
|
558
|
+
// events (see `feedCapture`). Declared here so the loop below can call
|
|
559
|
+
// them; they are assigned when capture is enabled.
|
|
560
|
+
let feedCapture: ((chunk: unknown) => void) | undefined;
|
|
561
|
+
let flushToolCalls: (() => void) | undefined;
|
|
562
|
+
if (capture !== undefined) {
|
|
563
|
+
capture.request = captureQoderGrpcRequest(request, metadata, realm);
|
|
564
|
+
capture.response = new Promise((resolve) => {
|
|
565
|
+
const summary: Record<string, unknown> = {
|
|
566
|
+
transport: "grpc",
|
|
567
|
+
chunks: 0,
|
|
568
|
+
answer_chars: 0,
|
|
569
|
+
reasoning_chars: 0,
|
|
570
|
+
tool_calls: 0,
|
|
571
|
+
models: [] as string[],
|
|
572
|
+
usage: undefined,
|
|
573
|
+
stop_reason: undefined,
|
|
574
|
+
status_code: 0,
|
|
575
|
+
// Materialized response content (bounded): the text the model
|
|
576
|
+
// emitted, the reasoning trace, and the tool calls that were
|
|
577
|
+
// announced. This is what Request Detail shows instead of a bare
|
|
578
|
+
// statistics block.
|
|
579
|
+
text: "",
|
|
580
|
+
reasoning: "",
|
|
581
|
+
toolCalls: [] as Array<{ index: number; id?: string; name?: string; arguments?: string }>,
|
|
582
|
+
};
|
|
583
|
+
// Per-index tool-call fragments (the upstream streams one call at a
|
|
584
|
+
// time: id + empty args, then argument fragments, index always 0).
|
|
585
|
+
const toolCallsByIndex = new Map<number, { id?: string; name?: string; arguments?: string }>();
|
|
586
|
+
const MAX_TEXT_CHARS = 16_000;
|
|
587
|
+
const MAX_TOOL_CALLS = 50;
|
|
588
|
+
const MAX_TOOL_ARGS_CHARS = 2_000;
|
|
589
|
+
// The summary is fed from the SAME `for await` loop that drives the
|
|
590
|
+
// production event stream (see `feedCapture` below) — a separate
|
|
591
|
+
// `data` listener raced the iterator and saw nothing (or a different
|
|
592
|
+
// decode), so tool calls and text never made it into the capture.
|
|
593
|
+
feedCapture = (chunk: unknown) => {
|
|
594
|
+
const rec = chunk as Record<string, unknown>;
|
|
595
|
+
summary["chunks"] = (summary["chunks"] as number) + 1;
|
|
596
|
+
const model = rec["model"];
|
|
597
|
+
if (typeof model === "string" && model.length > 0) {
|
|
598
|
+
const models = summary["models"] as string[];
|
|
599
|
+
if (!models.includes(model)) models.push(model);
|
|
600
|
+
}
|
|
601
|
+
const usage = rec["usage"];
|
|
602
|
+
if (usage !== undefined && typeof usage === "object") summary["usage"] = usage;
|
|
603
|
+
const choices = Array.isArray(rec["choices"]) ? (rec["choices"] as Array<Record<string, unknown>>) : [];
|
|
604
|
+
for (const choice of choices) {
|
|
605
|
+
const delta = choice["delta"] as Record<string, unknown> | undefined;
|
|
606
|
+
const content = delta?.["content"];
|
|
607
|
+
if (typeof content === "string" && content.length > 0) {
|
|
608
|
+
summary["answer_chars"] = (summary["answer_chars"] as number) + content.length;
|
|
609
|
+
const text = summary["text"] as string;
|
|
610
|
+
if (text.length < MAX_TEXT_CHARS) {
|
|
611
|
+
summary["text"] = (text + content).slice(0, MAX_TEXT_CHARS);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
const reasoning = delta?.["reasoning_content"];
|
|
615
|
+
if (typeof reasoning === "string" && reasoning.length > 0) {
|
|
616
|
+
summary["reasoning_chars"] = (summary["reasoning_chars"] as number) + reasoning.length;
|
|
617
|
+
const reasoningText = summary["reasoning"] as string;
|
|
618
|
+
if (reasoningText.length < MAX_TEXT_CHARS) {
|
|
619
|
+
summary["reasoning"] = (reasoningText + reasoning).slice(0, MAX_TEXT_CHARS);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
const calls = delta?.["tool_calls"];
|
|
623
|
+
if (Array.isArray(calls) && calls.length > 0) {
|
|
624
|
+
summary["tool_calls"] = (summary["tool_calls"] as number) + calls.length;
|
|
625
|
+
for (const rawTc of calls) {
|
|
626
|
+
const tc = fromProtoStruct(rawTc);
|
|
627
|
+
const index = typeof tc["index"] === "number" ? (tc["index"] as number) : 0;
|
|
628
|
+
const entry = toolCallsByIndex.get(index) ?? {};
|
|
629
|
+
const declaredId = tc["id"];
|
|
630
|
+
if (typeof declaredId === "string" && declaredId.trim().length > 0) entry.id = declaredId.trim();
|
|
631
|
+
const fn = (tc["function"] ?? {}) as Record<string, unknown>;
|
|
632
|
+
const name = fn["name"];
|
|
633
|
+
if (typeof name === "string" && name.trim().length > 0) entry.name = name.trim();
|
|
634
|
+
const args = fn["arguments"];
|
|
635
|
+
if (typeof args === "string" && args.length > 0) {
|
|
636
|
+
entry.arguments = ((entry.arguments ?? "") + args).slice(0, MAX_TOOL_ARGS_CHARS);
|
|
637
|
+
}
|
|
638
|
+
toolCallsByIndex.set(index, entry);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
const finish = choice["finish_reason"];
|
|
642
|
+
if (typeof finish === "string" && finish.length > 0) summary["stop_reason"] = finish;
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
flushToolCalls = () => {
|
|
646
|
+
const entries = [...toolCallsByIndex.entries()]
|
|
647
|
+
.sort((a, b) => a[0] - b[0])
|
|
648
|
+
.slice(0, MAX_TOOL_CALLS)
|
|
649
|
+
.map(([index, entry]) => ({ index, ...entry }));
|
|
650
|
+
if (entries.length > 0) summary["toolCalls"] = entries;
|
|
651
|
+
};
|
|
652
|
+
stream.on("status", (s: { code?: number; details?: string }) => {
|
|
653
|
+
summary["status_code"] = s.code ?? 0;
|
|
654
|
+
summary["grpc_message"] = s.details ?? "";
|
|
655
|
+
});
|
|
656
|
+
stream.on("error", (err: Error) => {
|
|
657
|
+
summary["error"] = err.message;
|
|
658
|
+
flushToolCalls?.();
|
|
659
|
+
resolve(summary);
|
|
660
|
+
});
|
|
661
|
+
stream.on("end", () => {
|
|
662
|
+
flushToolCalls?.();
|
|
663
|
+
resolve(summary);
|
|
664
|
+
});
|
|
665
|
+
});
|
|
666
|
+
}
|
|
486
667
|
|
|
487
668
|
const abortHandler = () => {
|
|
488
669
|
aborted = true;
|
|
@@ -502,6 +683,8 @@ export async function* qoderGrpcToCanonicalEvents(
|
|
|
502
683
|
const fragmentState: QoderToolFragmentState = {};
|
|
503
684
|
for await (const chunk of stream) {
|
|
504
685
|
if (aborted || signal.aborted) break;
|
|
686
|
+
// Feed the capture summary from this same chunk (single consumer).
|
|
687
|
+
feedCapture?.(chunk);
|
|
505
688
|
// The upstream names the model it actually ran in the chunk's `model`
|
|
506
689
|
// field. Surface it once as `response_start` so probes and telemetry
|
|
507
690
|
// can report the real echoed model (the gRPC surface has no HTTP body
|
|
@@ -599,24 +782,45 @@ export async function* qoderGrpcToCanonicalEvents(
|
|
|
599
782
|
yield {
|
|
600
783
|
type: "terminal",
|
|
601
784
|
sequence_number: nextSeq(),
|
|
602
|
-
|
|
785
|
+
// `tool_use` and `refusal` are *successful* terminal verdicts:
|
|
786
|
+
// the model decided to call a tool / declined to answer, but the
|
|
787
|
+
// response itself is valid — `complete`, never `failed`. A
|
|
788
|
+
// `failed` state here made attempt-finalize treat a healthy tool
|
|
789
|
+
// turn as an upstream failure ("empty-handed") and replay it.
|
|
790
|
+
// A key `error` (or `length`) is the failed case.
|
|
791
|
+
state: stop === "stop" || stop === "tool_use" || stop === "refusal" ? "complete" : "failed",
|
|
603
792
|
stop_reason: stop,
|
|
604
793
|
provider_stop_reason: choice.finish_reason,
|
|
794
|
+
// The upstream streams `usage` in a trailing chunk *after* the
|
|
795
|
+
// finish-reason chunk. A separate `usage` event after the
|
|
796
|
+
// terminal is rejected by strict Anthropic Messages encoders
|
|
797
|
+
// (the 2.7.8 regression), so the usage is carried here on the
|
|
798
|
+
// terminal instead — exactly like the IDE surface does.
|
|
799
|
+
...(pendingUsage === undefined ? {} : { usage: pendingUsage }),
|
|
605
800
|
};
|
|
801
|
+
if (pendingUsage !== undefined) terminalEmitted = true;
|
|
606
802
|
}
|
|
607
803
|
}
|
|
608
804
|
}
|
|
609
805
|
if (chunk.usage) {
|
|
610
806
|
const usage = usageFromProvider(chunk.usage);
|
|
611
807
|
if (usage !== undefined) {
|
|
612
|
-
|
|
808
|
+
// Keep the last usage chunk; it is attached to the terminal above
|
|
809
|
+
// (and only if no terminal ever arrives, emitted as a standalone
|
|
810
|
+
// event at stream end).
|
|
811
|
+
pendingUsage = usage;
|
|
613
812
|
}
|
|
614
813
|
}
|
|
615
814
|
}
|
|
616
815
|
// Tool-call deltas are emitted as they arrive — there is no end-of-stream
|
|
617
816
|
// buffer to flush; the terminal event is yielded by the finish-reason
|
|
618
817
|
// branch above (or synthesized by the caller if the stream ends without
|
|
619
|
-
// one).
|
|
818
|
+
// one). If no terminal carried the pending usage (the stream ended
|
|
819
|
+
// without a finish reason), emit it as a standalone event so telemetry
|
|
820
|
+
// still sees the upstream's numbers.
|
|
821
|
+
if (!terminalEmitted && pendingUsage !== undefined) {
|
|
822
|
+
yield { type: "usage", sequence_number: nextSeq(), usage: pendingUsage };
|
|
823
|
+
}
|
|
620
824
|
} catch (err: any) {
|
|
621
825
|
const status = err?.code;
|
|
622
826
|
const details = err?.details ?? err?.message ?? "";
|
|
@@ -644,7 +848,7 @@ export async function* qoderGrpcToCanonicalEvents(
|
|
|
644
848
|
"quota_exceeded",
|
|
645
849
|
429,
|
|
646
850
|
"Qoder quota exhausted on the gRPC surface (request {requestId})",
|
|
647
|
-
{},
|
|
851
|
+
{ providerCode: "quota_exceeded" },
|
|
648
852
|
"upstream",
|
|
649
853
|
);
|
|
650
854
|
}
|
|
@@ -163,6 +163,8 @@ function mapQoderFinishReason(raw: unknown): CanonicalStopReason | undefined {
|
|
|
163
163
|
if (raw === "length") return "length";
|
|
164
164
|
if (raw === "tool_calls") return "tool_use";
|
|
165
165
|
if (raw === "content_filter") return "content_filter";
|
|
166
|
+
if (raw === "error") return "error";
|
|
167
|
+
if (raw === "refusal") return "refusal";
|
|
166
168
|
return undefined;
|
|
167
169
|
}
|
|
168
170
|
|
|
@@ -402,7 +404,9 @@ export async function* qoderBodyToCanonicalEvents(
|
|
|
402
404
|
yield {
|
|
403
405
|
type: "terminal",
|
|
404
406
|
sequence_number: seq++,
|
|
405
|
-
|
|
407
|
+
// `error` is a failed terminal; every other stop (stop, tool_use,
|
|
408
|
+
// refusal, content_filter, length) is a complete turn.
|
|
409
|
+
state: stopReason === "error" ? "failed" : "complete",
|
|
406
410
|
...(stopReason === undefined ? {} : { stop_reason: stopReason }),
|
|
407
411
|
...(_providerStop === undefined ? {} : { provider_stop_reason: _providerStop }),
|
|
408
412
|
usage: usageFromProvider(rawUsage),
|