cartethyia 2.7.9 → 2.8.0
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 +3 -2
- package/dist/dashboard/assets/{BannedPage-BxoG1MrL.js → BannedPage-Cc0vKpMG.js} +1 -1
- package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-ixwgKk53.js → CliToolDetailPage-Bqj2aa1T.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BmttNV3U.js → CliToolsPage-CXsaOAO2.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BE3qwwfC.js → CombosPage-BeLpJ4ma.js} +1 -1
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-CRZdmMIX.js → ConsoleLogPage-DnV0uZ3m.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-Dv_FJ6Er.js → CustomizationPage-CBxAjo63.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-BOoO8rpz.js → InjectSystemPromptPanel-BWFLECmz.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-ChJuNNVN.js → LoginPage-CgoPc5B9.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BZAJtPg1.js → ModelPicker-D9bFQern.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
- package/dist/dashboard/assets/{OverviewPage-BE40s3u5.js → OverviewPage-B_p4tL8k.js} +1 -1
- package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js +8 -0
- package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js.gz +0 -0
- package/dist/dashboard/assets/{ProviderIcon-CQ6bPUf_.js → ProviderIcon-D_0PiZtg.js} +1 -1
- package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-BJ87Pp0U.js → ProxyPage-BwNC_-Cx.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js +1 -0
- package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-C5HPWqEv.js → SettingsPage-CctTga2C.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-CFNcATJJ.js → SetupPage-febLU4r1.js} +1 -1
- package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-YoD4tY5l.js → StudioPage-M6fvR5Wz.js} +1 -1
- package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-DxjaAhb_.js → UsagePage-nyeuRqz9.js} +1 -1
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-BTlkq_Uk.js → api-keys-BFOlvrJC.js} +1 -1
- package/dist/dashboard/assets/api-keys-BFOlvrJC.js.gz +0 -0
- package/dist/dashboard/assets/app-Dx2QgsNj.css +2 -0
- package/dist/dashboard/assets/app-Dx2QgsNj.css.gz +0 -0
- package/dist/dashboard/assets/assert-CqTLw5e7.js +1 -0
- package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DdbT_7Am.js → badge-C83EqLB0.js} +1 -1
- package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
- package/dist/dashboard/assets/{banner-CsGN-nbm.js → banner-D1xn7pIe.js} +1 -1
- package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
- package/dist/dashboard/assets/{card-mgZ_TLLZ.js → card-DU3Lq5Lm.js} +1 -1
- package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-A1-v8-03.js → cli-tools-Dw1U_drK.js} +1 -1
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-CJ4Zty89.js → clipboard-button-CEL5Sg_J.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-CEL5Sg_J.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-B5L2jPQ_.js → dialog-BjFR5UHy.js} +1 -1
- package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-DkjUouAj.js → drawer-HvBW0AjI.js} +1 -1
- package/dist/dashboard/assets/drawer-HvBW0AjI.js.gz +0 -0
- package/dist/dashboard/assets/entry-BSu3gFlt.js +4 -0
- package/dist/dashboard/assets/entry-BSu3gFlt.js.gz +0 -0
- package/dist/dashboard/assets/{entry-B-LVLV6o.js → entry-CECVELyy.js} +1 -1
- package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CAhdYl3B.js → entry-DYYofQ7W.js} +1 -1
- package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-CrsdLVsn.js → formatters-DLW4i8zK.js} +1 -1
- package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
- package/dist/dashboard/assets/{index-BU64Lqvp.js → index-1-fCqe64.js} +2 -2
- package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
- package/dist/dashboard/assets/{inline-B2aDLyyg.js → inline-DGvU2oLK.js} +1 -1
- package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
- package/dist/dashboard/assets/{input-BCVkdR9d.js → input-iNnO1X2K.js} +1 -1
- package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
- package/dist/dashboard/assets/{layout-DNcAdMK-.js → layout-0q8UGypb.js} +1 -1
- package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-DgrIH0hw.js → list-row-C0w2bQjJ.js} +1 -1
- package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
- package/dist/dashboard/assets/{live-Cae01iu8.js → live-M5aJ_xCa.js} +1 -1
- package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
- package/dist/dashboard/assets/{network-CRib7H_2.js → network-BKeQGT3K.js} +1 -1
- package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BKQE8Xpz.js → numbers-CivAk3da.js} +1 -1
- package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-ecPvG_8g.js → pagination-CpzvI5ep.js} +1 -1
- package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +0 -0
- package/dist/dashboard/assets/providers/zro.svg +3 -0
- package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
- package/dist/dashboard/assets/{routing-Ckm1UXuC.js → routing-BFQX2VBc.js} +1 -1
- package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
- package/dist/dashboard/assets/{select-ChoFWyKz.js → select-CXbck0jG.js} +2 -2
- package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
- package/dist/dashboard/assets/{settings-CKjFPlKW.js → settings-C_XvMHh9.js} +1 -1
- package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DWCwU3fh.js → stack-DggRiCxE.js} +1 -1
- package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
- package/dist/dashboard/assets/{state-Cg3VL5J_.js → state-ClFzfbYp.js} +1 -1
- package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
- package/dist/dashboard/assets/{switch-Db70BMix.js → switch-CTIFxKqS.js} +1 -1
- package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BqdE0LuM.js → useMutation-BClG3yWj.js} +1 -1
- package/dist/dashboard/assets/useMutation-BClG3yWj.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 +54 -53
- package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
- package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
- package/package.json +1 -1
- package/src/boot/app.ts +10 -13
- package/src/boot/dependencies.ts +1 -1
- package/src/boot/flush.ts +20 -0
- package/src/boot/lifecycle.ts +7 -5
- package/src/config/routing.ts +26 -0
- package/src/config/spec-metadata.ts +72 -0
- package/src/config/spec.ts +20 -0
- package/src/console/api-keys/contracts.ts +12 -1
- package/src/console/api-keys/routes.ts +40 -28
- package/src/console/auth/account-reset.ts +7 -9
- package/src/console/backup/contracts.ts +4 -0
- package/src/console/backup/export-endpoint.ts +7 -5
- package/src/console/cli-tools/service.ts +6 -12
- package/src/console/console-router-guards.ts +23 -16
- package/src/console/domain-registration.ts +0 -2
- package/src/console/inferhub/normalize.ts +24 -28
- package/src/console/inferhub/types.ts +3 -0
- package/src/console/ip-bans/routes.ts +7 -13
- package/src/console/observability/store.ts +0 -53
- package/src/console/providers/catalog/contracts.ts +2 -2
- package/src/console/providers/catalog/discover-constants.ts +6 -0
- package/src/console/providers/catalog/routes.ts +1 -0
- package/src/console/providers/catalog/store.ts +21 -16
- package/src/console/quota/account-quota-global.ts +14 -24
- package/src/console/quota/account-quota-shared.ts +49 -0
- package/src/console/quota/account-quota-tenant.ts +6 -18
- package/src/console/quota/quota-view.ts +19 -3
- package/src/console/routing/pools/operations.ts +31 -46
- package/src/console/shared/errors.ts +33 -6
- package/src/main.ts +2 -3
- package/src/network/pool/byte-accounting.ts +3 -2
- package/src/network/pool/selector.ts +182 -39
- package/src/network/pool-cooldown-store.ts +89 -14
- package/src/network/pool-health.ts +49 -61
- package/src/network/rate-limit-verdict.ts +1 -1
- package/src/network/ssrf.ts +6 -6
- package/src/observability/event-log.ts +7 -361
- package/src/observability/performance-metrics.ts +9 -0
- package/src/observability/runtime-metrics.ts +14 -9
- package/src/observability/telemetry-buffer.ts +15 -25
- package/src/observability/telemetry-reconciliation.ts +9 -37
- package/src/observability/telemetry-row.ts +125 -0
- package/src/persistence/db-mode.ts +4 -1
- package/src/persistence/dialect.ts +18 -0
- package/src/persistence/migrate.ts +6 -3
- package/src/persistence/schema.ts +8 -0
- package/src/persistence/ttl-cache.ts +20 -2
- package/src/protocol/codecs.ts +1 -1
- package/src/protocol/dsml.ts +34 -86
- package/src/protocol/markup-stream-parser.ts +195 -0
- package/src/protocol/reasoning-text.ts +19 -0
- package/src/protocol/request/chat.ts +10 -10
- package/src/protocol/request/gemini.ts +8 -2
- package/src/protocol/request/messages.ts +3 -4
- package/src/protocol/response/chat.ts +12 -20
- package/src/protocol/response/omp-tool-call-markup.ts +43 -86
- package/src/protocol/response/responses.ts +18 -2
- package/src/protocol/stop-reason.ts +23 -1
- package/src/protocol/think-split.ts +112 -0
- package/src/protocol/translation/dialect/qwen.ts +26 -58
- package/src/providers/adapters/compatible-adapter.ts +71 -12
- package/src/providers/authentication/oauth-client.ts +1 -1
- package/src/providers/authentication/oauth-device-flow.ts +98 -8
- package/src/providers/authentication/oauth-refresh-service.ts +30 -49
- package/src/providers/configured-provider.ts +148 -0
- package/src/providers/default-registry.ts +61 -36
- package/src/providers/discovery/probe-phases.ts +14 -4
- package/src/providers/discovery/probing-service.ts +15 -2
- package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
- package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +5 -1
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +173 -17
- package/src/providers/integrations/buddy/buddy-identity.ts +19 -6
- package/src/providers/integrations/buddy/capabilities.ts +2 -2
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +3 -0
- package/src/providers/integrations/buddy/codebuddy/provider.ts +4 -0
- package/src/providers/integrations/buddy/codebuddy/shared.ts +37 -22
- package/src/providers/integrations/claude/claude-quota.ts +14 -17
- package/src/providers/integrations/cline/cline.ts +2 -2
- package/src/providers/integrations/commandcode/quota.ts +22 -28
- package/src/providers/integrations/cursor/cursor-auth.ts +0 -8
- package/src/providers/integrations/cursor/cursor-exec.ts +9 -2
- package/src/providers/integrations/cursor/cursor-request.ts +26 -12
- package/src/providers/integrations/cursor/cursor-shared.ts +7 -4
- package/src/providers/integrations/cursor/cursor.ts +89 -40
- package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
- package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
- package/src/providers/integrations/devin/protocol/devin-proto.ts +1 -27
- package/src/providers/integrations/factory/factory-oauth.ts +17 -44
- package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
- package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
- package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
- package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
- package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
- package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
- package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
- package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
- package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
- package/src/providers/integrations/freebuff/freebuff.ts +682 -0
- package/src/providers/integrations/grok/grok-oauth.ts +48 -45
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
- package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
- package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +9 -9
- package/src/providers/integrations/hermes/index.ts +2 -0
- package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
- package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
- package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
- package/src/providers/integrations/kiro/kiro.ts +15 -19
- package/src/providers/integrations/ollamacloud/index.ts +2 -0
- package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
- package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
- package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
- package/src/providers/integrations/perch/perch-credential.ts +25 -0
- package/src/providers/integrations/perch/perch-protocol.ts +0 -3
- package/src/providers/integrations/perch/perch-quota.ts +2 -16
- package/src/providers/integrations/perch/perch.ts +3 -13
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +18 -59
- package/src/providers/integrations/qoder/qoder-adapter.ts +1 -12
- package/src/providers/integrations/qoder/qoder-auth.ts +101 -29
- package/src/providers/integrations/qoder/qoder-catalog.ts +20 -5
- package/src/providers/integrations/qoder/qoder-chat.ts +7 -21
- package/src/providers/integrations/qoder/qoder-checkin.ts +4 -4
- package/src/providers/integrations/qoder/qoder-grpc.ts +0 -1
- package/src/providers/integrations/qoder/qoder-ide.ts +0 -107
- package/src/providers/integrations/qoder/qoder.ts +0 -1
- package/src/providers/integrations/trae/trae-protocol.ts +9 -5
- package/src/providers/integrations/trae/trae-quota.ts +6 -28
- package/src/providers/integrations/trae/trae-realm.ts +38 -0
- package/src/providers/integrations/trae/trae.ts +7 -24
- package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
- package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
- package/src/providers/integrations/zro/zro.ts +204 -0
- package/src/providers/operations/account-error-classification.ts +199 -307
- package/src/providers/operations/account-health-service.ts +82 -1
- package/src/providers/operations/account-refresh-status.ts +22 -37
- package/src/providers/operations/account-tier.ts +15 -16
- package/src/providers/operations/client-version-resolver.ts +1 -1
- package/src/providers/operations/client-versions.ts +5 -51
- package/src/providers/operations/cooldown-rules.ts +11 -16
- package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
- package/src/providers/operations/provider-catalog-service.ts +1 -1
- package/src/providers/operations/provider-credential-service.ts +102 -32
- package/src/providers/operations/provider-rules/github.ts +48 -0
- package/src/providers/operations/provider-rules/grok.ts +33 -0
- package/src/providers/operations/provider-rules/inception.ts +49 -0
- package/src/providers/operations/provider-rules/index.ts +32 -0
- package/src/providers/operations/provider-rules/perch.ts +37 -0
- package/src/providers/operations/provider-rules/qoder.ts +70 -0
- package/src/providers/operations/provider-rules/trae.ts +81 -0
- package/src/providers/provider-contracts.ts +4 -2
- package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
- package/src/providers/provider-metadata.ts +40 -10
- package/src/providers/provider-probe.ts +15 -1
- package/src/providers/provider-registry.ts +42 -33
- package/src/providers/quota/quota-limit-windows.ts +16 -5
- package/src/providers/quota/quota-support.ts +1 -1
- package/src/providers/quota/quota-window-parser.ts +90 -18
- package/src/providers/quota/refresh.ts +75 -18
- package/src/security/api-key-auth-cache.ts +1 -1
- package/src/security/api-key-auth.ts +6 -2
- package/src/transport/dispatch/attempt-finalize.ts +14 -13
- package/src/transport/dispatch/attempt-loop.ts +71 -17
- package/src/transport/dispatch/generation.ts +2 -8
- package/src/transport/dispatch/native-shared.ts +25 -0
- package/src/transport/dispatch/proxy-request.ts +8 -46
- package/src/transport/dispatch/responses-compact.ts +2 -8
- package/src/transport/dispatch/streaming/finalize.ts +9 -6
- package/src/transport/dispatch/streaming/stream.ts +8 -36
- package/src/transport/dispatch/systemone.ts +2 -8
- package/src/transport/dispatch/token-count.ts +2 -6
- package/src/transport/dispatch/upstream.ts +63 -0
- package/src/transport/drain-endpoint.ts +31 -4
- package/src/transport/failure-policy.ts +16 -30
- package/src/transport/guards/body-policy.ts +7 -1
- package/src/transport/guards/error-lifecycle.ts +13 -1
- package/src/transport/guards/request-context.ts +22 -14
- package/src/transport/request/preparer.ts +81 -71
- package/src/transport/routing/bounded-lru.ts +14 -0
- package/src/transport/routing/drain-reserve.ts +129 -0
- package/src/transport/routing/plan-gate.ts +7 -1
- package/src/transport/routing/round-robin.ts +60 -0
- package/src/transport/routing/route-catalog.ts +123 -111
- package/src/transport/routing/route-model.ts +51 -9
- package/src/transport/routing/router.ts +393 -32
- package/src/transport/surface/adapters.ts +11 -2
- package/src/transport/surface/chat/adapter.ts +2 -2
- package/src/transport/surface/chat/encode.ts +47 -4
- package/src/transport/surface/chat/parse.ts +2 -12
- package/src/transport/surface/completion.ts +44 -33
- package/src/transport/surface/content-parts.ts +21 -0
- package/src/transport/surface/messages/adapter.ts +7 -1
- package/src/transport/surface/messages/encode.ts +11 -3
- package/src/transport/surface/messages/parse.ts +2 -1
- package/src/transport/surface/messages/stream.ts +33 -17
- package/src/transport/surface/responses/encode.ts +22 -5
- package/src/transport/surface/responses/parse.ts +3 -11
- package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +0 -8
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js +0 -1
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
- package/dist/dashboard/assets/app-BX2nnKqs.css +0 -2
- package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CndthXFQ.js +0 -1
- package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
- package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
- package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
- package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
- package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
- package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
- package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
- package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
- package/dist/dashboard/assets/entry-D2xD0Tmd.js +0 -4
- package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
- package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
- package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
- package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
- package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
- package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
- package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
- package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
- package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
- package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
- package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
- package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
- package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
- package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
- package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
- package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
- package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
- package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
- package/src/providers/bundled-discovery-providers.ts +0 -98
- package/src/providers/configured-openai-providers.ts +0 -120
- package/src/providers/operations/model-discovery-cache.ts +0 -34
- package/src/providers/operations/probe-fetch.ts +0 -15
- package/src/providers/operations/provider-version-cache.ts +0 -30
- package/src/providers/provider-capabilities.ts +0 -33
- package/src/providers/provider-credential-envelope.ts +0 -43
- /package/dist/dashboard/assets/{app-B7kIWFm5.js → app-DIXLjojg.js} +0 -0
- /package/dist/dashboard/assets/{app-B7kIWFm5.js.gz → app-DIXLjojg.js.gz} +0 -0
|
@@ -16,10 +16,14 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { CartethyiaDatabase } from "../../../persistence/db-handle";
|
|
18
18
|
import type { ValidatedFetch } from "../../../network/outbound-fetch";
|
|
19
|
-
import { getQoderVersion } from "../../operations/client-versions";
|
|
20
19
|
import { qoderRealmForProvider, QODER_REALMS } from "./qoder-realm";
|
|
20
|
+
import { exchangeQoderPatForHost } from "./qoder-auth";
|
|
21
21
|
import {
|
|
22
|
+
asNumber,
|
|
23
|
+
asRecord,
|
|
22
24
|
buildCampaignHeaders,
|
|
25
|
+
normalizeCampaign,
|
|
26
|
+
safeParse,
|
|
23
27
|
} from "./qoder-checkin";
|
|
24
28
|
import { qoderMachineHeaders } from "./qoder-umid";
|
|
25
29
|
import {
|
|
@@ -38,31 +42,6 @@ export const QODER_CREDIT_PROVIDER_IDS = ["qoder", "qodercn"] as const;
|
|
|
38
42
|
|
|
39
43
|
const PAT_PREFIX = "pt-";
|
|
40
44
|
|
|
41
|
-
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
42
|
-
if (typeof value !== "object" || value === null) return null;
|
|
43
|
-
return value as Record<string, unknown>;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function asNumber(value: unknown): number | null {
|
|
47
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
48
|
-
if (typeof value === "string" && value.trim() !== "") {
|
|
49
|
-
const parsed = Number(value);
|
|
50
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
51
|
-
}
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function normalizeCampaign(raw: Record<string, unknown>): Record<string, unknown> {
|
|
56
|
-
const benefit = asRecord(raw["benefit"]);
|
|
57
|
-
return {
|
|
58
|
-
campaign_id: String(raw["campaignId"] ?? raw["campaign_id"] ?? ""),
|
|
59
|
-
campaign_key: String(raw["campaignKey"] ?? raw["campaign_key"] ?? ""),
|
|
60
|
-
action_type: String(raw["actionType"] ?? raw["action_type"] ?? ""),
|
|
61
|
-
claim_status: String(raw["claimStatus"] ?? raw["claim_status"] ?? ""),
|
|
62
|
-
...(benefit !== null ? { benefit } : {}),
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
45
|
/**
|
|
67
46
|
* Exchanges a PAT (`pt-…`) for a job token (`jt-…`) on the realm's openapi
|
|
68
47
|
* host. Returns the job token, or `null` when the exchange fails.
|
|
@@ -70,7 +49,8 @@ function normalizeCampaign(raw: Record<string, unknown>): Record<string, unknown
|
|
|
70
49
|
* The real client binds the machine identity at exchange time
|
|
71
50
|
* (`getMachineIdentityRequestFields`), so `machine_id` + `machine_token` are
|
|
72
51
|
* sent when available — the job token is then bound to that machine. The
|
|
73
|
-
*
|
|
52
|
+
* request is the shared `exchangeQoderPatForHost` (same v1 body and client
|
|
53
|
+
* identity headers as the auth path), parameterized by this site's own host.
|
|
74
54
|
*/
|
|
75
55
|
async function exchangePatToJobToken(
|
|
76
56
|
base: string,
|
|
@@ -78,30 +58,17 @@ async function exchangePatToJobToken(
|
|
|
78
58
|
fetcher: ValidatedFetch,
|
|
79
59
|
machine?: { readonly id?: string; readonly token?: string },
|
|
80
60
|
): Promise<string | null> {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
"Cosy-Version": version,
|
|
93
|
-
},
|
|
94
|
-
body: JSON.stringify(body),
|
|
95
|
-
signal: AbortSignal.timeout(25_000),
|
|
96
|
-
});
|
|
97
|
-
if (!res.ok) return null;
|
|
98
|
-
const payload: unknown = await res.json().catch(() => null);
|
|
99
|
-
const rec = asRecord(payload);
|
|
100
|
-
const token = String(rec?.["token"] ?? rec?.["device_token"] ?? rec?.["access_token"] ?? "");
|
|
101
|
-
return token.length > 0 ? token : null;
|
|
102
|
-
} catch {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
61
|
+
const exchanged = await exchangeQoderPatForHost({
|
|
62
|
+
pat,
|
|
63
|
+
url: `${base}/api/v1/jobToken/exchange`,
|
|
64
|
+
...(machine === undefined ? {} : { machine }),
|
|
65
|
+
fetcher,
|
|
66
|
+
signal: AbortSignal.timeout(25_000),
|
|
67
|
+
tokenKeys: ["token", "device_token", "access_token"],
|
|
68
|
+
});
|
|
69
|
+
if (exchanged === null) return null;
|
|
70
|
+
if (!exchanged.response.ok) return null;
|
|
71
|
+
return exchanged.result?.token ?? null;
|
|
105
72
|
}
|
|
106
73
|
|
|
107
74
|
/**
|
|
@@ -248,11 +215,3 @@ export async function claimQoderCredits(args: {
|
|
|
248
215
|
return { state: "error", amount: 0, detail: "Claim request failed." };
|
|
249
216
|
}
|
|
250
217
|
}
|
|
251
|
-
|
|
252
|
-
function safeParse(raw: string): unknown {
|
|
253
|
-
try {
|
|
254
|
-
return JSON.parse(raw);
|
|
255
|
-
} catch {
|
|
256
|
-
return null;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
@@ -30,7 +30,7 @@ import { abortGatewayError, createUpstreamDeadlineLifecycle } from "../../operat
|
|
|
30
30
|
import { getQoderVersion } from "../../operations/client-versions";
|
|
31
31
|
import { qoderMachineHostname, qoderMachineOs } from "../../operations/cli-platform";
|
|
32
32
|
import { mapUpstreamHttpError } from "../../../transport/failure-policy";
|
|
33
|
-
import { QODER_CN_MODEL_CONFIGS, QODER_INTL_MODEL_CONFIGS
|
|
33
|
+
import { QODER_CN_MODEL_CONFIGS, QODER_INTL_MODEL_CONFIGS } from "./qoder-catalog";
|
|
34
34
|
import { isQoderVirtualModel, qoderDispatchModelId } from "./qoder-virtual";
|
|
35
35
|
import { isQoderPat, qoderOAuthAuth, qoderSessionId, resolveQoderAuth } from "./qoder-auth";
|
|
36
36
|
import { buildQoderRequest, callQoder } from "./qoder-chat";
|
|
@@ -84,16 +84,6 @@ export function resolveQoderTransport(
|
|
|
84
84
|
return chosen;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
/**
|
|
88
|
-
* The widest selectable context window for one model key (from the live list's
|
|
89
|
-
* `context_config`; falls back to the static roster's stated window).
|
|
90
|
-
*/
|
|
91
|
-
function qoderMaxContextFor(modelId: string, modelConfig: QoderModelConfig, realm: QoderRealm): number {
|
|
92
|
-
const discovered = qoderDiscoveredMaxContexts(realm).get(modelId);
|
|
93
|
-
if (discovered !== undefined) return discovered;
|
|
94
|
-
return modelConfig.max_input_tokens;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
87
|
class QoderAdapter implements ProviderAdapter {
|
|
98
88
|
readonly provider_id: ProviderId;
|
|
99
89
|
private readonly realm: QoderRealm;
|
|
@@ -256,7 +246,6 @@ class QoderAdapter implements ProviderAdapter {
|
|
|
256
246
|
modelId,
|
|
257
247
|
request,
|
|
258
248
|
auth.userId,
|
|
259
|
-
qoderMaxContextFor(modelId, modelConfig, this.realm),
|
|
260
249
|
{
|
|
261
250
|
messages: legacy.messages as Array<Record<string, unknown>>,
|
|
262
251
|
tools: legacy.tools as Array<Record<string, unknown>>,
|
|
@@ -114,6 +114,91 @@ export function qoderSessionId(userId: string, modelId: string): string {
|
|
|
114
114
|
|
|
115
115
|
type QoderFetcher = (url: string, init: RequestInit) => Promise<Response>;
|
|
116
116
|
|
|
117
|
+
/** The fields a PAT→job-token exchange response carries. */
|
|
118
|
+
export interface ExchangeQoderPatResult {
|
|
119
|
+
readonly token: string;
|
|
120
|
+
readonly name?: string;
|
|
121
|
+
readonly userType?: string;
|
|
122
|
+
readonly refreshToken?: string;
|
|
123
|
+
readonly expiresAt?: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* One PAT→job-token exchange against an explicit endpoint URL. Shared by
|
|
128
|
+
* `exchangeQoderPat` (auth) and the claim flow: both POST the same v1 body
|
|
129
|
+
* (`{personal_token, machine_id?, machine_token?}`) with the client identity
|
|
130
|
+
* headers and parse the same response token. Parameterized by URL so each call
|
|
131
|
+
* site keeps its own verified host (`center.qoder.sh` vs the openapi host);
|
|
132
|
+
* the header set is the one verified live against both (2026-11-05/05).
|
|
133
|
+
* Returns the raw `Response` plus the parsed result (or `null` when the body
|
|
134
|
+
* carried no usable token); `null` itself means the request failed to reach
|
|
135
|
+
* upstream. Callers decide how to surface each case (throw vs null).
|
|
136
|
+
*/
|
|
137
|
+
export async function exchangeQoderPatForHost(args: {
|
|
138
|
+
readonly pat: string;
|
|
139
|
+
readonly url: string;
|
|
140
|
+
readonly machine?: { readonly id?: string; readonly token?: string };
|
|
141
|
+
readonly fetcher: QoderFetcher;
|
|
142
|
+
readonly signal: AbortSignal;
|
|
143
|
+
readonly version?: string;
|
|
144
|
+
/** Response keys to read the job token from, tried in order. Defaults to `["token"]`. */
|
|
145
|
+
readonly tokenKeys?: readonly string[];
|
|
146
|
+
}): Promise<{ readonly response: Response; readonly result: ExchangeQoderPatResult | null } | null> {
|
|
147
|
+
const body: Record<string, unknown> = { personal_token: args.pat };
|
|
148
|
+
if (args.machine?.id) body.machine_id = args.machine.id;
|
|
149
|
+
if (args.machine?.token) body.machine_token = args.machine.token;
|
|
150
|
+
const version = args.version ?? getQoderVersion();
|
|
151
|
+
const tokenKeys = args.tokenKeys ?? ["token"];
|
|
152
|
+
let response: Response;
|
|
153
|
+
try {
|
|
154
|
+
response = await args.fetcher(args.url, {
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers: {
|
|
157
|
+
"Content-Type": "application/json",
|
|
158
|
+
Accept: "application/json",
|
|
159
|
+
"User-Agent": `qoder/${version}`,
|
|
160
|
+
"X-Request-ID": randomUUID().toUpperCase(),
|
|
161
|
+
"Cosy-ClientType": "5",
|
|
162
|
+
"Cosy-Version": version,
|
|
163
|
+
"Cosy-MachineOS": qoderMachineOs(),
|
|
164
|
+
},
|
|
165
|
+
body: JSON.stringify(body),
|
|
166
|
+
signal: args.signal,
|
|
167
|
+
});
|
|
168
|
+
} catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
if (!response.ok) return { response, result: null };
|
|
172
|
+
let responseBody: unknown;
|
|
173
|
+
try {
|
|
174
|
+
responseBody = await response.json();
|
|
175
|
+
} catch {
|
|
176
|
+
return { response, result: null };
|
|
177
|
+
}
|
|
178
|
+
if (typeof responseBody !== "object" || responseBody === null || Array.isArray(responseBody)) {
|
|
179
|
+
return { response, result: null };
|
|
180
|
+
}
|
|
181
|
+
const rec = responseBody as Record<string, unknown>;
|
|
182
|
+
let token = "";
|
|
183
|
+
for (const key of tokenKeys) {
|
|
184
|
+
if (typeof rec[key] === "string" && (rec[key] as string).length > 0) {
|
|
185
|
+
token = rec[key] as string;
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (token.length === 0) return { response, result: null };
|
|
190
|
+
return {
|
|
191
|
+
response,
|
|
192
|
+
result: {
|
|
193
|
+
token,
|
|
194
|
+
...(typeof rec["name"] === "string" ? { name: rec["name"] as string } : {}),
|
|
195
|
+
...(typeof rec["userType"] === "string" ? { userType: rec["userType"] as string } : {}),
|
|
196
|
+
...(typeof rec["refresh_token"] === "string" ? { refreshToken: rec["refresh_token"] as string } : {}),
|
|
197
|
+
...(typeof rec["expires_at"] === "string" ? { expiresAt: rec["expires_at"] as string } : {}),
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
117
202
|
/**
|
|
118
203
|
* Exchanges a PAT (`pt-…`) for a short-lived job token (`jt-…`) using the
|
|
119
204
|
* modern v1 endpoint. Verified live (2026-11-05): `POST
|
|
@@ -135,41 +220,28 @@ async function exchangeQoderPat(
|
|
|
135
220
|
realm: QoderRealm = "intl",
|
|
136
221
|
): Promise<QoderAuth> {
|
|
137
222
|
const machineId = qoderMachineIdFromPat(pat);
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
"User-Agent": `qoder/${version}`,
|
|
144
|
-
"X-Request-ID": randomUUID().toUpperCase(),
|
|
145
|
-
"Cosy-ClientType": "5",
|
|
146
|
-
"Cosy-Version": version,
|
|
147
|
-
"Cosy-MachineOS": qoderMachineOs(),
|
|
148
|
-
},
|
|
149
|
-
body: JSON.stringify({ personal_token: pat, machine_id: machineId }),
|
|
223
|
+
const exchanged = await exchangeQoderPatForHost({
|
|
224
|
+
pat,
|
|
225
|
+
url: QODER_REALMS[realm].jobTokenUrl,
|
|
226
|
+
machine: { id: machineId },
|
|
227
|
+
fetcher,
|
|
150
228
|
signal,
|
|
229
|
+
version,
|
|
151
230
|
});
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
try {
|
|
155
|
-
responseBody = await response.json();
|
|
156
|
-
} catch {
|
|
157
|
-
throw new GatewayError("platform_unavailable", 502, "Qoder PAT exchange returned invalid JSON (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
231
|
+
if (exchanged === null) {
|
|
232
|
+
throw new GatewayError("platform_unavailable", 502, "Qoder PAT exchange failed to reach upstream (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
158
233
|
}
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const token = typeof rec["token"] === "string" ? (rec["token"] as string) : "";
|
|
164
|
-
if (!token) {
|
|
165
|
-
throw new GatewayError("platform_unavailable", 502, "Qoder PAT exchange returned incomplete credentials (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
234
|
+
if (!exchanged.response.ok) throw await mapUpstreamHttpError(exchanged.response, "qoder");
|
|
235
|
+
const result = exchanged.result;
|
|
236
|
+
if (result === null) {
|
|
237
|
+
throw new GatewayError("platform_unavailable", 502, "Qoder PAT exchange returned no usable token (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
166
238
|
}
|
|
167
239
|
return {
|
|
168
240
|
userId: machineId,
|
|
169
|
-
userName:
|
|
170
|
-
userType:
|
|
171
|
-
securityOauthToken: token,
|
|
172
|
-
refreshToken:
|
|
241
|
+
userName: result.name ?? "",
|
|
242
|
+
userType: result.userType ?? "personal_standard",
|
|
243
|
+
securityOauthToken: result.token,
|
|
244
|
+
refreshToken: result.refreshToken ?? "",
|
|
173
245
|
machineId,
|
|
174
246
|
};
|
|
175
247
|
}
|
|
@@ -322,7 +322,7 @@ export function mapQoderCatalogResponse(raw: unknown): QoderCatalogEntry[] {
|
|
|
322
322
|
* to a base-catalog/majority vote that can sit below the roster's ceiling
|
|
323
323
|
* (e.g. `qwen3.7-plus` votes 64k while the roster says 131k).
|
|
324
324
|
*/
|
|
325
|
-
function
|
|
325
|
+
export function qoderRosterMaxOutput(providerId: string, key: string): number | undefined {
|
|
326
326
|
const configs = providerId === "qodercn" ? QODER_CN_MODEL_CONFIGS : QODER_INTL_MODEL_CONFIGS;
|
|
327
327
|
return configs[key]?.max_output_tokens;
|
|
328
328
|
}
|
|
@@ -332,7 +332,7 @@ export function qoderCatalogModel(
|
|
|
332
332
|
entry: QoderCatalogEntry,
|
|
333
333
|
providerId: string,
|
|
334
334
|
): ModelDefinition {
|
|
335
|
-
const rosterOutput =
|
|
335
|
+
const rosterOutput = qoderRosterMaxOutput(providerId, entry.key);
|
|
336
336
|
return defineModel({
|
|
337
337
|
id: entry.key,
|
|
338
338
|
providerId,
|
|
@@ -350,7 +350,7 @@ export function qoderCatalogModel(
|
|
|
350
350
|
free: entry.isFree,
|
|
351
351
|
freeTier: entry.isFree,
|
|
352
352
|
// The live row carries no output ceiling, so inherit the static roster's
|
|
353
|
-
// stated one when it exists (see `
|
|
353
|
+
// stated one when it exists (see `qoderRosterMaxOutput`).
|
|
354
354
|
...(rosterOutput === undefined ? {} : { out: rosterOutput }),
|
|
355
355
|
warnings: QODER_PROVIDER_WARNINGS,
|
|
356
356
|
});
|
|
@@ -446,6 +446,21 @@ export function qoderDiscoveredMaxContexts(realm: QoderRealm): ReadonlyMap<strin
|
|
|
446
446
|
return discoveryMaxContextByKey.get(realm) ?? new Map();
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
+
/**
|
|
450
|
+
* The widest selectable context window for one model key.
|
|
451
|
+
*
|
|
452
|
+
* The live list's `context_config` is the authority when discovery has run:
|
|
453
|
+
* it names every window the account can switch to, and the widest one is what
|
|
454
|
+
* a dispatch should send (`model_config.max_input_tokens`) so a conversation is
|
|
455
|
+
* never capped at the *currently active* window (often 180k) when the account
|
|
456
|
+
* can use 1M. Falls back to the static roster's stated window otherwise.
|
|
457
|
+
*/
|
|
458
|
+
export function qoderMaxContextFor(modelId: string, modelConfig: QoderModelConfig, realm: QoderRealm): number {
|
|
459
|
+
const discovered = qoderDiscoveredMaxContexts(realm).get(modelId);
|
|
460
|
+
if (discovered !== undefined) return discovered;
|
|
461
|
+
return modelConfig.max_input_tokens;
|
|
462
|
+
}
|
|
463
|
+
|
|
449
464
|
export async function discoverQoderModels(input: {
|
|
450
465
|
readonly realm: QoderRealm;
|
|
451
466
|
/** Account secret the injected signer signs with; blank fails fast. */
|
|
@@ -526,7 +541,7 @@ export async function discoverQoderModels(input: {
|
|
|
526
541
|
// is published.
|
|
527
542
|
const virtuals = QODER_VIRTUAL_MODELS.filter((virtual) => virtual.target === entry.key);
|
|
528
543
|
if (virtuals.length === 0) return [qoderCatalogModel(entry, providerId)];
|
|
529
|
-
const rosterOutput =
|
|
544
|
+
const rosterOutput = qoderRosterMaxOutput(providerId, entry.key);
|
|
530
545
|
return virtuals.map((virtual) =>
|
|
531
546
|
defineModel({
|
|
532
547
|
id: virtual.id,
|
|
@@ -538,7 +553,7 @@ export async function discoverQoderModels(input: {
|
|
|
538
553
|
reasoning: entry.isReasoning,
|
|
539
554
|
// A virtual override wins; otherwise inherit the static roster's
|
|
540
555
|
// ceiling for the slot instead of a base-catalog/majority estimate
|
|
541
|
-
// (see `
|
|
556
|
+
// (see `qoderRosterMaxOutput`).
|
|
542
557
|
...(virtual.outputLimit !== undefined
|
|
543
558
|
? { out: virtual.outputLimit }
|
|
544
559
|
: rosterOutput === undefined
|
|
@@ -14,9 +14,10 @@ import type {
|
|
|
14
14
|
} from "../../../protocol/canonical-model";
|
|
15
15
|
import { toolResultParts, toolCallParts } from "../../../protocol/canonical-model";
|
|
16
16
|
import { reasoningEffortFromIntent } from "../../../protocol/reasoning";
|
|
17
|
+
import { reasoningTextForReplay } from "../../../protocol/reasoning-text";
|
|
17
18
|
import { resolveImageSource } from "../../../protocol/codecs";
|
|
18
19
|
import { getQoderVersion } from "../../operations/client-versions";
|
|
19
|
-
import {
|
|
20
|
+
import { qoderMaxContextFor, qoderRosterMaxOutput } from "./qoder-catalog";
|
|
20
21
|
import { transformQoderBody, qoderBuildCosyHeaders } from "./qoder-cosy";
|
|
21
22
|
import { qoderSessionId } from "./qoder-auth";
|
|
22
23
|
import { flattenCanonicalContent, qoderToolChoice, qoderTools } from "./qoder-body";
|
|
@@ -69,12 +70,6 @@ function qoderStableRecordId(modelId: string, messages: CanonicalRequest["messag
|
|
|
69
70
|
* never capped at the *currently active* window (often 180k) when the account
|
|
70
71
|
* can use 1M. Falls back to the static roster's stated window otherwise.
|
|
71
72
|
*/
|
|
72
|
-
function qoderMaxContextFor(modelId: string, modelConfig: QoderModelConfig, realm: QoderRealm): number {
|
|
73
|
-
const discovered = qoderDiscoveredMaxContexts(realm).get(modelId);
|
|
74
|
-
if (discovered !== undefined) return discovered;
|
|
75
|
-
return modelConfig.max_input_tokens;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
73
|
function qoderRequestedMaxTokens(
|
|
79
74
|
request: CanonicalRequest,
|
|
80
75
|
modelConfig: QoderModelConfig,
|
|
@@ -91,7 +86,7 @@ function qoderRequestedMaxTokens(
|
|
|
91
86
|
const configured =
|
|
92
87
|
typeof modelConfig.max_output_tokens === "number"
|
|
93
88
|
? modelConfig.max_output_tokens
|
|
94
|
-
: qoderRosterMaxOutput(modelConfig.id
|
|
89
|
+
: qoderRosterMaxOutput(realm === "cn" ? "qodercn" : "qoder", modelConfig.id) ?? 65_536;
|
|
95
90
|
const controls = request.generation_controls as Record<string, unknown>;
|
|
96
91
|
const requested =
|
|
97
92
|
typeof controls["max_tokens"] === "number"
|
|
@@ -102,17 +97,6 @@ function qoderRequestedMaxTokens(
|
|
|
102
97
|
return typeof requested === "number" && requested > 0 ? Math.min(requested, configured) : configured;
|
|
103
98
|
}
|
|
104
99
|
|
|
105
|
-
/**
|
|
106
|
-
* The static roster's stated output ceiling for one key, when the roster states
|
|
107
|
-
* one. Dispatch builds a live-discovered model's config without an output limit
|
|
108
|
-
* (upstream's model list reports none); this lookup restores the roster's
|
|
109
|
-
* ceiling so such a model is not capped at the 65_536 last resort.
|
|
110
|
-
*/
|
|
111
|
-
function qoderRosterMaxOutput(modelId: string, realm: QoderRealm): number | undefined {
|
|
112
|
-
const configs = realm === "cn" ? QODER_CN_MODEL_CONFIGS : QODER_INTL_MODEL_CONFIGS;
|
|
113
|
-
return configs[modelId]?.max_output_tokens;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
100
|
/**
|
|
117
101
|
* Whether this model needs the prior turn's reasoning echoed back.
|
|
118
102
|
*
|
|
@@ -159,7 +143,9 @@ function qoderReasoningEcho(
|
|
|
159
143
|
return hasReasoningTrace ? { reasoning_content: "" } : {};
|
|
160
144
|
}
|
|
161
145
|
const text = reasoning
|
|
162
|
-
.map((part) =>
|
|
146
|
+
.map((part) =>
|
|
147
|
+
part.kind === "reasoning" ? (reasoningTextForReplay(part) ?? part.payload ?? "") : "",
|
|
148
|
+
)
|
|
163
149
|
.join("");
|
|
164
150
|
return { reasoning_content: text };
|
|
165
151
|
}
|
|
@@ -448,7 +434,7 @@ export function buildQoderRequest(
|
|
|
448
434
|
// for every model alike, so the request would declare a cap the catalog
|
|
449
435
|
// does not publish. A discovered model's config carries no output limit,
|
|
450
436
|
// so consult the roster for the key before the 65_536 last resort.
|
|
451
|
-
max_output_tokens: modelConfig.max_output_tokens ?? qoderRosterMaxOutput(
|
|
437
|
+
max_output_tokens: modelConfig.max_output_tokens ?? qoderRosterMaxOutput(realm === "cn" ? "qodercn" : "qoder", modelId) ?? 65_536,
|
|
452
438
|
source: modelConfig.source ?? "system",
|
|
453
439
|
},
|
|
454
440
|
messages: qoderMessages,
|
|
@@ -79,12 +79,12 @@ function campaignHeaders(credential: string): Record<string, string> {
|
|
|
79
79
|
return buildCampaignHeaders(credential);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
82
|
+
export function asRecord(value: unknown): Record<string, unknown> | null {
|
|
83
83
|
if (typeof value !== "object" || value === null) return null;
|
|
84
84
|
return value as Record<string, unknown>;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
function asNumber(value: unknown): number | null {
|
|
87
|
+
export function asNumber(value: unknown): number | null {
|
|
88
88
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
89
89
|
if (typeof value === "string" && value.trim() !== "") {
|
|
90
90
|
const parsed = Number(value);
|
|
@@ -93,7 +93,7 @@ function asNumber(value: unknown): number | null {
|
|
|
93
93
|
return null;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
function safeParse(raw: string): unknown {
|
|
96
|
+
export function safeParse(raw: string): unknown {
|
|
97
97
|
try {
|
|
98
98
|
return JSON.parse(raw);
|
|
99
99
|
} catch {
|
|
@@ -127,7 +127,7 @@ function asState(state: string, today: string, lastClaimedAt: unknown): { active
|
|
|
127
127
|
* consumes. The API returns camelCase keys (`campaignId`, `actionType`,
|
|
128
128
|
* `claimStatus`); the fixtures and the donor hub use snake_case — accept both.
|
|
129
129
|
*/
|
|
130
|
-
function normalizeCampaign(raw: Record<string, unknown>): Record<string, unknown> {
|
|
130
|
+
export function normalizeCampaign(raw: Record<string, unknown>): Record<string, unknown> {
|
|
131
131
|
const benefit = asRecord(raw["benefit"]);
|
|
132
132
|
return {
|
|
133
133
|
campaign_id: String(raw["campaignId"] ?? raw["campaign_id"] ?? ""),
|
|
@@ -362,7 +362,6 @@ export function buildQoderGrpcRequest(
|
|
|
362
362
|
modelId: string,
|
|
363
363
|
request: CanonicalRequest,
|
|
364
364
|
userId: string,
|
|
365
|
-
_contextWindow = 1_000_000,
|
|
366
365
|
legacy?: {
|
|
367
366
|
readonly messages: Array<Record<string, unknown>>;
|
|
368
367
|
readonly tools?: Array<Record<string, unknown>>;
|
|
@@ -479,110 +479,3 @@ export function buildQoderIdeRequest(
|
|
|
479
479
|
};
|
|
480
480
|
}
|
|
481
481
|
|
|
482
|
-
/**
|
|
483
|
-
* Qoder upstream streams are flaky: the IDE surface sometimes answers a full
|
|
484
|
-
* normal stop with reasoning only and no output text/tool calls (observed ~1
|
|
485
|
-
* in 4 runs with Sonus). That is not a valid completion — the lab would show a
|
|
486
|
-
* thought with "No text came back". Replay such a stream before committing to
|
|
487
|
-
* it. `sniffQoderStream` below inspects just enough of the body to make that
|
|
488
|
-
* call without buffering the whole (possibly long) stream.
|
|
489
|
-
*/
|
|
490
|
-
|
|
491
|
-
/**
|
|
492
|
-
* Reads the upstream body just far enough to decide whether it carries real
|
|
493
|
-
* output, then returns a replay stream that re-emits the already-read bytes
|
|
494
|
-
* followed by the rest of the original body. If the body ends before any
|
|
495
|
-
* meaningful output is seen, `meaningful` is false and the caller replays the
|
|
496
|
-
* whole request (empty / thought-only recovery) without ever buffering a large
|
|
497
|
-
* response: the original `drainQoderResponse` read the entire stream before
|
|
498
|
-
* forwarding, which made every stream appear to complete in one burst at the
|
|
499
|
-
* end.
|
|
500
|
-
*/
|
|
501
|
-
async function sniffQoderStream(
|
|
502
|
-
response: Response,
|
|
503
|
-
signal: AbortSignal,
|
|
504
|
-
): Promise<{ meaningful: boolean; response: Response }> {
|
|
505
|
-
const body = response.body;
|
|
506
|
-
if (body === null) return { meaningful: true, response };
|
|
507
|
-
const reader = body.getReader();
|
|
508
|
-
const decoder = new TextDecoder();
|
|
509
|
-
const pending: Uint8Array[] = [];
|
|
510
|
-
let inspected = "";
|
|
511
|
-
let meaningful = false;
|
|
512
|
-
for (let i = 0; i < 32 && !meaningful; i += 1) {
|
|
513
|
-
const { done, value } = await reader.read();
|
|
514
|
-
if (signal.aborted) break;
|
|
515
|
-
if (done) break;
|
|
516
|
-
pending.push(value);
|
|
517
|
-
inspected += decoder.decode(value, { stream: true });
|
|
518
|
-
// Peek at completed SSE frames only: a content delta or a tool-call
|
|
519
|
-
// fragment means real output is coming. Reasoning-only streams are NOT
|
|
520
|
-
// meaningful (they are exactly what empty-stream recovery replays), and a
|
|
521
|
-
// finish reason alone is not output either.
|
|
522
|
-
if (/data:/.test(inspected)) {
|
|
523
|
-
for (const frame of inspected.split(/\r?\n\r?\n/)) {
|
|
524
|
-
if (!frame.includes("data:")) continue;
|
|
525
|
-
if (frame.includes('"content":"') || frame.includes('"tool_calls"')) {
|
|
526
|
-
meaningful = true;
|
|
527
|
-
break;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
if (inspected.length > 65536) {
|
|
532
|
-
meaningful = true;
|
|
533
|
-
break;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
// Replay: previously-read bytes first, then continue the original body.
|
|
537
|
-
let offset = 0;
|
|
538
|
-
const replay = new ReadableStream<Uint8Array>({
|
|
539
|
-
start(controller) {
|
|
540
|
-
for (; offset < pending.length; offset += 1) controller.enqueue(pending[offset]);
|
|
541
|
-
},
|
|
542
|
-
async pull(controller) {
|
|
543
|
-
if (offset < pending.length) {
|
|
544
|
-
controller.enqueue(pending[offset]);
|
|
545
|
-
offset += 1;
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
const { done, value } = await reader.read();
|
|
549
|
-
if (signal.aborted) {
|
|
550
|
-
controller.close();
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
if (done) {
|
|
554
|
-
controller.close();
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
controller.enqueue(value);
|
|
558
|
-
},
|
|
559
|
-
cancel(reason) {
|
|
560
|
-
return reader.cancel(reason);
|
|
561
|
-
},
|
|
562
|
-
});
|
|
563
|
-
return { meaningful, response: new Response(replay, response) };
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
/**
|
|
567
|
-
* Runs the IDE request up to `1 + maxReplays` times, returning the first
|
|
568
|
-
* response whose stream carried real output (text or tool calls). A 200 with
|
|
569
|
-
* no output (empty or thought-only) is replayed rather than surfaced. The
|
|
570
|
-
* stream is forwarded progressively (not buffered to completion).
|
|
571
|
-
*/
|
|
572
|
-
async function fetchQoderIdeWithEmptyRecovery(
|
|
573
|
-
requestOnce: () => Promise<Response>,
|
|
574
|
-
signal: AbortSignal,
|
|
575
|
-
maxReplays = 2,
|
|
576
|
-
): Promise<Response> {
|
|
577
|
-
let last: Response | undefined;
|
|
578
|
-
for (let attempt = 0; attempt <= maxReplays; attempt += 1) {
|
|
579
|
-
const res = await requestOnce();
|
|
580
|
-
if (!res.ok || !res.body) return res;
|
|
581
|
-
const sniff = await sniffQoderStream(res, signal);
|
|
582
|
-
last = sniff.response;
|
|
583
|
-
if (sniff.meaningful) return last;
|
|
584
|
-
}
|
|
585
|
-
return last!;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
export { fetchQoderIdeWithEmptyRecovery };
|
|
@@ -187,7 +187,7 @@ export function buildTraeChatBody(
|
|
|
187
187
|
}
|
|
188
188
|
const parts: TraeContentPart[] = [];
|
|
189
189
|
const toolCalls: TraeToolCall[] = [];
|
|
190
|
-
|
|
190
|
+
const toolResults: Array<{ callId: string; text: string }> = [];
|
|
191
191
|
for (const part of message.content) {
|
|
192
192
|
if (part.kind === "toolCall") {
|
|
193
193
|
toolCalls.push({
|
|
@@ -198,7 +198,7 @@ export function buildTraeChatBody(
|
|
|
198
198
|
continue;
|
|
199
199
|
}
|
|
200
200
|
if (part.kind === "toolResult") {
|
|
201
|
-
|
|
201
|
+
toolResults.push({ callId: part.call_id, text: toolResultText(part.content) });
|
|
202
202
|
continue;
|
|
203
203
|
}
|
|
204
204
|
const mapped = toTraePart(part);
|
|
@@ -207,15 +207,19 @@ export function buildTraeChatBody(
|
|
|
207
207
|
|
|
208
208
|
// A tool answer becomes its own `tool` turn, which is the only shape
|
|
209
209
|
// upstream accepts for it — leaving it inside the assistant turn is
|
|
210
|
-
// rejected as a broken tool sequence.
|
|
211
|
-
|
|
210
|
+
// rejected as a broken tool sequence. One turn per result: a message
|
|
211
|
+
// whose content carries multiple `toolResult` parts (parallel tool calls)
|
|
212
|
+
// must emit one `tool` turn per call id, or upstream rejects the missing
|
|
213
|
+
// ones with `tool call failed: … No tool output found for function call
|
|
214
|
+
// <id>`.
|
|
215
|
+
for (const toolResult of toolResults) {
|
|
212
216
|
messages.push({
|
|
213
217
|
role: "tool",
|
|
214
218
|
content: [{ type: "text", text: toolResult.text }],
|
|
215
219
|
tool_call_id: toolResult.callId,
|
|
216
220
|
});
|
|
217
|
-
continue;
|
|
218
221
|
}
|
|
222
|
+
if (toolResults.length > 0) continue;
|
|
219
223
|
// Empty turns are skipped: upstream rejects them, and emitting
|
|
220
224
|
// `content: ""` turns leaves a ghost turn in the conversation.
|
|
221
225
|
if (parts.length === 0 && toolCalls.length === 0) continue;
|