cartethyia 2.7.8 → 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 -3
- package/README.md +29 -59
- package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js +1 -0
- package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-BRWKPQPf.js → CliToolDetailPage-Bqj2aa1T.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BP-Y5iHN.js → CliToolsPage-CXsaOAO2.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js +1 -0
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js +1 -0
- 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-Cv-2Ol_K.js → InjectSystemPromptPanel-BWFLECmz.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-bKfz_b1K.js → LoginPage-CgoPc5B9.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-B-WQxx_H.js → ModelPicker-D9bFQern.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js +1 -0
- 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-D_0PiZtg.js +1 -0
- 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-emlTuxz6.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-CctTga2C.js +1 -0
- package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-CUZrKs9r.js → SetupPage-febLU4r1.js} +1 -1
- package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-jWmUdDpy.js → StudioPage-M6fvR5Wz.js} +1 -1
- package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js +56 -0
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BFOlvrJC.js +1 -0
- 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-CidQVoNo.js → assert-CqTLw5e7.js} +1 -1
- package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DyI_mJfi.js → badge-C83EqLB0.js} +1 -1
- package/dist/dashboard/assets/badge-C83EqLB0.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-B-yXtCqb.js → banner-D1xn7pIe.js} +1 -1
- package/dist/dashboard/assets/banner-D1xn7pIe.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-_HwAEqFF.js → card-DU3Lq5Lm.js} +1 -1
- package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js +1 -0
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-ByjCk0o9.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-BZhOlfz-.js → dialog-BjFR5UHy.js} +1 -1
- package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-BADqIRYP.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-COaC-irN.js → entry-CECVELyy.js} +1 -1
- package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
- package/dist/dashboard/assets/entry-DYYofQ7W.js +1 -0
- package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-DcHJyRiu.js → formatters-DLW4i8zK.js} +1 -1
- package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
- package/dist/dashboard/assets/{index-CgKmFngv.js → index-1-fCqe64.js} +2 -2
- package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
- package/dist/dashboard/assets/{inline-B2BirKry.js → inline-DGvU2oLK.js} +1 -1
- package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
- package/dist/dashboard/assets/{input-D-qV9dKY.js → input-iNnO1X2K.js} +1 -1
- package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
- package/dist/dashboard/assets/{layout-B7tVWhtV.js → layout-0q8UGypb.js} +1 -1
- package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-CtkGE3bF.js → list-row-C0w2bQjJ.js} +1 -1
- package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
- package/dist/dashboard/assets/{live-DGizF06-.js → live-M5aJ_xCa.js} +1 -1
- package/dist/dashboard/assets/live-M5aJ_xCa.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-C9UE-bOr.js → network-BKeQGT3K.js} +1 -1
- package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BnAd22a_.js → numbers-CivAk3da.js} +1 -1
- package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-BAywyMzS.js → pagination-CpzvI5ep.js} +1 -1
- package/dist/dashboard/assets/pagination-CpzvI5ep.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/providers/zro.svg +3 -0
- package/dist/dashboard/assets/providers/zro.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/quota-DiPD6UKH.js +1 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
- package/dist/dashboard/assets/routing-BFQX2VBc.js +1 -0
- package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
- package/dist/dashboard/assets/{select-BCOcv8WK.js → select-CXbck0jG.js} +2 -2
- package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
- package/dist/dashboard/assets/settings-C_XvMHh9.js +1 -0
- package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
- package/dist/dashboard/assets/{stack-ByKt3Q1g.js → stack-DggRiCxE.js} +1 -1
- package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
- package/dist/dashboard/assets/{state-DXbGTx7N.js → state-ClFzfbYp.js} +1 -1
- package/dist/dashboard/assets/state-ClFzfbYp.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-ZeTsW623.js → switch-CTIFxKqS.js} +1 -1
- package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-CbCP4w1o.js → useMutation-BClG3yWj.js} +1 -1
- package/dist/dashboard/assets/useMutation-BClG3yWj.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 +59 -56
- 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/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -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/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 +12 -4
- package/src/boot/flush.ts +20 -0
- package/src/boot/lifecycle.ts +7 -5
- package/src/config/cooldowns.ts +1 -1
- package/src/config/routing.ts +26 -0
- package/src/config/spec-metadata.ts +72 -0
- package/src/config/spec.ts +20 -6
- package/src/console/api-keys/contracts.ts +12 -1
- package/src/console/api-keys/routes.ts +50 -31
- 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/console-router.ts +7 -0
- package/src/console/dashboard-assets.ts +5 -5
- package/src/console/domain-registration.ts +27 -2
- package/src/console/inferhub/normalize.ts +24 -28
- package/src/console/inferhub/types.ts +3 -0
- package/src/console/ip-bans/routes.ts +93 -0
- package/src/console/observability/store.ts +0 -53
- package/src/console/providers/catalog/catalog-projections.ts +5 -2
- package/src/console/providers/catalog/contracts.ts +123 -0
- package/src/console/providers/catalog/discover-constants.ts +6 -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 +65 -4
- package/src/console/providers/catalog/store-helpers.ts +30 -1
- package/src/console/providers/catalog/store.ts +249 -4
- package/src/console/quota/account-quota-global.ts +97 -19
- package/src/console/quota/account-quota-shared.ts +49 -0
- package/src/console/quota/account-quota-tenant.ts +6 -18
- package/src/console/quota/native-identity.ts +2 -0
- package/src/console/quota/quota-view.ts +19 -3
- package/src/console/routing/pools/operations.ts +31 -46
- package/src/console/settings/contracts.ts +18 -0
- package/src/console/settings/store.ts +32 -0
- package/src/console/shared/errors.ts +33 -6
- package/src/console/system-prompt/contracts.ts +0 -1
- package/src/main.ts +3 -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/event-query.ts +18 -4
- 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 +30 -0
- package/src/persistence/migrate.ts +6 -3
- package/src/persistence/schema.ts +28 -2
- package/src/persistence/ttl-cache.ts +20 -2
- package/src/protocol/codecs.ts +1 -1
- package/src/protocol/dsml.ts +35 -87
- package/src/protocol/gateway-error.ts +1 -0
- package/src/protocol/markup-stream-parser.ts +195 -0
- package/src/protocol/reasoning-text.ts +19 -0
- package/src/protocol/reasoning.ts +1 -1
- package/src/protocol/request/chat.ts +14 -14
- package/src/protocol/request/gemini.ts +8 -2
- package/src/protocol/request/messages.ts +4 -5
- package/src/protocol/response/chat.ts +12 -20
- package/src/protocol/response/gemini.ts +6 -1
- package/src/protocol/response/omp-tool-call-markup.ts +43 -86
- package/src/protocol/response/responses.ts +18 -2
- package/src/protocol/sse-decoder.ts +25 -8
- package/src/protocol/stop-reason.ts +23 -1
- package/src/protocol/stream-error-frames.ts +13 -3
- package/src/protocol/think-split.ts +112 -0
- package/src/protocol/translation/dialect/qwen.ts +26 -58
- 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 +78 -17
- 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 +153 -59
- package/src/providers/configured-provider.ts +148 -0
- package/src/providers/default-registry.ts +117 -27
- package/src/providers/discovery/discovery-types.ts +12 -0
- package/src/providers/discovery/probe-phases.ts +74 -33
- package/src/providers/discovery/probing-service.ts +293 -165
- 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 +121 -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 +191 -35
- 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 +89 -6
- 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 +6 -43
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +2 -1
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +23 -1
- package/src/providers/integrations/buddy/codebuddy/provider.ts +6 -2
- package/src/providers/integrations/buddy/codebuddy/shared.ts +38 -27
- 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-catalog.ts +2 -0
- package/src/providers/integrations/cursor/cursor-exec.ts +745 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +2 -0
- package/src/providers/integrations/cursor/cursor-request.ts +35 -20
- package/src/providers/integrations/cursor/cursor-shared.ts +26 -5
- package/src/providers/integrations/cursor/cursor.ts +139 -70
- 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/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 +558 -0
- package/src/providers/integrations/devin/protocol/framing.ts +124 -0
- package/src/providers/integrations/factory/factory-oauth.ts +17 -44
- package/src/providers/integrations/factory/factory.ts +2 -2
- 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/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/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/hermes-oauth.ts +94 -0
- package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +13 -21
- 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/fingerprint.ts +1 -1
- package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
- package/src/providers/integrations/perch/perch-catalog.ts +105 -0
- package/src/providers/integrations/perch/perch-credential.ts +25 -0
- package/src/providers/integrations/perch/perch-oauth.ts +170 -0
- package/src/providers/integrations/perch/perch-protocol.ts +208 -0
- package/src/providers/integrations/perch/perch-quota.ts +123 -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 +305 -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 +19 -60
- package/src/providers/integrations/qoder/qoder-adapter.ts +90 -40
- package/src/providers/integrations/qoder/qoder-auth.ts +101 -29
- package/src/providers/integrations/qoder/qoder-cache.ts +32 -0
- package/src/providers/integrations/qoder/qoder-catalog.ts +26 -6
- package/src/providers/integrations/qoder/qoder-chat.ts +7 -21
- package/src/providers/integrations/qoder/qoder-checkin.ts +6 -6
- package/src/providers/integrations/qoder/qoder-grpc.ts +210 -7
- package/src/providers/integrations/qoder/qoder-ide.ts +5 -108
- 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 -7
- 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 +12 -11
- package/src/providers/integrations/trae/trae-quota.ts +96 -36
- package/src/providers/integrations/trae/trae-realm.ts +74 -15
- package/src/providers/integrations/trae/trae.ts +99 -26
- 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 +235 -278
- package/src/providers/operations/account-health-service.ts +273 -26
- package/src/providers/operations/account-refresh-status.ts +269 -0
- package/src/providers/operations/account-tier.ts +66 -0
- package/src/providers/operations/client-version-resolver.ts +1 -1
- package/src/providers/operations/client-versions.ts +42 -150
- package/src/providers/operations/cooldown-rules.ts +56 -0
- package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
- package/src/providers/operations/provider-catalog-seeder.ts +4 -0
- 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 +33 -2
- package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
- package/src/providers/provider-metadata.ts +99 -14
- package/src/providers/provider-model-definition.ts +36 -0
- 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 +96 -18
- package/src/security/abuse.ts +71 -0
- 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 +9 -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 +9 -36
- package/src/transport/dispatch/systemone.ts +2 -8
- package/src/transport/dispatch/token-count.ts +2 -6
- package/src/transport/dispatch/upstream.ts +67 -1
- package/src/transport/drain-endpoint.ts +31 -4
- package/src/transport/failure-policy.ts +129 -37
- 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 +418 -33
- 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 +9 -2
- 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 +34 -6
- package/src/transport/surface/responses/parse.ts +3 -11
- 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-DeIbIiBU.js +0 -1
- package/dist/dashboard/assets/BannedPage-DeIbIiBU.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-BRWKPQPf.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BP-Y5iHN.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-B7JHozVa.js +0 -1
- package/dist/dashboard/assets/CombosPage-B7JHozVa.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-Cv-2Ol_K.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-bKfz_b1K.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-B-WQxx_H.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js +0 -1
- package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js +0 -7
- package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.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-BXPdOpG4.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-emlTuxz6.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js +0 -1
- package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js +0 -1
- package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-CUZrKs9r.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-jWmUdDpy.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-5F9tGVGG.js +0 -56
- package/dist/dashboard/assets/UsagePage-5F9tGVGG.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-BEsU8hKs.css +0 -2
- package/dist/dashboard/assets/app-BEsU8hKs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
- package/dist/dashboard/assets/badge-DyI_mJfi.js.gz +0 -0
- package/dist/dashboard/assets/banner-B-yXtCqb.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-_HwAEqFF.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-ByjCk0o9.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BZhOlfz-.js.gz +0 -0
- package/dist/dashboard/assets/drawer-BADqIRYP.js.gz +0 -0
- package/dist/dashboard/assets/entry-COaC-irN.js.gz +0 -0
- package/dist/dashboard/assets/entry-Ca7LSCet.js +0 -4
- package/dist/dashboard/assets/entry-Ca7LSCet.js.gz +0 -0
- package/dist/dashboard/assets/entry-PDd27uCd.js +0 -1
- package/dist/dashboard/assets/entry-PDd27uCd.js.gz +0 -0
- package/dist/dashboard/assets/formatters-DcHJyRiu.js.gz +0 -0
- package/dist/dashboard/assets/index-CgKmFngv.js.gz +0 -0
- package/dist/dashboard/assets/inline-B2BirKry.js.gz +0 -0
- package/dist/dashboard/assets/input-D-qV9dKY.js.gz +0 -0
- package/dist/dashboard/assets/layout-B7tVWhtV.js.gz +0 -0
- package/dist/dashboard/assets/list-row-CtkGE3bF.js.gz +0 -0
- package/dist/dashboard/assets/live-DGizF06-.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-C9UE-bOr.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BnAd22a_.js.gz +0 -0
- package/dist/dashboard/assets/pagination-BAywyMzS.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-YSoQOUh6.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-fnCsxgNO.js +0 -1
- package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
- package/dist/dashboard/assets/select-BCOcv8WK.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-ByKt3Q1g.js.gz +0 -0
- package/dist/dashboard/assets/state-DXbGTx7N.js.gz +0 -0
- package/dist/dashboard/assets/switch-ZeTsW623.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/bundled-discovery-providers.ts +0 -98
- package/src/providers/configured-openai-providers.ts +0 -120
- 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/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-CTxoqyYB.js → app-DIXLjojg.js} +0 -0
- /package/dist/dashboard/assets/{app-CTxoqyYB.js.gz → app-DIXLjojg.js.gz} +0 -0
|
@@ -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,
|
|
@@ -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
|
*
|
|
@@ -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"] ?? ""),
|
|
@@ -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";
|
|
@@ -361,7 +362,6 @@ export function buildQoderGrpcRequest(
|
|
|
361
362
|
modelId: string,
|
|
362
363
|
request: CanonicalRequest,
|
|
363
364
|
userId: string,
|
|
364
|
-
_contextWindow = 1_000_000,
|
|
365
365
|
legacy?: {
|
|
366
366
|
readonly messages: Array<Record<string, unknown>>;
|
|
367
367
|
readonly tools?: Array<Record<string, unknown>>;
|
|
@@ -440,6 +440,65 @@ export function qoderGrpcMetadata(auth: QoderAuth, modelId = ""): grpc.Metadata
|
|
|
440
440
|
return metadata;
|
|
441
441
|
}
|
|
442
442
|
|
|
443
|
+
/**
|
|
444
|
+
* gRPC metadata headers safe to persist in the provider-request panel.
|
|
445
|
+
*
|
|
446
|
+
* Mirrors the HTTP capture allowlist (`CAPTURED_REQUEST_HEADERS` in
|
|
447
|
+
* `transport/dispatch/attempt-finalize.ts`): session/identity framing is kept,
|
|
448
|
+
* but credentials are never stored. `authorization` (the Bearer job token) is
|
|
449
|
+
* deliberately excluded — the HTTP wrapper never captures it, and the gRPC
|
|
450
|
+
* capture must not either.
|
|
451
|
+
*/
|
|
452
|
+
const GRPC_CAPTURED_HEADERS = [
|
|
453
|
+
"x-request-id",
|
|
454
|
+
"x-session-id",
|
|
455
|
+
"cosy-version",
|
|
456
|
+
"cosy-clienttype",
|
|
457
|
+
"cosy-machineos",
|
|
458
|
+
"cosy-machinehostname",
|
|
459
|
+
"x-opencode-client",
|
|
460
|
+
"x-opencode-session",
|
|
461
|
+
"x-opencode-session-id",
|
|
462
|
+
"x-opencode-parent-session-id",
|
|
463
|
+
"x-opencode-request",
|
|
464
|
+
"x-opencode-project",
|
|
465
|
+
"x-conversation-id",
|
|
466
|
+
"x-conversation-request-id",
|
|
467
|
+
"x-conversation-message-id",
|
|
468
|
+
"x-root-request-id",
|
|
469
|
+
"x-trace-id",
|
|
470
|
+
"traceparent",
|
|
471
|
+
"b3",
|
|
472
|
+
] as const;
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* A gRPC exchange is not a `fetch`, so the HTTP capture wrapper never sees it.
|
|
476
|
+
* The adapter fills the sink directly with a faithful rendering of the proto
|
|
477
|
+
* call: the request line (grpc method, host, allowlisted metadata, and the
|
|
478
|
+
* translated body) and a deferred response that resolves to the collected
|
|
479
|
+
* stream summary.
|
|
480
|
+
*/
|
|
481
|
+
export function captureQoderGrpcRequest(
|
|
482
|
+
request: Record<string, unknown>,
|
|
483
|
+
metadata: grpc.Metadata,
|
|
484
|
+
realm: QoderRealm,
|
|
485
|
+
): Record<string, unknown> {
|
|
486
|
+
const host = qoderGrpcHost(realm);
|
|
487
|
+
const map = metadata.getMap() as Record<string, string | string[]>;
|
|
488
|
+
const headers: Record<string, string> = {};
|
|
489
|
+
for (const name of GRPC_CAPTURED_HEADERS) {
|
|
490
|
+
const value = map[name];
|
|
491
|
+
if (value === undefined) continue;
|
|
492
|
+
headers[name] = Array.isArray(value) ? value.join(", ") : String(value);
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
method: "POST",
|
|
496
|
+
url: `grpc://${host}/model.chat.ChatService/ChatCompletionStream`,
|
|
497
|
+
headers,
|
|
498
|
+
body: request,
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
443
502
|
/** Maps a proto `finish_reason` to the canonical stop reason. */
|
|
444
503
|
function mapFinishReason(raw: string | undefined): CanonicalStopReason | undefined {
|
|
445
504
|
switch (raw) {
|
|
@@ -450,6 +509,10 @@ function mapFinishReason(raw: string | undefined): CanonicalStopReason | undefin
|
|
|
450
509
|
return "tool_use";
|
|
451
510
|
case "length":
|
|
452
511
|
return "length";
|
|
512
|
+
case "error":
|
|
513
|
+
return "error";
|
|
514
|
+
case "refusal":
|
|
515
|
+
return "refusal";
|
|
453
516
|
default:
|
|
454
517
|
return undefined;
|
|
455
518
|
}
|
|
@@ -466,6 +529,7 @@ export async function* qoderGrpcToCanonicalEvents(
|
|
|
466
529
|
metadata: grpc.Metadata,
|
|
467
530
|
realm: QoderRealm,
|
|
468
531
|
signal: AbortSignal,
|
|
532
|
+
capture?: ProviderCaptureExchange,
|
|
469
533
|
): AsyncGenerator<CanonicalEvent> {
|
|
470
534
|
const ChatService = qoderGrpcClient();
|
|
471
535
|
const host = qoderGrpcHost(realm);
|
|
@@ -483,6 +547,122 @@ export async function* qoderGrpcToCanonicalEvents(
|
|
|
483
547
|
// clients reject content after the finish reason. A second terminal is
|
|
484
548
|
// skipped too (the upstream can re-emit the finish frame).
|
|
485
549
|
let finished = false;
|
|
550
|
+
// Last usage chunk seen; attached to the terminal event (the upstream
|
|
551
|
+
// streams usage after the finish-reason chunk, and a standalone `usage`
|
|
552
|
+
// event after terminal breaks strict Messages encoders).
|
|
553
|
+
let pendingUsage: UsageRecord | undefined;
|
|
554
|
+
/** Whether the terminal already carried the pending usage. */
|
|
555
|
+
let terminalEmitted = false;
|
|
556
|
+
// Capture sinks fed from the same `for await` loop that drives production
|
|
557
|
+
// events (see `feedCapture`). Declared here so the loop below can call
|
|
558
|
+
// them; they are assigned when capture is enabled.
|
|
559
|
+
let feedCapture: ((chunk: unknown) => void) | undefined;
|
|
560
|
+
let flushToolCalls: (() => void) | undefined;
|
|
561
|
+
if (capture !== undefined) {
|
|
562
|
+
capture.request = captureQoderGrpcRequest(request, metadata, realm);
|
|
563
|
+
capture.response = new Promise((resolve) => {
|
|
564
|
+
const summary: Record<string, unknown> = {
|
|
565
|
+
transport: "grpc",
|
|
566
|
+
chunks: 0,
|
|
567
|
+
answer_chars: 0,
|
|
568
|
+
reasoning_chars: 0,
|
|
569
|
+
tool_calls: 0,
|
|
570
|
+
models: [] as string[],
|
|
571
|
+
usage: undefined,
|
|
572
|
+
stop_reason: undefined,
|
|
573
|
+
status_code: 0,
|
|
574
|
+
// Materialized response content (bounded): the text the model
|
|
575
|
+
// emitted, the reasoning trace, and the tool calls that were
|
|
576
|
+
// announced. This is what Request Detail shows instead of a bare
|
|
577
|
+
// statistics block.
|
|
578
|
+
text: "",
|
|
579
|
+
reasoning: "",
|
|
580
|
+
toolCalls: [] as Array<{ index: number; id?: string; name?: string; arguments?: string }>,
|
|
581
|
+
};
|
|
582
|
+
// Per-index tool-call fragments (the upstream streams one call at a
|
|
583
|
+
// time: id + empty args, then argument fragments, index always 0).
|
|
584
|
+
const toolCallsByIndex = new Map<number, { id?: string; name?: string; arguments?: string }>();
|
|
585
|
+
const MAX_TEXT_CHARS = 16_000;
|
|
586
|
+
const MAX_TOOL_CALLS = 50;
|
|
587
|
+
const MAX_TOOL_ARGS_CHARS = 2_000;
|
|
588
|
+
// The summary is fed from the SAME `for await` loop that drives the
|
|
589
|
+
// production event stream (see `feedCapture` below) — a separate
|
|
590
|
+
// `data` listener raced the iterator and saw nothing (or a different
|
|
591
|
+
// decode), so tool calls and text never made it into the capture.
|
|
592
|
+
feedCapture = (chunk: unknown) => {
|
|
593
|
+
const rec = chunk as Record<string, unknown>;
|
|
594
|
+
summary["chunks"] = (summary["chunks"] as number) + 1;
|
|
595
|
+
const model = rec["model"];
|
|
596
|
+
if (typeof model === "string" && model.length > 0) {
|
|
597
|
+
const models = summary["models"] as string[];
|
|
598
|
+
if (!models.includes(model)) models.push(model);
|
|
599
|
+
}
|
|
600
|
+
const usage = rec["usage"];
|
|
601
|
+
if (usage !== undefined && typeof usage === "object") summary["usage"] = usage;
|
|
602
|
+
const choices = Array.isArray(rec["choices"]) ? (rec["choices"] as Array<Record<string, unknown>>) : [];
|
|
603
|
+
for (const choice of choices) {
|
|
604
|
+
const delta = choice["delta"] as Record<string, unknown> | undefined;
|
|
605
|
+
const content = delta?.["content"];
|
|
606
|
+
if (typeof content === "string" && content.length > 0) {
|
|
607
|
+
summary["answer_chars"] = (summary["answer_chars"] as number) + content.length;
|
|
608
|
+
const text = summary["text"] as string;
|
|
609
|
+
if (text.length < MAX_TEXT_CHARS) {
|
|
610
|
+
summary["text"] = (text + content).slice(0, MAX_TEXT_CHARS);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
const reasoning = delta?.["reasoning_content"];
|
|
614
|
+
if (typeof reasoning === "string" && reasoning.length > 0) {
|
|
615
|
+
summary["reasoning_chars"] = (summary["reasoning_chars"] as number) + reasoning.length;
|
|
616
|
+
const reasoningText = summary["reasoning"] as string;
|
|
617
|
+
if (reasoningText.length < MAX_TEXT_CHARS) {
|
|
618
|
+
summary["reasoning"] = (reasoningText + reasoning).slice(0, MAX_TEXT_CHARS);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
const calls = delta?.["tool_calls"];
|
|
622
|
+
if (Array.isArray(calls) && calls.length > 0) {
|
|
623
|
+
summary["tool_calls"] = (summary["tool_calls"] as number) + calls.length;
|
|
624
|
+
for (const rawTc of calls) {
|
|
625
|
+
const tc = fromProtoStruct(rawTc);
|
|
626
|
+
const index = typeof tc["index"] === "number" ? (tc["index"] as number) : 0;
|
|
627
|
+
const entry = toolCallsByIndex.get(index) ?? {};
|
|
628
|
+
const declaredId = tc["id"];
|
|
629
|
+
if (typeof declaredId === "string" && declaredId.trim().length > 0) entry.id = declaredId.trim();
|
|
630
|
+
const fn = (tc["function"] ?? {}) as Record<string, unknown>;
|
|
631
|
+
const name = fn["name"];
|
|
632
|
+
if (typeof name === "string" && name.trim().length > 0) entry.name = name.trim();
|
|
633
|
+
const args = fn["arguments"];
|
|
634
|
+
if (typeof args === "string" && args.length > 0) {
|
|
635
|
+
entry.arguments = ((entry.arguments ?? "") + args).slice(0, MAX_TOOL_ARGS_CHARS);
|
|
636
|
+
}
|
|
637
|
+
toolCallsByIndex.set(index, entry);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
const finish = choice["finish_reason"];
|
|
641
|
+
if (typeof finish === "string" && finish.length > 0) summary["stop_reason"] = finish;
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
flushToolCalls = () => {
|
|
645
|
+
const entries = [...toolCallsByIndex.entries()]
|
|
646
|
+
.sort((a, b) => a[0] - b[0])
|
|
647
|
+
.slice(0, MAX_TOOL_CALLS)
|
|
648
|
+
.map(([index, entry]) => ({ index, ...entry }));
|
|
649
|
+
if (entries.length > 0) summary["toolCalls"] = entries;
|
|
650
|
+
};
|
|
651
|
+
stream.on("status", (s: { code?: number; details?: string }) => {
|
|
652
|
+
summary["status_code"] = s.code ?? 0;
|
|
653
|
+
summary["grpc_message"] = s.details ?? "";
|
|
654
|
+
});
|
|
655
|
+
stream.on("error", (err: Error) => {
|
|
656
|
+
summary["error"] = err.message;
|
|
657
|
+
flushToolCalls?.();
|
|
658
|
+
resolve(summary);
|
|
659
|
+
});
|
|
660
|
+
stream.on("end", () => {
|
|
661
|
+
flushToolCalls?.();
|
|
662
|
+
resolve(summary);
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
}
|
|
486
666
|
|
|
487
667
|
const abortHandler = () => {
|
|
488
668
|
aborted = true;
|
|
@@ -502,6 +682,8 @@ export async function* qoderGrpcToCanonicalEvents(
|
|
|
502
682
|
const fragmentState: QoderToolFragmentState = {};
|
|
503
683
|
for await (const chunk of stream) {
|
|
504
684
|
if (aborted || signal.aborted) break;
|
|
685
|
+
// Feed the capture summary from this same chunk (single consumer).
|
|
686
|
+
feedCapture?.(chunk);
|
|
505
687
|
// The upstream names the model it actually ran in the chunk's `model`
|
|
506
688
|
// field. Surface it once as `response_start` so probes and telemetry
|
|
507
689
|
// can report the real echoed model (the gRPC surface has no HTTP body
|
|
@@ -599,24 +781,45 @@ export async function* qoderGrpcToCanonicalEvents(
|
|
|
599
781
|
yield {
|
|
600
782
|
type: "terminal",
|
|
601
783
|
sequence_number: nextSeq(),
|
|
602
|
-
|
|
784
|
+
// `tool_use` and `refusal` are *successful* terminal verdicts:
|
|
785
|
+
// the model decided to call a tool / declined to answer, but the
|
|
786
|
+
// response itself is valid — `complete`, never `failed`. A
|
|
787
|
+
// `failed` state here made attempt-finalize treat a healthy tool
|
|
788
|
+
// turn as an upstream failure ("empty-handed") and replay it.
|
|
789
|
+
// A key `error` (or `length`) is the failed case.
|
|
790
|
+
state: stop === "stop" || stop === "tool_use" || stop === "refusal" ? "complete" : "failed",
|
|
603
791
|
stop_reason: stop,
|
|
604
792
|
provider_stop_reason: choice.finish_reason,
|
|
793
|
+
// The upstream streams `usage` in a trailing chunk *after* the
|
|
794
|
+
// finish-reason chunk. A separate `usage` event after the
|
|
795
|
+
// terminal is rejected by strict Anthropic Messages encoders
|
|
796
|
+
// (the 2.7.8 regression), so the usage is carried here on the
|
|
797
|
+
// terminal instead — exactly like the IDE surface does.
|
|
798
|
+
...(pendingUsage === undefined ? {} : { usage: pendingUsage }),
|
|
605
799
|
};
|
|
800
|
+
if (pendingUsage !== undefined) terminalEmitted = true;
|
|
606
801
|
}
|
|
607
802
|
}
|
|
608
803
|
}
|
|
609
|
-
if (chunk.usage
|
|
804
|
+
if (chunk.usage) {
|
|
610
805
|
const usage = usageFromProvider(chunk.usage);
|
|
611
806
|
if (usage !== undefined) {
|
|
612
|
-
|
|
807
|
+
// Keep the last usage chunk; it is attached to the terminal above
|
|
808
|
+
// (and only if no terminal ever arrives, emitted as a standalone
|
|
809
|
+
// event at stream end).
|
|
810
|
+
pendingUsage = usage;
|
|
613
811
|
}
|
|
614
812
|
}
|
|
615
813
|
}
|
|
616
814
|
// Tool-call deltas are emitted as they arrive — there is no end-of-stream
|
|
617
815
|
// buffer to flush; the terminal event is yielded by the finish-reason
|
|
618
816
|
// branch above (or synthesized by the caller if the stream ends without
|
|
619
|
-
// one).
|
|
817
|
+
// one). If no terminal carried the pending usage (the stream ended
|
|
818
|
+
// without a finish reason), emit it as a standalone event so telemetry
|
|
819
|
+
// still sees the upstream's numbers.
|
|
820
|
+
if (!terminalEmitted && pendingUsage !== undefined) {
|
|
821
|
+
yield { type: "usage", sequence_number: nextSeq(), usage: pendingUsage };
|
|
822
|
+
}
|
|
620
823
|
} catch (err: any) {
|
|
621
824
|
const status = err?.code;
|
|
622
825
|
const details = err?.details ?? err?.message ?? "";
|
|
@@ -644,7 +847,7 @@ export async function* qoderGrpcToCanonicalEvents(
|
|
|
644
847
|
"quota_exceeded",
|
|
645
848
|
429,
|
|
646
849
|
"Qoder quota exhausted on the gRPC surface (request {requestId})",
|
|
647
|
-
{},
|
|
850
|
+
{ providerCode: "quota_exceeded" },
|
|
648
851
|
"upstream",
|
|
649
852
|
);
|
|
650
853
|
}
|
|
@@ -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),
|
|
@@ -475,110 +479,3 @@ export function buildQoderIdeRequest(
|
|
|
475
479
|
};
|
|
476
480
|
}
|
|
477
481
|
|
|
478
|
-
/**
|
|
479
|
-
* Qoder upstream streams are flaky: the IDE surface sometimes answers a full
|
|
480
|
-
* normal stop with reasoning only and no output text/tool calls (observed ~1
|
|
481
|
-
* in 4 runs with Sonus). That is not a valid completion — the lab would show a
|
|
482
|
-
* thought with "No text came back". Replay such a stream before committing to
|
|
483
|
-
* it. `sniffQoderStream` below inspects just enough of the body to make that
|
|
484
|
-
* call without buffering the whole (possibly long) stream.
|
|
485
|
-
*/
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* Reads the upstream body just far enough to decide whether it carries real
|
|
489
|
-
* output, then returns a replay stream that re-emits the already-read bytes
|
|
490
|
-
* followed by the rest of the original body. If the body ends before any
|
|
491
|
-
* meaningful output is seen, `meaningful` is false and the caller replays the
|
|
492
|
-
* whole request (empty / thought-only recovery) without ever buffering a large
|
|
493
|
-
* response: the original `drainQoderResponse` read the entire stream before
|
|
494
|
-
* forwarding, which made every stream appear to complete in one burst at the
|
|
495
|
-
* end.
|
|
496
|
-
*/
|
|
497
|
-
async function sniffQoderStream(
|
|
498
|
-
response: Response,
|
|
499
|
-
signal: AbortSignal,
|
|
500
|
-
): Promise<{ meaningful: boolean; response: Response }> {
|
|
501
|
-
const body = response.body;
|
|
502
|
-
if (body === null) return { meaningful: true, response };
|
|
503
|
-
const reader = body.getReader();
|
|
504
|
-
const decoder = new TextDecoder();
|
|
505
|
-
const pending: Uint8Array[] = [];
|
|
506
|
-
let inspected = "";
|
|
507
|
-
let meaningful = false;
|
|
508
|
-
for (let i = 0; i < 32 && !meaningful; i += 1) {
|
|
509
|
-
const { done, value } = await reader.read();
|
|
510
|
-
if (signal.aborted) break;
|
|
511
|
-
if (done) break;
|
|
512
|
-
pending.push(value);
|
|
513
|
-
inspected += decoder.decode(value, { stream: true });
|
|
514
|
-
// Peek at completed SSE frames only: a content delta or a tool-call
|
|
515
|
-
// fragment means real output is coming. Reasoning-only streams are NOT
|
|
516
|
-
// meaningful (they are exactly what empty-stream recovery replays), and a
|
|
517
|
-
// finish reason alone is not output either.
|
|
518
|
-
if (/data:/.test(inspected)) {
|
|
519
|
-
for (const frame of inspected.split(/\r?\n\r?\n/)) {
|
|
520
|
-
if (!frame.includes("data:")) continue;
|
|
521
|
-
if (frame.includes('"content":"') || frame.includes('"tool_calls"')) {
|
|
522
|
-
meaningful = true;
|
|
523
|
-
break;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
if (inspected.length > 65536) {
|
|
528
|
-
meaningful = true;
|
|
529
|
-
break;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
// Replay: previously-read bytes first, then continue the original body.
|
|
533
|
-
let offset = 0;
|
|
534
|
-
const replay = new ReadableStream<Uint8Array>({
|
|
535
|
-
start(controller) {
|
|
536
|
-
for (; offset < pending.length; offset += 1) controller.enqueue(pending[offset]);
|
|
537
|
-
},
|
|
538
|
-
async pull(controller) {
|
|
539
|
-
if (offset < pending.length) {
|
|
540
|
-
controller.enqueue(pending[offset]);
|
|
541
|
-
offset += 1;
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
const { done, value } = await reader.read();
|
|
545
|
-
if (signal.aborted) {
|
|
546
|
-
controller.close();
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
|
-
if (done) {
|
|
550
|
-
controller.close();
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
controller.enqueue(value);
|
|
554
|
-
},
|
|
555
|
-
cancel(reason) {
|
|
556
|
-
return reader.cancel(reason);
|
|
557
|
-
},
|
|
558
|
-
});
|
|
559
|
-
return { meaningful, response: new Response(replay, response) };
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
/**
|
|
563
|
-
* Runs the IDE request up to `1 + maxReplays` times, returning the first
|
|
564
|
-
* response whose stream carried real output (text or tool calls). A 200 with
|
|
565
|
-
* no output (empty or thought-only) is replayed rather than surfaced. The
|
|
566
|
-
* stream is forwarded progressively (not buffered to completion).
|
|
567
|
-
*/
|
|
568
|
-
async function fetchQoderIdeWithEmptyRecovery(
|
|
569
|
-
requestOnce: () => Promise<Response>,
|
|
570
|
-
signal: AbortSignal,
|
|
571
|
-
maxReplays = 2,
|
|
572
|
-
): Promise<Response> {
|
|
573
|
-
let last: Response | undefined;
|
|
574
|
-
for (let attempt = 0; attempt <= maxReplays; attempt += 1) {
|
|
575
|
-
const res = await requestOnce();
|
|
576
|
-
if (!res.ok || !res.body) return res;
|
|
577
|
-
const sniff = await sniffQoderStream(res, signal);
|
|
578
|
-
last = sniff.response;
|
|
579
|
-
if (sniff.meaningful) return last;
|
|
580
|
-
}
|
|
581
|
-
return last!;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
export { fetchQoderIdeWithEmptyRecovery };
|