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
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
// Freebuff (Codebuff Desktop) adapter — TypeScript, Freebuff provider, Bun gateway.
|
|
2
|
+
//
|
|
3
|
+
// The upstream wire is OpenAI-compatible chat on `/api/v1/chat/completions`,
|
|
4
|
+
// but requires a stateful preflight per session:
|
|
5
|
+
// 1. `POST /api/v1/freebuff/session/admission` — buys/opens a model session
|
|
6
|
+
// (catalog handle `fbm1.*` in `x-freebuff-model`, desktop identity group,
|
|
7
|
+
// Ed25519 device signature).
|
|
8
|
+
// 2. `POST /api/v1/agent-runs` START — opens an agent run (desktop worker
|
|
9
|
+
// agent id).
|
|
10
|
+
// 3. `POST /api/v1/chat/completions` — the actual chat call. It needs the
|
|
11
|
+
// desktop identity group on its headers and the desktop body shape:
|
|
12
|
+
// `codebuff_metadata` (instance/multi-session/trace/repo/step/run/client/
|
|
13
|
+
// cost_mode), `provider.allow_fallbacks`, the 37 official tools, and the
|
|
14
|
+
// official worker system template (LOCKED — see freebuff-constants.ts).
|
|
15
|
+
// 4. `POST /api/v1/agent-runs` FINISH, then `DELETE /api/v1/freebuff/session`
|
|
16
|
+
// — teardown.
|
|
17
|
+
//
|
|
18
|
+
// Verified live 2026-11-09 (see documentation/research/freebuff/): the full
|
|
19
|
+
// shape returns 200; missing identity group → 410 `freebuff_update_required`;
|
|
20
|
+
// system-prompt mutation → 403 `third_party_client_warning` /
|
|
21
|
+
// `free_mode_cli_required`.
|
|
22
|
+
|
|
23
|
+
import { createHash } from "node:crypto";
|
|
24
|
+
import type { CanonicalEvent, CanonicalRequest } from "../../../protocol/canonical-model";
|
|
25
|
+
import { GatewayError } from "../../../protocol/gateway-error";
|
|
26
|
+
import { isRecord } from "../../../protocol/codecs";
|
|
27
|
+
import { encodeWireRequest, decodeWireStream } from "../../../protocol/registry";
|
|
28
|
+
import { resolveDialect } from "../../../protocol/translation/dialect/resolver";
|
|
29
|
+
import { postUpstreamJson } from "../../../protocol/upstream/post-json";
|
|
30
|
+
import { mapUpstreamHttpError, throwIfHtmlResponse } from "../../../transport/failure-policy";
|
|
31
|
+
import type {
|
|
32
|
+
ProviderAdapter,
|
|
33
|
+
ProviderDispatchContext,
|
|
34
|
+
ProviderDispatchTarget,
|
|
35
|
+
} from "../../provider-contracts";
|
|
36
|
+
import {
|
|
37
|
+
FREEBUFF_OFFICIAL_TOOLS,
|
|
38
|
+
FREEBUFF_WORKER_SYSTEM_PROMPT,
|
|
39
|
+
} from "./freebuff-constants";
|
|
40
|
+
import { freebuffDispatchModelId } from "./freebuff-virtual";
|
|
41
|
+
|
|
42
|
+
export const FREEBUFF_PROVIDER_ID = "freebuff" as const;
|
|
43
|
+
|
|
44
|
+
// --- Wire constants (matching scripts/provider-probing/freebuff/_lib.mjs) ---
|
|
45
|
+
|
|
46
|
+
export const FREEBUFF_API_HOST = "https://www.codebuff.com";
|
|
47
|
+
export const FREEBUFF_MODEL_CATALOG_PATH = "/api/v1/freebuff/models";
|
|
48
|
+
export const FREEBUFF_SESSION_ADMISSION_PATH = "/api/v1/freebuff/session/admission";
|
|
49
|
+
export const FREEBUFF_SESSION_PATH = "/api/v1/freebuff/session";
|
|
50
|
+
export const FREEBUFF_DEVICE_KEYS_PATH = "/api/v1/freebuff/device-keys";
|
|
51
|
+
export const FREEBUFF_AGENT_RUNS_PATH = "/api/v1/agent-runs";
|
|
52
|
+
export const FREEBUFF_CHAT_COMPLETIONS_PATH = "/api/v1/chat/completions";
|
|
53
|
+
|
|
54
|
+
export const FREEBUFF_CLIENT_HEADER = "x-freebuff-client";
|
|
55
|
+
export const FREEBUFF_CLIENT_VERSION_HEADER = "x-freebuff-client-version";
|
|
56
|
+
export const FREEBUFF_INSTALL_ID_HEADER = "x-freebuff-install-id";
|
|
57
|
+
export const FREEBUFF_MODEL_HEADER = "x-freebuff-model";
|
|
58
|
+
export const FREEBUFF_WALLET_SPEND_LIMIT_HEADER = "x-freebuff-wallet-spend-limit";
|
|
59
|
+
export const FREEBUFF_INSTANCE_HEADER = "x-freebuff-instance-id";
|
|
60
|
+
export const FREEBUFF_MULTI_SESSION_HEADER = "x-freebuff-multi-session";
|
|
61
|
+
export const FREEBUFF_PURCHASE_CONTINUITY_HEADER = "x-freebuff-purchase-continuity";
|
|
62
|
+
export const FREEBUFF_DESKTOP_ATTEMPT_HEADER = "x-freebuff-desktop-attempt-id";
|
|
63
|
+
export const FREEBUFF_CATALOG_FETCH_HEADER = "x-freebuff-catalog-fetch";
|
|
64
|
+
export const FREEBUFF_FIRST_TAB_DISCOUNT_HEADER = "x-freebuff-first-tab-discount";
|
|
65
|
+
export const FREEBUFF_TIMEZONE_HEADER = "x-fb-timezone";
|
|
66
|
+
export const FREEBUFF_SSE_GZIP_RECOVERY_HEADER = "x-freebuff-sse-gzip-recovery";
|
|
67
|
+
export const FREEBUFF_ACTING_USER_HEADER = "x-freebuff-acting-user-id";
|
|
68
|
+
export const FREEBUFF_CLIENT_HEADER_VALUE = "desktop";
|
|
69
|
+
|
|
70
|
+
export const FREEBUFF_CATALOG_PROTOCOL_HEADER = "x-freebuff-catalog-protocol";
|
|
71
|
+
export const FREEBUFF_CATALOG_PROTOCOL_VERSION = "1";
|
|
72
|
+
|
|
73
|
+
export const FREEBUFF_DEVICE_KEY_HEADER = "x-freebuff-device-key";
|
|
74
|
+
export const FREEBUFF_DEVICE_TIMESTAMP_HEADER = "x-freebuff-device-ts";
|
|
75
|
+
export const FREEBUFF_DEVICE_SIGNATURE_HEADER = "x-freebuff-device-sig";
|
|
76
|
+
|
|
77
|
+
export const FREEBUFF_CHAT_USER_AGENT =
|
|
78
|
+
"ai-sdk/openai-compatible/0.0.0-test/codebuff ai-sdk/provider-utils/3.0.25 runtime/bun/1.4.2";
|
|
79
|
+
export const FREEBUFF_ADMISSION_USER_AGENT = "Bun/1.4.2";
|
|
80
|
+
|
|
81
|
+
export const FREEBUFF_DESKTOP_THREAD_LOCAL_AGENT_ID = "freebuff-desktop-thread-local-v3";
|
|
82
|
+
export const FREEBUFF_DESKTOP_AGENT_VERSION = "0.0.201";
|
|
83
|
+
|
|
84
|
+
export const FREEBUFF_AGENT_RUN_AGENT_ID = FREEBUFF_DESKTOP_THREAD_LOCAL_AGENT_ID;
|
|
85
|
+
|
|
86
|
+
// --- Ed25519 device signer (ported from _lib.mjs) ---
|
|
87
|
+
|
|
88
|
+
function base64UrlEncode(bytes: Uint8Array): string {
|
|
89
|
+
return Buffer.from(bytes).toString("base64url");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function base64UrlDecode(s: string): Uint8Array {
|
|
93
|
+
return new Uint8Array(Buffer.from(s, "base64url"));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function sha256Hex(bytes: Uint8Array | string | null): Promise<string> {
|
|
97
|
+
const data = bytes ? new Uint8Array(typeof bytes === "string" ? new TextEncoder().encode(bytes) : bytes) : new Uint8Array(0);
|
|
98
|
+
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
99
|
+
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface FreebuffDeviceIdentity {
|
|
103
|
+
readonly version: 1;
|
|
104
|
+
readonly publicKey: string;
|
|
105
|
+
readonly privateKey: string;
|
|
106
|
+
readonly keyId: string;
|
|
107
|
+
readonly installId: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface FreebuffAuthState {
|
|
111
|
+
readonly accountId?: string | undefined;
|
|
112
|
+
readonly device?: FreebuffDeviceIdentity | undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Signs a Freebuff request body with the account's device identity.
|
|
117
|
+
* Canonical payload:
|
|
118
|
+
* freebuff-device-v1\n<METHOD>\n<path>\n<timestampMs>\n<bodySha256>\n<fetchId>
|
|
119
|
+
*/
|
|
120
|
+
export async function freebuffSignRequest(
|
|
121
|
+
device: FreebuffDeviceIdentity,
|
|
122
|
+
options: {
|
|
123
|
+
readonly method: string;
|
|
124
|
+
readonly path: string;
|
|
125
|
+
readonly body?: Uint8Array | string | null;
|
|
126
|
+
readonly fetchId?: string;
|
|
127
|
+
readonly timestampMs?: number;
|
|
128
|
+
},
|
|
129
|
+
): Promise<Record<string, string>> {
|
|
130
|
+
const timestampMs = options.timestampMs ?? Date.now();
|
|
131
|
+
const payload = [
|
|
132
|
+
"freebuff-device-v1",
|
|
133
|
+
options.method.toUpperCase(),
|
|
134
|
+
options.path,
|
|
135
|
+
String(timestampMs),
|
|
136
|
+
await sha256Hex(options.body ?? null),
|
|
137
|
+
options.fetchId ?? "",
|
|
138
|
+
].join("\n");
|
|
139
|
+
const privateKey = await crypto.subtle.importKey(
|
|
140
|
+
"pkcs8",
|
|
141
|
+
base64UrlDecode(device.privateKey).buffer as ArrayBuffer,
|
|
142
|
+
{ name: "Ed25519" },
|
|
143
|
+
false,
|
|
144
|
+
["sign"],
|
|
145
|
+
);
|
|
146
|
+
const signature = new Uint8Array(
|
|
147
|
+
await crypto.subtle.sign(
|
|
148
|
+
{ name: "Ed25519" },
|
|
149
|
+
privateKey,
|
|
150
|
+
new TextEncoder().encode(payload),
|
|
151
|
+
),
|
|
152
|
+
);
|
|
153
|
+
return {
|
|
154
|
+
[FREEBUFF_DEVICE_KEY_HEADER]: device.keyId,
|
|
155
|
+
[FREEBUFF_DEVICE_TIMESTAMP_HEADER]: String(timestampMs),
|
|
156
|
+
[FREEBUFF_DEVICE_SIGNATURE_HEADER]: base64UrlEncode(signature),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export async function freebuffMintDeviceKey(
|
|
161
|
+
token: string,
|
|
162
|
+
options: {
|
|
163
|
+
readonly fetchFn?: typeof fetch;
|
|
164
|
+
readonly accountId?: string;
|
|
165
|
+
} = {},
|
|
166
|
+
): Promise<FreebuffDeviceIdentity> {
|
|
167
|
+
const fetchFn = options.fetchFn ?? globalThis.fetch;
|
|
168
|
+
const pair = await crypto.subtle.generateKey({ name: "Ed25519" }, true, ["sign", "verify"]);
|
|
169
|
+
const publicKey = new Uint8Array(await crypto.subtle.exportKey("raw", pair.publicKey));
|
|
170
|
+
const privateKey = new Uint8Array(await crypto.subtle.exportKey("pkcs8", pair.privateKey));
|
|
171
|
+
const publicKeyB64 = base64UrlEncode(publicKey);
|
|
172
|
+
const privateKeyB64 = base64UrlEncode(privateKey);
|
|
173
|
+
const res = await fetchFn(`${FREEBUFF_API_HOST}${FREEBUFF_DEVICE_KEYS_PATH}`, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: {
|
|
176
|
+
Authorization: `Bearer ${token}`,
|
|
177
|
+
"content-type": "application/json",
|
|
178
|
+
},
|
|
179
|
+
body: JSON.stringify({ publicKey: publicKeyB64, client: FREEBUFF_CLIENT_HEADER_VALUE }),
|
|
180
|
+
});
|
|
181
|
+
if (!res.ok) {
|
|
182
|
+
const text = await res.text().catch(() => "");
|
|
183
|
+
let upstreamInvalid: { error?: unknown; message?: unknown } | undefined;
|
|
184
|
+
try {
|
|
185
|
+
upstreamInvalid = JSON.parse(text) as { error?: unknown; message?: unknown };
|
|
186
|
+
} catch {
|
|
187
|
+
upstreamInvalid = undefined;
|
|
188
|
+
}
|
|
189
|
+
const upstreamError =
|
|
190
|
+
upstreamInvalid && typeof upstreamInvalid["error"] === "string"
|
|
191
|
+
? upstreamInvalid["error"]
|
|
192
|
+
: undefined;
|
|
193
|
+
// The upstream refuses this device with a structured reason (e.g.
|
|
194
|
+
// `account_suspended`, `free_mode_cli_required`). Surface it to the
|
|
195
|
+
// operator as its own stable code and the upstream's exact HTTP status —
|
|
196
|
+
// NOT the synthetic 401 below. A pasted/login token that is simply bad
|
|
197
|
+
// (`authentication_failed`) stays 401.
|
|
198
|
+
if (upstreamError !== undefined) {
|
|
199
|
+
const upstreamMessage =
|
|
200
|
+
upstreamInvalid && typeof upstreamInvalid["message"] === "string"
|
|
201
|
+
? upstreamInvalid["message"]
|
|
202
|
+
: "Freebuff refused this device key";
|
|
203
|
+
throw new GatewayError(
|
|
204
|
+
"policy_rejected",
|
|
205
|
+
res.status,
|
|
206
|
+
`${upstreamMessage} (request {requestId}).`,
|
|
207
|
+
{ error: upstreamError, upstreamStatus: res.status },
|
|
208
|
+
"upstream",
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
throw new GatewayError(
|
|
212
|
+
"authentication_failed",
|
|
213
|
+
401,
|
|
214
|
+
`Freebuff device-key registration failed (HTTP ${res.status}) (request {requestId}). ${text.slice(0, 200)}`,
|
|
215
|
+
{},
|
|
216
|
+
"upstream",
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
const body: unknown = await res.json().catch(() => null);
|
|
220
|
+
const keyId = isRecord(body) && typeof body["keyId"] === "string" ? body["keyId"] : undefined;
|
|
221
|
+
if (!keyId) {
|
|
222
|
+
throw new GatewayError("authentication_failed", 401, "Freebuff device-key registration returned no keyId (request {requestId}).", {}, "upstream");
|
|
223
|
+
}
|
|
224
|
+
const scopeKey = options.accountId ? `user:${options.accountId}` : `token:${createHash("sha256").update(token).digest("hex").slice(0, 32)}`;
|
|
225
|
+
return {
|
|
226
|
+
version: 1,
|
|
227
|
+
publicKey: publicKeyB64,
|
|
228
|
+
privateKey: privateKeyB64,
|
|
229
|
+
keyId: `${scopeKey}|${keyId}`,
|
|
230
|
+
installId: crypto.randomUUID(),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// --- Session preflight (admission + agent-runs) ---
|
|
235
|
+
|
|
236
|
+
export interface FreebuffAdmission {
|
|
237
|
+
instanceId: string;
|
|
238
|
+
admittedRes: Response;
|
|
239
|
+
admittedBody: Record<string, unknown> | null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export interface FreebuffAgentRun {
|
|
243
|
+
readonly runId: string;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export interface FreebuffSession {
|
|
247
|
+
readonly admission: FreebuffAdmission;
|
|
248
|
+
readonly agentRun: FreebuffAgentRun;
|
|
249
|
+
readonly instanceId: string;
|
|
250
|
+
readonly runId: string;
|
|
251
|
+
readonly catalogFetchId: string;
|
|
252
|
+
readonly modelHandle: string;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function readAuthState(credential: NonNullable<ProviderDispatchContext["credential"]>): FreebuffAuthState {
|
|
256
|
+
const authState = credential.auth_state;
|
|
257
|
+
if (!isRecord(authState)) return {};
|
|
258
|
+
const accountId = typeof authState["accountId"] === "string" ? authState["accountId"] : undefined;
|
|
259
|
+
const deviceRaw = authState["device"];
|
|
260
|
+
if (!isRecord(deviceRaw)) return { accountId };
|
|
261
|
+
const version = deviceRaw["version"];
|
|
262
|
+
const publicKey = deviceRaw["publicKey"];
|
|
263
|
+
const privateKey = deviceRaw["privateKey"];
|
|
264
|
+
const keyId = deviceRaw["keyId"];
|
|
265
|
+
const installId = deviceRaw["installId"];
|
|
266
|
+
if (
|
|
267
|
+
version === 1 &&
|
|
268
|
+
typeof publicKey === "string" &&
|
|
269
|
+
typeof privateKey === "string" &&
|
|
270
|
+
typeof keyId === "string" &&
|
|
271
|
+
typeof installId === "string"
|
|
272
|
+
) {
|
|
273
|
+
return {
|
|
274
|
+
accountId,
|
|
275
|
+
device: { version: 1, publicKey, privateKey, keyId, installId },
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
return { accountId };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function resolveInstallId(authState: FreebuffAuthState): string {
|
|
282
|
+
return authState.device?.installId ?? crypto.randomUUID();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function resolveAccountId(authState: FreebuffAuthState, token: string): string {
|
|
286
|
+
return authState.accountId ?? `token:${createHash("sha256").update(token).digest("hex").slice(0, 16)}`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function resolveCatalogHandle(
|
|
290
|
+
token: string,
|
|
291
|
+
model: string,
|
|
292
|
+
context: ProviderDispatchContext,
|
|
293
|
+
): Promise<{ handle: string; fetchId: string }> {
|
|
294
|
+
const fetchFn = context.outbound_fetch ?? globalThis.fetch;
|
|
295
|
+
const res = await fetchFn(`${FREEBUFF_API_HOST}${FREEBUFF_MODEL_CATALOG_PATH}`, {
|
|
296
|
+
method: "GET",
|
|
297
|
+
headers: {
|
|
298
|
+
Authorization: `Bearer ${token}`,
|
|
299
|
+
[FREEBUFF_CATALOG_PROTOCOL_HEADER]: FREEBUFF_CATALOG_PROTOCOL_VERSION,
|
|
300
|
+
[FREEBUFF_CLIENT_HEADER]: FREEBUFF_CLIENT_HEADER_VALUE,
|
|
301
|
+
},
|
|
302
|
+
signal: context.abort_signal,
|
|
303
|
+
});
|
|
304
|
+
if (!res.ok) {
|
|
305
|
+
throw await mapUpstreamHttpError(res, FREEBUFF_PROVIDER_ID);
|
|
306
|
+
}
|
|
307
|
+
const body: unknown = await res.json().catch(() => null);
|
|
308
|
+
if (!isRecord(body)) {
|
|
309
|
+
throw new GatewayError("platform_unavailable", 502, "Freebuff catalog returned an invalid body (request {requestId}).", {}, "upstream");
|
|
310
|
+
}
|
|
311
|
+
const fetchId = typeof body["fetchId"] === "string" ? body["fetchId"] : "";
|
|
312
|
+
const rows = body["rows"];
|
|
313
|
+
if (!Array.isArray(rows)) {
|
|
314
|
+
throw new GatewayError("platform_unavailable", 502, "Freebuff catalog returned no rows (request {requestId}).", {}, "upstream");
|
|
315
|
+
}
|
|
316
|
+
// Key match (m-<hex>) first, then legacy-digest, then display-name fragment.
|
|
317
|
+
const requestedKey = model;
|
|
318
|
+
let row: unknown | undefined = rows.find((r) => isRecord(r) && r["key"] === requestedKey);
|
|
319
|
+
if (row === undefined) {
|
|
320
|
+
row = rows.find((r) => isRecord(r) && Array.isArray(r["legacyDigests"]) && (r["legacyDigests"] as unknown[]).some((d) => typeof d === "string" && freebuffLegacyModelDigest(model) === d));
|
|
321
|
+
}
|
|
322
|
+
if (row === undefined) {
|
|
323
|
+
const slug = model.toLowerCase();
|
|
324
|
+
row = rows.find((r) => {
|
|
325
|
+
if (!isRecord(r)) return false;
|
|
326
|
+
const display = typeof r["displayName"] === "string" ? r["displayName"].toLowerCase() : "";
|
|
327
|
+
return display.includes(slug) || slug.includes(display);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
if (!isRecord(row)) {
|
|
331
|
+
throw new GatewayError("invalid_request", 400, `Freebuff catalog has no handle for model ${model} (request {requestId}).`, { providerId: FREEBUFF_PROVIDER_ID });
|
|
332
|
+
}
|
|
333
|
+
const handle = row["handle"];
|
|
334
|
+
if (typeof handle !== "string" || handle.length === 0) {
|
|
335
|
+
throw new GatewayError("invalid_request", 400, `Freebuff catalog row for ${model} has no handle (request {requestId}).`, { providerId: FREEBUFF_PROVIDER_ID });
|
|
336
|
+
}
|
|
337
|
+
return { handle, fetchId };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** FNV-1a digest matching the server's legacy-model digest. */
|
|
341
|
+
function freebuffLegacyModelDigest(modelId: string): string {
|
|
342
|
+
const input = `freebuff-legacy-model:${modelId}`;
|
|
343
|
+
let h1 = 0x811c9dc5;
|
|
344
|
+
let h2 = 0x01000193 ^ 0x5bd1e995;
|
|
345
|
+
for (let i = 0; i < input.length; i++) {
|
|
346
|
+
const c = input.charCodeAt(i);
|
|
347
|
+
h1 = Math.imul(h1 ^ c, 0x01000193) >>> 0;
|
|
348
|
+
h2 = Math.imul(h2 ^ c, 0x5bd1e995) >>> 0;
|
|
349
|
+
}
|
|
350
|
+
return h1.toString(16).padStart(8, "0") + h2.toString(16).padStart(8, "0");
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async function freebuffAdmission(
|
|
354
|
+
token: string,
|
|
355
|
+
authState: FreebuffAuthState,
|
|
356
|
+
modelHandle: string,
|
|
357
|
+
catalogFetchId: string,
|
|
358
|
+
context: ProviderDispatchContext,
|
|
359
|
+
): Promise<FreebuffAdmission> {
|
|
360
|
+
const fetchFn = context.outbound_fetch ?? globalThis.fetch;
|
|
361
|
+
const instanceId = crypto.randomUUID();
|
|
362
|
+
const attemptId = crypto.randomUUID();
|
|
363
|
+
const installId = resolveInstallId(authState);
|
|
364
|
+
const headers: Record<string, string> = {
|
|
365
|
+
Authorization: `Bearer ${token}`,
|
|
366
|
+
[FREEBUFF_CATALOG_PROTOCOL_HEADER]: FREEBUFF_CATALOG_PROTOCOL_VERSION,
|
|
367
|
+
[FREEBUFF_CLIENT_HEADER]: FREEBUFF_CLIENT_HEADER_VALUE,
|
|
368
|
+
[FREEBUFF_TIMEZONE_HEADER]: Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC",
|
|
369
|
+
[FREEBUFF_FIRST_TAB_DISCOUNT_HEADER]: "0",
|
|
370
|
+
[FREEBUFF_MODEL_HEADER]: modelHandle,
|
|
371
|
+
[FREEBUFF_WALLET_SPEND_LIMIT_HEADER]: "0",
|
|
372
|
+
[FREEBUFF_INSTANCE_HEADER]: instanceId,
|
|
373
|
+
[FREEBUFF_MULTI_SESSION_HEADER]: "1",
|
|
374
|
+
[FREEBUFF_PURCHASE_CONTINUITY_HEADER]: "1",
|
|
375
|
+
[FREEBUFF_DESKTOP_ATTEMPT_HEADER]: attemptId,
|
|
376
|
+
[FREEBUFF_INSTALL_ID_HEADER]: installId,
|
|
377
|
+
"user-agent": FREEBUFF_ADMISSION_USER_AGENT,
|
|
378
|
+
};
|
|
379
|
+
if (catalogFetchId.length > 0) headers[FREEBUFF_CATALOG_FETCH_HEADER] = catalogFetchId;
|
|
380
|
+
if (authState.device) {
|
|
381
|
+
Object.assign(
|
|
382
|
+
headers,
|
|
383
|
+
await freebuffSignRequest(authState.device, {
|
|
384
|
+
method: "POST",
|
|
385
|
+
path: FREEBUFF_SESSION_ADMISSION_PATH,
|
|
386
|
+
fetchId: catalogFetchId,
|
|
387
|
+
}),
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
const res = await fetchFn(`${FREEBUFF_API_HOST}${FREEBUFF_SESSION_ADMISSION_PATH}`, {
|
|
391
|
+
method: "POST",
|
|
392
|
+
headers,
|
|
393
|
+
body: null,
|
|
394
|
+
signal: context.abort_signal,
|
|
395
|
+
});
|
|
396
|
+
const body: unknown = await res.json().catch(() => null);
|
|
397
|
+
return { instanceId, admittedRes: res, admittedBody: isRecord(body) ? body : null };
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async function freebuffAgentRunStart(
|
|
401
|
+
token: string,
|
|
402
|
+
authState: FreebuffAuthState,
|
|
403
|
+
context: ProviderDispatchContext,
|
|
404
|
+
): Promise<FreebuffAgentRun> {
|
|
405
|
+
const fetchFn = context.outbound_fetch ?? globalThis.fetch;
|
|
406
|
+
const body = JSON.stringify({ action: "START", agentId: FREEBUFF_AGENT_RUN_AGENT_ID, ancestorRunIds: [] });
|
|
407
|
+
const headers: Record<string, string> = {
|
|
408
|
+
"content-type": "application/json",
|
|
409
|
+
Authorization: `Bearer ${token}`,
|
|
410
|
+
"user-agent": FREEBUFF_ADMISSION_USER_AGENT,
|
|
411
|
+
};
|
|
412
|
+
if (authState.accountId) headers[FREEBUFF_ACTING_USER_HEADER] = authState.accountId;
|
|
413
|
+
const res = await fetchFn(`${FREEBUFF_API_HOST}${FREEBUFF_AGENT_RUNS_PATH}`, {
|
|
414
|
+
method: "POST",
|
|
415
|
+
headers,
|
|
416
|
+
body,
|
|
417
|
+
signal: context.abort_signal,
|
|
418
|
+
});
|
|
419
|
+
if (!res.ok) {
|
|
420
|
+
throw await mapUpstreamHttpError(res, FREEBUFF_PROVIDER_ID);
|
|
421
|
+
}
|
|
422
|
+
const parsed: unknown = await res.json().catch(() => null);
|
|
423
|
+
const runId = isRecord(parsed) && typeof parsed["runId"] === "string" ? parsed["runId"] : undefined;
|
|
424
|
+
if (!runId) {
|
|
425
|
+
throw new GatewayError("platform_unavailable", 502, "Freebuff agent-runs START returned no runId (request {requestId}).", {}, "upstream");
|
|
426
|
+
}
|
|
427
|
+
return { runId };
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async function freebuffAgentRunFinish(
|
|
431
|
+
token: string,
|
|
432
|
+
authState: FreebuffAuthState,
|
|
433
|
+
runId: string,
|
|
434
|
+
context: ProviderDispatchContext,
|
|
435
|
+
): Promise<void> {
|
|
436
|
+
const fetchFn = context.outbound_fetch ?? globalThis.fetch;
|
|
437
|
+
const body = JSON.stringify({
|
|
438
|
+
action: "FINISH",
|
|
439
|
+
runId,
|
|
440
|
+
status: "completed",
|
|
441
|
+
totalSteps: 0,
|
|
442
|
+
directCredits: 0,
|
|
443
|
+
totalCredits: 0,
|
|
444
|
+
steps: [],
|
|
445
|
+
});
|
|
446
|
+
const headers: Record<string, string> = {
|
|
447
|
+
"content-type": "application/json",
|
|
448
|
+
Authorization: `Bearer ${token}`,
|
|
449
|
+
"user-agent": FREEBUFF_ADMISSION_USER_AGENT,
|
|
450
|
+
};
|
|
451
|
+
if (authState.accountId) headers[FREEBUFF_ACTING_USER_HEADER] = authState.accountId;
|
|
452
|
+
try {
|
|
453
|
+
await fetchFn(`${FREEBUFF_API_HOST}${FREEBUFF_AGENT_RUNS_PATH}`, {
|
|
454
|
+
method: "POST",
|
|
455
|
+
headers,
|
|
456
|
+
body,
|
|
457
|
+
signal: context.abort_signal,
|
|
458
|
+
});
|
|
459
|
+
} catch {
|
|
460
|
+
// best-effort teardown
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async function freebuffReleaseSession(
|
|
465
|
+
token: string,
|
|
466
|
+
authState: FreebuffAuthState,
|
|
467
|
+
instanceId: string,
|
|
468
|
+
catalogFetchId: string,
|
|
469
|
+
context: ProviderDispatchContext,
|
|
470
|
+
): Promise<void> {
|
|
471
|
+
const fetchFn = context.outbound_fetch ?? globalThis.fetch;
|
|
472
|
+
const headers: Record<string, string> = {
|
|
473
|
+
Authorization: `Bearer ${token}`,
|
|
474
|
+
[FREEBUFF_CLIENT_HEADER]: FREEBUFF_CLIENT_HEADER_VALUE,
|
|
475
|
+
[FREEBUFF_INSTANCE_HEADER]: instanceId,
|
|
476
|
+
[FREEBUFF_MULTI_SESSION_HEADER]: "1",
|
|
477
|
+
"user-agent": FREEBUFF_ADMISSION_USER_AGENT,
|
|
478
|
+
};
|
|
479
|
+
if (authState.device) {
|
|
480
|
+
Object.assign(
|
|
481
|
+
headers,
|
|
482
|
+
await freebuffSignRequest(authState.device, {
|
|
483
|
+
method: "DELETE",
|
|
484
|
+
path: FREEBUFF_SESSION_PATH,
|
|
485
|
+
fetchId: catalogFetchId,
|
|
486
|
+
}),
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
try {
|
|
490
|
+
await fetchFn(`${FREEBUFF_API_HOST}${FREEBUFF_SESSION_PATH}`, {
|
|
491
|
+
method: "DELETE",
|
|
492
|
+
headers,
|
|
493
|
+
signal: context.abort_signal,
|
|
494
|
+
});
|
|
495
|
+
} catch {
|
|
496
|
+
// best-effort teardown
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// --- Chat request build ---
|
|
501
|
+
|
|
502
|
+
function expiresbuffChatBody(
|
|
503
|
+
request: CanonicalRequest,
|
|
504
|
+
payload: Record<string, unknown>,
|
|
505
|
+
session: FreebuffSession,
|
|
506
|
+
): Record<string, unknown> {
|
|
507
|
+
const metadata: Record<string, unknown> = {
|
|
508
|
+
freebuff_instance_id: session.instanceId,
|
|
509
|
+
freebuff_multi_session: "1",
|
|
510
|
+
trace_session_id: crypto.randomUUID(),
|
|
511
|
+
repo_snapshot:
|
|
512
|
+
'{"gitAvailable":false,"repositoryVisibility":"unknown","fileCount":0,"fileCountIsLowerBound":false,"testFileCount":0,"changedFileCount":0,"changedFileScanTruncated":false}',
|
|
513
|
+
llm_step_number: "1",
|
|
514
|
+
run_id: session.runId,
|
|
515
|
+
client_id: clientIdOf(request),
|
|
516
|
+
cost_mode: "free",
|
|
517
|
+
};
|
|
518
|
+
const body: Record<string, unknown> = {
|
|
519
|
+
...payload,
|
|
520
|
+
model: session.modelHandle,
|
|
521
|
+
tools: FREEBUFF_OFFICIAL_TOOLS.map((tool) => JSON.parse(JSON.stringify(tool))),
|
|
522
|
+
tool_choice: "auto",
|
|
523
|
+
stream: true,
|
|
524
|
+
codebuff_metadata: metadata,
|
|
525
|
+
provider: { allow_fallbacks: true },
|
|
526
|
+
};
|
|
527
|
+
// The upstream wants the official worker template as the system message, and
|
|
528
|
+
// the caller's own turns (user/assistant/tool) must survive intact. Prepend
|
|
529
|
+
// the worker as a system message; never overwrite the caller's first message
|
|
530
|
+
// (which is usually the user turn).
|
|
531
|
+
const messages = body["messages"];
|
|
532
|
+
if (Array.isArray(messages)) {
|
|
533
|
+
body["messages"] = [
|
|
534
|
+
{ role: "system", content: FREEBUFF_WORKER_SYSTEM_PROMPT },
|
|
535
|
+
...messages,
|
|
536
|
+
];
|
|
537
|
+
} else {
|
|
538
|
+
body["messages"] = [{ role: "system", content: FREEBUFF_WORKER_SYSTEM_PROMPT }];
|
|
539
|
+
}
|
|
540
|
+
return body;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
let clientIdCounter = 0;
|
|
544
|
+
function clientIdOf(_request: CanonicalRequest): string {
|
|
545
|
+
// 13-char base36 client id (official SDK shape).
|
|
546
|
+
clientIdCounter = (clientIdCounter + 1) % 36;
|
|
547
|
+
const rand = (Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2)).slice(0, 12);
|
|
548
|
+
return rand + clientIdCounter.toString(36);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// --- Adapter ---
|
|
552
|
+
|
|
553
|
+
export function createFreebuffAdapter(): ProviderAdapter {
|
|
554
|
+
return {
|
|
555
|
+
provider_id: FREEBUFF_PROVIDER_ID,
|
|
556
|
+
|
|
557
|
+
systemPrompt(): string {
|
|
558
|
+
return FREEBUFF_WORKER_SYSTEM_PROMPT;
|
|
559
|
+
},
|
|
560
|
+
|
|
561
|
+
async *dispatch(
|
|
562
|
+
request: CanonicalRequest,
|
|
563
|
+
candidate: ProviderDispatchTarget,
|
|
564
|
+
context: ProviderDispatchContext,
|
|
565
|
+
): AsyncIterable<CanonicalEvent> {
|
|
566
|
+
const credential = context.credential;
|
|
567
|
+
const secret = credential.secret ? new TextDecoder().decode(credential.secret).trim() : "";
|
|
568
|
+
if (!secret) {
|
|
569
|
+
throw new GatewayError("authentication_failed", 401, "Freebuff credential secret is missing (request {requestId}).", { providerId: FREEBUFF_PROVIDER_ID });
|
|
570
|
+
}
|
|
571
|
+
const authState = readAuthState(credential);
|
|
572
|
+
if (!authState.device) {
|
|
573
|
+
throw new GatewayError("authentication_failed", 401, "Freebuff device identity is missing — sign in again (request {requestId}).", { providerId: FREEBUFF_PROVIDER_ID });
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// 1. Resolve catalog handle + fetchId (ephemeral, per dispatch).
|
|
577
|
+
// A caller-facing virtual name is translated to its `m-<hex>` roster
|
|
578
|
+
// key first (see freebuff-virtual.ts); the handle resolution then
|
|
579
|
+
// works unchanged, in the same vocabulary as the catalog rows.
|
|
580
|
+
const modelId = freebuffDispatchModelId(candidate.model_id || request.model);
|
|
581
|
+
const { handle: modelHandle, fetchId } = await resolveCatalogHandle(secret, modelId, context);
|
|
582
|
+
|
|
583
|
+
// 2. Admission.
|
|
584
|
+
const admission = await freebuffAdmission(secret, authState, modelHandle, fetchId, context);
|
|
585
|
+
if (!admission.admittedRes.ok) {
|
|
586
|
+
// A 409 purchase_in_use means a previous session still holds the model.
|
|
587
|
+
// Release it and retry admission once.
|
|
588
|
+
if (admission.admittedRes.status === 409 && admission.admittedBody?.["error"] === "purchase_in_use") {
|
|
589
|
+
const holder = admission.admittedBody?.["currentInstanceId"];
|
|
590
|
+
if (typeof holder === "string") {
|
|
591
|
+
await freebuffReleaseSession(secret, authState, holder, fetchId, context);
|
|
592
|
+
const retry = await freebuffAdmission(secret, authState, modelHandle, fetchId, context);
|
|
593
|
+
if (!retry.admittedRes.ok) throw await mapUpstreamHttpError(retry.admittedRes, FREEBUFF_PROVIDER_ID);
|
|
594
|
+
admission.instanceId = retry.instanceId;
|
|
595
|
+
admission.admittedRes = retry.admittedRes;
|
|
596
|
+
admission.admittedBody = retry.admittedBody;
|
|
597
|
+
}
|
|
598
|
+
} else {
|
|
599
|
+
throw await mapUpstreamHttpError(admission.admittedRes, FREEBUFF_PROVIDER_ID);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// 3. Agent-runs START.
|
|
604
|
+
const agentRun = await freebuffAgentRunStart(secret, authState, context);
|
|
605
|
+
const session: FreebuffSession = {
|
|
606
|
+
admission,
|
|
607
|
+
agentRun,
|
|
608
|
+
instanceId: admission.instanceId,
|
|
609
|
+
runId: agentRun.runId,
|
|
610
|
+
catalogFetchId: fetchId,
|
|
611
|
+
modelHandle,
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
try {
|
|
615
|
+
// 4. Build payload via the standard OpenAI chat encoder.
|
|
616
|
+
const dialectPolicy = resolveDialect(candidate.model_id, candidate.provider_id, candidate.wire_family, candidate.dialect);
|
|
617
|
+
const payload = await encodeWireRequest(candidate.wire_family, request, {
|
|
618
|
+
dialectPolicy,
|
|
619
|
+
});
|
|
620
|
+
const body = expiresbuffChatBody(request, payload, session);
|
|
621
|
+
|
|
622
|
+
// 5. Chat headers: desktop identity group + device signature.
|
|
623
|
+
const headers: Record<string, string> = {
|
|
624
|
+
"content-type": "application/json",
|
|
625
|
+
Authorization: `Bearer ${secret}`,
|
|
626
|
+
"user-agent": FREEBUFF_CHAT_USER_AGENT,
|
|
627
|
+
[FREEBUFF_CLIENT_HEADER]: FREEBUFF_CLIENT_HEADER_VALUE,
|
|
628
|
+
[FREEBUFF_CLIENT_VERSION_HEADER]: FREEBUFF_DESKTOP_AGENT_VERSION,
|
|
629
|
+
[FREEBUFF_INSTALL_ID_HEADER]: authState.device.installId,
|
|
630
|
+
[FREEBUFF_ACTING_USER_HEADER]: resolveAccountId(authState, secret),
|
|
631
|
+
[FREEBUFF_CATALOG_FETCH_HEADER]: fetchId,
|
|
632
|
+
[FREEBUFF_SSE_GZIP_RECOVERY_HEADER]: "1",
|
|
633
|
+
...(await freebuffSignRequest(authState.device, {
|
|
634
|
+
method: "POST",
|
|
635
|
+
path: FREEBUFF_CHAT_COMPLETIONS_PATH,
|
|
636
|
+
body: JSON.stringify(body),
|
|
637
|
+
fetchId,
|
|
638
|
+
})),
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
const upstream = await postUpstreamJson(
|
|
642
|
+
`${FREEBUFF_API_HOST}${FREEBUFF_CHAT_COMPLETIONS_PATH}`,
|
|
643
|
+
headers,
|
|
644
|
+
body,
|
|
645
|
+
context,
|
|
646
|
+
context.outbound_fetch ?? globalThis.fetch,
|
|
647
|
+
);
|
|
648
|
+
const res = upstream.res;
|
|
649
|
+
try {
|
|
650
|
+
if (!res.ok) {
|
|
651
|
+
await throwIfHtmlResponse(res);
|
|
652
|
+
throw await mapUpstreamHttpError(res, FREEBUFF_PROVIDER_ID);
|
|
653
|
+
}
|
|
654
|
+
await throwIfHtmlResponse(res);
|
|
655
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
656
|
+
if (!contentType.includes("text/event-stream")) {
|
|
657
|
+
// Non-SSE body on 2xx — error envelope or protocol violation.
|
|
658
|
+
const raw = await res.text().catch(() => "");
|
|
659
|
+
const parsed: unknown = raw ? (() => { try { return JSON.parse(raw); } catch { return null; } })() : null;
|
|
660
|
+
if (isRecord(parsed) && parsed["error"] !== undefined) {
|
|
661
|
+
throw await mapUpstreamHttpError(new Response(raw, { status: 200, statusText: "OK", headers: { "content-type": "application/json" } }), FREEBUFF_PROVIDER_ID);
|
|
662
|
+
}
|
|
663
|
+
throw new GatewayError("platform_unavailable", 502, "Freebuff chat returned a non-SSE body (request {requestId}).", {}, "upstream");
|
|
664
|
+
}
|
|
665
|
+
if (!res.body) {
|
|
666
|
+
throw new GatewayError("platform_unavailable", 502, "Freebuff chat returned an empty stream (request {requestId}).", {}, "upstream");
|
|
667
|
+
}
|
|
668
|
+
yield* decodeWireStream(candidate.wire_family, res.body, request, {
|
|
669
|
+
signal: context.abort_signal,
|
|
670
|
+
dialectPolicy,
|
|
671
|
+
});
|
|
672
|
+
} finally {
|
|
673
|
+
upstream.release();
|
|
674
|
+
}
|
|
675
|
+
} finally {
|
|
676
|
+
// 6. Teardown.
|
|
677
|
+
await freebuffAgentRunFinish(secret, authState, session.runId, context);
|
|
678
|
+
await freebuffReleaseSession(secret, authState, session.instanceId, fetchId, context);
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
};
|
|
682
|
+
}
|