cartethyia 2.7.7 → 2.7.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +0 -1
- package/README.md +29 -59
- package/dist/dashboard/assets/BannedPage-BxoG1MrL.js +1 -0
- package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-CLUhmjUy.js → CliToolDetailPage-ixwgKk53.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-CQmrqqrk.js → CliToolsPage-BmttNV3U.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BE3qwwfC.js +1 -0
- package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-C-e2AMz6.js → InjectSystemPromptPanel-BOoO8rpz.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-J8wPbmfT.js → LoginPage-ChJuNNVN.js} +1 -1
- package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-C6WlpU5E.js → ModelPicker-BZAJtPg1.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BE40s3u5.js +1 -0
- package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +8 -0
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-BBTRBAQ3.js → ProxyPage-BJ87Pp0U.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
- package/dist/dashboard/assets/{QuotaPage-B2WqMSG1.js → QuotaPage-gkSyRAKa.js} +1 -1
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js +1 -0
- package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-LND6_Ss7.js → SetupPage-CFNcATJJ.js} +1 -1
- package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-BzNnm6Bi.js → StudioPage-YoD4tY5l.js} +1 -1
- package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DxjaAhb_.js +56 -0
- package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BTlkq_Uk.js +1 -0
- package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
- package/dist/dashboard/assets/app-BX2nnKqs.css +2 -0
- package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CndthXFQ.js +1 -0
- package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
- package/dist/dashboard/assets/{badge-BaX2GTrn.js → badge-DdbT_7Am.js} +1 -1
- package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
- package/dist/dashboard/assets/ban-durations-B58FyRxC.js +1 -0
- package/dist/dashboard/assets/ban-durations-B58FyRxC.js.gz +0 -0
- package/dist/dashboard/assets/{banner-DFH4Bkte.js → banner-CsGN-nbm.js} +1 -1
- package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
- package/dist/dashboard/assets/brand/bansos_page.webp +0 -0
- package/dist/dashboard/assets/brand/denia_bansos.webp +0 -0
- package/dist/dashboard/assets/brand/favicon.webp +0 -0
- package/dist/dashboard/assets/brand/login_register.webp +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.webp +0 -0
- package/dist/dashboard/assets/brand/og_bansos.webp +0 -0
- package/dist/dashboard/assets/brand/og_image.webp +0 -0
- package/dist/dashboard/assets/brand/sidebar_ico.webp +0 -0
- package/dist/dashboard/assets/{card-CxDw0lK6.js → card-mgZ_TLLZ.js} +1 -1
- package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-A1-v8-03.js +1 -0
- package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-DfS1ntiq.js → clipboard-button-CJ4Zty89.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-BK-NRr5u.js → dialog-B5L2jPQ_.js} +1 -1
- package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-QqPul6sC.js → drawer-DkjUouAj.js} +1 -1
- package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
- package/dist/dashboard/assets/{entry-BMlYJVFj.js → entry-B-LVLV6o.js} +1 -1
- package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
- package/dist/dashboard/assets/entry-CAhdYl3B.js +1 -0
- package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
- package/dist/dashboard/assets/entry-D2xD0Tmd.js +4 -0
- package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-DP-5E01-.js → formatters-CrsdLVsn.js} +1 -1
- package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
- package/dist/dashboard/assets/{index-D36vCZht.js → index-BU64Lqvp.js} +2 -2
- package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
- package/dist/dashboard/assets/{inline-CHJQwxLa.js → inline-B2aDLyyg.js} +1 -1
- package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
- package/dist/dashboard/assets/{input-BlmlizVV.js → input-BCVkdR9d.js} +1 -1
- package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
- package/dist/dashboard/assets/{layout-Bz93I-yE.js → layout-DNcAdMK-.js} +1 -1
- package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-Ml16KCfu.js → list-row-DgrIH0hw.js} +1 -1
- package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
- package/dist/dashboard/assets/{live-7_QpnYGu.js → live-Cae01iu8.js} +1 -1
- package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-GU7XdEjz.js → mutation-BQ0ccH_A.js} +1 -1
- package/dist/dashboard/assets/mutation-BQ0ccH_A.js.gz +0 -0
- package/dist/dashboard/assets/{network-CuMDaTzT.js → network-CRib7H_2.js} +1 -1
- package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-CoqpaDMx.js → numbers-BKQE8Xpz.js} +1 -1
- package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-B5aOoweZ.js → pagination-ecPvG_8g.js} +1 -1
- package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
- package/dist/dashboard/assets/providers/devin.svg +9 -0
- package/dist/dashboard/assets/providers/devin.svg.gz +0 -0
- package/dist/dashboard/assets/providers/windsurf.svg +10 -0
- package/dist/dashboard/assets/providers/windsurf.svg.gz +0 -0
- package/dist/dashboard/assets/{query-keys-YSoQOUh6.js → query-keys-IKLh0YFd.js} +1 -1
- package/dist/dashboard/assets/query-keys-IKLh0YFd.js.gz +0 -0
- package/dist/dashboard/assets/routing-Ckm1UXuC.js +1 -0
- package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
- package/dist/dashboard/assets/{select-C9Ssle9b.js → select-ChoFWyKz.js} +1 -1
- package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
- package/dist/dashboard/assets/settings-CKjFPlKW.js +1 -0
- package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
- package/dist/dashboard/assets/{stack-j7cXjgYX.js → stack-DWCwU3fh.js} +1 -1
- package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
- package/dist/dashboard/assets/{state-DHmEnLa2.js → state-Cg3VL5J_.js} +1 -1
- package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
- package/dist/dashboard/assets/story/25817331.webp +0 -0
- package/dist/dashboard/assets/story/Shorekeeper.webp +0 -0
- package/dist/dashboard/assets/story/cartethyia-god.webp +0 -0
- package/dist/dashboard/assets/story/fleurdelys_plus.webp +0 -0
- package/dist/dashboard/assets/story/jinhsi-blossom.webp +0 -0
- package/dist/dashboard/assets/story/phrolova-a.webp +0 -0
- package/dist/dashboard/assets/story/requestdeniawokkjpg.webp +0 -0
- package/dist/dashboard/assets/story/wuthering-waves-hiyuki-aemeath-hiyuki-rover.webp +0 -0
- package/dist/dashboard/assets/{switch-DmZcyTIC.js → switch-Db70BMix.js} +1 -1
- package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-CbCP4w1o.js → useMutation-BqdE0LuM.js} +1 -1
- package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.webp +0 -0
- package/dist/dashboard/console-backgrounds_initial.webp +0 -0
- package/dist/dashboard/donation/qris-risun-marke.webp +0 -0
- package/dist/dashboard/index.html +3 -3
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +57 -55
- package/migrations/full-postgres/0044_add_models_warning_metadata.sql +10 -0
- package/migrations/full-postgres/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
- package/migrations/lite-sqlite3/0044_add_models_warning_metadata.sql +11 -0
- package/migrations/lite-sqlite3/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
- package/package.json +1 -1
- package/src/boot/dependencies.ts +11 -3
- package/src/config/cooldowns.ts +1 -1
- package/src/config/spec.ts +0 -6
- package/src/console/api-keys/routes.ts +11 -4
- package/src/console/console-router.ts +7 -0
- package/src/console/dashboard-assets.ts +5 -5
- package/src/console/domain-registration.ts +27 -0
- package/src/console/ip-bans/routes.ts +99 -0
- package/src/console/providers/catalog/catalog-projections.ts +5 -2
- package/src/console/providers/catalog/contracts.ts +123 -0
- package/src/console/providers/catalog/model-operations.ts +122 -10
- package/src/console/providers/catalog/provider-operations.ts +116 -8
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/routes.ts +64 -4
- package/src/console/providers/catalog/store-helpers.ts +30 -1
- package/src/console/providers/catalog/store.ts +243 -3
- package/src/console/quota/account-quota-global.ts +89 -1
- package/src/console/quota/native-identity.ts +2 -0
- package/src/console/settings/contracts.ts +18 -0
- package/src/console/settings/store.ts +32 -0
- package/src/console/system-prompt/contracts.ts +0 -1
- package/src/main.ts +1 -0
- package/src/observability/event-query.ts +18 -4
- package/src/persistence/dialect.ts +12 -0
- package/src/persistence/schema.ts +20 -2
- package/src/protocol/dsml.ts +1 -1
- package/src/protocol/gateway-error.ts +1 -0
- package/src/protocol/reasoning.ts +1 -1
- package/src/protocol/request/chat.ts +4 -4
- package/src/protocol/request/messages.ts +1 -1
- package/src/protocol/response/gemini.ts +6 -1
- package/src/protocol/sse-decoder.ts +25 -8
- package/src/protocol/stream-error-frames.ts +13 -3
- package/src/protocol/translation/quirks.ts +1 -1
- package/src/protocol/translation/tool-repair.ts +2 -2
- package/src/protocol/upstream/send-claude-messages.ts +15 -4
- package/src/protocol/upstream-parsing.ts +1 -1
- package/src/providers/adapters/compatible-adapter.ts +7 -5
- package/src/providers/authentication/oauth-refresh-service.ts +149 -36
- package/src/providers/default-registry.ts +68 -3
- package/src/providers/discovery/discovery-types.ts +12 -0
- package/src/providers/discovery/probe-phases.ts +61 -30
- package/src/providers/discovery/probing-service.ts +279 -164
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +117 -2
- package/src/providers/integrations/antigravity/antigravity.ts +13 -2
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +72 -34
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +18 -18
- package/src/providers/integrations/buddy/buddy-checkin.ts +17 -39
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +2 -2
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +5 -5
- package/src/providers/integrations/buddy/buddy-identity.ts +70 -0
- package/src/providers/integrations/buddy/buddy-media.ts +4 -5
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +2 -2
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +1 -1
- package/src/providers/integrations/buddy/buddy-usage-shared.ts +1 -3
- package/src/providers/integrations/buddy/capabilities.ts +4 -41
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +2 -1
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +20 -1
- package/src/providers/integrations/buddy/codebuddy/provider.ts +2 -2
- package/src/providers/integrations/buddy/codebuddy/shared.ts +6 -10
- package/src/providers/integrations/cursor/cursor-catalog.ts +2 -0
- package/src/providers/integrations/cursor/cursor-exec.ts +738 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +2 -0
- package/src/providers/integrations/cursor/cursor-request.ts +9 -8
- package/src/providers/integrations/cursor/cursor-shared.ts +19 -1
- package/src/providers/integrations/cursor/cursor.ts +50 -30
- package/src/providers/integrations/cursor/protocol/tool-canonical.ts +26 -100
- package/src/providers/integrations/devin/devin-auth.ts +353 -0
- package/src/providers/integrations/devin/devin-catalog.ts +207 -0
- package/src/providers/integrations/devin/devin-http.ts +91 -0
- package/src/providers/integrations/devin/devin-quota.ts +214 -0
- package/src/providers/integrations/devin/devin-shared.ts +111 -0
- package/src/providers/integrations/devin/devin.ts +539 -0
- package/src/providers/integrations/devin/protocol/devin-proto.ts +584 -0
- package/src/providers/integrations/devin/protocol/framing.ts +124 -0
- package/src/providers/integrations/factory/factory.ts +2 -2
- package/src/providers/integrations/freepi/freepi-catalog.ts +60 -0
- package/src/providers/integrations/freepi/freepi-discovery.ts +82 -0
- package/src/providers/integrations/freepi/freepi-headers.ts +21 -0
- package/src/providers/integrations/freepi/freepi-oauth.ts +142 -0
- package/src/providers/integrations/freepi/freepi-protocol.ts +242 -0
- package/src/providers/integrations/freepi/freepi-quota.ts +126 -0
- package/src/providers/integrations/freepi/freepi-realm.ts +35 -0
- package/src/providers/integrations/freepi/freepi.ts +292 -0
- package/src/providers/integrations/gemini.ts +2 -2
- package/src/providers/integrations/hermes/hermes-oauth.ts +94 -0
- package/src/providers/integrations/hermes.ts +4 -12
- package/src/providers/integrations/opencode/fingerprint.ts +1 -1
- package/src/providers/integrations/perch/perch-catalog.ts +105 -0
- package/src/providers/integrations/perch/perch-oauth.ts +170 -0
- package/src/providers/integrations/perch/perch-protocol.ts +211 -0
- package/src/providers/integrations/perch/perch-quota.ts +137 -0
- package/src/providers/integrations/perch/perch-realm.ts +82 -0
- package/src/providers/integrations/perch/perch-system-prompt.ts +13 -0
- package/src/providers/integrations/perch/perch.ts +315 -0
- package/src/providers/integrations/perch/saffron-system-prompt.txt +8 -0
- package/src/providers/integrations/qoder/chat-proto.ts +328 -328
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +1 -1
- package/src/providers/integrations/qoder/qoder-adapter.ts +89 -28
- package/src/providers/integrations/qoder/qoder-cache.ts +32 -0
- package/src/providers/integrations/qoder/qoder-catalog.ts +6 -1
- package/src/providers/integrations/qoder/qoder-checkin.ts +2 -2
- package/src/providers/integrations/qoder/qoder-grpc.ts +209 -5
- package/src/providers/integrations/qoder/qoder-ide.ts +5 -1
- package/src/providers/integrations/qoder/qoder-identity-cache.ts +184 -0
- package/src/providers/integrations/qoder/qoder-native-identity-store.ts +83 -18
- package/src/providers/integrations/qoder/qoder-realm.ts +9 -6
- package/src/providers/integrations/qoder/qoder-umid.ts +109 -342
- package/src/providers/integrations/qoder/qoder-virtual.ts +33 -1
- package/src/providers/integrations/qoder/qoder.ts +3 -6
- package/src/providers/integrations/trae/trae-catalog.ts +9 -14
- package/src/providers/integrations/trae/trae-headers.ts +36 -11
- package/src/providers/integrations/trae/trae-oauth.ts +14 -17
- package/src/providers/integrations/trae/trae-protocol.ts +3 -6
- package/src/providers/integrations/trae/trae-quota.ts +103 -21
- package/src/providers/integrations/trae/trae-realm.ts +36 -15
- package/src/providers/integrations/trae/trae.ts +97 -7
- package/src/providers/operations/account-error-classification.ts +66 -1
- package/src/providers/operations/account-health-service.ts +192 -26
- package/src/providers/operations/account-refresh-status.ts +284 -0
- package/src/providers/operations/account-tier.ts +67 -0
- package/src/providers/operations/byok-wire-profile.ts +9 -12
- package/src/providers/operations/client-versions.ts +37 -99
- package/src/providers/operations/cooldown-rules.ts +61 -0
- package/src/providers/operations/provider-catalog-seeder.ts +4 -0
- package/src/providers/provider-contracts.ts +29 -0
- package/src/providers/provider-metadata.ts +59 -4
- package/src/providers/provider-model-definition.ts +36 -0
- package/src/providers/quota/refresh.ts +21 -0
- package/src/security/abuse.ts +71 -0
- package/src/transport/dispatch/proxy-request.ts +1 -0
- package/src/transport/dispatch/streaming/stream.ts +1 -0
- package/src/transport/dispatch/upstream.ts +4 -1
- package/src/transport/failure-policy.ts +113 -7
- package/src/transport/routing/router.ts +25 -1
- package/src/transport/surface/messages/adapter.ts +2 -1
- package/src/transport/surface/responses/encode.ts +12 -1
- package/src/workers/daily-checkin.ts +1 -1
- package/src/workers/quota-refresh-sweep.ts +1 -1
- package/src/workers/scheduler.ts +22 -4
- package/dist/dashboard/assets/BannedPage-B05aF8Dg.js +0 -1
- package/dist/dashboard/assets/BannedPage-B05aF8Dg.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-CLUhmjUy.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-CQmrqqrk.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CHG_1Wr3.js +0 -1
- package/dist/dashboard/assets/CombosPage-CHG_1Wr3.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DPgbklad.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-DPgbklad.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-pnfAqr_-.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-pnfAqr_-.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-DGJuU2A6.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-DGJuU2A6.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-C-e2AMz6.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-J8wPbmfT.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-C6WlpU5E.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BSQnF2Lp.js +0 -1
- package/dist/dashboard/assets/OverviewPage-BSQnF2Lp.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-DK_e0FJz.js +0 -7
- package/dist/dashboard/assets/ProviderDetailPage-DK_e0FJz.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-D_a3r6Gc.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-D_a3r6Gc.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BBTRBAQ3.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-B2WqMSG1.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-D7xp5Ij_.js +0 -1
- package/dist/dashboard/assets/SettingsPage-D7xp5Ij_.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-LND6_Ss7.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-BzNnm6Bi.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-C1TbL24N.js +0 -56
- package/dist/dashboard/assets/UsagePage-C1TbL24N.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-KvX83mRe.js +0 -1
- package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
- package/dist/dashboard/assets/app-mweRcsKp.css +0 -2
- package/dist/dashboard/assets/app-mweRcsKp.css.gz +0 -0
- package/dist/dashboard/assets/assert-CidQVoNo.js +0 -1
- package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
- package/dist/dashboard/assets/badge-BaX2GTrn.js.gz +0 -0
- package/dist/dashboard/assets/banner-DFH4Bkte.js.gz +0 -0
- package/dist/dashboard/assets/brand/bansos_page.png +0 -0
- package/dist/dashboard/assets/brand/denia_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/favicon.jpg +0 -0
- package/dist/dashboard/assets/brand/login_register.jpg +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
- package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/og_image.jpg +0 -0
- package/dist/dashboard/assets/brand/sidebar_ico.jpg +0 -0
- package/dist/dashboard/assets/card-CxDw0lK6.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js +0 -1
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DfS1ntiq.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BK-NRr5u.js.gz +0 -0
- package/dist/dashboard/assets/drawer-QqPul6sC.js.gz +0 -0
- package/dist/dashboard/assets/entry-BMlYJVFj.js.gz +0 -0
- package/dist/dashboard/assets/entry-Dmx7gBiN.js +0 -1
- package/dist/dashboard/assets/entry-Dmx7gBiN.js.gz +0 -0
- package/dist/dashboard/assets/entry-vqGb8yVz.js +0 -4
- package/dist/dashboard/assets/entry-vqGb8yVz.js.gz +0 -0
- package/dist/dashboard/assets/formatters-DP-5E01-.js.gz +0 -0
- package/dist/dashboard/assets/index-D36vCZht.js.gz +0 -0
- package/dist/dashboard/assets/inline-CHJQwxLa.js.gz +0 -0
- package/dist/dashboard/assets/input-BlmlizVV.js.gz +0 -0
- package/dist/dashboard/assets/layout-Bz93I-yE.js.gz +0 -0
- package/dist/dashboard/assets/list-row-Ml16KCfu.js.gz +0 -0
- package/dist/dashboard/assets/live-7_QpnYGu.js.gz +0 -0
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +0 -1
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
- package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
- package/dist/dashboard/assets/network-CuMDaTzT.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CoqpaDMx.js.gz +0 -0
- package/dist/dashboard/assets/pagination-B5aOoweZ.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-YSoQOUh6.js.gz +0 -0
- package/dist/dashboard/assets/routing-fnCsxgNO.js +0 -1
- package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
- package/dist/dashboard/assets/select-C9Ssle9b.js.gz +0 -0
- package/dist/dashboard/assets/settings-BxslsErn.js +0 -1
- package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
- package/dist/dashboard/assets/stack-j7cXjgYX.js.gz +0 -0
- package/dist/dashboard/assets/state-DHmEnLa2.js.gz +0 -0
- package/dist/dashboard/assets/switch-DmZcyTIC.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.jpg +0 -0
- package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
- package/dist/dashboard/donation/qris-risun-marke.png +0 -0
- package/src/providers/integrations/buddy/turing-device-token.ts +0 -262
- package/src/providers/integrations/buddy/workbuddy/cn-oauth.ts +0 -30
- package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +0 -58
- package/src/providers/integrations/buddy/workbuddy/oauth.ts +0 -31
- package/src/providers/integrations/buddy/workbuddy/provider.ts +0 -209
- package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -161
- package/src/providers/integrations/buddy/workbuddy/shared.ts +0 -285
- /package/dist/dashboard/assets/{app-DRnQLhsc.js → app-B7kIWFm5.js} +0 -0
- /package/dist/dashboard/assets/{app-DRnQLhsc.js.gz → app-B7kIWFm5.js.gz} +0 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// Perch quota collector.
|
|
2
|
+
//
|
|
3
|
+
// Reads the account's plan/allowance surface from the hosted API
|
|
4
|
+
// (`GET /api/perchai/account` with the stored access token). The account
|
|
5
|
+
// endpoint reports `planCode`, `planName`, `membershipRole`, usage meters
|
|
6
|
+
// (`usageMeter.monthlyUsd`, `roostRolling`), and `tierSelectionRequired`.
|
|
7
|
+
// Reads are Bearer-authenticated; never throws — a failed read returns a
|
|
8
|
+
// null result so the router treats it as unknown rather than dead.
|
|
9
|
+
|
|
10
|
+
import type { FetchLike, ProviderQuotaResult, ProviderQuotaWindow } from "../../quota/quota-contracts";
|
|
11
|
+
import { cleanError, percentWindow, text } from "../../quota/quota-contracts";
|
|
12
|
+
import type { QuotaCollectionContext } from "../../quota/quota-support";
|
|
13
|
+
import { PERCH_APP_URL } from "./perch-realm";
|
|
14
|
+
|
|
15
|
+
interface PerchSessionCredential {
|
|
16
|
+
readonly accessToken?: string;
|
|
17
|
+
readonly access_token?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function credentialToken(credential: string): string {
|
|
21
|
+
try {
|
|
22
|
+
const parsed = JSON.parse(credential) as PerchSessionCredential;
|
|
23
|
+
const found = parsed.accessToken ?? parsed.access_token;
|
|
24
|
+
return typeof found === "string" ? found : "";
|
|
25
|
+
} catch {
|
|
26
|
+
return credential.trim();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function readAccount(
|
|
31
|
+
baseUrl: string,
|
|
32
|
+
token: string,
|
|
33
|
+
fetcher: FetchLike,
|
|
34
|
+
): Promise<Record<string, unknown> | null> {
|
|
35
|
+
if (!token) return null;
|
|
36
|
+
return fetcher(`${baseUrl}/api/perchai/account`, {
|
|
37
|
+
method: "GET",
|
|
38
|
+
headers: {
|
|
39
|
+
accept: "application/json",
|
|
40
|
+
authorization: `Bearer ${token}`,
|
|
41
|
+
},
|
|
42
|
+
signal: AbortSignal.timeout(15_000),
|
|
43
|
+
})
|
|
44
|
+
.then(async (res) => {
|
|
45
|
+
if (!res.ok) return null;
|
|
46
|
+
try {
|
|
47
|
+
return (await res.json()) as Record<string, unknown>;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
.catch(() => null);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const USD_WINDOW_KIND = "usd_monthly";
|
|
56
|
+
|
|
57
|
+
/** Reads the account quota and returns a normalized result. */
|
|
58
|
+
export async function fetchPerchQuota(
|
|
59
|
+
credential: string,
|
|
60
|
+
fetcher: FetchLike,
|
|
61
|
+
_context?: QuotaCollectionContext,
|
|
62
|
+
): Promise<ProviderQuotaResult> {
|
|
63
|
+
try {
|
|
64
|
+
const token = credentialToken(credential);
|
|
65
|
+
const account = await readAccount(PERCH_APP_URL, token, fetcher);
|
|
66
|
+
if (!account) {
|
|
67
|
+
return { source: "perch", plan: null, windows: [], error: "Perch account endpoint unavailable" };
|
|
68
|
+
}
|
|
69
|
+
const session = account["session"] as Record<string, unknown> | undefined;
|
|
70
|
+
const plan = session && typeof session["planCode"] === "string" ? text(session["planCode"]) : null;
|
|
71
|
+
const meter = account["usageMeter"] as Record<string, unknown> | undefined;
|
|
72
|
+
const windows: ProviderQuotaWindow[] = [];
|
|
73
|
+
|
|
74
|
+
const monthlyUsd = meter?.["monthlyUsd"];
|
|
75
|
+
if (typeof monthlyUsd === "number") {
|
|
76
|
+
// Starter includes $20/month of hosted usage; reset at month end.
|
|
77
|
+
const used = monthlyUsd as number;
|
|
78
|
+
const limit = 20;
|
|
79
|
+
windows.push(
|
|
80
|
+
percentWindow(
|
|
81
|
+
USD_WINDOW_KIND,
|
|
82
|
+
"Monthly USD usage",
|
|
83
|
+
used > 0 ? (used / limit) * 100 : 0,
|
|
84
|
+
null,
|
|
85
|
+
used,
|
|
86
|
+
limit,
|
|
87
|
+
"pooled",
|
|
88
|
+
),
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const roost = meter?.["roostRolling"] as Record<string, unknown> | undefined;
|
|
93
|
+
if (roost && typeof roost["window5hCapUsd"] === "number") {
|
|
94
|
+
const cap = roost["window5hCapUsd"] as number;
|
|
95
|
+
const used = typeof roost["window5hUsd"] === "number" ? (roost["window5hUsd"] as number) : 0;
|
|
96
|
+
windows.push(
|
|
97
|
+
percentWindow(
|
|
98
|
+
"usd_5h",
|
|
99
|
+
"Roost 5-hour rolling",
|
|
100
|
+
cap > 0 ? (used / cap) * 100 : 0,
|
|
101
|
+
null,
|
|
102
|
+
used,
|
|
103
|
+
cap,
|
|
104
|
+
"independent",
|
|
105
|
+
),
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (roost && typeof roost["window7dCapUsd"] === "number") {
|
|
109
|
+
const cap = roost["window7dCapUsd"] as number;
|
|
110
|
+
const used = typeof roost["window7dUsd"] === "number" ? (roost["window7dUsd"] as number) : 0;
|
|
111
|
+
windows.push(
|
|
112
|
+
percentWindow(
|
|
113
|
+
"usd_weekly",
|
|
114
|
+
"Roost weekly rolling",
|
|
115
|
+
cap > 0 ? (used / cap) * 100 : 0,
|
|
116
|
+
null,
|
|
117
|
+
used,
|
|
118
|
+
cap,
|
|
119
|
+
"independent",
|
|
120
|
+
),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const profile = account["profile"] as Record<string, unknown> | undefined;
|
|
125
|
+
const email = profile && typeof profile["email"] === "string" ? text(profile["email"]) : undefined;
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
source: "perch",
|
|
129
|
+
plan,
|
|
130
|
+
windows,
|
|
131
|
+
error: null,
|
|
132
|
+
...(email ? { accountLabel: email } : {}),
|
|
133
|
+
};
|
|
134
|
+
} catch (error) {
|
|
135
|
+
return { source: "perch", plan: null, windows: [], error: cleanError(error) };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Perch realm constants.
|
|
2
|
+
//
|
|
3
|
+
// Perch (perchai.app) is an AI operator platform. Accounts authenticate via
|
|
4
|
+
// Supabase OAuth (Google/GitHub) with PKCE; the data plane is direct HTTP
|
|
5
|
+
// (`POST /api/perch-terminal/model-call`) with a CLI turn ticket — no CLI
|
|
6
|
+
// process, desktop app, MCP, or ACP transport. Free Starter accounts route
|
|
7
|
+
// through the Roost `standard` / `standard_max` tiers.
|
|
8
|
+
//
|
|
9
|
+
// Wire facts (probed live 2026-11-08 on a Google OAuth account):
|
|
10
|
+
// - Supabase project host: zlfuvsfjtgsdtqcaykia.supabase.co
|
|
11
|
+
// - Publishable key used as `apikey` for OAuth flows (Raw JWT anon key is
|
|
12
|
+
// not shipped to the bundle; `sb_publishable_...` works for authorize +
|
|
13
|
+
// token exchange).
|
|
14
|
+
// - Authorize: GET /auth/v1/authorize?provider=google&redirect_to=...
|
|
15
|
+
// &client_id=<publishable>&code_challenge=...&code_challenge_method=S256
|
|
16
|
+
// - Token: POST /auth/v1/token?grant_type=pkce with `auth_code`
|
|
17
|
+
// (NOT `code` — GoTrue v2.197.0 rejects `authorization_code`), plus
|
|
18
|
+
// `code_verifier`.
|
|
19
|
+
// - Refresh: POST /auth/v1/token?grant_type=refresh_token.
|
|
20
|
+
// - Plan selection (Starter free): GET/POST /api/perch-terminal/cli-plan
|
|
21
|
+
// (POST body {plan:"starter", noticeVersion}).
|
|
22
|
+
// - Inference: the hosted model-call API is surface-gated
|
|
23
|
+
// (`perch_surface_required`) and requires the full CLI turn envelope:
|
|
24
|
+
// turn ticket, attribution, Roost tier, and the Saffron system-prompt
|
|
25
|
+
// prefix (see perch.ts / perch-protocol.ts).
|
|
26
|
+
|
|
27
|
+
/** Default Supabase project host for Perch accounts. */
|
|
28
|
+
export const PERCH_SUPABASE_HOST = "zlfuvsfjtgsdtqcaykia.supabase.co";
|
|
29
|
+
|
|
30
|
+
/** Supabase publishable key (used as `apikey`; not a JWT). */
|
|
31
|
+
export const PERCH_SUPABASE_PUBLISHABLE_KEY = "sb_publishable_w4R_pNkUpygBFIljZCoOlA_67ACg0FO";
|
|
32
|
+
|
|
33
|
+
/** Hosted app origin (the CLI's `appUrl`). */
|
|
34
|
+
export const PERCH_APP_URL = "https://app.perchai.app";
|
|
35
|
+
|
|
36
|
+
/** Provider id used by the registry. */
|
|
37
|
+
export const PERCH_PROVIDER_ID = "perch";
|
|
38
|
+
|
|
39
|
+
/** Context windows reported for the Roost tiers (client claims, 2.4.125). */
|
|
40
|
+
export const PERCH_CONTEXT_STANDARD = 200_000;
|
|
41
|
+
export const PERCH_CONTEXT_STANDARD_1M = 1_000_000;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Starter-pool model pins.
|
|
45
|
+
*
|
|
46
|
+
* Perch surfaces a fixed set of "Starter" model options on the free plan
|
|
47
|
+
* (bundle `k8e` / `zre`). A pin is selected by sending the upstream *backing*
|
|
48
|
+
* model id as `manualModelOptionId` (+ `preferredModelId`) with
|
|
49
|
+
* `strictManual: true` — verified live 2026-11-08: `glm-5.3-flash-starter`
|
|
50
|
+
* answered `model=z-ai/glm-5.3-flash`, `crouton-alpha` answered
|
|
51
|
+
* `model=stealth/crouton-alpha`, `deepseek-flash-starter` answered
|
|
52
|
+
* `model=deepseek-v4.1-flash-1017`. Sending only `preferredModelId` is
|
|
53
|
+
* ignored by the server (it falls back to the Roost default). `beam` and the
|
|
54
|
+
* slot backings were not verified (turn rate limit).
|
|
55
|
+
*/
|
|
56
|
+
export interface PerchStarterPin {
|
|
57
|
+
/** Roost tier to send alongside the pin. */
|
|
58
|
+
readonly roostTier: string;
|
|
59
|
+
/** The upstream backing model id (openrouter/…). */
|
|
60
|
+
readonly preferredModelId: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const PERCH_STARTER_PINS: Readonly<Record<string, PerchStarterPin>> = {
|
|
64
|
+
"glm-5.3-flash-starter": {
|
|
65
|
+
roostTier: "standard",
|
|
66
|
+
preferredModelId: "openrouter-z-ai-glm-5-3-flash-crusoe",
|
|
67
|
+
},
|
|
68
|
+
"deepseek-flash-starter": {
|
|
69
|
+
roostTier: "standard",
|
|
70
|
+
preferredModelId: "openrouter-deepseek-deepseek-v4-1-flash-crusoe",
|
|
71
|
+
},
|
|
72
|
+
"crouton-alpha": {
|
|
73
|
+
roostTier: "standard",
|
|
74
|
+
preferredModelId: "openrouter-stealth-crouton-alpha",
|
|
75
|
+
},
|
|
76
|
+
"beam-1.0-starter": {
|
|
77
|
+
roostTier: "standard",
|
|
78
|
+
preferredModelId: "openrouter-reflection-beam-1-0-501b-a23b",
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Perch required system prompt — TypeScript, Perch adapter, Bun standalone binary.
|
|
2
|
+
//
|
|
3
|
+
// This literal is the shipped value. The standalone binary cannot read
|
|
4
|
+
// `saffron-system-prompt.txt` from disk: Bun bundles JS but not runtime
|
|
5
|
+
// readFileSync() assets, so the old file-backed constant silently fell back
|
|
6
|
+
// to a two-line stub in dist/cartethyia.exe and the upstream answered
|
|
7
|
+
// `403 perch_surface_required`. Keep this in sync with the research copy in
|
|
8
|
+
// saffron-system-prompt.txt (a test asserts they match).
|
|
9
|
+
//
|
|
10
|
+
// Verified live 2026-11-08: the full 28k Saffron prompt answers, this
|
|
11
|
+
// trimmed prefix answers, a plain assistant placeholder returns 403.
|
|
12
|
+
|
|
13
|
+
export const PERCH_SAFFRON_SYSTEM_PROMPT: string = "You are Saffron — Perch's workspace operator.\n\nSharp, experienced, been around long enough to know what matters and what's noise.\nYou work with analysts, operators, and founders on financial analysis, audits,\ndocument review, and whatever the workspace needs. You have opinions. You're direct.\nYou genuinely enjoy finding something interesting buried in a mess.\n\nYou're not an assistant. You're the operator who gets the work done.\n";
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
// Perch adapter (pure HTTP).
|
|
2
|
+
//
|
|
3
|
+
// Auth: Supabase PKCE OAuth (see perch-oauth.ts). Inference: direct
|
|
4
|
+
// `POST /api/perch-terminal/model-call` with the CLI turn envelope — a fresh
|
|
5
|
+
// turn ticket minted with `{surface:"cli", profile:"standard"}`, the Roost
|
|
6
|
+
// tier (`roostModelChoice`), the account attribution, and the Saffron
|
|
7
|
+
// system-prompt prefix (the server fingerprints it and refuses anything else
|
|
8
|
+
// with `403 perch_surface_required`; verified live 2026-11-08).
|
|
9
|
+
//
|
|
10
|
+
// The stream is SSE (`answer_delta` / `done`); the adapter translates it into
|
|
11
|
+
// canonical events. No CLI process, no spawn, no external dependency.
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
CanonicalEvent,
|
|
15
|
+
CanonicalRequest,
|
|
16
|
+
UsageRecord,
|
|
17
|
+
} from "../../../protocol/canonical-model";
|
|
18
|
+
import { canonicalTerminal } from "../../../protocol/codecs";
|
|
19
|
+
import { GatewayError } from "../../../protocol/gateway-error";
|
|
20
|
+
import {
|
|
21
|
+
assertWireFamily,
|
|
22
|
+
readCredentialSecret,
|
|
23
|
+
type OutboundFetch,
|
|
24
|
+
type ProviderAdapter,
|
|
25
|
+
type ProviderDispatchContext,
|
|
26
|
+
type ProviderDispatchTarget,
|
|
27
|
+
type ProviderId,
|
|
28
|
+
} from "../../provider-contracts";
|
|
29
|
+
import { usageFromProvider } from "../../provider-usage";
|
|
30
|
+
import {
|
|
31
|
+
PERCH_APP_URL,
|
|
32
|
+
PERCH_CLI_USER_AGENT,
|
|
33
|
+
PERCH_SAFFRON_SYSTEM_PROMPT,
|
|
34
|
+
buildPerchModelCallBody,
|
|
35
|
+
parsePerchSse,
|
|
36
|
+
summarizePerchStream,
|
|
37
|
+
} from "./perch-protocol";
|
|
38
|
+
import {
|
|
39
|
+
PERCH_MODEL_PINS,
|
|
40
|
+
PERCH_MODEL_ROOST_TIERS,
|
|
41
|
+
} from "./perch-catalog";
|
|
42
|
+
import { PERCH_PROVIDER_ID } from "./perch-realm";
|
|
43
|
+
|
|
44
|
+
interface PerchAdapterOptions {
|
|
45
|
+
readonly providerId?: string;
|
|
46
|
+
/** Explicit transport override for tests; live dispatch uses `context.outbound_fetch`. */
|
|
47
|
+
readonly fetch?: OutboundFetch;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface PerchAccountSession {
|
|
51
|
+
readonly userId: string | null;
|
|
52
|
+
readonly workspaceId: string | null;
|
|
53
|
+
readonly orgId: string | null;
|
|
54
|
+
readonly planCode: string | null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class PerchAdapter implements ProviderAdapter {
|
|
58
|
+
readonly provider_id: ProviderId;
|
|
59
|
+
private readonly fetchOverride: OutboundFetch | undefined;
|
|
60
|
+
|
|
61
|
+
constructor(options: PerchAdapterOptions = {}) {
|
|
62
|
+
this.provider_id = (options.providerId ?? PERCH_PROVIDER_ID) as ProviderId;
|
|
63
|
+
this.fetchOverride = options.fetch;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The Saffron system-prompt prefix the upstream fingerprints. */
|
|
67
|
+
systemPrompt(): string | undefined {
|
|
68
|
+
return PERCH_SAFFRON_SYSTEM_PROMPT;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Reads `userId`/`workspaceId` from the account endpoint for attribution. */
|
|
72
|
+
async #accountSession(access: string, fetchFn: OutboundFetch): Promise<PerchAccountSession> {
|
|
73
|
+
try {
|
|
74
|
+
const res = await fetchFn(`${PERCH_APP_URL}/api/perchai/account`, {
|
|
75
|
+
headers: { authorization: `Bearer ${access}`, accept: "application/json" },
|
|
76
|
+
signal: AbortSignal.timeout(15_000),
|
|
77
|
+
});
|
|
78
|
+
if (!res.ok) return { userId: null, workspaceId: null, orgId: null, planCode: null };
|
|
79
|
+
const body = (await res.json()) as { session?: Record<string, unknown> };
|
|
80
|
+
const ctx = body.session ?? {};
|
|
81
|
+
return {
|
|
82
|
+
userId: typeof ctx["userId"] === "string" ? ctx["userId"] : null,
|
|
83
|
+
workspaceId: typeof ctx["workspaceId"] === "string" ? ctx["workspaceId"] : null,
|
|
84
|
+
orgId: typeof ctx["orgId"] === "string" ? ctx["orgId"] : null,
|
|
85
|
+
planCode: typeof ctx["planCode"] === "string" ? ctx["planCode"] : null,
|
|
86
|
+
};
|
|
87
|
+
} catch {
|
|
88
|
+
return { userId: null, workspaceId: null, orgId: null, planCode: null };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Mints a CLI turn ticket (`{surface:"cli", profile:"standard"}`). */
|
|
93
|
+
async #mintTicket(access: string, fetchFn: OutboundFetch): Promise<{ ticket: string; runId: string }> {
|
|
94
|
+
const res = await fetchFn(`${PERCH_APP_URL}/api/perch-terminal/turn-ticket`, {
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers: {
|
|
97
|
+
authorization: `Bearer ${access}`,
|
|
98
|
+
"content-type": "application/json",
|
|
99
|
+
"user-agent": PERCH_CLI_USER_AGENT,
|
|
100
|
+
"x-perch-locale": "en",
|
|
101
|
+
},
|
|
102
|
+
body: JSON.stringify({ surface: "cli", profile: "standard" }),
|
|
103
|
+
signal: AbortSignal.timeout(15_000),
|
|
104
|
+
});
|
|
105
|
+
const body = (await res.json().catch(() => null)) as { ok?: boolean; ticket?: string; runId?: string; error?: string; errorCode?: string } | null;
|
|
106
|
+
if (!res.ok || !body?.ok || !body.ticket || !body.runId) {
|
|
107
|
+
const message = body?.error ?? `HTTP ${res.status}`;
|
|
108
|
+
const providerCode = body?.errorCode;
|
|
109
|
+
// Upstream verdict: 429/turn_rate_limited cools this account so routing
|
|
110
|
+
// tries the next one instead of hammering the same key all day.
|
|
111
|
+
if (res.status === 429 || providerCode === "turn_rate_limited") {
|
|
112
|
+
throw upstreamPerchError("rate_limited", 429, `Perch turn ticket mint failed: ${message}`, providerCode);
|
|
113
|
+
}
|
|
114
|
+
throw upstreamPerchError("authentication_failed", 401, `Perch turn ticket mint failed: ${message}`, providerCode);
|
|
115
|
+
}
|
|
116
|
+
return { ticket: body.ticket, runId: body.runId };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async *dispatch(
|
|
120
|
+
request: CanonicalRequest,
|
|
121
|
+
candidate: ProviderDispatchTarget,
|
|
122
|
+
context: ProviderDispatchContext,
|
|
123
|
+
): AsyncIterable<CanonicalEvent> {
|
|
124
|
+
if (candidate.wire_family !== "chat") {
|
|
125
|
+
assertWireFamily(candidate, "chat", "perch");
|
|
126
|
+
}
|
|
127
|
+
const secret = readCredentialSecret(context.credential, "Perch credential is required");
|
|
128
|
+
const modelId = candidate.model_id || request.model || "standard";
|
|
129
|
+
const access = secret.startsWith("{") ? parseAccessToken(secret) : secret.trim();
|
|
130
|
+
// Probes pass a validated outbound fetch (with capture wrapped around it);
|
|
131
|
+
// live dispatch passes one too. `globalThis.fetch` here silently split the
|
|
132
|
+
// probe from the route's egress policy and left Request Detail empty.
|
|
133
|
+
const fetchFn = context.outbound_fetch ?? this.fetchOverride ?? globalThis.fetch;
|
|
134
|
+
|
|
135
|
+
let seq = 0;
|
|
136
|
+
yield {
|
|
137
|
+
type: "message_start",
|
|
138
|
+
sequence_number: ++seq,
|
|
139
|
+
model: modelId,
|
|
140
|
+
timestamp: Date.now(),
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const ticket = await this.#mintTicket(access, fetchFn);
|
|
144
|
+
const account = await this.#accountSession(access, fetchFn);
|
|
145
|
+
|
|
146
|
+
const body = buildPerchModelCallBody(request, modelId, {
|
|
147
|
+
userId: account.userId,
|
|
148
|
+
workspaceId: account.workspaceId,
|
|
149
|
+
orgId: account.orgId,
|
|
150
|
+
planCode: account.planCode,
|
|
151
|
+
runId: ticket.runId,
|
|
152
|
+
pin: PERCH_MODEL_PINS[modelId],
|
|
153
|
+
roostTier: PERCH_MODEL_ROOST_TIERS[modelId],
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const controller = new AbortController();
|
|
157
|
+
const onAbort = () => controller.abort();
|
|
158
|
+
context.abort_signal.addEventListener("abort", onAbort, { once: true });
|
|
159
|
+
let streamText = "";
|
|
160
|
+
try {
|
|
161
|
+
const res = await fetchFn(`${PERCH_APP_URL}/api/perch-terminal/model-call`, {
|
|
162
|
+
method: "POST",
|
|
163
|
+
headers: {
|
|
164
|
+
authorization: `Bearer ${access}`,
|
|
165
|
+
"content-type": "application/json",
|
|
166
|
+
accept: "text/event-stream",
|
|
167
|
+
"user-agent": PERCH_CLI_USER_AGENT,
|
|
168
|
+
"x-perch-locale": "en",
|
|
169
|
+
"x-perch-call-delta": "1",
|
|
170
|
+
"x-perch-turn-ticket": ticket.ticket,
|
|
171
|
+
},
|
|
172
|
+
body: JSON.stringify(body),
|
|
173
|
+
signal: controller.signal,
|
|
174
|
+
});
|
|
175
|
+
const raw = await res.text();
|
|
176
|
+
if (!res.ok) {
|
|
177
|
+
let message = `Perch model-call failed: HTTP ${res.status}`;
|
|
178
|
+
let providerCode: string | undefined;
|
|
179
|
+
try {
|
|
180
|
+
const parsed = JSON.parse(raw) as { error?: string; errorCode?: string; limit?: Record<string, unknown> };
|
|
181
|
+
if (parsed.error) message = parsed.error;
|
|
182
|
+
providerCode = parsed.errorCode;
|
|
183
|
+
if (parsed.errorCode === "starter_model_blocked" || parsed.errorCode === "plan_model_blocked") {
|
|
184
|
+
throw upstreamPerchError("policy_rejected", 403, message, providerCode);
|
|
185
|
+
}
|
|
186
|
+
if (res.status === 429 || parsed.errorCode === "turn_rate_limited") {
|
|
187
|
+
throw upstreamPerchError("rate_limited", 429, message, providerCode);
|
|
188
|
+
}
|
|
189
|
+
} catch (e) {
|
|
190
|
+
if (e instanceof GatewayError) throw e;
|
|
191
|
+
}
|
|
192
|
+
throw upstreamPerchError("platform_unavailable", 502, message, providerCode);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const events = parsePerchSse(raw);
|
|
196
|
+
const result = summarizePerchStream(events);
|
|
197
|
+
streamText = result.text;
|
|
198
|
+
|
|
199
|
+
if (!result.ok) {
|
|
200
|
+
const message = result.error ?? "Perch returned no answer";
|
|
201
|
+
const category = result.errorCode === "usage_limit_reached" ? "usage_limit" : "server_error";
|
|
202
|
+
yield {
|
|
203
|
+
type: "error",
|
|
204
|
+
sequence_number: ++seq,
|
|
205
|
+
category,
|
|
206
|
+
message,
|
|
207
|
+
retryable: false,
|
|
208
|
+
timestamp: Date.now(),
|
|
209
|
+
};
|
|
210
|
+
yield canonicalTerminal({
|
|
211
|
+
sequenceNumber: ++seq,
|
|
212
|
+
state: "failed",
|
|
213
|
+
stopReason: "error",
|
|
214
|
+
providerStopReason: result.errorCode ?? "error",
|
|
215
|
+
});
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (streamText.length > 0) {
|
|
220
|
+
yield {
|
|
221
|
+
type: "content_delta",
|
|
222
|
+
sequence_number: ++seq,
|
|
223
|
+
content: { kind: "text", text: streamText },
|
|
224
|
+
timestamp: Date.now(),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const usage = result.usage ? usageFromProvider(normalizePerchUsage(result.usage)) : undefined;
|
|
229
|
+
const usageRecord: UsageRecord = usage ?? {
|
|
230
|
+
input_tokens: 0,
|
|
231
|
+
cached_input_tokens: "unavailable",
|
|
232
|
+
cache_write_tokens: "unavailable",
|
|
233
|
+
uncached_input_tokens: "unavailable",
|
|
234
|
+
output_tokens: 0,
|
|
235
|
+
reasoning_tokens: "unavailable",
|
|
236
|
+
estimated_cost: null,
|
|
237
|
+
total_tokens: 0,
|
|
238
|
+
};
|
|
239
|
+
yield {
|
|
240
|
+
type: "usage",
|
|
241
|
+
sequence_number: ++seq,
|
|
242
|
+
usage: usageRecord,
|
|
243
|
+
timestamp: Date.now(),
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const terminalEvent = canonicalTerminal({
|
|
247
|
+
sequenceNumber: ++seq,
|
|
248
|
+
state: "complete",
|
|
249
|
+
stopReason: "stop",
|
|
250
|
+
providerStopReason: "stop",
|
|
251
|
+
usage: usageRecord,
|
|
252
|
+
});
|
|
253
|
+
yield {
|
|
254
|
+
...terminalEvent,
|
|
255
|
+
...(result.model ? { provider_metadata: { model: result.model, provider: result.provider } } : {}),
|
|
256
|
+
};
|
|
257
|
+
} catch (error) {
|
|
258
|
+
if (error instanceof GatewayError) throw error;
|
|
259
|
+
if (controller.signal.aborted) {
|
|
260
|
+
throw new GatewayError("transport_closed", 499, "Perch request aborted");
|
|
261
|
+
}
|
|
262
|
+
throw new GatewayError("transport_unavailable", 502, `Perch request failed: ${(error as Error).message}`);
|
|
263
|
+
} finally {
|
|
264
|
+
context.abort_signal.removeEventListener("abort", onAbort);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Maps Perch's camelCase usage fields onto the canonical snake_case names. */
|
|
270
|
+
function normalizePerchUsage(usage: Record<string, unknown>): Record<string, unknown> {
|
|
271
|
+
const out: Record<string, unknown> = { ...usage };
|
|
272
|
+
if (out["input_tokens"] === undefined && typeof usage["inputTokens"] === "number") {
|
|
273
|
+
out["input_tokens"] = usage["inputTokens"];
|
|
274
|
+
}
|
|
275
|
+
if (out["output_tokens"] === undefined && typeof usage["outputTokens"] === "number") {
|
|
276
|
+
out["output_tokens"] = usage["outputTokens"];
|
|
277
|
+
}
|
|
278
|
+
if (out["total_tokens"] === undefined && typeof usage["totalTokens"] === "number") {
|
|
279
|
+
out["total_tokens"] = usage["totalTokens"];
|
|
280
|
+
}
|
|
281
|
+
return out;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function parseAccessToken(secret: string): string {
|
|
285
|
+
try {
|
|
286
|
+
const parsed = JSON.parse(secret) as Record<string, unknown>;
|
|
287
|
+
const found = parsed["accessToken"] ?? parsed["access_token"];
|
|
288
|
+
return typeof found === "string" ? found : secret.trim();
|
|
289
|
+
} catch {
|
|
290
|
+
return secret.trim();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Builds an upstream verdict for the Perch surface. `origin: "upstream"` is
|
|
296
|
+
* what lets the account health machine see the failure: `attempt-finalize`
|
|
297
|
+
* only records health for `GatewayError`s whose origin is upstream, so a
|
|
298
|
+
* `turn_rate_limited` minted as a local error never cooled the account and
|
|
299
|
+
* routing kept dialing the same key.
|
|
300
|
+
*/
|
|
301
|
+
function upstreamPerchError(
|
|
302
|
+
code: GatewayError["code"],
|
|
303
|
+
status: number,
|
|
304
|
+
message: string,
|
|
305
|
+
providerCode: string | undefined,
|
|
306
|
+
): GatewayError {
|
|
307
|
+
return new GatewayError(code, status, message, {
|
|
308
|
+
providerId: PERCH_PROVIDER_ID,
|
|
309
|
+
...(providerCode ? { providerCode } : {}),
|
|
310
|
+
}, "upstream");
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function createPerchAdapter(options: PerchAdapterOptions = {}): PerchAdapter {
|
|
314
|
+
return new PerchAdapter(options);
|
|
315
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
You are Saffron — Perch's workspace operator.
|
|
2
|
+
|
|
3
|
+
Sharp, experienced, been around long enough to know what matters and what's noise.
|
|
4
|
+
You work with analysts, operators, and founders on financial analysis, audits,
|
|
5
|
+
document review, and whatever the workspace needs. You have opinions. You're direct.
|
|
6
|
+
You genuinely enjoy finding something interesting buried in a mess.
|
|
7
|
+
|
|
8
|
+
You're not an assistant. You're the operator who gets the work done.
|