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,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devin (Codeium Cascade) Connect framing.
|
|
3
|
+
*
|
|
4
|
+
* Connect protocol over HTTP/1.1: a request body is a single frame
|
|
5
|
+
* `[flags 1 byte][length 4 bytes BE][payload]`; the response is a stream of
|
|
6
|
+
* the same frames. Flags: 0x01 = payload gzip-compressed, 0x02 = end-of-
|
|
7
|
+
* stream (trailer JSON). Ported from oh-my-pi / opencode-devin-provider
|
|
8
|
+
* (MIT) and adapted to Cartethyia.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { randomBytes } from "node:crypto";
|
|
12
|
+
import { gzipSync, gunzipSync } from "node:zlib";
|
|
13
|
+
|
|
14
|
+
export const CONNECT_COMPRESSED_FLAG = 0x01;
|
|
15
|
+
export const CONNECT_END_STREAM_FLAG = 0x02;
|
|
16
|
+
|
|
17
|
+
/** Cap on one frame payload, guarding against a hostile length prefix. */
|
|
18
|
+
export const MAX_CONNECT_FRAME_PAYLOAD = 16 * 1024 * 1024;
|
|
19
|
+
|
|
20
|
+
/** Request bodies at or above this size are gzip-compressed. */
|
|
21
|
+
export const MIN_COMPRESSED_BODY_BYTES = 1024;
|
|
22
|
+
|
|
23
|
+
export class ConnectFrameError extends Error {
|
|
24
|
+
constructor(message: string) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "ConnectFrameError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ConnectFrame {
|
|
31
|
+
readonly flags: number;
|
|
32
|
+
readonly payload: Uint8Array;
|
|
33
|
+
readonly compressed: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Encodes one payload into a Connect frame, compressing when `compress` is set. */
|
|
37
|
+
export function encodeFrame(payload: Uint8Array, compress: boolean): Buffer {
|
|
38
|
+
const useCompression = compress && payload.byteLength >= MIN_COMPRESSED_BODY_BYTES;
|
|
39
|
+
const body = useCompression ? gzipSync(payload) : Buffer.from(payload);
|
|
40
|
+
const flags = useCompression ? CONNECT_COMPRESSED_FLAG : 0;
|
|
41
|
+
const header = Buffer.alloc(5);
|
|
42
|
+
header[0] = flags;
|
|
43
|
+
header.writeUInt32BE(body.length, 1);
|
|
44
|
+
return Buffer.concat([header, body]);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Decodes one frame from a buffer containing exactly one frame. */
|
|
48
|
+
export function decodeFrame(buf: Buffer): ConnectFrame {
|
|
49
|
+
if (buf.length < 5) {
|
|
50
|
+
throw new ConnectFrameError(`frame shorter than 5-byte header (${buf.length})`);
|
|
51
|
+
}
|
|
52
|
+
const flags = buf[0]!;
|
|
53
|
+
const length = buf.readUInt32BE(1);
|
|
54
|
+
if (length > MAX_CONNECT_FRAME_PAYLOAD) {
|
|
55
|
+
throw new ConnectFrameError(`frame payload ${length} exceeds ${MAX_CONNECT_FRAME_PAYLOAD} cap`);
|
|
56
|
+
}
|
|
57
|
+
const payload = buf.subarray(5, 5 + length);
|
|
58
|
+
return {
|
|
59
|
+
flags,
|
|
60
|
+
payload,
|
|
61
|
+
compressed: (flags & CONNECT_COMPRESSED_FLAG) !== 0,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Decompresses a frame payload if its compressed flag is set. */
|
|
66
|
+
export function deframe(payload: Uint8Array, compressed: boolean): Buffer {
|
|
67
|
+
return compressed ? gunzipSync(payload) : Buffer.from(payload);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Incremental frame reader over a byte stream. Consumes as many complete
|
|
72
|
+
* frames as the input holds; a trailing partial frame is buffered until more
|
|
73
|
+
* bytes arrive.
|
|
74
|
+
*/
|
|
75
|
+
export class ConnectFrameDecoder {
|
|
76
|
+
#buffer = Buffer.alloc(0);
|
|
77
|
+
|
|
78
|
+
push(chunk: Uint8Array): ConnectFrame[] {
|
|
79
|
+
this.#buffer = this.#buffer.length === 0 ? Buffer.from(chunk) : Buffer.concat([this.#buffer, chunk]);
|
|
80
|
+
const frames: ConnectFrame[] = [];
|
|
81
|
+
for (;;) {
|
|
82
|
+
if (this.#buffer.length < 5) break;
|
|
83
|
+
const length = this.#buffer.readUInt32BE(1);
|
|
84
|
+
if (length > MAX_CONNECT_FRAME_PAYLOAD) {
|
|
85
|
+
throw new ConnectFrameError(`frame payload ${length} exceeds ${MAX_CONNECT_FRAME_PAYLOAD} cap`);
|
|
86
|
+
}
|
|
87
|
+
if (this.#buffer.length < 5 + length) break;
|
|
88
|
+
frames.push(decodeFrame(this.#buffer.subarray(0, 5 + length)));
|
|
89
|
+
this.#buffer = this.#buffer.subarray(5 + length);
|
|
90
|
+
}
|
|
91
|
+
return frames;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** True when a partial frame is buffered (stream ended mid-frame). */
|
|
95
|
+
get hasPartial(): boolean {
|
|
96
|
+
return this.#buffer.length > 0;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Parses the Connect end-of-stream trailer JSON: `{error:{code,message}}`. */
|
|
101
|
+
export interface ConnectTrailer {
|
|
102
|
+
readonly error?: { readonly code?: string; readonly message?: string };
|
|
103
|
+
readonly details?: unknown;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function parseTrailer(payload: Uint8Array): ConnectTrailer {
|
|
107
|
+
const text = new TextDecoder().decode(payload).trim();
|
|
108
|
+
if (!text) return {};
|
|
109
|
+
try {
|
|
110
|
+
const parsed = JSON.parse(text) as ConnectTrailer;
|
|
111
|
+
return parsed;
|
|
112
|
+
} catch {
|
|
113
|
+
return {};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Deterministic random UUID for execution ids (avoids importing node crypto in browser-safe paths). */
|
|
118
|
+
export function newExecutionId(): string {
|
|
119
|
+
const bytes = randomBytes(16);
|
|
120
|
+
bytes[6] = (bytes[6]! & 0x0f) | 0x40;
|
|
121
|
+
bytes[8] = (bytes[8]! & 0x3f) | 0x80;
|
|
122
|
+
const hex = bytes.toString("hex");
|
|
123
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
124
|
+
}
|
|
@@ -555,7 +555,7 @@ class FactoryDroidAdapter implements ProviderAdapter {
|
|
|
555
555
|
yield {
|
|
556
556
|
type: "content_delta",
|
|
557
557
|
sequence_number: seq++,
|
|
558
|
-
content: { kind: "reasoning", payload: null, summary: output.thought },
|
|
558
|
+
content: { kind: "reasoning", payload: null, summary: output.thought, ...(output.thoughtSignature ? { signature: output.thoughtSignature } : {}) },
|
|
559
559
|
} as CanonicalEvent;
|
|
560
560
|
}
|
|
561
561
|
if (output.text) {
|
|
@@ -606,7 +606,7 @@ class FactoryDroidAdapter implements ProviderAdapter {
|
|
|
606
606
|
yield {
|
|
607
607
|
type: "content_delta",
|
|
608
608
|
sequence_number: seq++,
|
|
609
|
-
content: { kind: "reasoning", payload: null, summary: output.thought },
|
|
609
|
+
content: { kind: "reasoning", payload: null, summary: output.thought, ...(output.thoughtSignature ? { signature: output.thoughtSignature } : {}) },
|
|
610
610
|
} as CanonicalEvent;
|
|
611
611
|
}
|
|
612
612
|
if (output.text) {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Free-pi model roster.
|
|
2
|
+
//
|
|
3
|
+
// The live roster is server-driven: `GET /client-version` returns the full
|
|
4
|
+
// model list (`models[]`, with `section` and `tier`). The static roster here
|
|
5
|
+
// is only the fallback when that call fails, mirroring the Trae pattern. All
|
|
6
|
+
// rows use the chat completions wire.
|
|
7
|
+
//
|
|
8
|
+
// Capability/funding facts (from the live 2026-11-08 sweep on a GitHub
|
|
9
|
+
// account): the four "Free Unlimited" rows (particle, deepseek-v4.1-flash,
|
|
10
|
+
// glm-5.3-flash, deepseek-v4-flash) stream for everyone; everything else is
|
|
11
|
+
// either premium (billed from the allowance) or defense (section "defense").
|
|
12
|
+
// Context is 200k and output 8k per the client bundle; these are the client's
|
|
13
|
+
// own claims, not upstream model truth.
|
|
14
|
+
|
|
15
|
+
import { defineModel, type ModelDefinition } from "../../provider-model-definition";
|
|
16
|
+
import {
|
|
17
|
+
FREEPI_CHAT_PATH,
|
|
18
|
+
FREEPI_CONTEXT_WINDOW,
|
|
19
|
+
FREEPI_MAX_OUTPUT_TOKENS,
|
|
20
|
+
FREEPI_PROVIDER_ID,
|
|
21
|
+
} from "./freepi-realm";
|
|
22
|
+
|
|
23
|
+
interface FreepiRow {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly displayName: string;
|
|
26
|
+
readonly free?: boolean;
|
|
27
|
+
readonly proOnly?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const FREEPI_ROWS: readonly FreepiRow[] = [
|
|
31
|
+
// Only the "Free Unlimited" rows ship in the static roster: they are the
|
|
32
|
+
// ones the live sweep confirmed stream for every account type (guest +
|
|
33
|
+
// GitHub). Paid/defense rows are left to live discovery, which keeps the
|
|
34
|
+
// whole catalog; the fallback must never advertise a model the account
|
|
35
|
+
// cannot serve.
|
|
36
|
+
{ id: "deepseek-v4.1-flash", displayName: "DeepSeek V4.1 Flash", free: true },
|
|
37
|
+
{ id: "glm-5.3-flash", displayName: "GLM 5.3 Flash", free: true },
|
|
38
|
+
{ id: "particle", displayName: "Particle · Free Unlimited", free: true },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
export const FREEPI_MODELS: readonly ModelDefinition[] = FREEPI_ROWS.map((row) =>
|
|
42
|
+
defineModel({
|
|
43
|
+
id: row.id,
|
|
44
|
+
providerId: FREEPI_PROVIDER_ID,
|
|
45
|
+
wireFamily: "chat",
|
|
46
|
+
endpoint: FREEPI_CHAT_PATH,
|
|
47
|
+
ctx: FREEPI_CONTEXT_WINDOW,
|
|
48
|
+
out: FREEPI_MAX_OUTPUT_TOKENS,
|
|
49
|
+
// No vision/audio/document claims: the upstream `/client-version` catalog
|
|
50
|
+
// publishes only id/name/section/tier, and the live sweep verified text
|
|
51
|
+
// completions but no other modality. Everything streamed reasoning
|
|
52
|
+
// content, and the agent wire carries tools, so reasoning + toolCall are
|
|
53
|
+
// the only declared capabilities.
|
|
54
|
+
vision: false,
|
|
55
|
+
reasoning: true,
|
|
56
|
+
toolCall: true,
|
|
57
|
+
...(row.free ? { free: true, freeTier: true } : {}),
|
|
58
|
+
...(row.proOnly ? { proOnly: true } : {}),
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Free-pi live model discovery.
|
|
2
|
+
//
|
|
3
|
+
// `GET /client-version` (Bearer) returns the full model catalog:
|
|
4
|
+
// `models[]` with `{id, name, section?, tier?}` plus `fusion`, `model`, and
|
|
5
|
+
// `notice`. Discovery parses that into `ModelDefinition` rows. The static
|
|
6
|
+
// roster remains the fallback when the live call fails (mirrors the Trae
|
|
7
|
+
// pattern).
|
|
8
|
+
//
|
|
9
|
+
// Normalization:
|
|
10
|
+
// - The CLI's default id is `deepseek/deepseek-v4-flash`, while the catalog
|
|
11
|
+
// lists the bare `deepseek-v4-flash`. They are one model; discovery maps
|
|
12
|
+
// the bare id onto the default id so a single row survives (no duplicate,
|
|
13
|
+
// no ambiguous pair inside the provider).
|
|
14
|
+
// - Every discovered row is kept as-is (no free/pro filtering); the static
|
|
15
|
+
// roster is the fallback and carries only the free rows.
|
|
16
|
+
// - No vision/audio/document claims: the catalog publishes only
|
|
17
|
+
// id/name/section/tier, and the live sweep verified text completions only.
|
|
18
|
+
|
|
19
|
+
import { defineModel, type ModelDefinition } from "../../provider-model-definition";
|
|
20
|
+
import {
|
|
21
|
+
FREEPI_CHAT_PATH,
|
|
22
|
+
FREEPI_CLI_VERSION,
|
|
23
|
+
FREEPI_CONTEXT_WINDOW,
|
|
24
|
+
FREEPI_DEFAULT_MODEL,
|
|
25
|
+
FREEPI_MAX_OUTPUT_TOKENS,
|
|
26
|
+
FREEPI_PROVIDER_ID,
|
|
27
|
+
} from "./freepi-realm";
|
|
28
|
+
|
|
29
|
+
/** Catalog id → canonical roster id for one model. */
|
|
30
|
+
const CATALOG_ID_REMAP: Readonly<Record<string, string>> = {
|
|
31
|
+
"deepseek-v4-flash": FREEPI_DEFAULT_MODEL,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Fetches the live model catalog. */
|
|
35
|
+
export async function discoverFreepiModels(input: {
|
|
36
|
+
readonly token: string;
|
|
37
|
+
readonly fetcher: typeof fetch;
|
|
38
|
+
readonly signal?: AbortSignal;
|
|
39
|
+
readonly providerId?: string;
|
|
40
|
+
}): Promise<readonly ModelDefinition[]> {
|
|
41
|
+
const url = new URL("/client-version", "https://api.freepi.ai");
|
|
42
|
+
const response = await input.fetcher(url, {
|
|
43
|
+
method: "GET",
|
|
44
|
+
headers: {
|
|
45
|
+
authorization: `Bearer ${input.token}`,
|
|
46
|
+
accept: "application/json",
|
|
47
|
+
"x-client-version": FREEPI_CLI_VERSION,
|
|
48
|
+
},
|
|
49
|
+
...(input.signal ? { signal: input.signal } : {}),
|
|
50
|
+
});
|
|
51
|
+
if (!response.ok) return [];
|
|
52
|
+
let parsed: Record<string, unknown>;
|
|
53
|
+
try {
|
|
54
|
+
parsed = (await response.json()) as Record<string, unknown>;
|
|
55
|
+
} catch {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
const rows = Array.isArray(parsed["models"]) ? (parsed["models"] as Record<string, unknown>[]) : [];
|
|
59
|
+
const seen = new Set<string>();
|
|
60
|
+
const out: ModelDefinition[] = [];
|
|
61
|
+
for (const row of rows) {
|
|
62
|
+
const rawId = String(row["id"] ?? "").trim();
|
|
63
|
+
if (!rawId) continue;
|
|
64
|
+
const id = CATALOG_ID_REMAP[rawId] ?? rawId;
|
|
65
|
+
if (seen.has(id)) continue;
|
|
66
|
+
seen.add(id);
|
|
67
|
+
out.push(
|
|
68
|
+
defineModel({
|
|
69
|
+
id,
|
|
70
|
+
providerId: input.providerId ?? FREEPI_PROVIDER_ID,
|
|
71
|
+
wireFamily: "chat",
|
|
72
|
+
endpoint: FREEPI_CHAT_PATH,
|
|
73
|
+
ctx: FREEPI_CONTEXT_WINDOW,
|
|
74
|
+
out: FREEPI_MAX_OUTPUT_TOKENS,
|
|
75
|
+
vision: false,
|
|
76
|
+
reasoning: true,
|
|
77
|
+
toolCall: true,
|
|
78
|
+
}),
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Free-pi request headers.
|
|
2
|
+
//
|
|
3
|
+
// The client sends `Authorization: Bearer <jwt>` plus `x-client-version` (the
|
|
4
|
+
// CLI version) on the chat wire, and uses `application/json`. There is no
|
|
5
|
+
// device fingerprint — the JWT is the whole identity.
|
|
6
|
+
|
|
7
|
+
import { FREEPI_CLI_VERSION } from "./freepi-realm";
|
|
8
|
+
|
|
9
|
+
/** Builds the header set for one free-pi request. */
|
|
10
|
+
export function buildFreepiHeaders(jwt: string, stream = false, sessionId?: string): Record<string, string> {
|
|
11
|
+
const headers: Record<string, string> = {
|
|
12
|
+
authorization: `Bearer ${jwt}`,
|
|
13
|
+
"content-type": "application/json",
|
|
14
|
+
accept: stream ? "text/event-stream" : "application/json",
|
|
15
|
+
"x-client-version": FREEPI_CLI_VERSION,
|
|
16
|
+
};
|
|
17
|
+
if (sessionId !== undefined && sessionId.length > 0) {
|
|
18
|
+
headers["x-session-id"] = sessionId;
|
|
19
|
+
}
|
|
20
|
+
return headers;
|
|
21
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// Free-pi credential login (device flow) and refresh.
|
|
2
|
+
//
|
|
3
|
+
// The backend has no standard OAuth token endpoint; the device flow is:
|
|
4
|
+
//
|
|
5
|
+
// 1. POST /auth/github/device -> { session_id, user_code, verification_uri, interval }
|
|
6
|
+
// 2. POST /auth/consent -> { session_id, consent_version: "4" }
|
|
7
|
+
// 3. POST /auth/token (poll) -> pending / { token }
|
|
8
|
+
//
|
|
9
|
+
// The issued token is a JWT used as `Authorization: Bearer` on every data-plane
|
|
10
|
+
// call. There is no refresh token: the JWT is long-lived (the CLI just re-runs
|
|
11
|
+
// the flow). Pasting a raw JWT is supported via `importCredential`.
|
|
12
|
+
|
|
13
|
+
import { OAuthClient } from "../../authentication/oauth-client";
|
|
14
|
+
import { decodeJwtPayload } from "../../authentication/oauth-flow-store";
|
|
15
|
+
import type {
|
|
16
|
+
OAuthDeviceStartResult,
|
|
17
|
+
OAuthDevicePollResult,
|
|
18
|
+
OAuthDeviceFlowContext,
|
|
19
|
+
OAuthExchangeResult,
|
|
20
|
+
OAuthImportInput,
|
|
21
|
+
} from "../../authentication/oauth-flow-store";
|
|
22
|
+
|
|
23
|
+
const FREEPI_DEVICE_START_PATH = "/auth/github/device";
|
|
24
|
+
const FREEPI_CONSENT_PATH = "/auth/consent";
|
|
25
|
+
const FREEPI_TOKEN_PATH = "/auth/token";
|
|
26
|
+
const FREEPI_BASE_URL = "https://api.freepi.ai";
|
|
27
|
+
const FREEPI_CONSENT_VERSION = "4";
|
|
28
|
+
|
|
29
|
+
export class FreepiOAuthClient extends OAuthClient {
|
|
30
|
+
readonly supportsDeviceCode = true;
|
|
31
|
+
readonly supportsBrowserCode = false;
|
|
32
|
+
protected readonly providerLabel = "free-pi";
|
|
33
|
+
protected readonly clientId = "";
|
|
34
|
+
protected readonly tokenUrl = "";
|
|
35
|
+
protected readonly scopes = "";
|
|
36
|
+
|
|
37
|
+
async startDeviceAuth(): Promise<OAuthDeviceStartResult> {
|
|
38
|
+
const res = await this.fetchFn(new URL(FREEPI_DEVICE_START_PATH, FREEPI_BASE_URL), {
|
|
39
|
+
method: "POST",
|
|
40
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
41
|
+
body: JSON.stringify({}),
|
|
42
|
+
});
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
const text = await res.text().catch(() => "");
|
|
45
|
+
throw new Error(`free-pi device flow start failed: HTTP ${res.status} ${text.slice(0, 300)}`);
|
|
46
|
+
}
|
|
47
|
+
const body = (await res.json()) as Record<string, unknown>;
|
|
48
|
+
const sessionId = String(body["session_id"] ?? "");
|
|
49
|
+
// The upstream requires consent before the token poll answers: without it,
|
|
50
|
+
// `/auth/token` returns 403 `consent_required`. Submit it once here so
|
|
51
|
+
// the device flow is complete before the dashboard starts polling.
|
|
52
|
+
if (sessionId) {
|
|
53
|
+
try {
|
|
54
|
+
await this.fetchFn(new URL(FREEPI_CONSENT_PATH, FREEPI_BASE_URL), {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
57
|
+
body: JSON.stringify({ session_id: sessionId, consent_version: FREEPI_CONSENT_VERSION }),
|
|
58
|
+
});
|
|
59
|
+
} catch {
|
|
60
|
+
// Consent failure is non-fatal at start; the poll will surface it.
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
verificationUri: String(body["verification_uri"] ?? ""),
|
|
65
|
+
userCode: String(body["user_code"] ?? ""),
|
|
66
|
+
deviceAuthId: sessionId,
|
|
67
|
+
intervalSeconds: Number(body["interval"] ?? 5),
|
|
68
|
+
expiresInSeconds: Number(body["expires_in"] ?? 900),
|
|
69
|
+
providerState: JSON.stringify({ session_id: sessionId }),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async pollDeviceAuth(deviceAuthId: string, _context?: OAuthDeviceFlowContext): Promise<OAuthDevicePollResult> {
|
|
74
|
+
let providerState: Record<string, unknown> = {};
|
|
75
|
+
try {
|
|
76
|
+
providerState = JSON.parse(_context?.providerState ?? "{}") as Record<string, unknown>;
|
|
77
|
+
} catch {
|
|
78
|
+
/* ignore */
|
|
79
|
+
}
|
|
80
|
+
const sessionId = String(providerState["session_id"] ?? deviceAuthId);
|
|
81
|
+
const res = await this.fetchFn(new URL(FREEPI_TOKEN_PATH, FREEPI_BASE_URL), {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
84
|
+
body: JSON.stringify({ session_id: sessionId }),
|
|
85
|
+
});
|
|
86
|
+
if (!res.ok) {
|
|
87
|
+
return { status: "failed", reason: `token poll failed: HTTP ${res.status}` };
|
|
88
|
+
}
|
|
89
|
+
const body = (await res.json()) as Record<string, unknown>;
|
|
90
|
+
if (typeof body["token"] === "string" && (body["token"] as string).length > 0) {
|
|
91
|
+
const token = body["token"] as string;
|
|
92
|
+
// The free-pi JWT carries the GitHub user identity (`sub` = GitHub user
|
|
93
|
+
// id, `email` when the account has one). Resolving it keeps one account
|
|
94
|
+
// per GitHub user — without it every device login minted the same
|
|
95
|
+
// generic label and the second account replaced the first (the identity
|
|
96
|
+
// fingerprint collided on the static label).
|
|
97
|
+
const claims = decodeJwtPayload(token);
|
|
98
|
+
const userId = typeof claims?.["sub"] === "string" ? claims["sub"] : undefined;
|
|
99
|
+
const email = typeof claims?.["email"] === "string" ? (claims["email"] as string).trim() : "";
|
|
100
|
+
const label = email.length > 0 ? email : userId ? `freepi-${userId}` : "freepi-device";
|
|
101
|
+
return {
|
|
102
|
+
status: "complete",
|
|
103
|
+
result: {
|
|
104
|
+
access: token,
|
|
105
|
+
refresh: "",
|
|
106
|
+
expiresAt: new Date(Date.now() + 30 * 24 * 3600 * 1000),
|
|
107
|
+
accountLabel: label,
|
|
108
|
+
// The upstream identity the token resolved, stored beside the token
|
|
109
|
+
// so a later login of the same GitHub user lands on the same row
|
|
110
|
+
// (and a *different* user on a different row).
|
|
111
|
+
...(userId || email
|
|
112
|
+
? { auth_state: { ...(userId ? { freepi_user_id: userId } : {}), ...(email ? { email } : {}) } }
|
|
113
|
+
: {}),
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (body["code"] === "consent_required") {
|
|
118
|
+
return { status: "failed", reason: "consent_required" };
|
|
119
|
+
}
|
|
120
|
+
return { status: "pending" };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async importCredential(input: OAuthImportInput): Promise<OAuthExchangeResult> {
|
|
124
|
+
const token = input.credential.trim();
|
|
125
|
+
if (!token) throw new Error("free-pi: a JWT credential is required");
|
|
126
|
+
const claims = decodeJwtPayload(token);
|
|
127
|
+
const userId = typeof claims?.["sub"] === "string" ? claims["sub"] : undefined;
|
|
128
|
+
const email = typeof claims?.["email"] === "string" ? (claims["email"] as string).trim() : "";
|
|
129
|
+
const label = email.length > 0 ? email : userId ? `freepi-${userId}` : input.accountLabel ?? "freepi-jwt";
|
|
130
|
+
return {
|
|
131
|
+
access: token,
|
|
132
|
+
refresh: "",
|
|
133
|
+
expiresAt: new Date(Date.now() + 30 * 24 * 3600 * 1000),
|
|
134
|
+
accountLabel: label,
|
|
135
|
+
...(userId || email
|
|
136
|
+
? { auth_state: { ...(userId ? { freepi_user_id: userId } : {}), ...(email ? { email } : {}) } }
|
|
137
|
+
: {}),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const freepiOAuthClient = new FreepiOAuthClient();
|