cartethyia 2.7.8 → 2.7.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +0 -1
- package/README.md +29 -59
- package/dist/dashboard/assets/BannedPage-BxoG1MrL.js +1 -0
- package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-BRWKPQPf.js → CliToolDetailPage-ixwgKk53.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BP-Y5iHN.js → CliToolsPage-BmttNV3U.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BE3qwwfC.js +1 -0
- package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-Cv-2Ol_K.js → InjectSystemPromptPanel-BOoO8rpz.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-bKfz_b1K.js → LoginPage-ChJuNNVN.js} +1 -1
- package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-B-WQxx_H.js → ModelPicker-BZAJtPg1.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BE40s3u5.js +1 -0
- package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +8 -0
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-emlTuxz6.js → ProxyPage-BJ87Pp0U.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
- package/dist/dashboard/assets/{QuotaPage-CMJKNYWb.js → QuotaPage-gkSyRAKa.js} +1 -1
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js +1 -0
- package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-CUZrKs9r.js → SetupPage-CFNcATJJ.js} +1 -1
- package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-jWmUdDpy.js → StudioPage-YoD4tY5l.js} +1 -1
- package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DxjaAhb_.js +56 -0
- package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BTlkq_Uk.js +1 -0
- package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
- package/dist/dashboard/assets/app-BX2nnKqs.css +2 -0
- package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CndthXFQ.js +1 -0
- package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DyI_mJfi.js → badge-DdbT_7Am.js} +1 -1
- package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
- package/dist/dashboard/assets/ban-durations-B58FyRxC.js +1 -0
- package/dist/dashboard/assets/ban-durations-B58FyRxC.js.gz +0 -0
- package/dist/dashboard/assets/{banner-B-yXtCqb.js → banner-CsGN-nbm.js} +1 -1
- package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
- package/dist/dashboard/assets/brand/bansos_page.webp +0 -0
- package/dist/dashboard/assets/brand/denia_bansos.webp +0 -0
- package/dist/dashboard/assets/brand/favicon.webp +0 -0
- package/dist/dashboard/assets/brand/login_register.webp +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.webp +0 -0
- package/dist/dashboard/assets/brand/og_bansos.webp +0 -0
- package/dist/dashboard/assets/brand/og_image.webp +0 -0
- package/dist/dashboard/assets/brand/sidebar_ico.webp +0 -0
- package/dist/dashboard/assets/{card-_HwAEqFF.js → card-mgZ_TLLZ.js} +1 -1
- package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-A1-v8-03.js +1 -0
- package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-ByjCk0o9.js → clipboard-button-CJ4Zty89.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-BZhOlfz-.js → dialog-B5L2jPQ_.js} +1 -1
- package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-BADqIRYP.js → drawer-DkjUouAj.js} +1 -1
- package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
- package/dist/dashboard/assets/{entry-COaC-irN.js → entry-B-LVLV6o.js} +1 -1
- package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
- package/dist/dashboard/assets/entry-CAhdYl3B.js +1 -0
- package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
- package/dist/dashboard/assets/entry-D2xD0Tmd.js +4 -0
- package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-DcHJyRiu.js → formatters-CrsdLVsn.js} +1 -1
- package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
- package/dist/dashboard/assets/{index-CgKmFngv.js → index-BU64Lqvp.js} +2 -2
- package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
- package/dist/dashboard/assets/{inline-B2BirKry.js → inline-B2aDLyyg.js} +1 -1
- package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
- package/dist/dashboard/assets/{input-D-qV9dKY.js → input-BCVkdR9d.js} +1 -1
- package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
- package/dist/dashboard/assets/{layout-B7tVWhtV.js → layout-DNcAdMK-.js} +1 -1
- package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-CtkGE3bF.js → list-row-DgrIH0hw.js} +1 -1
- package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
- package/dist/dashboard/assets/{live-DGizF06-.js → live-Cae01iu8.js} +1 -1
- package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-GU7XdEjz.js → mutation-BQ0ccH_A.js} +1 -1
- package/dist/dashboard/assets/mutation-BQ0ccH_A.js.gz +0 -0
- package/dist/dashboard/assets/{network-C9UE-bOr.js → network-CRib7H_2.js} +1 -1
- package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BnAd22a_.js → numbers-BKQE8Xpz.js} +1 -1
- package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-BAywyMzS.js → pagination-ecPvG_8g.js} +1 -1
- package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
- package/dist/dashboard/assets/providers/devin.svg +9 -0
- package/dist/dashboard/assets/providers/devin.svg.gz +0 -0
- package/dist/dashboard/assets/providers/windsurf.svg +10 -0
- package/dist/dashboard/assets/providers/windsurf.svg.gz +0 -0
- package/dist/dashboard/assets/{query-keys-YSoQOUh6.js → query-keys-IKLh0YFd.js} +1 -1
- package/dist/dashboard/assets/query-keys-IKLh0YFd.js.gz +0 -0
- package/dist/dashboard/assets/routing-Ckm1UXuC.js +1 -0
- package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
- package/dist/dashboard/assets/{select-BCOcv8WK.js → select-ChoFWyKz.js} +1 -1
- package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
- package/dist/dashboard/assets/settings-CKjFPlKW.js +1 -0
- package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
- package/dist/dashboard/assets/{stack-ByKt3Q1g.js → stack-DWCwU3fh.js} +1 -1
- package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
- package/dist/dashboard/assets/{state-DXbGTx7N.js → state-Cg3VL5J_.js} +1 -1
- package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
- package/dist/dashboard/assets/story/25817331.webp +0 -0
- package/dist/dashboard/assets/story/Shorekeeper.webp +0 -0
- package/dist/dashboard/assets/story/cartethyia-god.webp +0 -0
- package/dist/dashboard/assets/story/fleurdelys_plus.webp +0 -0
- package/dist/dashboard/assets/story/jinhsi-blossom.webp +0 -0
- package/dist/dashboard/assets/story/phrolova-a.webp +0 -0
- package/dist/dashboard/assets/story/requestdeniawokkjpg.webp +0 -0
- package/dist/dashboard/assets/story/wuthering-waves-hiyuki-aemeath-hiyuki-rover.webp +0 -0
- package/dist/dashboard/assets/{switch-ZeTsW623.js → switch-Db70BMix.js} +1 -1
- package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-CbCP4w1o.js → useMutation-BqdE0LuM.js} +1 -1
- package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.webp +0 -0
- package/dist/dashboard/console-backgrounds_initial.webp +0 -0
- package/dist/dashboard/donation/qris-risun-marke.webp +0 -0
- package/dist/dashboard/index.html +3 -3
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +57 -55
- package/migrations/full-postgres/0044_add_models_warning_metadata.sql +10 -0
- package/migrations/full-postgres/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
- package/migrations/lite-sqlite3/0044_add_models_warning_metadata.sql +11 -0
- package/migrations/lite-sqlite3/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
- package/package.json +1 -1
- package/src/boot/dependencies.ts +11 -3
- package/src/config/cooldowns.ts +1 -1
- package/src/config/spec.ts +0 -6
- package/src/console/api-keys/routes.ts +11 -4
- package/src/console/console-router.ts +7 -0
- package/src/console/dashboard-assets.ts +5 -5
- package/src/console/domain-registration.ts +27 -0
- package/src/console/ip-bans/routes.ts +99 -0
- package/src/console/providers/catalog/catalog-projections.ts +5 -2
- package/src/console/providers/catalog/contracts.ts +123 -0
- package/src/console/providers/catalog/model-operations.ts +122 -10
- package/src/console/providers/catalog/provider-operations.ts +116 -8
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/routes.ts +64 -4
- package/src/console/providers/catalog/store-helpers.ts +30 -1
- package/src/console/providers/catalog/store.ts +243 -3
- package/src/console/quota/account-quota-global.ts +89 -1
- package/src/console/quota/native-identity.ts +2 -0
- package/src/console/settings/contracts.ts +18 -0
- package/src/console/settings/store.ts +32 -0
- package/src/console/system-prompt/contracts.ts +0 -1
- package/src/main.ts +1 -0
- package/src/observability/event-query.ts +18 -4
- package/src/persistence/dialect.ts +12 -0
- package/src/persistence/schema.ts +20 -2
- package/src/protocol/dsml.ts +1 -1
- package/src/protocol/gateway-error.ts +1 -0
- package/src/protocol/reasoning.ts +1 -1
- package/src/protocol/request/chat.ts +4 -4
- package/src/protocol/request/messages.ts +1 -1
- package/src/protocol/response/gemini.ts +6 -1
- package/src/protocol/sse-decoder.ts +25 -8
- package/src/protocol/stream-error-frames.ts +13 -3
- package/src/protocol/translation/quirks.ts +1 -1
- package/src/protocol/translation/tool-repair.ts +2 -2
- package/src/protocol/upstream/send-claude-messages.ts +15 -4
- package/src/protocol/upstream-parsing.ts +1 -1
- package/src/providers/adapters/compatible-adapter.ts +7 -5
- package/src/providers/authentication/oauth-refresh-service.ts +149 -36
- package/src/providers/default-registry.ts +68 -3
- package/src/providers/discovery/discovery-types.ts +12 -0
- package/src/providers/discovery/probe-phases.ts +61 -30
- package/src/providers/discovery/probing-service.ts +279 -164
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +117 -2
- package/src/providers/integrations/antigravity/antigravity.ts +13 -2
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +72 -34
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +18 -18
- package/src/providers/integrations/buddy/buddy-checkin.ts +17 -39
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +2 -2
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +5 -5
- package/src/providers/integrations/buddy/buddy-identity.ts +70 -0
- package/src/providers/integrations/buddy/buddy-media.ts +4 -5
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +2 -2
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +1 -1
- package/src/providers/integrations/buddy/buddy-usage-shared.ts +1 -3
- package/src/providers/integrations/buddy/capabilities.ts +4 -41
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +2 -1
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +20 -1
- package/src/providers/integrations/buddy/codebuddy/provider.ts +2 -2
- package/src/providers/integrations/buddy/codebuddy/shared.ts +6 -10
- package/src/providers/integrations/cursor/cursor-catalog.ts +2 -0
- package/src/providers/integrations/cursor/cursor-exec.ts +738 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +2 -0
- package/src/providers/integrations/cursor/cursor-request.ts +9 -8
- package/src/providers/integrations/cursor/cursor-shared.ts +19 -1
- package/src/providers/integrations/cursor/cursor.ts +50 -30
- package/src/providers/integrations/cursor/protocol/tool-canonical.ts +26 -100
- package/src/providers/integrations/devin/devin-auth.ts +353 -0
- package/src/providers/integrations/devin/devin-catalog.ts +207 -0
- package/src/providers/integrations/devin/devin-http.ts +91 -0
- package/src/providers/integrations/devin/devin-quota.ts +214 -0
- package/src/providers/integrations/devin/devin-shared.ts +111 -0
- package/src/providers/integrations/devin/devin.ts +539 -0
- package/src/providers/integrations/devin/protocol/devin-proto.ts +584 -0
- package/src/providers/integrations/devin/protocol/framing.ts +124 -0
- package/src/providers/integrations/factory/factory.ts +2 -2
- package/src/providers/integrations/freepi/freepi-catalog.ts +60 -0
- package/src/providers/integrations/freepi/freepi-discovery.ts +82 -0
- package/src/providers/integrations/freepi/freepi-headers.ts +21 -0
- package/src/providers/integrations/freepi/freepi-oauth.ts +142 -0
- package/src/providers/integrations/freepi/freepi-protocol.ts +242 -0
- package/src/providers/integrations/freepi/freepi-quota.ts +126 -0
- package/src/providers/integrations/freepi/freepi-realm.ts +35 -0
- package/src/providers/integrations/freepi/freepi.ts +292 -0
- package/src/providers/integrations/gemini.ts +2 -2
- package/src/providers/integrations/hermes/hermes-oauth.ts +94 -0
- package/src/providers/integrations/hermes.ts +4 -12
- package/src/providers/integrations/opencode/fingerprint.ts +1 -1
- package/src/providers/integrations/perch/perch-catalog.ts +105 -0
- package/src/providers/integrations/perch/perch-oauth.ts +170 -0
- package/src/providers/integrations/perch/perch-protocol.ts +211 -0
- package/src/providers/integrations/perch/perch-quota.ts +137 -0
- package/src/providers/integrations/perch/perch-realm.ts +82 -0
- package/src/providers/integrations/perch/perch-system-prompt.ts +13 -0
- package/src/providers/integrations/perch/perch.ts +315 -0
- package/src/providers/integrations/perch/saffron-system-prompt.txt +8 -0
- package/src/providers/integrations/qoder/chat-proto.ts +328 -328
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +1 -1
- package/src/providers/integrations/qoder/qoder-adapter.ts +89 -28
- package/src/providers/integrations/qoder/qoder-cache.ts +32 -0
- package/src/providers/integrations/qoder/qoder-catalog.ts +6 -1
- package/src/providers/integrations/qoder/qoder-checkin.ts +2 -2
- package/src/providers/integrations/qoder/qoder-grpc.ts +210 -6
- package/src/providers/integrations/qoder/qoder-ide.ts +5 -1
- package/src/providers/integrations/qoder/qoder-identity-cache.ts +184 -0
- package/src/providers/integrations/qoder/qoder-native-identity-store.ts +83 -18
- package/src/providers/integrations/qoder/qoder-realm.ts +9 -6
- package/src/providers/integrations/qoder/qoder-umid.ts +109 -342
- package/src/providers/integrations/qoder/qoder-virtual.ts +33 -1
- package/src/providers/integrations/qoder/qoder.ts +3 -6
- package/src/providers/integrations/trae/trae-catalog.ts +9 -14
- package/src/providers/integrations/trae/trae-headers.ts +36 -11
- package/src/providers/integrations/trae/trae-oauth.ts +14 -17
- package/src/providers/integrations/trae/trae-protocol.ts +3 -6
- package/src/providers/integrations/trae/trae-quota.ts +103 -21
- package/src/providers/integrations/trae/trae-realm.ts +36 -15
- package/src/providers/integrations/trae/trae.ts +97 -7
- package/src/providers/operations/account-error-classification.ts +66 -1
- package/src/providers/operations/account-health-service.ts +192 -26
- package/src/providers/operations/account-refresh-status.ts +284 -0
- package/src/providers/operations/account-tier.ts +67 -0
- package/src/providers/operations/client-versions.ts +37 -99
- package/src/providers/operations/cooldown-rules.ts +61 -0
- package/src/providers/operations/provider-catalog-seeder.ts +4 -0
- package/src/providers/provider-contracts.ts +29 -0
- package/src/providers/provider-metadata.ts +59 -4
- package/src/providers/provider-model-definition.ts +36 -0
- package/src/providers/quota/refresh.ts +21 -0
- package/src/security/abuse.ts +71 -0
- package/src/transport/dispatch/proxy-request.ts +1 -0
- package/src/transport/dispatch/streaming/stream.ts +1 -0
- package/src/transport/dispatch/upstream.ts +4 -1
- package/src/transport/failure-policy.ts +113 -7
- package/src/transport/routing/router.ts +25 -1
- package/src/transport/surface/messages/adapter.ts +2 -1
- package/src/transport/surface/responses/encode.ts +12 -1
- package/src/workers/daily-checkin.ts +1 -1
- package/src/workers/quota-refresh-sweep.ts +1 -1
- package/src/workers/scheduler.ts +22 -4
- package/dist/dashboard/assets/BannedPage-DeIbIiBU.js +0 -1
- package/dist/dashboard/assets/BannedPage-DeIbIiBU.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-BRWKPQPf.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BP-Y5iHN.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-B7JHozVa.js +0 -1
- package/dist/dashboard/assets/CombosPage-B7JHozVa.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-Cv-2Ol_K.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-bKfz_b1K.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-B-WQxx_H.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js +0 -1
- package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js +0 -7
- package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-emlTuxz6.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js +0 -1
- package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-CUZrKs9r.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-jWmUdDpy.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-5F9tGVGG.js +0 -56
- package/dist/dashboard/assets/UsagePage-5F9tGVGG.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-KvX83mRe.js +0 -1
- package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
- package/dist/dashboard/assets/app-BEsU8hKs.css +0 -2
- package/dist/dashboard/assets/app-BEsU8hKs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CidQVoNo.js +0 -1
- package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
- package/dist/dashboard/assets/badge-DyI_mJfi.js.gz +0 -0
- package/dist/dashboard/assets/banner-B-yXtCqb.js.gz +0 -0
- package/dist/dashboard/assets/brand/bansos_page.png +0 -0
- package/dist/dashboard/assets/brand/denia_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/favicon.jpg +0 -0
- package/dist/dashboard/assets/brand/login_register.jpg +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
- package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/og_image.jpg +0 -0
- package/dist/dashboard/assets/brand/sidebar_ico.jpg +0 -0
- package/dist/dashboard/assets/card-_HwAEqFF.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js +0 -1
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-ByjCk0o9.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BZhOlfz-.js.gz +0 -0
- package/dist/dashboard/assets/drawer-BADqIRYP.js.gz +0 -0
- package/dist/dashboard/assets/entry-COaC-irN.js.gz +0 -0
- package/dist/dashboard/assets/entry-Ca7LSCet.js +0 -4
- package/dist/dashboard/assets/entry-Ca7LSCet.js.gz +0 -0
- package/dist/dashboard/assets/entry-PDd27uCd.js +0 -1
- package/dist/dashboard/assets/entry-PDd27uCd.js.gz +0 -0
- package/dist/dashboard/assets/formatters-DcHJyRiu.js.gz +0 -0
- package/dist/dashboard/assets/index-CgKmFngv.js.gz +0 -0
- package/dist/dashboard/assets/inline-B2BirKry.js.gz +0 -0
- package/dist/dashboard/assets/input-D-qV9dKY.js.gz +0 -0
- package/dist/dashboard/assets/layout-B7tVWhtV.js.gz +0 -0
- package/dist/dashboard/assets/list-row-CtkGE3bF.js.gz +0 -0
- package/dist/dashboard/assets/live-DGizF06-.js.gz +0 -0
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +0 -1
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
- package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
- package/dist/dashboard/assets/network-C9UE-bOr.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BnAd22a_.js.gz +0 -0
- package/dist/dashboard/assets/pagination-BAywyMzS.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-YSoQOUh6.js.gz +0 -0
- package/dist/dashboard/assets/routing-fnCsxgNO.js +0 -1
- package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
- package/dist/dashboard/assets/select-BCOcv8WK.js.gz +0 -0
- package/dist/dashboard/assets/settings-BxslsErn.js +0 -1
- package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
- package/dist/dashboard/assets/stack-ByKt3Q1g.js.gz +0 -0
- package/dist/dashboard/assets/state-DXbGTx7N.js.gz +0 -0
- package/dist/dashboard/assets/switch-ZeTsW623.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.jpg +0 -0
- package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
- package/dist/dashboard/donation/qris-risun-marke.png +0 -0
- package/src/providers/integrations/buddy/turing-device-token.ts +0 -262
- package/src/providers/integrations/buddy/workbuddy/cn-oauth.ts +0 -30
- package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +0 -58
- package/src/providers/integrations/buddy/workbuddy/oauth.ts +0 -31
- package/src/providers/integrations/buddy/workbuddy/provider.ts +0 -209
- package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -161
- package/src/providers/integrations/buddy/workbuddy/shared.ts +0 -285
- /package/dist/dashboard/assets/{app-CTxoqyYB.js → app-B7kIWFm5.js} +0 -0
- /package/dist/dashboard/assets/{app-CTxoqyYB.js.gz → app-B7kIWFm5.js.gz} +0 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// Free-pi wire protocol: request body and SSE translation.
|
|
2
|
+
//
|
|
3
|
+
// The wire is OpenAI-completions (`/v1/chat/completions`) with a JWT bearer.
|
|
4
|
+
// Streaming returns OpenAI-style SSE: `data:` JSON chunks (no `event:` name),
|
|
5
|
+
// terminated by `[DONE]`. Errors come as HTTP status + JSON
|
|
6
|
+
// `{code, message, error: {code, message}}`.
|
|
7
|
+
//
|
|
8
|
+
// The client (0.6.14) sends `model` (the catalog id), `messages`, `stream`,
|
|
9
|
+
// and optionally `max_tokens`; the upstream resolves the catalog id to the
|
|
10
|
+
// real model and echoes it in each chunk (`choices[0].delta.model`).
|
|
11
|
+
|
|
12
|
+
import type { CanonicalRequest, ContentPart } from "../../../protocol/canonical-model";
|
|
13
|
+
import { GatewayError } from "../../../protocol/gateway-error";
|
|
14
|
+
|
|
15
|
+
/** Upstream chat body (OpenAI-completions shape). */
|
|
16
|
+
export interface FreepiChatBody {
|
|
17
|
+
readonly model: string;
|
|
18
|
+
readonly messages: readonly FreepiMessage[];
|
|
19
|
+
readonly stream: boolean;
|
|
20
|
+
readonly max_tokens?: number;
|
|
21
|
+
readonly temperature?: number;
|
|
22
|
+
readonly top_p?: number;
|
|
23
|
+
readonly tools?: readonly FreepiToolDeclaration[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface FreepiMessage {
|
|
27
|
+
readonly role: "system" | "user" | "assistant" | "tool";
|
|
28
|
+
readonly content: string;
|
|
29
|
+
readonly tool_call_id?: string;
|
|
30
|
+
readonly tool_calls?: readonly FreepiToolCall[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface FreepiToolDeclaration {
|
|
34
|
+
readonly type: "function";
|
|
35
|
+
readonly function: {
|
|
36
|
+
readonly name: string;
|
|
37
|
+
readonly description?: string;
|
|
38
|
+
readonly parameters: unknown;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface FreepiToolCall {
|
|
43
|
+
readonly id: string;
|
|
44
|
+
readonly type: "function";
|
|
45
|
+
readonly function: { readonly name: string; readonly arguments: string };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function textOf(part: ContentPart): string {
|
|
49
|
+
return part.kind === "text" ? part.text : "";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function toolArguments(args: unknown): string {
|
|
53
|
+
if (typeof args === "string") return args;
|
|
54
|
+
try {
|
|
55
|
+
return JSON.stringify(args ?? {});
|
|
56
|
+
} catch {
|
|
57
|
+
return "{}";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function toolResultText(content: readonly ContentPart[] | string): string {
|
|
62
|
+
if (typeof content === "string") return content;
|
|
63
|
+
return content
|
|
64
|
+
.map((part) => (part.kind === "text" ? part.text : ""))
|
|
65
|
+
.filter((text) => text.length > 0)
|
|
66
|
+
.join("\n");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Builds the upstream chat body from a canonical request. */
|
|
70
|
+
export function buildFreepiChatBody(
|
|
71
|
+
modelId: string,
|
|
72
|
+
request: CanonicalRequest,
|
|
73
|
+
maxOutputTokens?: number,
|
|
74
|
+
): FreepiChatBody {
|
|
75
|
+
const messages: FreepiMessage[] = [];
|
|
76
|
+
const systemParts: string[] = [];
|
|
77
|
+
for (const part of request.system ?? []) {
|
|
78
|
+
const text = textOf(part);
|
|
79
|
+
if (text.length > 0) systemParts.push(text);
|
|
80
|
+
}
|
|
81
|
+
for (const part of request.instructions ?? []) {
|
|
82
|
+
const text = textOf(part);
|
|
83
|
+
if (text.length > 0) systemParts.push(text);
|
|
84
|
+
}
|
|
85
|
+
if (systemParts.length > 0) {
|
|
86
|
+
messages.push({ role: "system", content: systemParts.join("\n\n") });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const message of request.messages) {
|
|
90
|
+
if (message.role === "system" || message.role === "developer") {
|
|
91
|
+
const text = message.content.map(textOf).filter((t) => t.length > 0).join("\n\n");
|
|
92
|
+
if (text.length > 0) systemParts.push(text);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const parts: string[] = [];
|
|
96
|
+
const toolCalls: FreepiToolCall[] = [];
|
|
97
|
+
let toolResult: { callId: string; text: string } | undefined;
|
|
98
|
+
for (const part of message.content) {
|
|
99
|
+
if (part.kind === "toolCall") {
|
|
100
|
+
toolCalls.push({
|
|
101
|
+
id: part.call_id,
|
|
102
|
+
type: "function",
|
|
103
|
+
function: { name: part.name, arguments: toolArguments(part.arguments) },
|
|
104
|
+
});
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (part.kind === "toolResult") {
|
|
108
|
+
toolResult = { callId: part.call_id, text: toolResultText(part.content) };
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const text = textOf(part);
|
|
112
|
+
if (text.length > 0) parts.push(text);
|
|
113
|
+
}
|
|
114
|
+
if (toolResult !== undefined) {
|
|
115
|
+
messages.push({ role: "tool", content: toolResult.text, tool_call_id: toolResult.callId });
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (parts.length === 0 && toolCalls.length === 0) continue;
|
|
119
|
+
messages.push({
|
|
120
|
+
role: message.role === "assistant" ? "assistant" : "user",
|
|
121
|
+
content: parts.join("\n"),
|
|
122
|
+
...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const body: FreepiChatBody = {
|
|
127
|
+
model: modelId,
|
|
128
|
+
messages,
|
|
129
|
+
stream: request.stream,
|
|
130
|
+
};
|
|
131
|
+
const extra: Record<string, unknown> = {};
|
|
132
|
+
if (maxOutputTokens !== undefined && Number.isFinite(maxOutputTokens) && maxOutputTokens > 0) {
|
|
133
|
+
extra["max_tokens"] = Math.min(maxOutputTokens, 8192);
|
|
134
|
+
}
|
|
135
|
+
const controls = request.generation_controls;
|
|
136
|
+
if (typeof controls.temperature === "number" && Number.isFinite(controls.temperature)) {
|
|
137
|
+
extra["temperature"] = controls.temperature;
|
|
138
|
+
}
|
|
139
|
+
if (typeof controls.top_p === "number" && Number.isFinite(controls.top_p)) {
|
|
140
|
+
extra["top_p"] = controls.top_p;
|
|
141
|
+
}
|
|
142
|
+
const tools = request.tools;
|
|
143
|
+
if (tools !== undefined && tools.length > 0) {
|
|
144
|
+
extra["tools"] = tools.map((tool) => ({
|
|
145
|
+
type: "function",
|
|
146
|
+
function: {
|
|
147
|
+
name: tool.name,
|
|
148
|
+
...(tool.description ? { description: tool.description } : {}),
|
|
149
|
+
parameters: tool.jsonSchema,
|
|
150
|
+
},
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
return { ...body, ...extra } as FreepiChatBody;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** One parsed SSE frame. */
|
|
157
|
+
export interface FreepiSseFrame {
|
|
158
|
+
readonly event: string;
|
|
159
|
+
readonly data: unknown;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Parses an SSE byte stream into frames (OpenAI-style, `data:` only). */
|
|
163
|
+
export async function* parseFreepiSse(
|
|
164
|
+
body: ReadableStream<Uint8Array>,
|
|
165
|
+
): AsyncIterable<FreepiSseFrame> {
|
|
166
|
+
const decoder = new TextDecoder();
|
|
167
|
+
const reader = body.getReader();
|
|
168
|
+
let buffer = "";
|
|
169
|
+
let data: string[] = [];
|
|
170
|
+
try {
|
|
171
|
+
while (true) {
|
|
172
|
+
const { done, value } = await reader.read();
|
|
173
|
+
if (done) break;
|
|
174
|
+
buffer += decoder.decode(value, { stream: true });
|
|
175
|
+
let newline = buffer.indexOf("\n");
|
|
176
|
+
while (newline >= 0) {
|
|
177
|
+
const line = buffer.slice(0, newline).replace(/\r$/, "");
|
|
178
|
+
buffer = buffer.slice(newline + 1);
|
|
179
|
+
if (line === "") {
|
|
180
|
+
if (data.length > 0) {
|
|
181
|
+
const payload = data.join("\n");
|
|
182
|
+
data = [];
|
|
183
|
+
if (payload.trim() === "[DONE]") {
|
|
184
|
+
yield { event: "done", data: { finish_reason: "stop" } };
|
|
185
|
+
} else {
|
|
186
|
+
let parsed: unknown = payload;
|
|
187
|
+
try {
|
|
188
|
+
parsed = JSON.parse(payload);
|
|
189
|
+
} catch {
|
|
190
|
+
parsed = payload;
|
|
191
|
+
}
|
|
192
|
+
yield { event: "message", data: parsed };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} else if (line.startsWith(":")) {
|
|
196
|
+
continue;
|
|
197
|
+
} else if (line.startsWith("data")) {
|
|
198
|
+
let value = line.slice(4);
|
|
199
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
200
|
+
data.push(value);
|
|
201
|
+
}
|
|
202
|
+
newline = buffer.indexOf("\n");
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (data.length > 0) {
|
|
206
|
+
const payload = data.join("\n");
|
|
207
|
+
if (payload.trim() === "[DONE]") {
|
|
208
|
+
yield { event: "done", data: { finish_reason: "stop" } };
|
|
209
|
+
} else {
|
|
210
|
+
let parsed: unknown = payload;
|
|
211
|
+
try {
|
|
212
|
+
parsed = JSON.parse(payload);
|
|
213
|
+
} catch {
|
|
214
|
+
parsed = payload;
|
|
215
|
+
}
|
|
216
|
+
yield { event: "message", data: parsed };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} finally {
|
|
220
|
+
reader.releaseLock();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Maps upstream error JSON (code/message/error nested) to a GatewayError. */
|
|
225
|
+
export function throwFreepiUpstreamError(code: unknown, message: unknown): never {
|
|
226
|
+
const msg = typeof message === "string" && message.trim() ? message : "free-pi upstream error";
|
|
227
|
+
const codeStr = code === undefined ? undefined : String(code);
|
|
228
|
+
// Entitlement/plan walls the gateway classifies as policy rejection (403):
|
|
229
|
+
// the account is not allowed to use this model/credential type, which is
|
|
230
|
+
// not a transient rate limit (quota_exceeded = 429).
|
|
231
|
+
if (codeStr === "guest_unavailable" || codeStr === "model_requires_credits") {
|
|
232
|
+
throw new GatewayError("policy_rejected", 403, `${msg} (request {requestId}). The free-pi account cannot serve this — check credits or sign in. 💸`, { upstream_code: codeStr }, "upstream");
|
|
233
|
+
}
|
|
234
|
+
// Server-side model unavailability → platform unavailable (503).
|
|
235
|
+
if (codeStr === "model_at_capacity" || codeStr === "model_unavailable") {
|
|
236
|
+
throw new GatewayError("platform_unavailable", 503, `${msg} (request {requestId}). Upstream has this model hard-down right now. 🔄`, { upstream_code: codeStr }, "upstream");
|
|
237
|
+
}
|
|
238
|
+
if (codeStr === "upstream_error") {
|
|
239
|
+
throw new GatewayError("platform_unavailable", 502, `${msg} (request {requestId}). The upstream model host errored. 🌙`, { upstream_code: codeStr }, "upstream");
|
|
240
|
+
}
|
|
241
|
+
throw new GatewayError("platform_unavailable", 502, `${msg} (request {requestId}). The free-pi upstream refused the request. 🌙`, { upstream_code: codeStr }, "upstream");
|
|
242
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Free-pi quota collector.
|
|
2
|
+
//
|
|
3
|
+
// Reads `GET /me` (Bearer) — the backend's account/allowance surface. The
|
|
4
|
+
// response carries the USD budget (`cap_usd_today`, `remaining_usd_today`),
|
|
5
|
+
// credit counts (`allowance_credits`, `remaining_credits_today`,
|
|
6
|
+
// `bonus_wallet_credits`, `premium_credits`, …), a `refill_at` reset time, and
|
|
7
|
+
// a usage lane (`standard`/`fallback`/`wait`). Never throws: a failed read
|
|
8
|
+
// returns a 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 { buildFreepiHeaders } from "./freepi-headers";
|
|
14
|
+
import { FREEPI_BASE_URL } from "./freepi-realm";
|
|
15
|
+
|
|
16
|
+
function readMe(baseUrl: string, credential: string, fetcher: FetchLike): Promise<Record<string, unknown> | null> {
|
|
17
|
+
return fetcher(new URL("/me", baseUrl), {
|
|
18
|
+
method: "GET",
|
|
19
|
+
headers: buildFreepiHeaders(credential),
|
|
20
|
+
signal: AbortSignal.timeout(15_000),
|
|
21
|
+
})
|
|
22
|
+
.then(async (res) => {
|
|
23
|
+
if (!res.ok) return null;
|
|
24
|
+
try {
|
|
25
|
+
return (await res.json()) as Record<string, unknown>;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
.catch(() => null);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Reads the account quota and returns a normalized result. */
|
|
34
|
+
export async function fetchFreepiQuota(
|
|
35
|
+
credential: string,
|
|
36
|
+
fetcher: FetchLike,
|
|
37
|
+
_context?: QuotaCollectionContext,
|
|
38
|
+
): Promise<ProviderQuotaResult> {
|
|
39
|
+
try {
|
|
40
|
+
const me = await readMe(FREEPI_BASE_URL, credential, fetcher);
|
|
41
|
+
if (!me) {
|
|
42
|
+
return {
|
|
43
|
+
source: "freepi",
|
|
44
|
+
plan: null,
|
|
45
|
+
windows: [],
|
|
46
|
+
error: "free-pi /me unavailable",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const plan = me["tier"] === undefined ? null : text(me["tier"]);
|
|
50
|
+
const windows: ProviderQuotaWindow[] = [];
|
|
51
|
+
|
|
52
|
+
const capUsd = me["cap_usd_today"];
|
|
53
|
+
const remainingUsd = me["remaining_usd_today"];
|
|
54
|
+
const refillAt = me["refill_at"];
|
|
55
|
+
if (typeof capUsd === "number" && typeof remainingUsd === "number") {
|
|
56
|
+
const limit = capUsd as number;
|
|
57
|
+
const remaining = remainingUsd as number;
|
|
58
|
+
windows.push(
|
|
59
|
+
percentWindow(
|
|
60
|
+
"usd_daily",
|
|
61
|
+
"Daily USD budget",
|
|
62
|
+
remaining,
|
|
63
|
+
refillAt === undefined ? null : text(refillAt),
|
|
64
|
+
limit - remaining,
|
|
65
|
+
limit,
|
|
66
|
+
"pooled",
|
|
67
|
+
),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const allowance = me["allowance_credits"];
|
|
72
|
+
const remainingCredits = me["remaining_credits_today"];
|
|
73
|
+
if (typeof allowance === "number" && typeof remainingCredits === "number") {
|
|
74
|
+
const limit = allowance as number;
|
|
75
|
+
const remaining = remainingCredits as number;
|
|
76
|
+
windows.push(
|
|
77
|
+
percentWindow(
|
|
78
|
+
"credits_daily",
|
|
79
|
+
"Daily credits",
|
|
80
|
+
remaining,
|
|
81
|
+
refillAt === undefined ? null : text(refillAt),
|
|
82
|
+
limit - remaining,
|
|
83
|
+
limit,
|
|
84
|
+
"pooled",
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const bonus = me["bonus_wallet_credits"];
|
|
90
|
+
if (typeof bonus === "number") {
|
|
91
|
+
windows.push({
|
|
92
|
+
kind: "bonus_credits",
|
|
93
|
+
label: "Bonus credits",
|
|
94
|
+
accounting: "pooled",
|
|
95
|
+
usedPercent: null,
|
|
96
|
+
remainingPercent: null,
|
|
97
|
+
resetsAt: null,
|
|
98
|
+
remaining: bonus as number,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const premium = me["premium_credits"];
|
|
103
|
+
if (typeof premium === "number") {
|
|
104
|
+
windows.push({
|
|
105
|
+
kind: "premium_credits",
|
|
106
|
+
label: "Premium credits",
|
|
107
|
+
accounting: "pooled",
|
|
108
|
+
usedPercent: null,
|
|
109
|
+
remainingPercent: null,
|
|
110
|
+
resetsAt: null,
|
|
111
|
+
remaining: premium as number,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const handle = me["handle"];
|
|
116
|
+
return {
|
|
117
|
+
source: "freepi",
|
|
118
|
+
plan,
|
|
119
|
+
windows,
|
|
120
|
+
error: null,
|
|
121
|
+
...(typeof handle === "string" && handle.trim() ? { accountLabel: handle } : {}),
|
|
122
|
+
};
|
|
123
|
+
} catch (error) {
|
|
124
|
+
return { source: "freepi", plan: null, windows: [], error: cleanError(error) };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Free-pi realm constants.
|
|
2
|
+
//
|
|
3
|
+
// A single deployment with no regional variants: the backend base URL is the
|
|
4
|
+
// same for everyone (FREEPI_BASE_URL defaults to https://api.freepi.ai). The
|
|
5
|
+
// chat wire is OpenAI-completions (`/v1/chat/completions`), and the credential
|
|
6
|
+
// is a JWT issued by the backend (device flow or guest), sent as
|
|
7
|
+
// `Authorization: Bearer <jwt>`.
|
|
8
|
+
|
|
9
|
+
/** Default backend base URL, matching FREEPI_BASE_URL. */
|
|
10
|
+
export const FREEPI_BASE_URL = "https://api.freepi.ai";
|
|
11
|
+
|
|
12
|
+
/** Chat completions path, relative to the base URL (OpenAI-completions). */
|
|
13
|
+
export const FREEPI_CHAT_PATH = "/v1/chat/completions";
|
|
14
|
+
|
|
15
|
+
/** Ad fetch/impression paths, relative to the base URL. */
|
|
16
|
+
export const FREEPI_AD_NEXT_PATH = "/ads/next";
|
|
17
|
+
export const FREEPI_AD_IMPRESSION_PATH = "/ads/impression";
|
|
18
|
+
|
|
19
|
+
/** Client version the gateway reports via `x-client-version`. */
|
|
20
|
+
export const FREEPI_CLI_VERSION = "0.6.14";
|
|
21
|
+
|
|
22
|
+
/** Consent version shipped in 0.6.14. */
|
|
23
|
+
export const FREEPI_CONSENT_VERSION = "4";
|
|
24
|
+
|
|
25
|
+
/** Provider id used by the registry. */
|
|
26
|
+
export const FREEPI_PROVIDER_ID = "freepi";
|
|
27
|
+
|
|
28
|
+
/** Static roster fallback; the live `/client-version` catalog replaces it. */
|
|
29
|
+
export const FREEPI_DEFAULT_MODEL = "deepseek/deepseek-v4-flash";
|
|
30
|
+
|
|
31
|
+
/** Context window the provider reports for every model (the client hardcodes 200k). */
|
|
32
|
+
export const FREEPI_CONTEXT_WINDOW = 200_000;
|
|
33
|
+
|
|
34
|
+
/** Maximum output tokens the client requests (hardcoded in the bundle). */
|
|
35
|
+
export const FREEPI_MAX_OUTPUT_TOKENS = 8192;
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// Free-pi adapter.
|
|
2
|
+
//
|
|
3
|
+
// OpenAI-completions wire with a JWT bearer. The model catalog is
|
|
4
|
+
// server-driven (`GET /client-version`); dispatch posts to
|
|
5
|
+
// `/v1/chat/completions` and translates the OpenAI-style SSE stream into
|
|
6
|
+
// canonical events.
|
|
7
|
+
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
import type {
|
|
10
|
+
CanonicalEvent,
|
|
11
|
+
CanonicalRequest,
|
|
12
|
+
CanonicalMessage,
|
|
13
|
+
CanonicalStopReason,
|
|
14
|
+
} from "../../../protocol/canonical-model";
|
|
15
|
+
import { GatewayError } from "../../../protocol/gateway-error";
|
|
16
|
+
import {
|
|
17
|
+
assertWireFamily,
|
|
18
|
+
readCredentialSecret,
|
|
19
|
+
type ProviderAdapter,
|
|
20
|
+
type ProviderDispatchContext,
|
|
21
|
+
type ProviderDispatchTarget,
|
|
22
|
+
type ProviderId,
|
|
23
|
+
} from "../../provider-contracts";
|
|
24
|
+
import { mapUpstreamHttpError } from "../../../transport/failure-policy";
|
|
25
|
+
import { abortGatewayError, createUpstreamDeadlineLifecycle } from "../../operations/upstream-deadline";
|
|
26
|
+
import { resolvePromptCacheKey } from "../../operations/session-resolution";
|
|
27
|
+
import { usageFromProvider } from "../../provider-usage";
|
|
28
|
+
import { buildFreepiChatBody, parseFreepiSse, throwFreepiUpstreamError } from "./freepi-protocol";
|
|
29
|
+
import { buildFreepiHeaders } from "./freepi-headers";
|
|
30
|
+
import {
|
|
31
|
+
FREEPI_AD_IMPRESSION_PATH,
|
|
32
|
+
FREEPI_AD_NEXT_PATH,
|
|
33
|
+
FREEPI_BASE_URL,
|
|
34
|
+
FREEPI_CHAT_PATH,
|
|
35
|
+
FREEPI_PROVIDER_ID,
|
|
36
|
+
} from "./freepi-realm";
|
|
37
|
+
|
|
38
|
+
const STOP_REASONS: Readonly<Record<string, CanonicalStopReason>> = {
|
|
39
|
+
stop: "stop",
|
|
40
|
+
length: "length",
|
|
41
|
+
tool_calls: "tool_use",
|
|
42
|
+
function_call: "tool_use",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
interface FreepiAdapterOptions {
|
|
46
|
+
readonly providerId?: string;
|
|
47
|
+
readonly fetch?: typeof fetch;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class FreepiAdapter implements ProviderAdapter {
|
|
51
|
+
readonly provider_id: ProviderId;
|
|
52
|
+
private readonly fetchFn: typeof fetch;
|
|
53
|
+
/**
|
|
54
|
+
* One stable ad-registered session id per account credential, so repeated
|
|
55
|
+
* chats reuse a session the upstream knows has shown ads. The session must
|
|
56
|
+
* exist for the chat wire: without it the server answers `ads_required`
|
|
57
|
+
* ("Ads aren't showing for this account, so free completions are paused").
|
|
58
|
+
*/
|
|
59
|
+
#sessionByCredential = new Map<string, string>();
|
|
60
|
+
|
|
61
|
+
constructor(options: FreepiAdapterOptions = {}) {
|
|
62
|
+
this.provider_id = (options.providerId ?? FREEPI_PROVIDER_ID) as ProviderId;
|
|
63
|
+
this.fetchFn = options.fetch ?? globalThis.fetch;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Ensures a session id exists for this credential and that the session has
|
|
68
|
+
* registered at least one ad impression (banner + inline). The upstream
|
|
69
|
+
* refuses the chat wire with `ads_required` until a session has shown ads,
|
|
70
|
+
* so the first call per credential pays the two ad-registration requests.
|
|
71
|
+
*
|
|
72
|
+
* Returns the session id to send as `x-session-id`. Falls back to a fresh
|
|
73
|
+
* random id when registration fails — a broken ad surface must not block
|
|
74
|
+
* chat entirely, and the error is caught upstream anyway.
|
|
75
|
+
*/
|
|
76
|
+
async #ensureSession(secret: string, outboundFetch: typeof fetch): Promise<string> {
|
|
77
|
+
const existing = this.#sessionByCredential.get(secret);
|
|
78
|
+
if (existing !== undefined) return existing;
|
|
79
|
+
const sessionId = randomUUID();
|
|
80
|
+
try {
|
|
81
|
+
for (const slot of ["banner", "inline"] as const) {
|
|
82
|
+
const next = await outboundFetch(new URL(`${FREEPI_AD_NEXT_PATH}?slot=${slot}`, FREEPI_BASE_URL), {
|
|
83
|
+
method: "GET",
|
|
84
|
+
headers: buildFreepiHeaders(secret, false, sessionId),
|
|
85
|
+
signal: AbortSignal.timeout(15_000),
|
|
86
|
+
});
|
|
87
|
+
if (!next.ok || next.status === 204) continue;
|
|
88
|
+
const ad = (await next.json()) as Record<string, unknown>;
|
|
89
|
+
const adId = ad["ad_id"];
|
|
90
|
+
const clickToken = ad["click_token"];
|
|
91
|
+
if (typeof adId !== "string" || typeof clickToken !== "string") continue;
|
|
92
|
+
await outboundFetch(new URL(FREEPI_AD_IMPRESSION_PATH, FREEPI_BASE_URL), {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: buildFreepiHeaders(secret, false, sessionId),
|
|
95
|
+
body: JSON.stringify({ ad_id: adId, click_token: clickToken, imp_fired: true }),
|
|
96
|
+
signal: AbortSignal.timeout(15_000),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
// The ad surface is best-effort; chat may still work or fail upstream.
|
|
101
|
+
}
|
|
102
|
+
this.#sessionByCredential.set(secret, sessionId);
|
|
103
|
+
return sessionId;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async *dispatch(
|
|
107
|
+
request: CanonicalRequest,
|
|
108
|
+
candidate: ProviderDispatchTarget,
|
|
109
|
+
context: ProviderDispatchContext,
|
|
110
|
+
): AsyncIterable<CanonicalEvent> {
|
|
111
|
+
if (candidate.wire_family !== "chat") {
|
|
112
|
+
assertWireFamily(candidate, "chat", "freepi");
|
|
113
|
+
}
|
|
114
|
+
const secret = readCredentialSecret(context.credential, "Free-pi credential is required");
|
|
115
|
+
const modelId = candidate.model_id || request.model;
|
|
116
|
+
const lifecycle = createUpstreamDeadlineLifecycle(context);
|
|
117
|
+
const outboundFetch =
|
|
118
|
+
(context.outbound_fetch as unknown as typeof fetch | undefined) ?? this.fetchFn;
|
|
119
|
+
|
|
120
|
+
const body = buildFreepiChatBody(modelId, request, candidate.output_limit ?? undefined);
|
|
121
|
+
|
|
122
|
+
const sessionKey = resolvePromptCacheKey(request, context);
|
|
123
|
+
const cacheSessionKey = sessionKey === undefined ? undefined : `${modelId}:${sessionKey}`;
|
|
124
|
+
|
|
125
|
+
const sessionId = await this.#ensureSession(secret, outboundFetch);
|
|
126
|
+
const headers = buildFreepiHeaders(secret, true, sessionId);
|
|
127
|
+
const url = `${FREEPI_BASE_URL}${FREEPI_CHAT_PATH}`;
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const response = await outboundFetch(url, {
|
|
131
|
+
method: "POST",
|
|
132
|
+
headers,
|
|
133
|
+
body: JSON.stringify(body),
|
|
134
|
+
signal: lifecycle.signal,
|
|
135
|
+
});
|
|
136
|
+
if (!response.ok) throw await mapUpstreamHttpError(response, this.provider_id);
|
|
137
|
+
if (!response.body) {
|
|
138
|
+
throw new GatewayError("platform_unavailable", 502, "Free-pi returned empty body (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
139
|
+
}
|
|
140
|
+
lifecycle.release();
|
|
141
|
+
yield* this.#events(
|
|
142
|
+
response.body as ReadableStream<Uint8Array>,
|
|
143
|
+
lifecycle.signal,
|
|
144
|
+
request.model,
|
|
145
|
+
cacheSessionKey,
|
|
146
|
+
request.messages,
|
|
147
|
+
);
|
|
148
|
+
} catch (err: unknown) {
|
|
149
|
+
const abortError = abortGatewayError(lifecycle, err, context.abort_signal);
|
|
150
|
+
if (abortError) throw abortError;
|
|
151
|
+
throw err;
|
|
152
|
+
} finally {
|
|
153
|
+
lifecycle.release();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async *#events(
|
|
158
|
+
body: ReadableStream<Uint8Array>,
|
|
159
|
+
_signal: AbortSignal,
|
|
160
|
+
requestModel: string,
|
|
161
|
+
sessionId?: string,
|
|
162
|
+
_messages?: readonly CanonicalMessage[],
|
|
163
|
+
): AsyncIterable<CanonicalEvent> {
|
|
164
|
+
let seq = 1;
|
|
165
|
+
yield { type: "response_start", sequence_number: seq++, model: requestModel } as CanonicalEvent;
|
|
166
|
+
let stopReason: CanonicalStopReason = "stop";
|
|
167
|
+
let usage: Record<string, unknown> | undefined;
|
|
168
|
+
const toolCallIds = new Map<string, string>();
|
|
169
|
+
const announced = new Set<string>();
|
|
170
|
+
|
|
171
|
+
for await (const frame of parseFreepiSse(body)) {
|
|
172
|
+
if (frame.event === "done") {
|
|
173
|
+
const data = frame.data as { finish_reason?: unknown };
|
|
174
|
+
if (typeof data.finish_reason === "string") {
|
|
175
|
+
stopReason = STOP_REASONS[data.finish_reason] ?? "stop";
|
|
176
|
+
}
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (typeof frame.data !== "object" || frame.data === null) continue;
|
|
180
|
+
const record = frame.data as Record<string, unknown>;
|
|
181
|
+
|
|
182
|
+
// Upstream error payloads can be HTTP 200 `{"error":{...}}`.
|
|
183
|
+
if (record["error"] !== undefined && typeof record["error"] === "object") {
|
|
184
|
+
const nested = record["error"] as Record<string, unknown>;
|
|
185
|
+
throwFreepiUpstreamError(nested["code"], nested["message"]);
|
|
186
|
+
}
|
|
187
|
+
if (record["code"] !== undefined) {
|
|
188
|
+
throwFreepiUpstreamError(record["code"], record["message"]);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const choices = Array.isArray(record["choices"]) ? (record["choices"] as Record<string, unknown>[]) : [];
|
|
192
|
+
const choice = choices[0];
|
|
193
|
+
if (!choice) continue;
|
|
194
|
+
const delta = choice["delta"] as Record<string, unknown> | undefined;
|
|
195
|
+
if (!delta) continue;
|
|
196
|
+
|
|
197
|
+
const text = delta["content"];
|
|
198
|
+
if (typeof text === "string" && text.length > 0) {
|
|
199
|
+
yield {
|
|
200
|
+
type: "content_delta",
|
|
201
|
+
sequence_number: seq++,
|
|
202
|
+
content: { kind: "text", text },
|
|
203
|
+
} as CanonicalEvent;
|
|
204
|
+
}
|
|
205
|
+
const reasoning = delta["reasoning_content"];
|
|
206
|
+
if (typeof reasoning === "string" && reasoning.length > 0) {
|
|
207
|
+
yield {
|
|
208
|
+
type: "content_delta",
|
|
209
|
+
sequence_number: seq++,
|
|
210
|
+
content: { kind: "reasoning", payload: null, summary: reasoning },
|
|
211
|
+
} as CanonicalEvent;
|
|
212
|
+
}
|
|
213
|
+
yield* this.#toolCallDeltas(delta["tool_calls"], announced, toolCallIds, () => seq++);
|
|
214
|
+
if (typeof choice["finish_reason"] === "string" && choice["finish_reason"] !== null) {
|
|
215
|
+
stopReason = STOP_REASONS[String(choice["finish_reason"])] ?? "stop";
|
|
216
|
+
}
|
|
217
|
+
const u = record["usage"];
|
|
218
|
+
if (typeof u === "object" && u !== null) usage = u as Record<string, unknown>;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const assessed = usage === undefined ? undefined : usageFromProvider(usage);
|
|
222
|
+
if (assessed !== undefined) {
|
|
223
|
+
yield { type: "usage", sequence_number: seq++, usage: assessed } as unknown as CanonicalEvent;
|
|
224
|
+
}
|
|
225
|
+
yield {
|
|
226
|
+
type: "terminal",
|
|
227
|
+
sequence_number: seq,
|
|
228
|
+
state: "complete",
|
|
229
|
+
stop_reason: stopReason,
|
|
230
|
+
...(assessed === undefined ? {} : { usage: assessed }),
|
|
231
|
+
...(sessionId !== undefined ? { prompt_cache_key: sessionId } : {}),
|
|
232
|
+
} as unknown as CanonicalEvent;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Emits canonical tool-call deltas from one OpenAI `tool_calls` array.
|
|
237
|
+
*
|
|
238
|
+
* The upstream spreads one call over many frames: the first carries id and
|
|
239
|
+
* name, the middle ones carry `arguments` as successive JSON fragments with
|
|
240
|
+
* an empty id, and the last repeats the id with no arguments. Grouping by
|
|
241
|
+
* `index` ties them together — the id is absent exactly when the arguments
|
|
242
|
+
* are present, so keying on id would drop every fragment.
|
|
243
|
+
*/
|
|
244
|
+
async *#toolCallDeltas(
|
|
245
|
+
raw: unknown,
|
|
246
|
+
announced: Set<string>,
|
|
247
|
+
toolCallIds: Map<string, string>,
|
|
248
|
+
nextSeq: () => number,
|
|
249
|
+
): AsyncIterable<CanonicalEvent> {
|
|
250
|
+
if (!Array.isArray(raw)) return;
|
|
251
|
+
for (const entry of raw) {
|
|
252
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
253
|
+
const call = entry as Record<string, unknown>;
|
|
254
|
+
const fn = call["function"];
|
|
255
|
+
const fnRecord = typeof fn === "object" && fn !== null ? (fn as Record<string, unknown>) : undefined;
|
|
256
|
+
const name = typeof fnRecord?.["name"] === "string" ? (fnRecord["name"] as string) : undefined;
|
|
257
|
+
const args = typeof fnRecord?.["arguments"] === "string" ? (fnRecord["arguments"] as string) : "";
|
|
258
|
+
const declaredId = typeof call["id"] === "string" ? call["id"] : "";
|
|
259
|
+
const index = typeof call["index"] === "number" ? call["index"] : 0;
|
|
260
|
+
const slot = `idx:${index}`;
|
|
261
|
+
|
|
262
|
+
if (declaredId.length > 0) {
|
|
263
|
+
toolCallIds.set(slot, declaredId);
|
|
264
|
+
if (announced.has(declaredId)) continue;
|
|
265
|
+
announced.add(declaredId);
|
|
266
|
+
yield {
|
|
267
|
+
type: "tool_call_delta",
|
|
268
|
+
sequence_number: nextSeq(),
|
|
269
|
+
call_id: declaredId,
|
|
270
|
+
name: name ?? "",
|
|
271
|
+
arguments_delta: "",
|
|
272
|
+
} as CanonicalEvent;
|
|
273
|
+
if (args.length === 0) continue;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (args.length === 0) continue;
|
|
277
|
+
const id = toolCallIds.get(slot);
|
|
278
|
+
if (id === undefined) continue;
|
|
279
|
+
yield {
|
|
280
|
+
type: "tool_call_delta",
|
|
281
|
+
sequence_number: nextSeq(),
|
|
282
|
+
call_id: id,
|
|
283
|
+
arguments_delta: args,
|
|
284
|
+
} as CanonicalEvent;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Adapter for the international free-pi deployment. */
|
|
290
|
+
export function createFreepiAdapter(options: FreepiAdapterOptions = {}): ProviderAdapter {
|
|
291
|
+
return new FreepiAdapter({ ...options, providerId: FREEPI_PROVIDER_ID });
|
|
292
|
+
}
|