cartethyia 2.7.8 → 2.7.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +0 -1
- package/README.md +29 -59
- package/dist/dashboard/assets/BannedPage-BxoG1MrL.js +1 -0
- package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-BRWKPQPf.js → CliToolDetailPage-ixwgKk53.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BP-Y5iHN.js → CliToolsPage-BmttNV3U.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BE3qwwfC.js +1 -0
- package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-Cv-2Ol_K.js → InjectSystemPromptPanel-BOoO8rpz.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-bKfz_b1K.js → LoginPage-ChJuNNVN.js} +1 -1
- package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-B-WQxx_H.js → ModelPicker-BZAJtPg1.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BE40s3u5.js +1 -0
- package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +8 -0
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-emlTuxz6.js → ProxyPage-BJ87Pp0U.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
- package/dist/dashboard/assets/{QuotaPage-CMJKNYWb.js → QuotaPage-gkSyRAKa.js} +1 -1
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js +1 -0
- package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-CUZrKs9r.js → SetupPage-CFNcATJJ.js} +1 -1
- package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-jWmUdDpy.js → StudioPage-YoD4tY5l.js} +1 -1
- package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DxjaAhb_.js +56 -0
- package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BTlkq_Uk.js +1 -0
- package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
- package/dist/dashboard/assets/app-BX2nnKqs.css +2 -0
- package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CndthXFQ.js +1 -0
- package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DyI_mJfi.js → badge-DdbT_7Am.js} +1 -1
- package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
- package/dist/dashboard/assets/ban-durations-B58FyRxC.js +1 -0
- package/dist/dashboard/assets/ban-durations-B58FyRxC.js.gz +0 -0
- package/dist/dashboard/assets/{banner-B-yXtCqb.js → banner-CsGN-nbm.js} +1 -1
- package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
- package/dist/dashboard/assets/brand/bansos_page.webp +0 -0
- package/dist/dashboard/assets/brand/denia_bansos.webp +0 -0
- package/dist/dashboard/assets/brand/favicon.webp +0 -0
- package/dist/dashboard/assets/brand/login_register.webp +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.webp +0 -0
- package/dist/dashboard/assets/brand/og_bansos.webp +0 -0
- package/dist/dashboard/assets/brand/og_image.webp +0 -0
- package/dist/dashboard/assets/brand/sidebar_ico.webp +0 -0
- package/dist/dashboard/assets/{card-_HwAEqFF.js → card-mgZ_TLLZ.js} +1 -1
- package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-A1-v8-03.js +1 -0
- package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-ByjCk0o9.js → clipboard-button-CJ4Zty89.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-BZhOlfz-.js → dialog-B5L2jPQ_.js} +1 -1
- package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-BADqIRYP.js → drawer-DkjUouAj.js} +1 -1
- package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
- package/dist/dashboard/assets/{entry-COaC-irN.js → entry-B-LVLV6o.js} +1 -1
- package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
- package/dist/dashboard/assets/entry-CAhdYl3B.js +1 -0
- package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
- package/dist/dashboard/assets/entry-D2xD0Tmd.js +4 -0
- package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-DcHJyRiu.js → formatters-CrsdLVsn.js} +1 -1
- package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
- package/dist/dashboard/assets/{index-CgKmFngv.js → index-BU64Lqvp.js} +2 -2
- package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
- package/dist/dashboard/assets/{inline-B2BirKry.js → inline-B2aDLyyg.js} +1 -1
- package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
- package/dist/dashboard/assets/{input-D-qV9dKY.js → input-BCVkdR9d.js} +1 -1
- package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
- package/dist/dashboard/assets/{layout-B7tVWhtV.js → layout-DNcAdMK-.js} +1 -1
- package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-CtkGE3bF.js → list-row-DgrIH0hw.js} +1 -1
- package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
- package/dist/dashboard/assets/{live-DGizF06-.js → live-Cae01iu8.js} +1 -1
- package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-GU7XdEjz.js → mutation-BQ0ccH_A.js} +1 -1
- package/dist/dashboard/assets/mutation-BQ0ccH_A.js.gz +0 -0
- package/dist/dashboard/assets/{network-C9UE-bOr.js → network-CRib7H_2.js} +1 -1
- package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BnAd22a_.js → numbers-BKQE8Xpz.js} +1 -1
- package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-BAywyMzS.js → pagination-ecPvG_8g.js} +1 -1
- package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
- package/dist/dashboard/assets/providers/devin.svg +9 -0
- package/dist/dashboard/assets/providers/devin.svg.gz +0 -0
- package/dist/dashboard/assets/providers/windsurf.svg +10 -0
- package/dist/dashboard/assets/providers/windsurf.svg.gz +0 -0
- package/dist/dashboard/assets/{query-keys-YSoQOUh6.js → query-keys-IKLh0YFd.js} +1 -1
- package/dist/dashboard/assets/query-keys-IKLh0YFd.js.gz +0 -0
- package/dist/dashboard/assets/routing-Ckm1UXuC.js +1 -0
- package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
- package/dist/dashboard/assets/{select-BCOcv8WK.js → select-ChoFWyKz.js} +1 -1
- package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
- package/dist/dashboard/assets/settings-CKjFPlKW.js +1 -0
- package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
- package/dist/dashboard/assets/{stack-ByKt3Q1g.js → stack-DWCwU3fh.js} +1 -1
- package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
- package/dist/dashboard/assets/{state-DXbGTx7N.js → state-Cg3VL5J_.js} +1 -1
- package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
- package/dist/dashboard/assets/story/25817331.webp +0 -0
- package/dist/dashboard/assets/story/Shorekeeper.webp +0 -0
- package/dist/dashboard/assets/story/cartethyia-god.webp +0 -0
- package/dist/dashboard/assets/story/fleurdelys_plus.webp +0 -0
- package/dist/dashboard/assets/story/jinhsi-blossom.webp +0 -0
- package/dist/dashboard/assets/story/phrolova-a.webp +0 -0
- package/dist/dashboard/assets/story/requestdeniawokkjpg.webp +0 -0
- package/dist/dashboard/assets/story/wuthering-waves-hiyuki-aemeath-hiyuki-rover.webp +0 -0
- package/dist/dashboard/assets/{switch-ZeTsW623.js → switch-Db70BMix.js} +1 -1
- package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-CbCP4w1o.js → useMutation-BqdE0LuM.js} +1 -1
- package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.webp +0 -0
- package/dist/dashboard/console-backgrounds_initial.webp +0 -0
- package/dist/dashboard/donation/qris-risun-marke.webp +0 -0
- package/dist/dashboard/index.html +3 -3
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +57 -55
- package/migrations/full-postgres/0044_add_models_warning_metadata.sql +10 -0
- package/migrations/full-postgres/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
- package/migrations/lite-sqlite3/0044_add_models_warning_metadata.sql +11 -0
- package/migrations/lite-sqlite3/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
- package/package.json +1 -1
- package/src/boot/dependencies.ts +11 -3
- package/src/config/cooldowns.ts +1 -1
- package/src/config/spec.ts +0 -6
- package/src/console/api-keys/routes.ts +11 -4
- package/src/console/console-router.ts +7 -0
- package/src/console/dashboard-assets.ts +5 -5
- package/src/console/domain-registration.ts +27 -0
- package/src/console/ip-bans/routes.ts +99 -0
- package/src/console/providers/catalog/catalog-projections.ts +5 -2
- package/src/console/providers/catalog/contracts.ts +123 -0
- package/src/console/providers/catalog/model-operations.ts +122 -10
- package/src/console/providers/catalog/provider-operations.ts +116 -8
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/routes.ts +64 -4
- package/src/console/providers/catalog/store-helpers.ts +30 -1
- package/src/console/providers/catalog/store.ts +243 -3
- package/src/console/quota/account-quota-global.ts +89 -1
- package/src/console/quota/native-identity.ts +2 -0
- package/src/console/settings/contracts.ts +18 -0
- package/src/console/settings/store.ts +32 -0
- package/src/console/system-prompt/contracts.ts +0 -1
- package/src/main.ts +1 -0
- package/src/observability/event-query.ts +18 -4
- package/src/persistence/dialect.ts +12 -0
- package/src/persistence/schema.ts +20 -2
- package/src/protocol/dsml.ts +1 -1
- package/src/protocol/gateway-error.ts +1 -0
- package/src/protocol/reasoning.ts +1 -1
- package/src/protocol/request/chat.ts +4 -4
- package/src/protocol/request/messages.ts +1 -1
- package/src/protocol/response/gemini.ts +6 -1
- package/src/protocol/sse-decoder.ts +25 -8
- package/src/protocol/stream-error-frames.ts +13 -3
- package/src/protocol/translation/quirks.ts +1 -1
- package/src/protocol/translation/tool-repair.ts +2 -2
- package/src/protocol/upstream/send-claude-messages.ts +15 -4
- package/src/protocol/upstream-parsing.ts +1 -1
- package/src/providers/adapters/compatible-adapter.ts +7 -5
- package/src/providers/authentication/oauth-refresh-service.ts +149 -36
- package/src/providers/default-registry.ts +68 -3
- package/src/providers/discovery/discovery-types.ts +12 -0
- package/src/providers/discovery/probe-phases.ts +61 -30
- package/src/providers/discovery/probing-service.ts +279 -164
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +117 -2
- package/src/providers/integrations/antigravity/antigravity.ts +13 -2
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +72 -34
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +18 -18
- package/src/providers/integrations/buddy/buddy-checkin.ts +17 -39
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +2 -2
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +5 -5
- package/src/providers/integrations/buddy/buddy-identity.ts +70 -0
- package/src/providers/integrations/buddy/buddy-media.ts +4 -5
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +2 -2
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +1 -1
- package/src/providers/integrations/buddy/buddy-usage-shared.ts +1 -3
- package/src/providers/integrations/buddy/capabilities.ts +4 -41
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +2 -1
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +20 -1
- package/src/providers/integrations/buddy/codebuddy/provider.ts +2 -2
- package/src/providers/integrations/buddy/codebuddy/shared.ts +6 -10
- package/src/providers/integrations/cursor/cursor-catalog.ts +2 -0
- package/src/providers/integrations/cursor/cursor-exec.ts +738 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +2 -0
- package/src/providers/integrations/cursor/cursor-request.ts +9 -8
- package/src/providers/integrations/cursor/cursor-shared.ts +19 -1
- package/src/providers/integrations/cursor/cursor.ts +50 -30
- package/src/providers/integrations/cursor/protocol/tool-canonical.ts +26 -100
- package/src/providers/integrations/devin/devin-auth.ts +353 -0
- package/src/providers/integrations/devin/devin-catalog.ts +207 -0
- package/src/providers/integrations/devin/devin-http.ts +91 -0
- package/src/providers/integrations/devin/devin-quota.ts +214 -0
- package/src/providers/integrations/devin/devin-shared.ts +111 -0
- package/src/providers/integrations/devin/devin.ts +539 -0
- package/src/providers/integrations/devin/protocol/devin-proto.ts +584 -0
- package/src/providers/integrations/devin/protocol/framing.ts +124 -0
- package/src/providers/integrations/factory/factory.ts +2 -2
- package/src/providers/integrations/freepi/freepi-catalog.ts +60 -0
- package/src/providers/integrations/freepi/freepi-discovery.ts +82 -0
- package/src/providers/integrations/freepi/freepi-headers.ts +21 -0
- package/src/providers/integrations/freepi/freepi-oauth.ts +142 -0
- package/src/providers/integrations/freepi/freepi-protocol.ts +242 -0
- package/src/providers/integrations/freepi/freepi-quota.ts +126 -0
- package/src/providers/integrations/freepi/freepi-realm.ts +35 -0
- package/src/providers/integrations/freepi/freepi.ts +292 -0
- package/src/providers/integrations/gemini.ts +2 -2
- package/src/providers/integrations/hermes/hermes-oauth.ts +94 -0
- package/src/providers/integrations/hermes.ts +4 -12
- package/src/providers/integrations/opencode/fingerprint.ts +1 -1
- package/src/providers/integrations/perch/perch-catalog.ts +105 -0
- package/src/providers/integrations/perch/perch-oauth.ts +170 -0
- package/src/providers/integrations/perch/perch-protocol.ts +211 -0
- package/src/providers/integrations/perch/perch-quota.ts +137 -0
- package/src/providers/integrations/perch/perch-realm.ts +82 -0
- package/src/providers/integrations/perch/perch-system-prompt.ts +13 -0
- package/src/providers/integrations/perch/perch.ts +315 -0
- package/src/providers/integrations/perch/saffron-system-prompt.txt +8 -0
- package/src/providers/integrations/qoder/chat-proto.ts +328 -328
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +1 -1
- package/src/providers/integrations/qoder/qoder-adapter.ts +89 -28
- package/src/providers/integrations/qoder/qoder-cache.ts +32 -0
- package/src/providers/integrations/qoder/qoder-catalog.ts +6 -1
- package/src/providers/integrations/qoder/qoder-checkin.ts +2 -2
- package/src/providers/integrations/qoder/qoder-grpc.ts +210 -6
- package/src/providers/integrations/qoder/qoder-ide.ts +5 -1
- package/src/providers/integrations/qoder/qoder-identity-cache.ts +184 -0
- package/src/providers/integrations/qoder/qoder-native-identity-store.ts +83 -18
- package/src/providers/integrations/qoder/qoder-realm.ts +9 -6
- package/src/providers/integrations/qoder/qoder-umid.ts +109 -342
- package/src/providers/integrations/qoder/qoder-virtual.ts +33 -1
- package/src/providers/integrations/qoder/qoder.ts +3 -6
- package/src/providers/integrations/trae/trae-catalog.ts +9 -14
- package/src/providers/integrations/trae/trae-headers.ts +36 -11
- package/src/providers/integrations/trae/trae-oauth.ts +14 -17
- package/src/providers/integrations/trae/trae-protocol.ts +3 -6
- package/src/providers/integrations/trae/trae-quota.ts +103 -21
- package/src/providers/integrations/trae/trae-realm.ts +36 -15
- package/src/providers/integrations/trae/trae.ts +97 -7
- package/src/providers/operations/account-error-classification.ts +66 -1
- package/src/providers/operations/account-health-service.ts +192 -26
- package/src/providers/operations/account-refresh-status.ts +284 -0
- package/src/providers/operations/account-tier.ts +67 -0
- package/src/providers/operations/client-versions.ts +37 -99
- package/src/providers/operations/cooldown-rules.ts +61 -0
- package/src/providers/operations/provider-catalog-seeder.ts +4 -0
- package/src/providers/provider-contracts.ts +29 -0
- package/src/providers/provider-metadata.ts +59 -4
- package/src/providers/provider-model-definition.ts +36 -0
- package/src/providers/quota/refresh.ts +21 -0
- package/src/security/abuse.ts +71 -0
- package/src/transport/dispatch/proxy-request.ts +1 -0
- package/src/transport/dispatch/streaming/stream.ts +1 -0
- package/src/transport/dispatch/upstream.ts +4 -1
- package/src/transport/failure-policy.ts +113 -7
- package/src/transport/routing/router.ts +25 -1
- package/src/transport/surface/messages/adapter.ts +2 -1
- package/src/transport/surface/responses/encode.ts +12 -1
- package/src/workers/daily-checkin.ts +1 -1
- package/src/workers/quota-refresh-sweep.ts +1 -1
- package/src/workers/scheduler.ts +22 -4
- package/dist/dashboard/assets/BannedPage-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.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 +0 -1
- 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/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/integrations/buddy/turing-device-token.ts +0 -262
- package/src/providers/integrations/buddy/workbuddy/cn-oauth.ts +0 -30
- package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +0 -58
- package/src/providers/integrations/buddy/workbuddy/oauth.ts +0 -31
- package/src/providers/integrations/buddy/workbuddy/provider.ts +0 -209
- package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -161
- package/src/providers/integrations/buddy/workbuddy/shared.ts +0 -285
- /package/dist/dashboard/assets/{app-CTxoqyYB.js → app-B7kIWFm5.js} +0 -0
- /package/dist/dashboard/assets/{app-CTxoqyYB.js.gz → app-B7kIWFm5.js.gz} +0 -0
|
@@ -241,7 +241,7 @@ class GeminiAdapter implements ProviderAdapter {
|
|
|
241
241
|
let seq = 1;
|
|
242
242
|
yield { type: "response_start", sequence_number: seq++, model: request.model } as CanonicalEvent;
|
|
243
243
|
if (output.thought) {
|
|
244
|
-
yield { type: "content_delta", sequence_number: seq++, content: { kind: "reasoning", payload: null, summary: output.thought } } as CanonicalEvent;
|
|
244
|
+
yield { type: "content_delta", sequence_number: seq++, content: { kind: "reasoning", payload: null, summary: output.thought, ...(output.thoughtSignature ? { signature: output.thoughtSignature } : {}) } } as CanonicalEvent;
|
|
245
245
|
}
|
|
246
246
|
if (output.text) {
|
|
247
247
|
yield { type: "content_delta", sequence_number: seq++, content: { kind: "text", text: output.text } } as CanonicalEvent;
|
|
@@ -277,7 +277,7 @@ class GeminiAdapter implements ProviderAdapter {
|
|
|
277
277
|
const { candidate: cand, parts } = geminiCandidate(parsed);
|
|
278
278
|
const output = responseParts(parts);
|
|
279
279
|
if (output.thought) {
|
|
280
|
-
yield { type: "content_delta", sequence_number: seq++, content: { kind: "reasoning", payload: null, summary: output.thought } } as CanonicalEvent;
|
|
280
|
+
yield { type: "content_delta", sequence_number: seq++, content: { kind: "reasoning", payload: null, summary: output.thought, ...(output.thoughtSignature ? { signature: output.thoughtSignature } : {}) } } as CanonicalEvent;
|
|
281
281
|
}
|
|
282
282
|
if (output.text) {
|
|
283
283
|
yield { type: "content_delta", sequence_number: seq++, content: { kind: "text", text: output.text } } as CanonicalEvent;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Hermes / Nous Portal OAuth client — RFC 8628 device flow + refresh.
|
|
2
|
+
//
|
|
3
|
+
// The Hermes Agent (Nous Research) offers a portal subscription via OAuth:
|
|
4
|
+
// `hermes setup --portal` runs a device-code flow against Nous Portal, and
|
|
5
|
+
// the resulting access token is an inference-scoped JWT spent on the
|
|
6
|
+
// OpenAI-compatible inference API. API keys still work for the free roster;
|
|
7
|
+
// this OAuth path is the subscription route (300+ models + paid tiers).
|
|
8
|
+
//
|
|
9
|
+
// Wire sourced from the official Hermes Agent source:
|
|
10
|
+
// - start: POST {portal}/api/oauth/device/code (form: client_id, scope)
|
|
11
|
+
// - poll: POST {portal}/api/oauth/token (form: grant_type=device_code,
|
|
12
|
+
// client_id, device_code) → {access_token, refresh_token, …}
|
|
13
|
+
// - refresh: POST {portal}/api/oauth/token (form: grant_type=refresh_token,
|
|
14
|
+
// client_id, refresh_token)
|
|
15
|
+
|
|
16
|
+
import { OAuthDeviceFlow } from "../../authentication/oauth-device-flow";
|
|
17
|
+
import type {
|
|
18
|
+
OAuthDeviceFlowContext,
|
|
19
|
+
OAuthDevicePollResult,
|
|
20
|
+
OAuthDeviceStartResult,
|
|
21
|
+
} from "../../authentication/oauth-flow-store";
|
|
22
|
+
import type { OAuthTokenRefreshResult } from "../../authentication/oauth-refresh-service";
|
|
23
|
+
import { postFormTokenRequest } from "../../authentication/oauth-flow-store";
|
|
24
|
+
|
|
25
|
+
const NOUS_PORTAL_URL = "https://portal.nousresearch.com";
|
|
26
|
+
const NOUS_CLIENT_ID = "hermes-cli";
|
|
27
|
+
const NOUS_SCOPE = "inference:invoke";
|
|
28
|
+
const NOUS_DEVICE_CODE_PATH = "/api/oauth/device/code";
|
|
29
|
+
const NOUS_TOKEN_PATH = "/api/oauth/token";
|
|
30
|
+
|
|
31
|
+
const FORM_HEADERS = { "content-type": "application/x-www-form-urlencoded", accept: "application/json" };
|
|
32
|
+
|
|
33
|
+
export class HermesOAuthClient extends OAuthDeviceFlow {
|
|
34
|
+
readonly supportsDeviceCode = true;
|
|
35
|
+
readonly supportsBrowserCode = false;
|
|
36
|
+
protected readonly providerLabel = "Hermes / Nous Portal";
|
|
37
|
+
protected readonly clientId = NOUS_CLIENT_ID;
|
|
38
|
+
protected readonly tokenUrl = `${NOUS_PORTAL_URL}${NOUS_TOKEN_PATH}`;
|
|
39
|
+
protected readonly scopes = NOUS_SCOPE;
|
|
40
|
+
|
|
41
|
+
override async startDeviceAuth(context?: OAuthDeviceFlowContext): Promise<OAuthDeviceStartResult> {
|
|
42
|
+
const started = await this.startGenericDeviceAuth({
|
|
43
|
+
tokenUrl: this.tokenUrl,
|
|
44
|
+
deviceAuthUrl: `${NOUS_PORTAL_URL}${NOUS_DEVICE_CODE_PATH}`,
|
|
45
|
+
clientId: NOUS_CLIENT_ID,
|
|
46
|
+
scope: NOUS_SCOPE,
|
|
47
|
+
formHeaders: FORM_HEADERS,
|
|
48
|
+
startParams: context?.parameters ?? {},
|
|
49
|
+
});
|
|
50
|
+
return { ...started, providerState: JSON.stringify({ device_code: started.deviceAuthId }) };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override async pollDeviceAuth(
|
|
54
|
+
deviceAuthId: string,
|
|
55
|
+
_context?: OAuthDeviceFlowContext,
|
|
56
|
+
): Promise<OAuthDevicePollResult> {
|
|
57
|
+
// The generic poll posts the device code and parses RFC 8628 error codes
|
|
58
|
+
// (`authorization_pending`/`slow_down` → pending). Its `complete` result
|
|
59
|
+
// is already a normalized OAuthExchangeResult.
|
|
60
|
+
return this.pollGenericDeviceAuth(deviceAuthId, {
|
|
61
|
+
tokenUrl: this.tokenUrl,
|
|
62
|
+
deviceAuthUrl: `${NOUS_PORTAL_URL}${NOUS_DEVICE_CODE_PATH}`,
|
|
63
|
+
clientId: NOUS_CLIENT_ID,
|
|
64
|
+
scope: NOUS_SCOPE,
|
|
65
|
+
formHeaders: FORM_HEADERS,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
override async refresh(refreshToken: string, signal?: AbortSignal): Promise<OAuthTokenRefreshResult> {
|
|
70
|
+
const payload = await postFormTokenRequest({
|
|
71
|
+
url: this.tokenUrl,
|
|
72
|
+
fetchFn: this.fetchFn,
|
|
73
|
+
params: {
|
|
74
|
+
grant_type: "refresh_token",
|
|
75
|
+
client_id: NOUS_CLIENT_ID,
|
|
76
|
+
refresh_token: refreshToken,
|
|
77
|
+
},
|
|
78
|
+
headers: FORM_HEADERS,
|
|
79
|
+
signal,
|
|
80
|
+
label: "Hermes / Nous Portal token refresh",
|
|
81
|
+
});
|
|
82
|
+
const record = payload as Record<string, unknown>;
|
|
83
|
+
const access = typeof record["access_token"] === "string" ? record["access_token"] : "";
|
|
84
|
+
if (!access) throw new Error("Hermes / Nous Portal refresh returned no access_token");
|
|
85
|
+
const expiresIn = typeof record["expires_in"] === "number" ? record["expires_in"] : 3600;
|
|
86
|
+
return {
|
|
87
|
+
access,
|
|
88
|
+
...(typeof record["refresh_token"] === "string" ? { refresh: record["refresh_token"] as string } : {}),
|
|
89
|
+
expiresAt: new Date(Date.now() + expiresIn * 1000),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const hermesOAuthClient = new HermesOAuthClient();
|
|
@@ -78,28 +78,20 @@ export const HERMES_MODELS: readonly ModelDefinition[] = [
|
|
|
78
78
|
//
|
|
79
79
|
// `meituan/longcat-2.0:free` is excluded: the catalog still lists the id, but
|
|
80
80
|
// dispatching it answers 404 "This model is no longer free", so listing it
|
|
81
|
-
// would be a card that can only ever fail. `meituan/longcat-2.5
|
|
81
|
+
// would be a card that can only ever fail. `meituan/longcat-2.5:free`
|
|
82
82
|
// is listed instead and answers 200.
|
|
83
83
|
//
|
|
84
84
|
// `stepfun/step-5-preview:free` is excluded: it is absent from the live catalog
|
|
85
85
|
// entirely and dispatching it answers 404 "This model's free period has
|
|
86
86
|
// ended" — a retired row, not a capacity blip, so it cannot be left in.
|
|
87
87
|
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
// sibling `laguna-s` is 131072, so a uniform value overstates one of them.
|
|
91
|
-
defineModel({ id: "poolside/laguna-s-2.1:free", ctx: 262144, out: 131072, reasoning: true, free: true, freeTier: true }),
|
|
92
|
-
defineModel({ id: "poolside/laguna-xs-2.1:free", ctx: 262144, out: 32768, reasoning: true, free: true, freeTier: true }),
|
|
93
|
-
defineModel({ id: "stepfun/step-3.7-flash:free", ctx: 262144, out: 256000, vision: true, reasoning: true, free: true, freeTier: true }),
|
|
88
|
+
// `poolside/laguna-s-2.1:free`, `poolside/laguna-xs-2.1:free`, and
|
|
89
|
+
// `stepfun/step-3.7-flash:free` are removed too: they fail at dispatch.
|
|
94
90
|
defineModel({ id: "upstage/solar-mini4:free", ctx: 524288, out: 131072, reasoning: true, free: true, freeTier: true }),
|
|
95
91
|
defineModel({ id: "reflection/beam-1.0-501b-a23b:free", ctx: 262144, out: 131072, reasoning: true, free: true, freeTier: true }),
|
|
96
92
|
// Vision-corrected against the live catalog: `architecture.input_modalities`
|
|
97
93
|
// lists `image` for this row, so it was published without vision it has.
|
|
98
|
-
defineModel({ id: "meituan/longcat-2.5
|
|
99
|
-
// Added after a live read: the catalog carries it, it is free, and a dispatch
|
|
100
|
-
// answers 200 — it was simply absent from this list, so a client asking for
|
|
101
|
-
// it resolved to nothing. 262144 out is the catalog's own cap.
|
|
102
|
-
defineModel({ id: "ornith/ornith-1.5-122b:free", ctx: 262144, out: 262144, vision: true, reasoning: true, free: true, freeTier: true }),
|
|
94
|
+
defineModel({ id: "meituan/longcat-2.5:free", ctx: 1048576, out: 131072, vision: true, reasoning: true, free: true, freeTier: true }),
|
|
103
95
|
];
|
|
104
96
|
|
|
105
97
|
|
|
@@ -9,7 +9,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
9
9
|
* can pin the exact identity sent before discovery resolves. Keep both in
|
|
10
10
|
* lockstep when the CLI releases.
|
|
11
11
|
*/
|
|
12
|
-
export const OPENCODE_CLIENT_VERSION = "2.2.
|
|
12
|
+
export const OPENCODE_CLIENT_VERSION = "2.2.6";
|
|
13
13
|
|
|
14
14
|
let lastTimestamp = 0;
|
|
15
15
|
let sequenceCounter = 0;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Perch model roster.
|
|
2
|
+
//
|
|
3
|
+
// Perch's hosted model layer ("Roost") exposes tier labels rather than raw
|
|
4
|
+
// model ids: `standard` and `standard_max` are the tiers included in the free
|
|
5
|
+
// Starter plan. In addition, the plan pins a small "Starter" model pool
|
|
6
|
+
// (`PERCH_STARTER_PINS`) whose pins are verified live 2026-11-08:
|
|
7
|
+
// `glm-5.3-flash-starter`, `deepseek-flash-starter`, and `crouton-alpha`
|
|
8
|
+
// answer 200 and the server reports the pinned backing model; `beam` was not
|
|
9
|
+
// verified (turn rate limit). Paid `pro` tiers are deliberately absent.
|
|
10
|
+
//
|
|
11
|
+
// Model ids here are the gateway-facing ids; the adapter maps them to the
|
|
12
|
+
// upstream wire (Roost tier + backing pin) via `PERCH_STARTER_PINS` /
|
|
13
|
+
// `roostModelChoice` — the upstream itself hides real model names.
|
|
14
|
+
|
|
15
|
+
import { defineModel, type ModelDefinition } from "../../provider-model-definition";
|
|
16
|
+
import {
|
|
17
|
+
PERCH_CONTEXT_STANDARD,
|
|
18
|
+
PERCH_CONTEXT_STANDARD_1M,
|
|
19
|
+
PERCH_PROVIDER_ID,
|
|
20
|
+
PERCH_STARTER_PINS,
|
|
21
|
+
} from "./perch-realm";
|
|
22
|
+
|
|
23
|
+
interface PerchRow {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly displayName: string;
|
|
26
|
+
readonly ctx: number;
|
|
27
|
+
/**
|
|
28
|
+
* Output ceiling. When absent it falls back to the CLI's `sUr` resolution
|
|
29
|
+
* (`perchOutputTokens`); starter-pool rows carry the client card's own
|
|
30
|
+
* `maxOutputTokens` (2.4.125) so a big request is not clamped below what
|
|
31
|
+
* the upstream actually serves.
|
|
32
|
+
*/
|
|
33
|
+
readonly out?: number;
|
|
34
|
+
/** `undefined` = plain Roost tier; else starter-pool pin id. */
|
|
35
|
+
readonly pin?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Roost tier sent for the plain tier rows (CLI canonical tier ids). */
|
|
39
|
+
const TIER_ROOST_TIERS: Readonly<Record<string, string>> = {
|
|
40
|
+
standard: "standard",
|
|
41
|
+
"standard 1m": "standard_max",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const PERCH_ROWS: readonly PerchRow[] = [
|
|
45
|
+
{ id: "standard", displayName: "Roost Standard", ctx: PERCH_CONTEXT_STANDARD },
|
|
46
|
+
{ id: "standard 1m", displayName: "Roost Standard 1M", ctx: PERCH_CONTEXT_STANDARD_1M },
|
|
47
|
+
// Starter-pool pins — context/output per the client's published cards
|
|
48
|
+
// (2.4.125): glm-5.3-flash 1048576, crouton-alpha 1048576 / 943718,
|
|
49
|
+
// beam-1.0 1048576 / 131072, deepseek-v4.1-flash-1017-starter 1048576 /
|
|
50
|
+
// 131072.
|
|
51
|
+
{ id: "glm-5.3-flash-starter", displayName: "GLM 5.3 Flash (Starter)", ctx: PERCH_CONTEXT_STANDARD_1M, out: 131_072, pin: "glm-5.3-flash-starter" },
|
|
52
|
+
{ id: "deepseek-flash-starter", displayName: "DeepSeek V4.1 Flash 1017 (Starter)", ctx: PERCH_CONTEXT_STANDARD_1M, out: 131_072, pin: "deepseek-flash-starter" },
|
|
53
|
+
{ id: "crouton-alpha", displayName: "Crouton Alpha (Stealth)", ctx: PERCH_CONTEXT_STANDARD_1M, out: 943_718, pin: "crouton-alpha" },
|
|
54
|
+
{ id: "beam-1.0-starter", displayName: "Beam 1.0 (Starter)", ctx: PERCH_CONTEXT_STANDARD_1M, out: 131_072, pin: "beam-1.0-starter" },
|
|
55
|
+
// Pinned 1M tier: the CLI normalizes `standard 1m` → `standard_max`; the
|
|
56
|
+
// adapter keys `roostModelChoice` off the catalog id so the right tier is
|
|
57
|
+
// sent even though the id is a label.
|
|
58
|
+
{ id: "standard 1m", displayName: "Roost Standard 1M", ctx: PERCH_CONTEXT_STANDARD_1M, pin: "standard-max" },
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Output cap per model, mirroring the CLI's own resolution (`sUr` in the
|
|
63
|
+
* bundled `perch.mjs`): up to 8k context → 4096, up to 32k → 8192, above
|
|
64
|
+
* 32k → 32768. Derive per model from its context window, never hardcode.
|
|
65
|
+
*/
|
|
66
|
+
function perchOutputTokens(contextWindow: number): number {
|
|
67
|
+
if (contextWindow <= 8_000) return 4_096;
|
|
68
|
+
if (contextWindow <= 32_000) return 8_192;
|
|
69
|
+
return 32_768;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const PERCH_MODELS: readonly ModelDefinition[] = PERCH_ROWS.map((row) =>
|
|
73
|
+
defineModel({
|
|
74
|
+
id: row.id,
|
|
75
|
+
providerId: PERCH_PROVIDER_ID,
|
|
76
|
+
wireFamily: "chat",
|
|
77
|
+
endpoint: "/api/perch-terminal/model-call",
|
|
78
|
+
ctx: row.ctx,
|
|
79
|
+
out: row.out ?? perchOutputTokens(row.ctx),
|
|
80
|
+
// The tier surfaces are chat-text; no modality claims beyond that. No
|
|
81
|
+
// `free`/`freeTier` flag is set: the upstream does not expose a
|
|
82
|
+
// per-model plan list (cli-plan only reports planRequired), and a free
|
|
83
|
+
// claim would be an unverified assertion rather than upstream truth.
|
|
84
|
+
vision: false,
|
|
85
|
+
reasoning: false,
|
|
86
|
+
toolCall: false,
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
/** Model-id → starter pin, for the adapter's dispatch-time lookup. */
|
|
91
|
+
export const PERCH_MODEL_PINS: Readonly<Record<string, string | undefined>> = Object.fromEntries(
|
|
92
|
+
PERCH_ROWS.map((row) => [row.id, row.pin]),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
/** Model-id → Roost tier, for the adapter's dispatch-time lookup. */
|
|
96
|
+
export const PERCH_MODEL_ROOST_TIERS: Readonly<Record<string, string>> = Object.fromEntries(
|
|
97
|
+
PERCH_ROWS.map((row) => [
|
|
98
|
+
row.id,
|
|
99
|
+
row.pin === undefined
|
|
100
|
+
? TIER_ROOST_TIERS[row.id] ?? "standard"
|
|
101
|
+
: row.pin === "standard-max"
|
|
102
|
+
? "standard_max"
|
|
103
|
+
: PERCH_STARTER_PINS[row.pin]?.roostTier ?? "standard",
|
|
104
|
+
]),
|
|
105
|
+
);
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// Perch OAuth client — Supabase PKCE browser flow + refresh.
|
|
2
|
+
//
|
|
3
|
+
// Perch authentication is Supabase GoTrue (project
|
|
4
|
+
// zlfuvsfjtgsdtqcaykia.supabase.co) with Google/GitHub providers. The web
|
|
5
|
+
// client ships only the publishable key (`sb_publishable_...`), which is
|
|
6
|
+
// accepted as `apikey` for authorize/token. The authorization code exchange
|
|
7
|
+
// is GoTrue-specific: `grant_type=pkce` with `auth_code` as the field name
|
|
8
|
+
// (NOT `authorization_code`/`code` — both are rejected with
|
|
9
|
+
// `unsupported_grant_type` / `validation_failed`), plus `code_verifier`.
|
|
10
|
+
//
|
|
11
|
+
// The browser flow is a loopback redirect (`http://127.0.0.1:<port>/callback`)
|
|
12
|
+
// exactly like the desktop app's own sign-in controller.
|
|
13
|
+
//
|
|
14
|
+
// After exchange, the official CLI stamps `user_metadata.signup_source=cli`
|
|
15
|
+
// (used by the server for surface entitlement); we repeat that so the account
|
|
16
|
+
// is recognized as a CLI surface.
|
|
17
|
+
|
|
18
|
+
import { OAuthClient } from "../../authentication/oauth-client";
|
|
19
|
+
import type { OAuthAuthorizeRequest, OAuthExchangeResult } from "../../authentication/oauth-flow-store";
|
|
20
|
+
import { postJsonTokenRequest } from "../../authentication/oauth-flow-store";
|
|
21
|
+
import type { OAuthTokenRefreshResult } from "../../authentication/oauth-refresh-service";
|
|
22
|
+
import {
|
|
23
|
+
PERCH_APP_URL,
|
|
24
|
+
PERCH_SUPABASE_HOST,
|
|
25
|
+
PERCH_SUPABASE_PUBLISHABLE_KEY,
|
|
26
|
+
} from "./perch-realm";
|
|
27
|
+
|
|
28
|
+
const PERCH_SUPABASE_URL = `https://${PERCH_SUPABASE_HOST}`;
|
|
29
|
+
const PERCH_AUTHORIZE_PATH = "/auth/v1/authorize";
|
|
30
|
+
const PERCH_TOKEN_PATH = "/auth/v1/token";
|
|
31
|
+
const PERCH_GRANT_PKCE = "pkce";
|
|
32
|
+
const PERCH_GRANT_REFRESH = "refresh_token";
|
|
33
|
+
const PERCH_APISEY_COOKIE = "apikey";
|
|
34
|
+
|
|
35
|
+
export class PerchOAuthClient extends OAuthClient {
|
|
36
|
+
readonly supportsDeviceCode = false;
|
|
37
|
+
override readonly supportsBrowserCode = true;
|
|
38
|
+
protected readonly providerLabel = "Perch";
|
|
39
|
+
protected readonly clientId = PERCH_SUPABASE_PUBLISHABLE_KEY;
|
|
40
|
+
protected readonly tokenUrl = `${PERCH_SUPABASE_URL}${PERCH_TOKEN_PATH}`;
|
|
41
|
+
protected override readonly authorizeUrl = `${PERCH_SUPABASE_URL}${PERCH_AUTHORIZE_PATH}`;
|
|
42
|
+
// Google validates scopes for accounts that never approved this client; a
|
|
43
|
+
// missing scope answers `Error 400: invalid_request / Missing required
|
|
44
|
+
// parameter: scope` (flowName=GeneralOAuthFlow). These are the GoTrue
|
|
45
|
+
// default Google scopes, sent explicitly.
|
|
46
|
+
protected readonly scopes = "openid email profile";
|
|
47
|
+
|
|
48
|
+
/** Loopback callback path (the desktop app uses the same shape). */
|
|
49
|
+
override readonly browserRedirectUri = "http://127.0.0.1:1455/callback";
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* GoTrue token responses carry the signed-in user (`user.email`). Without
|
|
53
|
+
* this the exchange falls back to the provider id as the account label and
|
|
54
|
+
* every login of every Perch account collides on the same identity
|
|
55
|
+
* fingerprint — the second login replaces the first instead of adding a new
|
|
56
|
+
* row.
|
|
57
|
+
*/
|
|
58
|
+
protected override extractAccountLabel(data: unknown): string | undefined {
|
|
59
|
+
const body = data as { user?: { email?: unknown } } | null | undefined;
|
|
60
|
+
const email = typeof body?.user?.email === "string" ? body.user.email.trim() : "";
|
|
61
|
+
return email.length > 0 ? email : undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
protected override extraAuthorizeParams(): Record<string, string> | undefined {
|
|
65
|
+
return {
|
|
66
|
+
provider: "google",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Supabase's authorize endpoint speaks `redirect_to`, not the RFC-style
|
|
72
|
+
* `redirect_uri` the shared PKCE helper emits — a call redirected to a
|
|
73
|
+
* `redirect_uri` value lands on a Supabase error page.
|
|
74
|
+
*
|
|
75
|
+
* GoTrue does NOT echo the `state` parameter on the callback — the redirect
|
|
76
|
+
* back to `redirect_to` carries only `?code=...` (verified live). The
|
|
77
|
+
* console's callback listener correlates by `state` and fails closed when
|
|
78
|
+
* state is absent, so the state is embedded in `redirect_to` itself:
|
|
79
|
+
* `http://127.0.0.1:1455/callback?state=<state>`. Supabase preserves the
|
|
80
|
+
* query it was given and appends `code`, so the callback carries both.
|
|
81
|
+
*/
|
|
82
|
+
override buildAuthorizeUrl(request: OAuthAuthorizeRequest): string {
|
|
83
|
+
const redirectTo = new URL(request.redirectUri);
|
|
84
|
+
redirectTo.searchParams.set("state", request.state);
|
|
85
|
+
const params = new URLSearchParams({
|
|
86
|
+
provider: "google",
|
|
87
|
+
client_id: this.clientId,
|
|
88
|
+
response_type: "code",
|
|
89
|
+
redirect_to: redirectTo.toString(),
|
|
90
|
+
scope: this.scopes,
|
|
91
|
+
state: request.state,
|
|
92
|
+
code_challenge: request.codeChallenge,
|
|
93
|
+
code_challenge_method: "S256",
|
|
94
|
+
});
|
|
95
|
+
return `${this.authorizeUrl}?${params.toString()}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* GoTrue exchange: `grant_type=pkce` (query) with `auth_code` (body) —
|
|
100
|
+
* not the RFC-style `authorization_code`/`code` pair the generic base
|
|
101
|
+
* class posts. GoTrue v2.197.0 accepts **only JSON** bodies here (a form
|
|
102
|
+
* body answers `bad_json`), so this overrides the form-encoded generic
|
|
103
|
+
* helper. The apikey goes in the header (not as `client_id`).
|
|
104
|
+
*
|
|
105
|
+
* The console's `exchangeCode` passes the flow state; it must be replayed
|
|
106
|
+
* in `redirect_to` exactly as `buildAuthorizeUrl` embedded it, because
|
|
107
|
+
* GoTrue compares the exchange redirect against the authorize one.
|
|
108
|
+
*/
|
|
109
|
+
override async exchangeCode(
|
|
110
|
+
code: string,
|
|
111
|
+
codeVerifier: string,
|
|
112
|
+
redirectUri: string,
|
|
113
|
+
state?: string,
|
|
114
|
+
): Promise<OAuthExchangeResult> {
|
|
115
|
+
const redirectTo = new URL(redirectUri);
|
|
116
|
+
if (state) redirectTo.searchParams.set("state", state);
|
|
117
|
+
const response = await postJsonTokenRequest({
|
|
118
|
+
url: `${this.tokenUrl}?grant_type=${PERCH_GRANT_PKCE}`,
|
|
119
|
+
fetchFn: this.fetchFn,
|
|
120
|
+
body: {
|
|
121
|
+
auth_code: code,
|
|
122
|
+
redirect_to: redirectTo.toString(),
|
|
123
|
+
code_verifier: codeVerifier,
|
|
124
|
+
},
|
|
125
|
+
headers: {
|
|
126
|
+
accept: "application/json",
|
|
127
|
+
[PERCH_APISEY_COOKIE]: PERCH_SUPABASE_PUBLISHABLE_KEY,
|
|
128
|
+
},
|
|
129
|
+
label: "Perch token exchange",
|
|
130
|
+
});
|
|
131
|
+
const result = this.toExchangeResult(this.parseTokenResponse(response));
|
|
132
|
+
await this.#stampCliSignup(result.access).catch(() => undefined);
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
override async refresh(refreshToken: string, signal?: AbortSignal): Promise<OAuthTokenRefreshResult> {
|
|
137
|
+
const response = await postJsonTokenRequest({
|
|
138
|
+
url: `${this.tokenUrl}?grant_type=${PERCH_GRANT_REFRESH}`,
|
|
139
|
+
fetchFn: this.fetchFn,
|
|
140
|
+
body: {
|
|
141
|
+
refresh_token: refreshToken,
|
|
142
|
+
},
|
|
143
|
+
headers: {
|
|
144
|
+
accept: "application/json",
|
|
145
|
+
[PERCH_APISEY_COOKIE]: PERCH_SUPABASE_PUBLISHABLE_KEY,
|
|
146
|
+
},
|
|
147
|
+
...(signal === undefined ? {} : { signal }),
|
|
148
|
+
label: "Perch token refresh",
|
|
149
|
+
});
|
|
150
|
+
return this.toRefreshResult(this.parseTokenResponse(response));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** The CLI stamps `signup_source=cli` after login; mirror it. */
|
|
154
|
+
async #stampCliSignup(access: string): Promise<void> {
|
|
155
|
+
await this.fetchFn(`${PERCH_SUPABASE_URL}/auth/v1/user`, {
|
|
156
|
+
method: "PUT",
|
|
157
|
+
headers: {
|
|
158
|
+
apikey: PERCH_SUPABASE_PUBLISHABLE_KEY,
|
|
159
|
+
Authorization: `Bearer ${access}`,
|
|
160
|
+
"content-type": "application/json",
|
|
161
|
+
},
|
|
162
|
+
body: JSON.stringify({ data: { signup_source: "cli" } }),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** The app origin the CLI writes into its session file. */
|
|
167
|
+
readonly appUrl = PERCH_APP_URL;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const perchOAuthClient = new PerchOAuthClient();
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// Perch wire protocol (pure HTTP, reverse-engineered from the official CLI).
|
|
2
|
+
//
|
|
3
|
+
// The hosted inference surface is `POST /api/perch-terminal/model-call`. The
|
|
4
|
+
// server surface-gates the call: it only accepts requests that carry the full
|
|
5
|
+
// CLI turn envelope — a fresh turn ticket minted with `{surface:"cli"}`,
|
|
6
|
+
// the Roost tier choice (`roostModelChoice`), the account attribution, and
|
|
7
|
+
// the Saffron system-prompt prefix (the server fingerprints it). Without that
|
|
8
|
+
// prompt the upstream answers `403 perch_surface_required` even with a valid
|
|
9
|
+
// token and ticket (verified live 2026-11-08).
|
|
10
|
+
//
|
|
11
|
+
// The stream is plain SSE with JSON frames:
|
|
12
|
+
// data: {"type":"keepalive"}
|
|
13
|
+
// data: {"type":"answer_delta","text":"..."}
|
|
14
|
+
// data: {"type":"done","ok":true,"text":"...","model":"...","usage":{...}}
|
|
15
|
+
// data: {"type":"model_call_failed",...}
|
|
16
|
+
// data: {"type":"error","message":"..."}
|
|
17
|
+
|
|
18
|
+
import type { CanonicalRequest } from "../../../protocol/canonical-model";
|
|
19
|
+
// The prompt lives in a plain TS module: Bun's standalone binary has no
|
|
20
|
+
// `saffron-system-prompt.txt` asset on disk, so a readFileSync()-backed
|
|
21
|
+
// constant silently became a two-line stub in the compiled binary and the
|
|
22
|
+
// upstream answered `403 perch_surface_required`.
|
|
23
|
+
export { PERCH_SAFFRON_SYSTEM_PROMPT } from "./perch-system-prompt";
|
|
24
|
+
import { PERCH_SAFFRON_SYSTEM_PROMPT } from "./perch-system-prompt";
|
|
25
|
+
import { PERCH_STARTER_PINS } from "./perch-realm";
|
|
26
|
+
|
|
27
|
+
/** Default hosted model id when the account endpoint is unreachable. */
|
|
28
|
+
export const PERCH_DEFAULT_CHAT_MODEL = "wandb-kimi-k2-6";
|
|
29
|
+
|
|
30
|
+
/** App origin (model-call + ticket + account + plan endpoints). */
|
|
31
|
+
export const PERCH_APP_URL = "https://app.perchai.app";
|
|
32
|
+
|
|
33
|
+
/** The CLI user-agent the upstream recognizes as a CLI surface. */
|
|
34
|
+
export const PERCH_CLI_USER_AGENT = "perchai-cli/unknown";
|
|
35
|
+
|
|
36
|
+
export interface PerchTurnContext {
|
|
37
|
+
readonly userId: string | null;
|
|
38
|
+
readonly workspaceId: string | null;
|
|
39
|
+
readonly orgId: string | null;
|
|
40
|
+
readonly planCode: string | null;
|
|
41
|
+
readonly runId: string;
|
|
42
|
+
/**
|
|
43
|
+
* Starter-pool pin (see `PERCH_STARTER_PINS`): when present the request
|
|
44
|
+
* carries the backing model id as `manualModelOptionId` +
|
|
45
|
+
* `preferredModelId` with `strictManual: true`, so the server actually
|
|
46
|
+
* serves the pinned model instead of the Roost default.
|
|
47
|
+
*/
|
|
48
|
+
readonly pin?: string | undefined;
|
|
49
|
+
/** Roost tier for `roostModelChoice` (`standard` | `standard_max`). */
|
|
50
|
+
readonly roostTier?: string | undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Builds the model-call body for one turn. The Saffron system-prompt prefix
|
|
55
|
+
* opens `request.messages` (required) and the caller's conversation follows.
|
|
56
|
+
* The tier is selected through `roostModelChoice` (`standard` | `standard 1m`).
|
|
57
|
+
*/
|
|
58
|
+
export function buildPerchModelCallBody(
|
|
59
|
+
request: CanonicalRequest,
|
|
60
|
+
tier: string,
|
|
61
|
+
ctx: PerchTurnContext,
|
|
62
|
+
preferredModelId = PERCH_DEFAULT_CHAT_MODEL,
|
|
63
|
+
): Record<string, unknown> {
|
|
64
|
+
const messages: Record<string, unknown>[] = [];
|
|
65
|
+
const systemParts = [
|
|
66
|
+
...(request.system ?? []),
|
|
67
|
+
...(request.instructions ?? []),
|
|
68
|
+
];
|
|
69
|
+
const systemText = systemParts
|
|
70
|
+
.map((part) => (part.kind === "text" ? part.text : ""))
|
|
71
|
+
.filter((t) => t.trim().length > 0)
|
|
72
|
+
.join("\n\n");
|
|
73
|
+
// The upstream fingerprints the *prefix* of the Saffron prompt, not the
|
|
74
|
+
// whole string (both the 28k original and this 432-char prefix answer 200,
|
|
75
|
+
// while a plain assistant placeholder answers 403). Keep Saffron as the
|
|
76
|
+
// opener unconditionally: a caller-supplied system turn is appended, never
|
|
77
|
+
// substituted, so live traffic cannot strip the surface identity. The probe
|
|
78
|
+
// path injects exactly this prefix via `systemPrompt()`, hence `startsWith`
|
|
79
|
+
// is a no-op there and the wire body stays byte-identical to the verified one.
|
|
80
|
+
const systemContent = systemText.startsWith(PERCH_SAFFRON_SYSTEM_PROMPT)
|
|
81
|
+
? systemText
|
|
82
|
+
: systemText
|
|
83
|
+
? `${PERCH_SAFFRON_SYSTEM_PROMPT}\n\n${systemText}`
|
|
84
|
+
: PERCH_SAFFRON_SYSTEM_PROMPT;
|
|
85
|
+
messages.push({ role: "system", content: systemContent });
|
|
86
|
+
for (const message of request.messages) {
|
|
87
|
+
const text = message.content
|
|
88
|
+
.map((part) => (part.kind === "text" ? part.text : ""))
|
|
89
|
+
.filter((t) => t.trim().length > 0)
|
|
90
|
+
.join("\n");
|
|
91
|
+
if (!text) continue;
|
|
92
|
+
messages.push({ role: message.role, content: text });
|
|
93
|
+
}
|
|
94
|
+
const maxTokens =
|
|
95
|
+
request.generation_controls.max_tokens ??
|
|
96
|
+
request.generation_controls.max_output_tokens ??
|
|
97
|
+
request.generation_controls.max_completion_tokens ??
|
|
98
|
+
32000;
|
|
99
|
+
const temperature = request.generation_controls.temperature ?? 0.6;
|
|
100
|
+
const runId = ctx.runId;
|
|
101
|
+
const pin = ctx.pin && ctx.pin !== "standard-max" ? ctx.pin : undefined;
|
|
102
|
+
const pinModel = pin ? PERCH_STARTER_PINS[pin]?.preferredModelId : undefined;
|
|
103
|
+
return {
|
|
104
|
+
request: {
|
|
105
|
+
messages,
|
|
106
|
+
temperature,
|
|
107
|
+
maxTokens: Number(maxTokens) || 32000,
|
|
108
|
+
},
|
|
109
|
+
runId,
|
|
110
|
+
lane: "chat",
|
|
111
|
+
strictManual: pinModel !== undefined,
|
|
112
|
+
preferredModelId: pinModel ?? preferredModelId,
|
|
113
|
+
avoidModelIds: [],
|
|
114
|
+
attribution: {
|
|
115
|
+
userId: ctx.userId,
|
|
116
|
+
workspaceId: ctx.workspaceId,
|
|
117
|
+
runId,
|
|
118
|
+
lane: "chat",
|
|
119
|
+
source: "cli",
|
|
120
|
+
billingMultiplier: null,
|
|
121
|
+
},
|
|
122
|
+
clientSurface: "cli",
|
|
123
|
+
resendAfterOutput: true,
|
|
124
|
+
roostModelChoice: ctx.roostTier ?? tier,
|
|
125
|
+
roostReasoning: true,
|
|
126
|
+
effort: { level: "high", orchestration: false },
|
|
127
|
+
...(pinModel ? { manualModelOptionId: pinModel } : {}),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface PerchModelCallDone {
|
|
132
|
+
readonly ok: boolean;
|
|
133
|
+
readonly text: string;
|
|
134
|
+
readonly model: string;
|
|
135
|
+
readonly provider: string;
|
|
136
|
+
readonly usage?: Record<string, unknown>;
|
|
137
|
+
readonly error?: string;
|
|
138
|
+
readonly errorCode?: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface PerchSseEvent {
|
|
142
|
+
readonly type: string;
|
|
143
|
+
readonly data: Record<string, unknown>;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Parses an SSE body into JSON frames. */
|
|
147
|
+
export function parsePerchSse(text: string): PerchSseEvent[] {
|
|
148
|
+
const events: PerchSseEvent[] = [];
|
|
149
|
+
for (const rawLine of text.split("\n")) {
|
|
150
|
+
const line = rawLine.replace(/\r$/, "");
|
|
151
|
+
if (!line.startsWith("data:")) continue;
|
|
152
|
+
const payload = line.slice(5).trim();
|
|
153
|
+
if (!payload) continue;
|
|
154
|
+
try {
|
|
155
|
+
const data = JSON.parse(payload) as Record<string, unknown>;
|
|
156
|
+
if (typeof data["type"] === "string") {
|
|
157
|
+
events.push({ type: data["type"] as string, data });
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
// Ignore non-JSON frames.
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return events;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Extracts the final result from a completed stream. */
|
|
167
|
+
export function summarizePerchStream(events: readonly PerchSseEvent[]): PerchModelCallDone {
|
|
168
|
+
let text = "";
|
|
169
|
+
let ok = false;
|
|
170
|
+
let model = "";
|
|
171
|
+
let provider = "";
|
|
172
|
+
let usage: Record<string, unknown> | undefined;
|
|
173
|
+
let error: string | undefined;
|
|
174
|
+
let errorCode: string | undefined;
|
|
175
|
+
|
|
176
|
+
for (const event of events) {
|
|
177
|
+
const d = event.data;
|
|
178
|
+
if (event.type === "answer_delta" && typeof d["text"] === "string") {
|
|
179
|
+
text += d["text"];
|
|
180
|
+
} else if (event.type === "reasoning_delta" && typeof d["text"] === "string") {
|
|
181
|
+
// Reasoning text is not part of the visible answer.
|
|
182
|
+
void d;
|
|
183
|
+
} else if (event.type === "done") {
|
|
184
|
+
ok = d["ok"] === true;
|
|
185
|
+
if (ok) {
|
|
186
|
+
if (typeof d["text"] === "string") text = d["text"];
|
|
187
|
+
if (typeof d["model"] === "string") model = d["model"];
|
|
188
|
+
if (typeof d["provider"] === "string") provider = d["provider"];
|
|
189
|
+
const usageRaw = d["usage"];
|
|
190
|
+
if (usageRaw && typeof usageRaw === "object") {
|
|
191
|
+
usage = usageRaw as Record<string, unknown>;
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
if (typeof d["error"] === "string") error = d["error"];
|
|
195
|
+
if (typeof d["errorCode"] === "string") errorCode = d["errorCode"];
|
|
196
|
+
}
|
|
197
|
+
} else if (event.type === "model_call_failed" || event.type === "error") {
|
|
198
|
+
if (error === undefined && typeof d["error"] === "string") error = d["error"];
|
|
199
|
+
if (errorCode === undefined && typeof d["errorCode"] === "string") errorCode = d["errorCode"];
|
|
200
|
+
if (errorCode === undefined && typeof d["code"] === "string") errorCode = d["code"] as string;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return { ok: ok || text.length > 0, text, model, provider, ...(usage ? { usage } : {}), ...(error ? { error } : {}), ...(errorCode ? { errorCode } : {}) };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Renders a canonical message list into a debug label (for capture). */
|
|
207
|
+
export function summarizeMessages(request: CanonicalRequest): string {
|
|
208
|
+
return request.messages
|
|
209
|
+
.map((m) => `${m.role}:${m.content.map((p) => (p.kind === "text" ? p.text.length : 0)).reduce((a, b) => a + b, 0)}`)
|
|
210
|
+
.join("|");
|
|
211
|
+
}
|