cartethyia 2.7.9 → 2.8.1
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 +6 -5
- package/dist/dashboard/assets/{BannedPage-BxoG1MrL.js → BannedPage-Dq530awD.js} +1 -1
- package/dist/dashboard/assets/BannedPage-Dq530awD.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-ixwgKk53.js → CliToolDetailPage-DZzaS-7S.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-DZzaS-7S.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BmttNV3U.js → CliToolsPage-DaZZ3cqP.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DaZZ3cqP.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BE3qwwfC.js → CombosPage-BlC_xdkX.js} +1 -1
- package/dist/dashboard/assets/CombosPage-BlC_xdkX.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-CRZdmMIX.js → ConsoleLogPage-D-rqeVtG.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-D-rqeVtG.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-Dv_FJ6Er.js → CustomizationPage-7xT5yg2E.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-7xT5yg2E.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-BOoO8rpz.js → InjectSystemPromptPanel-6Teoq9Se.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-6Teoq9Se.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-ChJuNNVN.js → LoginPage-CZuI0MAR.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CZuI0MAR.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BZAJtPg1.js → ModelPicker-C1hNTlkr.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-C1hNTlkr.js.gz +0 -0
- package/dist/dashboard/assets/{OverviewPage-BE40s3u5.js → OverviewPage-0yfhSI1C.js} +1 -1
- package/dist/dashboard/assets/OverviewPage-0yfhSI1C.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js +8 -0
- package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js.gz +0 -0
- package/dist/dashboard/assets/{ProviderIcon-CQ6bPUf_.js → ProviderIcon-Ddhm1InL.js} +1 -1
- package/dist/dashboard/assets/ProviderIcon-Ddhm1InL.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js +9 -0
- package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js +1 -0
- package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-C5HPWqEv.js → SettingsPage-BzZAvYSX.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-BzZAvYSX.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-CFNcATJJ.js → SetupPage--s-EfEIL.js} +1 -1
- package/dist/dashboard/assets/SetupPage--s-EfEIL.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-YoD4tY5l.js → StudioPage-BjXFGSK4.js} +1 -1
- package/dist/dashboard/assets/StudioPage-BjXFGSK4.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-DxjaAhb_.js → UsagePage-CP8F8BN7.js} +1 -1
- package/dist/dashboard/assets/UsagePage-CP8F8BN7.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-BTlkq_Uk.js → api-keys-2hc12Uz2.js} +1 -1
- package/dist/dashboard/assets/api-keys-2hc12Uz2.js.gz +0 -0
- package/dist/dashboard/assets/app-DGXbNfAR.css +2 -0
- package/dist/dashboard/assets/app-DGXbNfAR.css.gz +0 -0
- package/dist/dashboard/assets/assert-BMcj1xZE.js +1 -0
- package/dist/dashboard/assets/assert-BMcj1xZE.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DdbT_7Am.js → badge-DkRUuVfz.js} +1 -1
- package/dist/dashboard/assets/badge-DkRUuVfz.js.gz +0 -0
- package/dist/dashboard/assets/{banner-CsGN-nbm.js → banner-CDjW841Q.js} +1 -1
- package/dist/dashboard/assets/banner-CDjW841Q.js.gz +0 -0
- package/dist/dashboard/assets/{card-mgZ_TLLZ.js → card-Bto5UlL9.js} +1 -1
- package/dist/dashboard/assets/card-Bto5UlL9.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-A1-v8-03.js → cli-tools-DaH6g9hL.js} +1 -1
- package/dist/dashboard/assets/cli-tools-DaH6g9hL.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-CJ4Zty89.js → clipboard-button-BYKBCzjk.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-BYKBCzjk.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-B5L2jPQ_.js → dialog-DL0Zftwv.js} +1 -1
- package/dist/dashboard/assets/dialog-DL0Zftwv.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-DkjUouAj.js → drawer-Dpx_UXaS.js} +1 -1
- package/dist/dashboard/assets/drawer-Dpx_UXaS.js.gz +0 -0
- package/dist/dashboard/assets/{entry-B-LVLV6o.js → entry-BV7-1NoM.js} +1 -1
- package/dist/dashboard/assets/entry-BV7-1NoM.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CAhdYl3B.js → entry-CGvdNU6r.js} +1 -1
- package/dist/dashboard/assets/entry-CGvdNU6r.js.gz +0 -0
- package/dist/dashboard/assets/entry-DScKYvZj.js +4 -0
- package/dist/dashboard/assets/entry-DScKYvZj.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-CrsdLVsn.js → formatters-BS3DZlZj.js} +1 -1
- package/dist/dashboard/assets/formatters-BS3DZlZj.js.gz +0 -0
- package/dist/dashboard/assets/{index-BU64Lqvp.js → index-SQnvvTWX.js} +2 -2
- package/dist/dashboard/assets/index-SQnvvTWX.js.gz +0 -0
- package/dist/dashboard/assets/{inline-B2aDLyyg.js → inline-Dl-cuLK0.js} +1 -1
- package/dist/dashboard/assets/inline-Dl-cuLK0.js.gz +0 -0
- package/dist/dashboard/assets/{input-BCVkdR9d.js → input-Bfx0EzRc.js} +1 -1
- package/dist/dashboard/assets/input-Bfx0EzRc.js.gz +0 -0
- package/dist/dashboard/assets/{layout-DNcAdMK-.js → layout-BxhbQ_JS.js} +1 -1
- package/dist/dashboard/assets/layout-BxhbQ_JS.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-DgrIH0hw.js → list-row-D49Y4ptn.js} +1 -1
- package/dist/dashboard/assets/list-row-D49Y4ptn.js.gz +0 -0
- package/dist/dashboard/assets/{live-Cae01iu8.js → live-CbVTdX9V.js} +1 -1
- package/dist/dashboard/assets/live-CbVTdX9V.js.gz +0 -0
- package/dist/dashboard/assets/{network-CRib7H_2.js → network-JGvDveof.js} +1 -1
- package/dist/dashboard/assets/network-JGvDveof.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BKQE8Xpz.js → numbers-Dw6kiew-.js} +1 -1
- package/dist/dashboard/assets/numbers-Dw6kiew-.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-ecPvG_8g.js → pagination-iUoI8GeE.js} +1 -1
- package/dist/dashboard/assets/pagination-iUoI8GeE.js.gz +0 -0
- package/dist/dashboard/assets/providers/sensenova-intl.png +0 -0
- package/dist/dashboard/assets/providers/sensenova.png +0 -0
- package/dist/dashboard/assets/providers/zro.svg +3 -0
- package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
- package/dist/dashboard/assets/{routing-Ckm1UXuC.js → routing-8jZr-3WE.js} +1 -1
- package/dist/dashboard/assets/routing-8jZr-3WE.js.gz +0 -0
- package/dist/dashboard/assets/{select-ChoFWyKz.js → select-oO1FxMVb.js} +2 -2
- package/dist/dashboard/assets/select-oO1FxMVb.js.gz +0 -0
- package/dist/dashboard/assets/{settings-CKjFPlKW.js → settings-CLr3kdIx.js} +1 -1
- package/dist/dashboard/assets/settings-CLr3kdIx.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DWCwU3fh.js → stack-Y3pDteZr.js} +1 -1
- package/dist/dashboard/assets/stack-Y3pDteZr.js.gz +0 -0
- package/dist/dashboard/assets/{state-Cg3VL5J_.js → state-CArX_Iak.js} +1 -1
- package/dist/dashboard/assets/state-CArX_Iak.js.gz +0 -0
- package/dist/dashboard/assets/{switch-Db70BMix.js → switch-DwlwmDO-.js} +1 -1
- package/dist/dashboard/assets/switch-DwlwmDO-.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BqdE0LuM.js → useMutation-DJ9GYEgf.js} +1 -1
- package/dist/dashboard/assets/useMutation-DJ9GYEgf.js.gz +0 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +54 -53
- package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
- package/migrations/full-postgres/0047_add_provider_device_flow_proxy.sql +17 -0
- package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
- package/migrations/lite-sqlite3/0047_add_provider_device_flow_proxy.sql +19 -0
- package/package.json +1 -1
- package/src/boot/app.ts +11 -14
- package/src/boot/dependencies.ts +5 -7
- package/src/boot/flush.ts +20 -0
- package/src/boot/lifecycle.ts +7 -5
- package/src/config/providers.ts +6 -5
- package/src/config/routing.ts +26 -0
- package/src/config/spec-metadata.ts +72 -0
- package/src/config/spec.ts +28 -8
- package/src/console/api-keys/contracts.ts +12 -1
- package/src/console/api-keys/routes.ts +40 -28
- package/src/console/auth/account-reset.ts +7 -9
- package/src/console/backup/contracts.ts +4 -0
- package/src/console/backup/export-endpoint.ts +7 -5
- package/src/console/backup/nine-router.ts +71 -10
- package/src/console/backup/service.ts +8 -1
- package/src/console/cli-tools/service.ts +6 -12
- package/src/console/console-router-guards.ts +23 -16
- package/src/console/console-router.ts +1 -1
- package/src/console/domain-registration.ts +7 -4
- package/src/console/inferhub/client.ts +1 -1
- package/src/console/inferhub/normalize.ts +24 -28
- package/src/console/inferhub/types.ts +3 -0
- package/src/console/ip-bans/routes.ts +7 -13
- package/src/console/observability/live.ts +2 -2
- package/src/console/observability/store.ts +0 -53
- package/src/console/providers/catalog/contracts.ts +9 -3
- package/src/console/providers/catalog/model-operations.ts +2 -3
- package/src/console/providers/catalog/provider-operations.ts +1 -1
- package/src/console/providers/catalog/routes.ts +3 -2
- package/src/console/providers/catalog/store.ts +1914 -1902
- package/src/console/providers/detail/routes.ts +1 -0
- package/src/console/providers/detail/store.ts +4 -0
- package/src/console/providers/oauth/callback-listener.ts +30 -11
- package/src/console/providers/oauth/device-egress.ts +123 -0
- package/src/console/providers/oauth/routes.ts +123 -16
- package/src/console/quota/account-quota-global.ts +14 -24
- package/src/console/quota/account-quota-shared.ts +49 -0
- package/src/console/quota/account-quota-tenant.ts +11 -23
- package/src/console/quota/account-test-egress.ts +2 -2
- package/src/console/quota/daily-growth-job.ts +1 -1
- package/src/console/quota/quota-view.ts +19 -3
- package/src/console/routing/pools/contracts.ts +1 -1
- package/src/console/routing/pools/operations.ts +32 -47
- package/src/console/routing/pools/probe-verdict.ts +2 -2
- package/src/console/routing/pools/relay-deploy.ts +1 -1
- package/src/console/routing/pools/store.ts +4 -4
- package/src/console/shared/errors.ts +33 -6
- package/src/main.ts +2 -3
- package/src/network/client.ts +67 -0
- package/src/network/egress/bidi-session.ts +322 -0
- package/src/network/egress/dial.ts +201 -0
- package/src/network/egress/factory.ts +191 -0
- package/src/network/{outbound-fetch.ts → egress/fetch.ts} +195 -29
- package/src/network/egress/tls-profile.ts +123 -0
- package/src/network/egress/ws.ts +175 -0
- package/src/network/pool/{byte-accounting.ts → accounting.ts} +3 -2
- package/src/network/{types.ts → pool/agent-config.ts} +1 -1
- package/src/network/pool/agent.ts +2 -2
- package/src/network/{pool-cooldown-store.ts → pool/cooldown-store.ts} +95 -20
- package/src/network/{pool-health-machine.ts → pool/health.ts} +205 -6
- package/src/network/pool/resolver.ts +10 -849
- package/src/network/pool/selector.ts +184 -41
- package/src/network/ssrf.ts +7 -7
- package/src/network/{retry.ts → util/retry.ts} +1 -1
- package/src/network/{rate-limit-verdict.ts → verdict.ts} +1 -1
- package/src/observability/event-log.ts +7 -361
- package/src/observability/performance-metrics.ts +9 -0
- package/src/observability/runtime-metrics.ts +14 -9
- package/src/observability/telemetry-buffer.ts +16 -26
- package/src/observability/telemetry-reconciliation.ts +9 -37
- package/src/observability/telemetry-row.ts +125 -0
- package/src/persistence/db-mode.ts +4 -1
- package/src/persistence/dialect.ts +18 -0
- package/src/persistence/migrate.ts +6 -3
- package/src/persistence/schema.ts +17 -0
- package/src/persistence/ttl-cache.ts +21 -3
- package/src/protocol/codecs.ts +59 -2
- package/src/protocol/dsml.ts +34 -86
- package/src/protocol/markup-stream-parser.ts +195 -0
- package/src/protocol/reasoning-text.ts +19 -0
- package/src/protocol/request/chat.ts +10 -10
- package/src/protocol/request/gemini.ts +8 -2
- package/src/protocol/request/messages.ts +34 -22
- package/src/protocol/response/chat.ts +12 -20
- package/src/protocol/response/messages.ts +34 -7
- package/src/protocol/response/omp-tool-call-markup.ts +43 -86
- package/src/protocol/response/responses.ts +18 -2
- package/src/protocol/stop-reason.ts +23 -1
- package/src/protocol/think-split.ts +112 -0
- package/src/protocol/translation/dialect/qwen.ts +26 -58
- package/src/protocol/translation/quirks.ts +15 -0
- package/src/protocol/upstream/send-claude-messages.ts +2 -2
- package/src/providers/adapters/compatible-adapter.ts +76 -12
- package/src/providers/authentication/oauth-client.ts +40 -6
- package/src/providers/authentication/oauth-device-flow.ts +105 -11
- package/src/providers/authentication/oauth-flow-store.ts +82 -1
- package/src/providers/authentication/oauth-refresh-service.ts +766 -758
- package/src/providers/configured-provider.ts +148 -0
- package/src/providers/default-registry.ts +137 -36
- package/src/providers/discovery/discovery-types.ts +7 -0
- package/src/providers/discovery/probe-phases.ts +20 -6
- package/src/providers/discovery/probing-service.ts +22 -2
- package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
- package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +16 -1
- package/src/providers/integrations/buddy/account.ts +969 -0
- package/src/providers/integrations/buddy/capabilities.ts +12 -12
- package/src/providers/integrations/buddy/{buddy-catalog-shared.ts → catalog.ts} +198 -136
- package/src/providers/integrations/buddy/{buddy-chat-shared.ts → chat.ts} +176 -20
- package/src/providers/integrations/buddy/codebuddy/index.ts +429 -0
- package/src/providers/integrations/buddy/codebuddy/shared.ts +41 -26
- package/src/providers/integrations/buddy/headers.ts +311 -0
- package/src/providers/integrations/buddy/lifecycle.ts +1018 -0
- package/src/providers/integrations/buddy/{buddy-media.ts → media.ts} +1 -1
- package/src/providers/integrations/claude/claude-quota.ts +14 -17
- package/src/providers/integrations/cline/cline-oauth.ts +10 -5
- package/src/providers/integrations/cline/cline.ts +5 -6
- package/src/providers/integrations/clixad/clixad-oauth.ts +98 -0
- package/src/providers/integrations/clixad/clixad-quota.ts +132 -0
- package/src/providers/integrations/clixad/provider.ts +145 -0
- package/src/providers/integrations/codex/codex-oauth.ts +8 -5
- package/src/providers/integrations/commandcode/quota.ts +22 -28
- package/src/providers/integrations/cursor/cursor-auth.ts +1 -9
- package/src/providers/integrations/cursor/cursor-exec.ts +9 -2
- package/src/providers/integrations/cursor/cursor-hardening.ts +279 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +605 -6
- package/src/providers/integrations/cursor/cursor-request.ts +26 -12
- package/src/providers/integrations/cursor/cursor-shared.ts +8 -5
- package/src/providers/integrations/cursor/cursor.ts +574 -110
- package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
- package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
- package/src/providers/integrations/deepai/deepai-catalog.ts +149 -0
- package/src/providers/integrations/deepai/deepai-common.ts +145 -0
- package/src/providers/integrations/deepai/deepai-oauth.ts +129 -0
- package/src/providers/integrations/deepai/deepai-quota.ts +97 -0
- package/src/providers/integrations/deepai/provider.ts +42 -0
- package/src/providers/integrations/devin/devin-auth.ts +7 -0
- package/src/providers/integrations/devin/protocol/devin-proto.ts +1 -27
- package/src/providers/integrations/factory/factory-oauth.ts +33 -51
- package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
- package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
- package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
- package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
- package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
- package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
- package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
- package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
- package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
- package/src/providers/integrations/freebuff/freebuff.ts +682 -0
- package/src/providers/integrations/freepi/freepi-catalog.ts +5 -0
- package/src/providers/integrations/freepi/freepi-discovery.ts +2 -2
- package/src/providers/integrations/freepi/freepi-headers.ts +2 -2
- package/src/providers/integrations/freepi/freepi-protocol.ts +11 -2
- package/src/providers/integrations/freepi/freepi-realm.ts +0 -3
- package/src/providers/integrations/freepi/freepi-session.ts +158 -0
- package/src/providers/integrations/freepi/freepi.ts +80 -54
- package/src/providers/integrations/github/github-discovery.ts +30 -13
- package/src/providers/integrations/github/github-oauth.ts +217 -44
- package/src/providers/integrations/github/github-quota.ts +22 -5
- package/src/providers/integrations/github/github.ts +40 -32
- package/src/providers/integrations/grok/grok-oauth.ts +52 -49
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
- package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
- package/src/providers/integrations/hermes/hermes-oauth.ts +3 -3
- package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +42 -13
- package/src/providers/integrations/hermes/index.ts +2 -0
- package/src/providers/integrations/kilo/kilo-oauth.ts +4 -4
- package/src/providers/integrations/kilo/kilo.ts +1 -1
- package/src/providers/integrations/kimi/kimi-oauth.ts +3 -3
- package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
- package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
- package/src/providers/integrations/kiro/kiro-oauth.ts +2 -2
- package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
- package/src/providers/integrations/kiro/kiro.ts +15 -19
- package/src/providers/integrations/muse/muse-oauth.ts +5 -5
- package/src/providers/integrations/ollamacloud/index.ts +2 -0
- package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
- package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
- package/src/providers/integrations/opencode/fingerprint.ts +1 -1
- package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
- package/src/providers/integrations/openrouter/provider.ts +1 -1
- package/src/providers/integrations/perch/perch-credential.ts +25 -0
- package/src/providers/integrations/perch/perch-protocol.ts +0 -6
- package/src/providers/integrations/perch/perch-quota.ts +2 -16
- package/src/providers/integrations/perch/perch.ts +6 -16
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +19 -60
- package/src/providers/integrations/qoder/qoder-adapter.ts +20 -125
- package/src/providers/integrations/qoder/qoder-auth.ts +109 -30
- package/src/providers/integrations/qoder/qoder-catalog.ts +21 -6
- package/src/providers/integrations/qoder/qoder-chat.ts +32 -29
- package/src/providers/integrations/qoder/qoder-checkin.ts +5 -5
- package/src/providers/integrations/qoder/qoder-cosy.ts +37 -9
- package/src/providers/integrations/qoder/qoder-oauth.ts +9 -2
- package/src/providers/integrations/qoder/qoder-realm.ts +13 -36
- package/src/providers/integrations/qoder/qoder-sse.ts +447 -68
- package/src/providers/integrations/qoder/qoder-types.ts +9 -0
- package/src/providers/integrations/qoder/qoder.ts +3 -26
- package/src/providers/integrations/sensenova/provider.ts +144 -0
- package/src/providers/integrations/trae/trae-protocol.ts +9 -5
- package/src/providers/integrations/trae/trae-quota.ts +21 -29
- package/src/providers/integrations/trae/trae-realm.ts +38 -0
- package/src/providers/integrations/trae/trae.ts +7 -24
- package/src/providers/integrations/vercel/provider.ts +117 -0
- package/src/providers/integrations/vercel/vercel-quota.ts +96 -0
- package/src/providers/integrations/xai/xai-oauth.ts +5 -5
- package/src/providers/integrations/zcode-start-plan/gifts.ts +1 -1
- package/src/providers/integrations/zcode-start-plan/oauth.ts +3 -3
- package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
- package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
- package/src/providers/integrations/zro/zro.ts +205 -0
- package/src/providers/operations/account-error-classification.ts +199 -307
- package/src/providers/operations/account-health-service.ts +82 -1
- package/src/providers/operations/account-refresh-status.ts +36 -37
- package/src/providers/operations/account-tier.ts +15 -16
- package/src/providers/operations/client-version-resolver.ts +1 -1
- package/src/providers/operations/client-versions.ts +100 -81
- package/src/providers/operations/cooldown-rules.ts +11 -16
- package/src/providers/operations/oauth-refresh-lead.ts +46 -22
- package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
- package/src/providers/operations/provider-catalog-service.ts +1 -1
- package/src/providers/operations/provider-credential-service.ts +118 -51
- package/src/providers/operations/provider-rules/github.ts +48 -0
- package/src/providers/operations/provider-rules/grok.ts +33 -0
- package/src/providers/operations/provider-rules/inception.ts +49 -0
- package/src/providers/operations/provider-rules/index.ts +32 -0
- package/src/providers/operations/provider-rules/perch.ts +37 -0
- package/src/providers/operations/provider-rules/qoder.ts +70 -0
- package/src/providers/operations/provider-rules/trae.ts +81 -0
- package/src/providers/operations/session-resolution.ts +17 -1
- package/src/providers/provider-contracts.ts +6 -4
- package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
- package/src/providers/provider-metadata.ts +70 -10
- package/src/providers/provider-probe.ts +15 -1
- package/src/providers/provider-registry.ts +42 -33
- package/src/providers/quota/quota-contracts.ts +2 -0
- package/src/providers/quota/quota-limit-windows.ts +16 -5
- package/src/providers/quota/quota-support.ts +1 -1
- package/src/providers/quota/quota-window-parser.ts +90 -18
- package/src/providers/quota/refresh.ts +100 -21
- package/src/security/api-key-auth-cache.ts +1 -1
- package/src/security/api-key-auth.ts +6 -2
- package/src/security/ip-boundary.ts +1 -1
- package/src/transport/dispatch/attempt-finalize.ts +15 -14
- package/src/transport/dispatch/attempt-loop.ts +73 -19
- package/src/transport/dispatch/generation.ts +2 -8
- package/src/transport/dispatch/handler-deps.ts +1 -1
- package/src/transport/dispatch/leases.ts +1 -1
- package/src/transport/dispatch/native-shared.ts +25 -0
- package/src/transport/dispatch/proxy-request.ts +8 -46
- package/src/transport/dispatch/responses-compact.ts +2 -8
- package/src/transport/dispatch/retry-policy.ts +1 -1
- package/src/transport/dispatch/streaming/finalize.ts +10 -7
- package/src/transport/dispatch/streaming/stream.ts +9 -37
- package/src/transport/dispatch/systemone.ts +2 -8
- package/src/transport/dispatch/token-count.ts +2 -6
- package/src/transport/dispatch/upstream.ts +63 -0
- package/src/transport/drain-endpoint.ts +31 -4
- package/src/transport/failure-policy.ts +16 -30
- package/src/transport/guards/body-policy.ts +7 -1
- package/src/transport/guards/error-lifecycle.ts +13 -1
- package/src/transport/guards/request-context.ts +22 -14
- package/src/transport/request/preparer.ts +81 -71
- package/src/transport/routing/bounded-lru.ts +14 -0
- package/src/transport/routing/drain-reserve.ts +129 -0
- package/src/transport/routing/plan-gate.ts +7 -1
- package/src/transport/routing/round-robin.ts +60 -0
- package/src/transport/routing/route-catalog.ts +123 -111
- package/src/transport/routing/route-model.ts +51 -9
- package/src/transport/routing/router.ts +393 -32
- package/src/transport/shutdown-notice.ts +1 -1
- package/src/transport/surface/adapters.ts +11 -2
- package/src/transport/surface/chat/adapter.ts +2 -2
- package/src/transport/surface/chat/encode.ts +47 -4
- package/src/transport/surface/chat/parse.ts +2 -12
- package/src/transport/surface/completion.ts +44 -33
- package/src/transport/surface/content-parts.ts +21 -0
- package/src/transport/surface/messages/adapter.ts +7 -1
- package/src/transport/surface/messages/encode.ts +11 -3
- package/src/transport/surface/messages/parse.ts +2 -1
- package/src/transport/surface/messages/stream.ts +33 -17
- package/src/transport/surface/responses/encode.ts +22 -5
- package/src/transport/surface/responses/parse.ts +3 -11
- package/src/workers/buddy-growth-tasks.ts +8 -8
- package/src/workers/daily-checkin.ts +4 -4
- package/src/workers/quota-refresh-sweep.ts +1 -1
- package/src/workers/zcode-gift-claim.ts +1 -1
- package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +0 -8
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js +0 -9
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js +0 -1
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
- package/dist/dashboard/assets/app-BX2nnKqs.css +0 -2
- package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CndthXFQ.js +0 -1
- package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
- package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
- package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
- package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
- package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
- package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
- package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
- package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
- package/dist/dashboard/assets/entry-D2xD0Tmd.js +0 -4
- package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
- package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
- package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
- package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
- package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
- package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
- package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
- package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
- package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
- package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
- package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
- package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
- package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
- package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
- package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
- package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
- package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
- package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
- package/src/network/pool-health.ts +0 -210
- package/src/providers/bundled-discovery-providers.ts +0 -98
- package/src/providers/configured-openai-providers.ts +0 -120
- package/src/providers/integrations/buddy/buddy-checkin.ts +0 -521
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +0 -76
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -130
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +0 -234
- package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -496
- package/src/providers/integrations/buddy/buddy-identity.ts +0 -300
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +0 -341
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +0 -417
- package/src/providers/integrations/buddy/buddy-server-tools.ts +0 -240
- package/src/providers/integrations/buddy/buddy-usage-shared.ts +0 -221
- package/src/providers/integrations/buddy/buddy-usage-store.ts +0 -181
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +0 -37
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +0 -185
- package/src/providers/integrations/buddy/codebuddy/oauth.ts +0 -47
- package/src/providers/integrations/buddy/codebuddy/provider.ts +0 -175
- package/src/providers/integrations/buddy/codebuddy/quota.ts +0 -154
- package/src/providers/integrations/qoder/chat-proto.ts +0 -328
- package/src/providers/integrations/qoder/qoder-grpc.ts +0 -871
- package/src/providers/integrations/qoder/qoder-ide.ts +0 -588
- package/src/providers/operations/model-discovery-cache.ts +0 -34
- package/src/providers/operations/probe-fetch.ts +0 -15
- package/src/providers/operations/provider-version-cache.ts +0 -30
- package/src/providers/provider-capabilities.ts +0 -33
- package/src/providers/provider-credential-envelope.ts +0 -43
- /package/dist/dashboard/assets/{app-B7kIWFm5.js → app-C0gTXEoN.js} +0 -0
- /package/dist/dashboard/assets/{app-B7kIWFm5.js.gz → app-C0gTXEoN.js.gz} +0 -0
- /package/src/network/{http2-bidi.ts → bidi.ts} +0 -0
- /package/src/network/pool/{pool-scripts.ts → scripts.ts} +0 -0
- /package/src/network/pool/{pool-state.ts → state.ts} +0 -0
- /package/src/network/{backoff.ts → util/backoff.ts} +0 -0
- /package/src/network/{deduplication.ts → util/dedupe.ts} +0 -0
- /package/src/network/{ip-utils.ts → util/ip.ts} +0 -0
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
import { defineModel, type ModelDefinition } from "../../provider-model-definition";
|
|
20
20
|
import {
|
|
21
21
|
FREEPI_CHAT_PATH,
|
|
22
|
-
FREEPI_CLI_VERSION,
|
|
23
22
|
FREEPI_CONTEXT_WINDOW,
|
|
24
23
|
FREEPI_DEFAULT_MODEL,
|
|
25
24
|
FREEPI_MAX_OUTPUT_TOKENS,
|
|
26
25
|
FREEPI_PROVIDER_ID,
|
|
27
26
|
} from "./freepi-realm";
|
|
27
|
+
import { getFreepiVersion } from "../../operations/client-versions";
|
|
28
28
|
|
|
29
29
|
/** Catalog id → canonical roster id for one model. */
|
|
30
30
|
const CATALOG_ID_REMAP: Readonly<Record<string, string>> = {
|
|
@@ -44,7 +44,7 @@ export async function discoverFreepiModels(input: {
|
|
|
44
44
|
headers: {
|
|
45
45
|
authorization: `Bearer ${input.token}`,
|
|
46
46
|
accept: "application/json",
|
|
47
|
-
"x-client-version":
|
|
47
|
+
"x-client-version": getFreepiVersion(),
|
|
48
48
|
},
|
|
49
49
|
...(input.signal ? { signal: input.signal } : {}),
|
|
50
50
|
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// CLI version) on the chat wire, and uses `application/json`. There is no
|
|
5
5
|
// device fingerprint — the JWT is the whole identity.
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { getFreepiVersion } from "../../operations/client-versions";
|
|
8
8
|
|
|
9
9
|
/** Builds the header set for one free-pi request. */
|
|
10
10
|
export function buildFreepiHeaders(jwt: string, stream = false, sessionId?: string): Record<string, string> {
|
|
@@ -12,7 +12,7 @@ export function buildFreepiHeaders(jwt: string, stream = false, sessionId?: stri
|
|
|
12
12
|
authorization: `Bearer ${jwt}`,
|
|
13
13
|
"content-type": "application/json",
|
|
14
14
|
accept: stream ? "text/event-stream" : "application/json",
|
|
15
|
-
"x-client-version":
|
|
15
|
+
"x-client-version": getFreepiVersion(),
|
|
16
16
|
};
|
|
17
17
|
if (sessionId !== undefined && sessionId.length > 0) {
|
|
18
18
|
headers["x-session-id"] = sessionId;
|
|
@@ -194,8 +194,8 @@ export async function* parseFreepiSse(
|
|
|
194
194
|
}
|
|
195
195
|
} else if (line.startsWith(":")) {
|
|
196
196
|
continue;
|
|
197
|
-
} else if (line.startsWith("data")) {
|
|
198
|
-
let value = line.slice(
|
|
197
|
+
} else if (line === "data" || line.startsWith("data:")) {
|
|
198
|
+
let value = line === "data" ? "" : line.slice(5);
|
|
199
199
|
if (value.startsWith(" ")) value = value.slice(1);
|
|
200
200
|
data.push(value);
|
|
201
201
|
}
|
|
@@ -238,5 +238,14 @@ export function throwFreepiUpstreamError(code: unknown, message: unknown): never
|
|
|
238
238
|
if (codeStr === "upstream_error") {
|
|
239
239
|
throw new GatewayError("platform_unavailable", 502, `${msg} (request {requestId}). The upstream model host errored. 🌙`, { upstream_code: codeStr }, "upstream");
|
|
240
240
|
}
|
|
241
|
+
// A connection-bound session refusal: the caller's transcript carries native
|
|
242
|
+
// items (tool_call_id) from a session this x-session-id does not own. Named
|
|
243
|
+
// distinctly so the adapter can rotate the session and rebase the history.
|
|
244
|
+
// The upstream also reports it as a free-form message with only a generic
|
|
245
|
+
// code, so match the wording too (donor: "does not belong to this connection").
|
|
246
|
+
const ownershipRefusal = /(?:does not|doesn't|not) belong to|belongs to (?:another|a different)/i.test(msg);
|
|
247
|
+
if (codeStr === "session_mismatch" || codeStr === "invalid_session" || codeStr === "stale_session" || ownershipRefusal) {
|
|
248
|
+
throw new GatewayError("upstream_conflict", 409, `Free-pi session ownership refused: ${msg} (request {requestId}). Rebasing onto a fresh session 🔄`, { upstream_code: codeStr, freepi_session_mismatch: true }, "upstream");
|
|
249
|
+
}
|
|
241
250
|
throw new GatewayError("platform_unavailable", 502, `${msg} (request {requestId}). The free-pi upstream refused the request. 🌙`, { upstream_code: codeStr }, "upstream");
|
|
242
251
|
}
|
|
@@ -16,9 +16,6 @@ export const FREEPI_CHAT_PATH = "/v1/chat/completions";
|
|
|
16
16
|
export const FREEPI_AD_NEXT_PATH = "/ads/next";
|
|
17
17
|
export const FREEPI_AD_IMPRESSION_PATH = "/ads/impression";
|
|
18
18
|
|
|
19
|
-
/** Client version the gateway reports via `x-client-version`. */
|
|
20
|
-
export const FREEPI_CLI_VERSION = "0.6.14";
|
|
21
|
-
|
|
22
19
|
/** Consent version shipped in 0.6.14. */
|
|
23
20
|
export const FREEPI_CONSENT_VERSION = "4";
|
|
24
21
|
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// Free-pi session identity and connection-bound history repair.
|
|
2
|
+
//
|
|
3
|
+
// Free-pi binds a conversation's native items (tool_call_id / assistant tool
|
|
4
|
+
// calls) to the `x-session-id` that opened it. Reusing one session id across
|
|
5
|
+
// unrelated conversations, or carrying a stale affinity onto a new account,
|
|
6
|
+
// makes the upstream answer "does not belong to this session". The donor's
|
|
7
|
+
// rule for connection-bound history (oh-my-pi's Copilot compat) is to rebuild
|
|
8
|
+
// prior turns from message content when the connection changes.
|
|
9
|
+
//
|
|
10
|
+
// So: one session per (credential, affinity) — a changed affinity *is* a fresh
|
|
11
|
+
// session — and on a session-ownership refusal, rotate the session and rebase
|
|
12
|
+
// the transcript into plain messages before one bounded retry.
|
|
13
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
14
|
+
import type { CanonicalRequest, CanonicalMessage, ContentPart } from "../../../protocol/canonical-model";
|
|
15
|
+
import { resolvePromptCacheKey } from "../../operations/session-resolution";
|
|
16
|
+
import type { ProviderDispatchContext } from "../../provider-contracts";
|
|
17
|
+
|
|
18
|
+
/** Stable, non-reversible identity of one free-pi session owner. */
|
|
19
|
+
export interface FreepiSessionOwner {
|
|
20
|
+
readonly credential: string;
|
|
21
|
+
readonly affinity: string | undefined;
|
|
22
|
+
/** Account id is part of the scope: a new account must never inherit a session. */
|
|
23
|
+
readonly accountId: string;
|
|
24
|
+
readonly modelId: string;
|
|
25
|
+
/** Digest of the owner, used as the bounded map key (no live JWT in keys). */
|
|
26
|
+
readonly key: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Builds one session owner from the dispatch inputs. */
|
|
30
|
+
export function freepiSessionOwner(input: {
|
|
31
|
+
readonly secret: string;
|
|
32
|
+
readonly context: ProviderDispatchContext;
|
|
33
|
+
readonly request: CanonicalRequest;
|
|
34
|
+
readonly modelId: string;
|
|
35
|
+
}): FreepiSessionOwner {
|
|
36
|
+
const affinity = resolvePromptCacheKey(input.request, input.context) ?? input.context.conversation_affinity;
|
|
37
|
+
const accountId = input.context.credential.account_id ?? "";
|
|
38
|
+
const key = createHash("sha256")
|
|
39
|
+
.update(JSON.stringify([input.secret, accountId, input.modelId, affinity ?? ""]))
|
|
40
|
+
.digest("hex")
|
|
41
|
+
.slice(0, 32);
|
|
42
|
+
return { credential: input.secret, affinity, accountId, modelId: input.modelId, key };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Returns a new owner whose affinity (and thus session key) cannot collide. */
|
|
46
|
+
export function withFreshFreepiAffinity(owner: FreepiSessionOwner): FreepiSessionOwner {
|
|
47
|
+
const affinity = `fresh-${randomUUID()}`;
|
|
48
|
+
const key = createHash("sha256")
|
|
49
|
+
.update(JSON.stringify([owner.credential, owner.accountId, owner.modelId, affinity]))
|
|
50
|
+
.digest("hex")
|
|
51
|
+
.slice(0, 32);
|
|
52
|
+
return { ...owner, affinity, key };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Bounded, per-owner sessions. */
|
|
56
|
+
export interface FreepiSessionEntry {
|
|
57
|
+
readonly sessionId: string;
|
|
58
|
+
readonly owner: FreepiSessionOwner;
|
|
59
|
+
/** True until the session's ad registration has been attempted once. */
|
|
60
|
+
fresh: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const FREEPI_SESSION_CACHE_MAX = 256;
|
|
64
|
+
|
|
65
|
+
export class FreepiSessionRegistry {
|
|
66
|
+
readonly #sessions = new Map<string, FreepiSessionEntry>();
|
|
67
|
+
|
|
68
|
+
/** Existing session for this owner, or a freshly minted one. */
|
|
69
|
+
get(owner: FreepiSessionOwner): FreepiSessionEntry {
|
|
70
|
+
const existing = this.#sessions.get(owner.key);
|
|
71
|
+
if (existing !== undefined) {
|
|
72
|
+
// Touch for LRU: the map is small, but a long-lived process must not
|
|
73
|
+
// pin a stale affinity forever.
|
|
74
|
+
this.#sessions.delete(owner.key);
|
|
75
|
+
const touched: FreepiSessionEntry = { ...existing, fresh: false };
|
|
76
|
+
this.#sessions.set(owner.key, touched);
|
|
77
|
+
return touched;
|
|
78
|
+
}
|
|
79
|
+
const entry: FreepiSessionEntry = { sessionId: randomUUID(), owner, fresh: true };
|
|
80
|
+
this.#sessions.set(owner.key, entry);
|
|
81
|
+
if (this.#sessions.size > FREEPI_SESSION_CACHE_MAX) {
|
|
82
|
+
const oldest = this.#sessions.keys().next();
|
|
83
|
+
if (!oldest.done) this.#sessions.delete(oldest.value);
|
|
84
|
+
}
|
|
85
|
+
return entry;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Drops one owner's session; the next call mints a fresh one. */
|
|
89
|
+
rotate(owner: FreepiSessionOwner): FreepiSessionEntry {
|
|
90
|
+
this.#sessions.delete(owner.key);
|
|
91
|
+
return this.get(owner);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Test seam. */
|
|
95
|
+
get size(): number {
|
|
96
|
+
return this.#sessions.size;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Upstream refusal shapes that say a native item/session belongs elsewhere. */
|
|
101
|
+
const SESSION_OWNERSHIP_REFUSAL = /(?:does not|doesn't|not) belong to|belongs to (?:another|a different)|session (?:mismatch|expired|invalid)|invalid session|unknown session|stale session|tool_call_id.*(?:unknown|invalid|mismatch)|not (?:found|valid) for (?:this )?session/i;
|
|
102
|
+
|
|
103
|
+
/** True when an upstream error is a session/connection ownership refusal. */
|
|
104
|
+
export function isFreepiSessionOwnershipError(error: unknown): boolean {
|
|
105
|
+
if (!(error instanceof Error)) return false;
|
|
106
|
+
const details = (error as Error & { details?: Readonly<Record<string, unknown>> }).details;
|
|
107
|
+
if (details?.["freepi_session_mismatch"] === true) return true;
|
|
108
|
+
const raw = typeof details?.["raw"] === "string" ? details["raw"] : "";
|
|
109
|
+
const message = `${error.message} ${raw}`;
|
|
110
|
+
return SESSION_OWNERSHIP_REFUSAL.test(message);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Rebases a connection-bound transcript into plain messages: a fresh session
|
|
115
|
+
* has never seen the old `tool_call_id`s, so replaying them verbatim is exactly
|
|
116
|
+
* what the upstream refuses. Prior tool calls and their results become text in
|
|
117
|
+
* the same conversation order (the donor's cold-session rebuild rule).
|
|
118
|
+
*/
|
|
119
|
+
export function rebaseFreepiRequest(request: CanonicalRequest): CanonicalRequest {
|
|
120
|
+
const messages: CanonicalMessage[] = [];
|
|
121
|
+
for (const message of request.messages) {
|
|
122
|
+
const parts: ContentPart[] = [];
|
|
123
|
+
let native = false;
|
|
124
|
+
for (const part of message.content) {
|
|
125
|
+
if (part.kind === "toolCall") {
|
|
126
|
+
native = true;
|
|
127
|
+
const args = typeof part.arguments === "string" ? part.arguments : JSON.stringify(part.arguments ?? {});
|
|
128
|
+
parts.push({ kind: "text", text: `[Earlier tool call ${part.name}: ${args}]` });
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (part.kind === "toolResult") {
|
|
132
|
+
native = true;
|
|
133
|
+
const text = typeof part.content === "string"
|
|
134
|
+
? part.content
|
|
135
|
+
: part.content.map((child) => (child.kind === "text" ? child.text : "")).join("\n");
|
|
136
|
+
parts.push({ kind: "text", text: `[Earlier tool result: ${text}]` });
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
parts.push(part);
|
|
140
|
+
}
|
|
141
|
+
// Keep the role, but collapse a native tool message onto a user/assistant
|
|
142
|
+
// text turn so no orphan `tool_call_id` reaches the fresh session.
|
|
143
|
+
const role = native && message.role === "tool" ? "user" : message.role;
|
|
144
|
+
messages.push({ ...message, role, content: parts });
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
...request,
|
|
148
|
+
messages,
|
|
149
|
+
generation_controls: {
|
|
150
|
+
...request.generation_controls,
|
|
151
|
+
// The old cache affinity is bound to the old session: drop it so the
|
|
152
|
+
// fresh session starts with a clean prefix.
|
|
153
|
+
"extension:prompt_cache_key": undefined,
|
|
154
|
+
"extension:responses.prompt_cache_key": undefined,
|
|
155
|
+
"extension:metadata_user_id": undefined,
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
// `/v1/chat/completions` and translates the OpenAI-style SSE stream into
|
|
6
6
|
// canonical events.
|
|
7
7
|
|
|
8
|
-
import { randomUUID } from "node:crypto";
|
|
9
8
|
import type {
|
|
10
9
|
CanonicalEvent,
|
|
11
10
|
CanonicalRequest,
|
|
@@ -23,10 +22,17 @@ import {
|
|
|
23
22
|
} from "../../provider-contracts";
|
|
24
23
|
import { mapUpstreamHttpError } from "../../../transport/failure-policy";
|
|
25
24
|
import { abortGatewayError, createUpstreamDeadlineLifecycle } from "../../operations/upstream-deadline";
|
|
26
|
-
import { resolvePromptCacheKey } from "../../operations/session-resolution";
|
|
27
25
|
import { usageFromProvider } from "../../provider-usage";
|
|
28
26
|
import { buildFreepiChatBody, parseFreepiSse, throwFreepiUpstreamError } from "./freepi-protocol";
|
|
29
27
|
import { buildFreepiHeaders } from "./freepi-headers";
|
|
28
|
+
import {
|
|
29
|
+
FreepiSessionRegistry,
|
|
30
|
+
freepiSessionOwner,
|
|
31
|
+
isFreepiSessionOwnershipError,
|
|
32
|
+
rebaseFreepiRequest,
|
|
33
|
+
withFreshFreepiAffinity,
|
|
34
|
+
type FreepiSessionOwner,
|
|
35
|
+
} from "./freepi-session";
|
|
30
36
|
import {
|
|
31
37
|
FREEPI_AD_IMPRESSION_PATH,
|
|
32
38
|
FREEPI_AD_NEXT_PATH,
|
|
@@ -51,12 +57,13 @@ export class FreepiAdapter implements ProviderAdapter {
|
|
|
51
57
|
readonly provider_id: ProviderId;
|
|
52
58
|
private readonly fetchFn: typeof fetch;
|
|
53
59
|
/**
|
|
54
|
-
* One
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
60
|
+
* One ad-registered session per (credential, account, model, affinity).
|
|
61
|
+
* A changed affinity is a fresh session by construction: free-pi binds
|
|
62
|
+
* native tool_call ids to the session that opened them, and reusing a
|
|
63
|
+
* session across conversations/accounts is what answers "does not belong
|
|
64
|
+
* to this session".
|
|
58
65
|
*/
|
|
59
|
-
#
|
|
66
|
+
readonly #sessions = new FreepiSessionRegistry();
|
|
60
67
|
|
|
61
68
|
constructor(options: FreepiAdapterOptions = {}) {
|
|
62
69
|
this.provider_id = (options.providerId ?? FREEPI_PROVIDER_ID) as ProviderId;
|
|
@@ -64,24 +71,19 @@ export class FreepiAdapter implements ProviderAdapter {
|
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
/**
|
|
67
|
-
* Ensures a session id exists for this
|
|
74
|
+
* Ensures a session id exists for this owner and that the session has
|
|
68
75
|
* registered at least one ad impression (banner + inline). The upstream
|
|
69
76
|
* refuses the chat wire with `ads_required` until a session has shown ads,
|
|
70
|
-
* so
|
|
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.
|
|
77
|
+
* so a freshly minted session pays the two ad-registration requests.
|
|
75
78
|
*/
|
|
76
|
-
async #ensureSession(secret: string, outboundFetch: typeof fetch): Promise<string> {
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
79
|
-
const sessionId = randomUUID();
|
|
79
|
+
async #ensureSession(secret: string, owner: FreepiSessionOwner, outboundFetch: typeof fetch): Promise<string> {
|
|
80
|
+
const entry = this.#sessions.get(owner);
|
|
81
|
+
if (!entry.fresh) return entry.sessionId;
|
|
80
82
|
try {
|
|
81
83
|
for (const slot of ["banner", "inline"] as const) {
|
|
82
84
|
const next = await outboundFetch(new URL(`${FREEPI_AD_NEXT_PATH}?slot=${slot}`, FREEPI_BASE_URL), {
|
|
83
85
|
method: "GET",
|
|
84
|
-
headers: buildFreepiHeaders(secret, false, sessionId),
|
|
86
|
+
headers: buildFreepiHeaders(secret, false, entry.sessionId),
|
|
85
87
|
signal: AbortSignal.timeout(15_000),
|
|
86
88
|
});
|
|
87
89
|
if (!next.ok || next.status === 204) continue;
|
|
@@ -91,7 +93,7 @@ export class FreepiAdapter implements ProviderAdapter {
|
|
|
91
93
|
if (typeof adId !== "string" || typeof clickToken !== "string") continue;
|
|
92
94
|
await outboundFetch(new URL(FREEPI_AD_IMPRESSION_PATH, FREEPI_BASE_URL), {
|
|
93
95
|
method: "POST",
|
|
94
|
-
headers: buildFreepiHeaders(secret, false, sessionId),
|
|
96
|
+
headers: buildFreepiHeaders(secret, false, entry.sessionId),
|
|
95
97
|
body: JSON.stringify({ ad_id: adId, click_token: clickToken, imp_fired: true }),
|
|
96
98
|
signal: AbortSignal.timeout(15_000),
|
|
97
99
|
});
|
|
@@ -99,8 +101,7 @@ export class FreepiAdapter implements ProviderAdapter {
|
|
|
99
101
|
} catch {
|
|
100
102
|
// The ad surface is best-effort; chat may still work or fail upstream.
|
|
101
103
|
}
|
|
102
|
-
|
|
103
|
-
return sessionId;
|
|
104
|
+
return entry.sessionId;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
async *dispatch(
|
|
@@ -113,45 +114,70 @@ export class FreepiAdapter implements ProviderAdapter {
|
|
|
113
114
|
}
|
|
114
115
|
const secret = readCredentialSecret(context.credential, "Free-pi credential is required");
|
|
115
116
|
const modelId = candidate.model_id || request.model;
|
|
116
|
-
const lifecycle = createUpstreamDeadlineLifecycle(context);
|
|
117
117
|
const outboundFetch =
|
|
118
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);
|
|
119
|
+
const owner = freepiSessionOwner({ secret, context, request, modelId });
|
|
127
120
|
const url = `${FREEPI_BASE_URL}${FREEPI_CHAT_PATH}`;
|
|
128
121
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
122
|
+
// Two attempts at most: a connection-bound refusal rotates the session and
|
|
123
|
+
// rebases the transcript (dropping native tool_call ids the fresh session
|
|
124
|
+
// never saw) before one retry. Anything after that is terminal.
|
|
125
|
+
let activeRequest = request;
|
|
126
|
+
let activeOwner = owner;
|
|
127
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
128
|
+
const sessionId = await this.#ensureSession(secret, activeOwner, outboundFetch);
|
|
129
|
+
const body = buildFreepiChatBody(modelId, activeRequest, candidate.output_limit ?? undefined);
|
|
130
|
+
const lifecycle = createUpstreamDeadlineLifecycle(context);
|
|
131
|
+
let events: CanonicalEvent[] = [];
|
|
132
|
+
try {
|
|
133
|
+
const response = await outboundFetch(url, {
|
|
134
|
+
method: "POST",
|
|
135
|
+
headers: buildFreepiHeaders(secret, true, sessionId),
|
|
136
|
+
body: JSON.stringify(body),
|
|
137
|
+
signal: lifecycle.signal,
|
|
138
|
+
});
|
|
139
|
+
if (!response.ok) throw await mapUpstreamHttpError(response, this.provider_id);
|
|
140
|
+
if (!response.body) {
|
|
141
|
+
throw new GatewayError("platform_unavailable", 502, "Free-pi returned empty body (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
142
|
+
}
|
|
143
|
+
lifecycle.release();
|
|
144
|
+
// Buffered until the stream proves it carries no session-ownership
|
|
145
|
+
// refusal: an SSE 200 `{"error":{...}}` can arrive after the first
|
|
146
|
+
// delta, and yielding events before that would make a retry replay
|
|
147
|
+
// half an answer to the client. Free-pi turns are short; correctness
|
|
148
|
+
// of the tool loop wins over one RTT of time-to-first-token.
|
|
149
|
+
events = [];
|
|
150
|
+
for await (const event of this.#events(
|
|
151
|
+
response.body as ReadableStream<Uint8Array>,
|
|
152
|
+
lifecycle.signal,
|
|
153
|
+
request.model,
|
|
154
|
+
`${modelId}:${activeOwner.affinity ?? "fresh"}`,
|
|
155
|
+
activeRequest.messages,
|
|
156
|
+
)) {
|
|
157
|
+
events.push(event);
|
|
158
|
+
}
|
|
159
|
+
} catch (err: unknown) {
|
|
160
|
+
const abortError = abortGatewayError(lifecycle, err, context.abort_signal);
|
|
161
|
+
if (abortError) throw abortError;
|
|
162
|
+
if (attempt === 0 && isFreepiSessionOwnershipError(err)) {
|
|
163
|
+
const rebased = rebaseFreepiRequest(request);
|
|
164
|
+
// A fresh affinity = a fresh session by construction. The old
|
|
165
|
+
// owner's session is dropped so it can never be reused by a later
|
|
166
|
+
// turn that still carries the stale tool_call ids.
|
|
167
|
+
this.#sessions.rotate(owner);
|
|
168
|
+
activeRequest = rebased;
|
|
169
|
+
activeOwner = withFreshFreepiAffinity(owner);
|
|
170
|
+
lifecycle.release();
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
throw err;
|
|
174
|
+
} finally {
|
|
175
|
+
lifecycle.release();
|
|
139
176
|
}
|
|
140
|
-
|
|
141
|
-
|
|
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();
|
|
177
|
+
yield* events;
|
|
178
|
+
return;
|
|
154
179
|
}
|
|
180
|
+
throw new GatewayError("upstream_conflict", 409, "Free-pi still refused the session after a rebase (request {requestId}). Resend the complete conversation 🔌", { providerId: this.provider_id }, "upstream");
|
|
155
181
|
}
|
|
156
182
|
|
|
157
183
|
async *#events(
|
|
@@ -24,6 +24,7 @@ import { isRecord } from "../../../protocol/codecs";
|
|
|
24
24
|
import { boundedUpstreamArray, boundedUpstreamNumber, sanitizeUpstreamLabel } from "../../provider-sanitize";
|
|
25
25
|
import { modelsDevCatalog } from "../../discovery/models-dev-catalog";
|
|
26
26
|
import type { DiscoveryInput } from "../../discovery/discovery-types";
|
|
27
|
+
import { resolveGithubCopilotMode } from "../../../config/providers";
|
|
27
28
|
import {
|
|
28
29
|
GITHUB_EXCLUDED_MODEL_IDS,
|
|
29
30
|
GITHUB_PROVIDER_ID,
|
|
@@ -33,7 +34,13 @@ import {
|
|
|
33
34
|
GITHUB_AUTO_MODELS,
|
|
34
35
|
recordGithubModelRoutes,
|
|
35
36
|
} from "./github";
|
|
36
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
GITHUB_REQUEST_HEADERS,
|
|
39
|
+
copilotGitHubDomain,
|
|
40
|
+
githubCopilotApiHostForProfile,
|
|
41
|
+
githubCopilotClientIds,
|
|
42
|
+
parseGithubCredential,
|
|
43
|
+
} from "./github-oauth";
|
|
37
44
|
import { createHash } from "node:crypto";
|
|
38
45
|
|
|
39
46
|
/** A context window above this is unsatisfiable headroom, not a real limit. */
|
|
@@ -165,7 +172,8 @@ function toModelDefinition(entry: Record<string, unknown>): ModelDefinition | un
|
|
|
165
172
|
export async function discoverGithubModels(
|
|
166
173
|
input: DiscoveryInput,
|
|
167
174
|
): Promise<readonly ModelDefinition[] | null> {
|
|
168
|
-
const
|
|
175
|
+
const credential = parseGithubCredential(input.credential);
|
|
176
|
+
const { access, apiHost, profile } = credential;
|
|
169
177
|
const fetcher = input.fetcher ?? fetch;
|
|
170
178
|
const timeoutSignal = AbortSignal.timeout(15_000);
|
|
171
179
|
const signal =
|
|
@@ -176,17 +184,26 @@ export async function discoverGithubModels(
|
|
|
176
184
|
// Machine id is per-account — see `githubCopilotClientIds`.
|
|
177
185
|
const accountKey = createHash("sha256").update(`${apiHost}:${access}`).digest("hex");
|
|
178
186
|
const { sessionId, machineId } = await githubCopilotClientIds(accountKey);
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
if (profile !== resolveGithubCopilotMode()) return null;
|
|
188
|
+
const headers: Record<string, string> = profile === "opencode-copilot"
|
|
189
|
+
? {
|
|
190
|
+
accept: "application/json",
|
|
191
|
+
authorization: `Bearer ${credential.github ?? access}`,
|
|
192
|
+
"user-agent": "opencode/0.1.0",
|
|
193
|
+
"x-github-api-version": "2026-08-01",
|
|
194
|
+
}
|
|
195
|
+
: {
|
|
196
|
+
accept: "application/json",
|
|
197
|
+
authorization: `Bearer ${access}`,
|
|
198
|
+
...GITHUB_REQUEST_HEADERS,
|
|
199
|
+
"VScode-SessionId": sessionId,
|
|
200
|
+
"VScode-MachineId": machineId,
|
|
201
|
+
"editor-device-id": machineId,
|
|
202
|
+
};
|
|
203
|
+
const discoveryHost = profile === "opencode-copilot"
|
|
204
|
+
? githubCopilotApiHostForProfile(profile, copilotGitHubDomain())
|
|
205
|
+
: apiHost;
|
|
206
|
+
const response = await fetcher(`https://${discoveryHost}/models`, { headers, signal });
|
|
190
207
|
if (!response.ok) return null;
|
|
191
208
|
const payload: unknown = await response.json();
|
|
192
209
|
if (!isRecord(payload)) return null;
|