cartethyia 2.7.8 → 2.8.0
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 +3 -3
- package/README.md +29 -59
- package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js +1 -0
- package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-BRWKPQPf.js → CliToolDetailPage-Bqj2aa1T.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BP-Y5iHN.js → CliToolsPage-CXsaOAO2.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js +1 -0
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-Cv-2Ol_K.js → InjectSystemPromptPanel-BWFLECmz.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-bKfz_b1K.js → LoginPage-CgoPc5B9.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-B-WQxx_H.js → ModelPicker-D9bFQern.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js +1 -0
- package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js +8 -0
- package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-emlTuxz6.js → ProxyPage-BwNC_-Cx.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js +1 -0
- package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CctTga2C.js +1 -0
- package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-CUZrKs9r.js → SetupPage-febLU4r1.js} +1 -1
- package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-jWmUdDpy.js → StudioPage-M6fvR5Wz.js} +1 -1
- package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js +56 -0
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BFOlvrJC.js +1 -0
- package/dist/dashboard/assets/api-keys-BFOlvrJC.js.gz +0 -0
- package/dist/dashboard/assets/app-Dx2QgsNj.css +2 -0
- package/dist/dashboard/assets/app-Dx2QgsNj.css.gz +0 -0
- package/dist/dashboard/assets/{assert-CidQVoNo.js → assert-CqTLw5e7.js} +1 -1
- package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DyI_mJfi.js → badge-C83EqLB0.js} +1 -1
- package/dist/dashboard/assets/badge-C83EqLB0.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-D1xn7pIe.js} +1 -1
- package/dist/dashboard/assets/banner-D1xn7pIe.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-DU3Lq5Lm.js} +1 -1
- package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js +1 -0
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-ByjCk0o9.js → clipboard-button-CEL5Sg_J.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-CEL5Sg_J.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-BZhOlfz-.js → dialog-BjFR5UHy.js} +1 -1
- package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-BADqIRYP.js → drawer-HvBW0AjI.js} +1 -1
- package/dist/dashboard/assets/drawer-HvBW0AjI.js.gz +0 -0
- package/dist/dashboard/assets/entry-BSu3gFlt.js +4 -0
- package/dist/dashboard/assets/entry-BSu3gFlt.js.gz +0 -0
- package/dist/dashboard/assets/{entry-COaC-irN.js → entry-CECVELyy.js} +1 -1
- package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
- package/dist/dashboard/assets/entry-DYYofQ7W.js +1 -0
- package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-DcHJyRiu.js → formatters-DLW4i8zK.js} +1 -1
- package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
- package/dist/dashboard/assets/{index-CgKmFngv.js → index-1-fCqe64.js} +2 -2
- package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
- package/dist/dashboard/assets/{inline-B2BirKry.js → inline-DGvU2oLK.js} +1 -1
- package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
- package/dist/dashboard/assets/{input-D-qV9dKY.js → input-iNnO1X2K.js} +1 -1
- package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
- package/dist/dashboard/assets/{layout-B7tVWhtV.js → layout-0q8UGypb.js} +1 -1
- package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-CtkGE3bF.js → list-row-C0w2bQjJ.js} +1 -1
- package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
- package/dist/dashboard/assets/{live-DGizF06-.js → live-M5aJ_xCa.js} +1 -1
- package/dist/dashboard/assets/live-M5aJ_xCa.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-BKeQGT3K.js} +1 -1
- package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BnAd22a_.js → numbers-CivAk3da.js} +1 -1
- package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-BAywyMzS.js → pagination-CpzvI5ep.js} +1 -1
- package/dist/dashboard/assets/pagination-CpzvI5ep.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/providers/zro.svg +3 -0
- package/dist/dashboard/assets/providers/zro.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/quota-DiPD6UKH.js +1 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
- package/dist/dashboard/assets/routing-BFQX2VBc.js +1 -0
- package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
- package/dist/dashboard/assets/{select-BCOcv8WK.js → select-CXbck0jG.js} +2 -2
- package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
- package/dist/dashboard/assets/settings-C_XvMHh9.js +1 -0
- package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
- package/dist/dashboard/assets/{stack-ByKt3Q1g.js → stack-DggRiCxE.js} +1 -1
- package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
- package/dist/dashboard/assets/{state-DXbGTx7N.js → state-ClFzfbYp.js} +1 -1
- package/dist/dashboard/assets/state-ClFzfbYp.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-CTIFxKqS.js} +1 -1
- package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-CbCP4w1o.js → useMutation-BClG3yWj.js} +1 -1
- package/dist/dashboard/assets/useMutation-BClG3yWj.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 +59 -56
- 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/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -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/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
- package/package.json +1 -1
- package/src/boot/app.ts +10 -13
- package/src/boot/dependencies.ts +12 -4
- package/src/boot/flush.ts +20 -0
- package/src/boot/lifecycle.ts +7 -5
- package/src/config/cooldowns.ts +1 -1
- package/src/config/routing.ts +26 -0
- package/src/config/spec-metadata.ts +72 -0
- package/src/config/spec.ts +20 -6
- package/src/console/api-keys/contracts.ts +12 -1
- package/src/console/api-keys/routes.ts +50 -31
- 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/cli-tools/service.ts +6 -12
- package/src/console/console-router-guards.ts +23 -16
- package/src/console/console-router.ts +7 -0
- package/src/console/dashboard-assets.ts +5 -5
- package/src/console/domain-registration.ts +27 -2
- package/src/console/inferhub/normalize.ts +24 -28
- package/src/console/inferhub/types.ts +3 -0
- package/src/console/ip-bans/routes.ts +93 -0
- package/src/console/observability/store.ts +0 -53
- package/src/console/providers/catalog/catalog-projections.ts +5 -2
- package/src/console/providers/catalog/contracts.ts +123 -0
- package/src/console/providers/catalog/discover-constants.ts +6 -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 +65 -4
- package/src/console/providers/catalog/store-helpers.ts +30 -1
- package/src/console/providers/catalog/store.ts +249 -4
- package/src/console/quota/account-quota-global.ts +97 -19
- package/src/console/quota/account-quota-shared.ts +49 -0
- package/src/console/quota/account-quota-tenant.ts +6 -18
- package/src/console/quota/native-identity.ts +2 -0
- package/src/console/quota/quota-view.ts +19 -3
- package/src/console/routing/pools/operations.ts +31 -46
- package/src/console/settings/contracts.ts +18 -0
- package/src/console/settings/store.ts +32 -0
- package/src/console/shared/errors.ts +33 -6
- package/src/console/system-prompt/contracts.ts +0 -1
- package/src/main.ts +3 -3
- package/src/network/pool/byte-accounting.ts +3 -2
- package/src/network/pool/selector.ts +182 -39
- package/src/network/pool-cooldown-store.ts +89 -14
- package/src/network/pool-health.ts +49 -61
- package/src/network/rate-limit-verdict.ts +1 -1
- package/src/network/ssrf.ts +6 -6
- package/src/observability/event-log.ts +7 -361
- package/src/observability/event-query.ts +18 -4
- package/src/observability/performance-metrics.ts +9 -0
- package/src/observability/runtime-metrics.ts +14 -9
- package/src/observability/telemetry-buffer.ts +15 -25
- 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 +30 -0
- package/src/persistence/migrate.ts +6 -3
- package/src/persistence/schema.ts +28 -2
- package/src/persistence/ttl-cache.ts +20 -2
- package/src/protocol/codecs.ts +1 -1
- package/src/protocol/dsml.ts +35 -87
- package/src/protocol/gateway-error.ts +1 -0
- package/src/protocol/markup-stream-parser.ts +195 -0
- package/src/protocol/reasoning-text.ts +19 -0
- package/src/protocol/reasoning.ts +1 -1
- package/src/protocol/request/chat.ts +14 -14
- package/src/protocol/request/gemini.ts +8 -2
- package/src/protocol/request/messages.ts +4 -5
- package/src/protocol/response/chat.ts +12 -20
- package/src/protocol/response/gemini.ts +6 -1
- package/src/protocol/response/omp-tool-call-markup.ts +43 -86
- package/src/protocol/response/responses.ts +18 -2
- package/src/protocol/sse-decoder.ts +25 -8
- package/src/protocol/stop-reason.ts +23 -1
- package/src/protocol/stream-error-frames.ts +13 -3
- package/src/protocol/think-split.ts +112 -0
- package/src/protocol/translation/dialect/qwen.ts +26 -58
- 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 +78 -17
- package/src/providers/authentication/oauth-client.ts +1 -1
- package/src/providers/authentication/oauth-device-flow.ts +98 -8
- package/src/providers/authentication/oauth-refresh-service.ts +153 -59
- package/src/providers/configured-provider.ts +148 -0
- package/src/providers/default-registry.ts +117 -27
- package/src/providers/discovery/discovery-types.ts +12 -0
- package/src/providers/discovery/probe-phases.ts +74 -33
- package/src/providers/discovery/probing-service.ts +293 -165
- 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 +121 -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 +191 -35
- 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 +89 -6
- 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 +6 -43
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +2 -1
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +23 -1
- package/src/providers/integrations/buddy/codebuddy/provider.ts +6 -2
- package/src/providers/integrations/buddy/codebuddy/shared.ts +38 -27
- package/src/providers/integrations/claude/claude-quota.ts +14 -17
- package/src/providers/integrations/cline/cline.ts +2 -2
- package/src/providers/integrations/commandcode/quota.ts +22 -28
- package/src/providers/integrations/cursor/cursor-auth.ts +0 -8
- package/src/providers/integrations/cursor/cursor-catalog.ts +2 -0
- package/src/providers/integrations/cursor/cursor-exec.ts +745 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +2 -0
- package/src/providers/integrations/cursor/cursor-request.ts +35 -20
- package/src/providers/integrations/cursor/cursor-shared.ts +26 -5
- package/src/providers/integrations/cursor/cursor.ts +139 -70
- 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/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 +558 -0
- package/src/providers/integrations/devin/protocol/framing.ts +124 -0
- package/src/providers/integrations/factory/factory-oauth.ts +17 -44
- package/src/providers/integrations/factory/factory.ts +2 -2
- 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 +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/grok/grok-oauth.ts +48 -45
- 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 +94 -0
- package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +13 -21
- package/src/providers/integrations/hermes/index.ts +2 -0
- 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-quota.ts +8 -4
- package/src/providers/integrations/kiro/kiro.ts +15 -19
- 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/perch/perch-catalog.ts +105 -0
- package/src/providers/integrations/perch/perch-credential.ts +25 -0
- package/src/providers/integrations/perch/perch-oauth.ts +170 -0
- package/src/providers/integrations/perch/perch-protocol.ts +208 -0
- package/src/providers/integrations/perch/perch-quota.ts +123 -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 +305 -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 +19 -60
- package/src/providers/integrations/qoder/qoder-adapter.ts +90 -40
- package/src/providers/integrations/qoder/qoder-auth.ts +101 -29
- package/src/providers/integrations/qoder/qoder-cache.ts +32 -0
- package/src/providers/integrations/qoder/qoder-catalog.ts +26 -6
- package/src/providers/integrations/qoder/qoder-chat.ts +7 -21
- package/src/providers/integrations/qoder/qoder-checkin.ts +6 -6
- package/src/providers/integrations/qoder/qoder-grpc.ts +210 -7
- package/src/providers/integrations/qoder/qoder-ide.ts +5 -108
- 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 -7
- 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 +12 -11
- package/src/providers/integrations/trae/trae-quota.ts +96 -36
- package/src/providers/integrations/trae/trae-realm.ts +74 -15
- package/src/providers/integrations/trae/trae.ts +99 -26
- 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 +204 -0
- package/src/providers/operations/account-error-classification.ts +235 -278
- package/src/providers/operations/account-health-service.ts +273 -26
- package/src/providers/operations/account-refresh-status.ts +269 -0
- package/src/providers/operations/account-tier.ts +66 -0
- package/src/providers/operations/client-version-resolver.ts +1 -1
- package/src/providers/operations/client-versions.ts +42 -150
- package/src/providers/operations/cooldown-rules.ts +56 -0
- package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
- package/src/providers/operations/provider-catalog-seeder.ts +4 -0
- package/src/providers/operations/provider-catalog-service.ts +1 -1
- package/src/providers/operations/provider-credential-service.ts +102 -32
- 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/provider-contracts.ts +33 -2
- package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
- package/src/providers/provider-metadata.ts +99 -14
- package/src/providers/provider-model-definition.ts +36 -0
- package/src/providers/provider-probe.ts +15 -1
- package/src/providers/provider-registry.ts +42 -33
- 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 +96 -18
- package/src/security/abuse.ts +71 -0
- package/src/security/api-key-auth-cache.ts +1 -1
- package/src/security/api-key-auth.ts +6 -2
- package/src/transport/dispatch/attempt-finalize.ts +14 -13
- package/src/transport/dispatch/attempt-loop.ts +71 -17
- package/src/transport/dispatch/generation.ts +2 -8
- package/src/transport/dispatch/native-shared.ts +25 -0
- package/src/transport/dispatch/proxy-request.ts +9 -46
- package/src/transport/dispatch/responses-compact.ts +2 -8
- package/src/transport/dispatch/streaming/finalize.ts +9 -6
- package/src/transport/dispatch/streaming/stream.ts +9 -36
- package/src/transport/dispatch/systemone.ts +2 -8
- package/src/transport/dispatch/token-count.ts +2 -6
- package/src/transport/dispatch/upstream.ts +67 -1
- package/src/transport/drain-endpoint.ts +31 -4
- package/src/transport/failure-policy.ts +129 -37
- 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 +418 -33
- 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 +9 -2
- 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 +34 -6
- package/src/transport/surface/responses/parse.ts +3 -11
- 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 +0 -1
- 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.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/quota-DJ1QlAOq.js +0 -1
- package/dist/dashboard/assets/quota-DJ1QlAOq.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/bundled-discovery-providers.ts +0 -98
- package/src/providers/configured-openai-providers.ts +0 -120
- 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/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-CTxoqyYB.js → app-DIXLjojg.js} +0 -0
- /package/dist/dashboard/assets/{app-CTxoqyYB.js.gz → app-DIXLjojg.js.gz} +0 -0
|
@@ -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
|
+
}
|
|
@@ -241,7 +241,7 @@ class GeminiAdapter implements ProviderAdapter {
|
|
|
241
241
|
let seq = 1;
|
|
242
242
|
yield { type: "response_start", sequence_number: seq++, model: request.model } as CanonicalEvent;
|
|
243
243
|
if (output.thought) {
|
|
244
|
-
yield { type: "content_delta", sequence_number: seq++, content: { kind: "reasoning", payload: null, summary: output.thought } } as CanonicalEvent;
|
|
244
|
+
yield { type: "content_delta", sequence_number: seq++, content: { kind: "reasoning", payload: null, summary: output.thought, ...(output.thoughtSignature ? { signature: output.thoughtSignature } : {}) } } as CanonicalEvent;
|
|
245
245
|
}
|
|
246
246
|
if (output.text) {
|
|
247
247
|
yield { type: "content_delta", sequence_number: seq++, content: { kind: "text", text: output.text } } as CanonicalEvent;
|
|
@@ -277,7 +277,7 @@ class GeminiAdapter implements ProviderAdapter {
|
|
|
277
277
|
const { candidate: cand, parts } = geminiCandidate(parsed);
|
|
278
278
|
const output = responseParts(parts);
|
|
279
279
|
if (output.thought) {
|
|
280
|
-
yield { type: "content_delta", sequence_number: seq++, content: { kind: "reasoning", payload: null, summary: output.thought } } as CanonicalEvent;
|
|
280
|
+
yield { type: "content_delta", sequence_number: seq++, content: { kind: "reasoning", payload: null, summary: output.thought, ...(output.thoughtSignature ? { signature: output.thoughtSignature } : {}) } } as CanonicalEvent;
|
|
281
281
|
}
|
|
282
282
|
if (output.text) {
|
|
283
283
|
yield { type: "content_delta", sequence_number: seq++, content: { kind: "text", text: output.text } } as CanonicalEvent;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/** xAI Grok Build device-code OAuth client. */
|
|
2
|
-
import { devicePollBackoff, nonEmptyTrimmedString, parseDeviceAuthStart, postFormTokenRequest, readJsonResponse } from "../../authentication/oauth-flow-store";
|
|
2
|
+
import { devicePollBackoff, nonEmptyTrimmedString, parseDeviceAuthStart, postFormTokenRequest, readJsonResponse, record } from "../../authentication/oauth-flow-store";
|
|
3
3
|
import type {
|
|
4
4
|
OAuthDeviceFlowContext,
|
|
5
5
|
OAuthDevicePollResult,
|
|
6
6
|
OAuthDeviceStartResult,
|
|
7
|
+
OAuthExchangeResult,
|
|
7
8
|
} from "../../authentication/oauth-flow-store";
|
|
8
9
|
import type { OAuthTokenRefreshResult } from "../../authentication/oauth-refresh-service";
|
|
9
10
|
import { OAuthDeviceFlow } from "../../authentication/oauth-device-flow";
|
|
10
|
-
import type { FetchLike } from "../../authentication/oauth-client";
|
|
11
|
+
import type { FetchLike, NormalizedTokenResponse } from "../../authentication/oauth-client";
|
|
11
12
|
import { XAI_AUTH_SERVER } from "../xai/xai-oauth";
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -178,56 +179,58 @@ export class GrokOAuthClient extends OAuthDeviceFlow {
|
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
override async pollDeviceAuth(deviceAuthId: string, _context?: OAuthDeviceFlowContext): Promise<OAuthDevicePollResult> {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const response = await this.fetchFn(GROK_TOKEN_URL, {
|
|
187
|
-
method: "POST",
|
|
188
|
-
headers: authHeaders(),
|
|
189
|
-
body,
|
|
182
|
+
return this.pollGenericDeviceAuth(deviceAuthId, {
|
|
183
|
+
tokenUrl: GROK_TOKEN_URL,
|
|
184
|
+
deviceAuthUrl: GROK_DEVICE_URL,
|
|
185
|
+
clientId: GROK_CLIENT_ID,
|
|
186
|
+
formHeaders: authHeaders(),
|
|
190
187
|
});
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
protected override unparsablePollReason(_status: number): string {
|
|
191
|
+
return "grok token endpoint returned invalid JSON";
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
protected override preVerdict(payload: unknown): OAuthDevicePollResult | undefined {
|
|
195
|
+
const root = record(payload) ?? {};
|
|
196
|
+
// Grok votes on the `error` field before the status: a 200 body can still
|
|
197
|
+
// state the poll state, and a pending/slow_down answer is honored wherever
|
|
198
|
+
// it appears on the wire.
|
|
199
|
+
return devicePollBackoff(nonEmptyTrimmedString(root.error), root.interval);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
protected override pollFailureReason(
|
|
203
|
+
status: number,
|
|
204
|
+
errorKey: string | undefined,
|
|
205
|
+
errorDescription: string | undefined,
|
|
206
|
+
): string {
|
|
207
|
+
// Grok's wire prefers the human-readable description over the error code,
|
|
208
|
+
// and falls back to the generic phrasing only when both are absent.
|
|
209
|
+
return nonEmptyTrimmedString(errorDescription) ?? nonEmptyTrimmedString(errorKey) ?? `token polling failed (${status})`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
protected override missingTokenReason(errorKey: string | undefined, errorDescription: string | undefined): string {
|
|
204
213
|
// A 2xx is not by itself a completed authorization: the endpoint can answer
|
|
205
214
|
// 200 with an `error` field and no token. Returning `failed` rather than
|
|
206
215
|
// letting the shared parser throw keeps the verdict inside the poll
|
|
207
216
|
// contract — the dashboard's poll error path stops polling but shows no
|
|
208
217
|
// reason, so a throw would look like a spinner that never ends.
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
const result = this.parseTokenResponse(payload);
|
|
219
|
-
const identity = await fetchGrokIdentity(result.access, this.fetchFn);
|
|
218
|
+
return nonEmptyTrimmedString(errorDescription) ?? nonEmptyTrimmedString(errorKey) ?? "grok token response omitted access_token";
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
protected override async afterDeviceTokenExchange(
|
|
222
|
+
_payload: unknown,
|
|
223
|
+
normalized: NormalizedTokenResponse,
|
|
224
|
+
): Promise<OAuthExchangeResult> {
|
|
225
|
+
const identity = await fetchGrokIdentity(normalized.access, this.fetchFn);
|
|
220
226
|
const authState = grokAuthState(identity);
|
|
221
|
-
return {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
...(authState === undefined ? {} : { auth_state: authState }),
|
|
229
|
-
}),
|
|
230
|
-
};
|
|
227
|
+
return this.toExchangeResult({
|
|
228
|
+
...normalized,
|
|
229
|
+
...(identity.label === undefined ? {} : { accountLabel: identity.label }),
|
|
230
|
+
// Omitted when the proxy reported nothing, which leaves any stored
|
|
231
|
+
// state alone rather than blanking it.
|
|
232
|
+
...(authState === undefined ? {} : { auth_state: authState }),
|
|
233
|
+
});
|
|
231
234
|
}
|
|
232
235
|
|
|
233
236
|
override async refresh(refreshToken: string, signal?: AbortSignal): Promise<OAuthTokenRefreshResult> {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// Keyed by account id: two Grok accounts carry different buckets, and a reading
|
|
13
13
|
// filed against the wrong one is worse than no reading at all.
|
|
14
14
|
|
|
15
|
+
import { TtlCache } from "../../../persistence/ttl-cache";
|
|
15
16
|
import type { ProviderQuotaWindow } from "../../quota/quota-contracts";
|
|
16
17
|
import { grokHeaderRequestsWindow, grokHeaderWindow, type GrokRateLimitReading } from "./grok-ratelimit";
|
|
17
18
|
|
|
@@ -27,39 +28,31 @@ const SNAPSHOT_TTL_MS = 5 * 60 * 1000;
|
|
|
27
28
|
|
|
28
29
|
interface Snapshot {
|
|
29
30
|
readonly windows: readonly ProviderQuotaWindow[];
|
|
30
|
-
readonly capturedAt: number;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
// Bounded FIFO cache keyed by account id; the TTL and `maxEntries` cap are
|
|
34
|
+
// enforced by the cache itself (oldest-first eviction past 4096 entries).
|
|
35
|
+
const snapshots = new TtlCache<Snapshot>({
|
|
36
|
+
ttlMs: SNAPSHOT_TTL_MS,
|
|
37
|
+
maxEntries: 4096,
|
|
38
|
+
});
|
|
34
39
|
|
|
35
40
|
/** Files a reading captured from a real dispatch against one account. */
|
|
36
41
|
export function rememberGrokRateLimit(
|
|
37
42
|
accountId: string,
|
|
38
43
|
reading: GrokRateLimitReading,
|
|
39
|
-
now = Date.now(),
|
|
40
44
|
): void {
|
|
41
45
|
const windows = [grokHeaderWindow(reading), grokHeaderRequestsWindow(reading)].filter(
|
|
42
46
|
(value): value is ProviderQuotaWindow => value !== null,
|
|
43
47
|
);
|
|
44
48
|
if (windows.length === 0) return;
|
|
45
|
-
snapshots.set(accountId, { windows
|
|
46
|
-
if (snapshots.size > 4096) {
|
|
47
|
-
// Bounded: an unbounded map keyed by account is a leak dressed as a cache.
|
|
48
|
-
for (const [key, value] of snapshots) {
|
|
49
|
-
if (now - value.capturedAt > SNAPSHOT_TTL_MS) snapshots.delete(key);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
49
|
+
snapshots.set(accountId, { windows });
|
|
52
50
|
}
|
|
53
51
|
|
|
54
52
|
/** The stored window for one account, or `null` when none is fresh enough. */
|
|
55
|
-
export function storedGrokRateLimit(accountId: string
|
|
53
|
+
export function storedGrokRateLimit(accountId: string): readonly ProviderQuotaWindow[] | null {
|
|
56
54
|
const snapshot = snapshots.get(accountId);
|
|
57
|
-
|
|
58
|
-
if (now - snapshot.capturedAt > SNAPSHOT_TTL_MS) {
|
|
59
|
-
snapshots.delete(accountId);
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
return snapshot.windows;
|
|
55
|
+
return snapshot?.windows ?? null;
|
|
63
56
|
}
|
|
64
57
|
|
|
65
58
|
/** Test seam. */
|
|
@@ -13,19 +13,25 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import type { CanonicalRequest } from "../../../protocol/canonical-model";
|
|
16
|
+
import { TtlCache } from "../../../persistence/ttl-cache";
|
|
16
17
|
|
|
17
18
|
/** Idle window after which a session's remembered index is discarded. */
|
|
18
19
|
const SESSION_TTL_MS = 30 * 60_000;
|
|
19
20
|
|
|
20
|
-
/** Bound on remembered sessions;
|
|
21
|
+
/** Bound on remembered sessions; least-recently-used is evicted past this. */
|
|
21
22
|
const MAX_SESSIONS = 5_000;
|
|
22
23
|
|
|
23
24
|
interface TurnEntry {
|
|
24
25
|
readonly turn: number;
|
|
25
|
-
lastUsedAt: number;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
// Bounded, LRU-ordered: a session re-inserts on every use so the newest
|
|
29
|
+
// session is the last evicted, exactly as the previous delete-then-set Map.
|
|
30
|
+
const turns = new TtlCache<TurnEntry>({
|
|
31
|
+
ttlMs: SESSION_TTL_MS,
|
|
32
|
+
maxEntries: MAX_SESSIONS,
|
|
33
|
+
lru: true,
|
|
34
|
+
});
|
|
29
35
|
const requestTurns = new WeakMap<object, number>();
|
|
30
36
|
|
|
31
37
|
/** User turns in the request, floored at 1 (a turn is never index 0). */
|
|
@@ -58,25 +64,14 @@ export function resolveGrokTurnIndex(
|
|
|
58
64
|
}
|
|
59
65
|
if (sessionId === undefined || sessionId.length === 0) return fromInput;
|
|
60
66
|
|
|
61
|
-
const
|
|
62
|
-
const previous = turns.get(sessionId);
|
|
63
|
-
const remembered =
|
|
64
|
-
previous !== undefined && now - previous.lastUsedAt <= SESSION_TTL_MS
|
|
65
|
-
? previous.turn
|
|
66
|
-
: undefined;
|
|
67
|
+
const remembered = turns.get(sessionId)?.turn;
|
|
67
68
|
|
|
68
69
|
// A full-history client already reports the true index; a delta-style client
|
|
69
70
|
// reports 1 every time, so the stored index is advanced by one for it.
|
|
70
71
|
const turn = remembered === undefined ? fromInput : Math.max(fromInput, remembered + 1);
|
|
71
72
|
|
|
72
73
|
// Re-insert so the entry is newest for eviction order.
|
|
73
|
-
turns.
|
|
74
|
-
if (turns.size >= MAX_SESSIONS) {
|
|
75
|
-
const oldest = turns.keys().next();
|
|
76
|
-
if (!oldest.done) turns.delete(oldest.value);
|
|
77
|
-
}
|
|
78
|
-
turns.set(sessionId, { turn, lastUsedAt: now });
|
|
74
|
+
turns.set(sessionId, { turn });
|
|
79
75
|
if (requestKey !== undefined) requestTurns.set(requestKey, turn);
|
|
80
76
|
return turn;
|
|
81
77
|
}
|
|
82
|
-
|