cartethyia 2.8.0 → 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 +3 -3
- package/dist/dashboard/assets/{BannedPage-Cc0vKpMG.js → BannedPage-Dq530awD.js} +1 -1
- package/dist/dashboard/assets/BannedPage-Dq530awD.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-Bqj2aa1T.js → CliToolDetailPage-DZzaS-7S.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-DZzaS-7S.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-CXsaOAO2.js → CliToolsPage-DaZZ3cqP.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DaZZ3cqP.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BeLpJ4ma.js → CombosPage-BlC_xdkX.js} +1 -1
- package/dist/dashboard/assets/CombosPage-BlC_xdkX.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-DnV0uZ3m.js → ConsoleLogPage-D-rqeVtG.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-D-rqeVtG.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-CBxAjo63.js → CustomizationPage-7xT5yg2E.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-7xT5yg2E.js.gz +0 -0
- package/dist/dashboard/assets/{HealthEventsModal-B5yEE0k6.js → HealthEventsModal-Clq8E9cW.js} +1 -1
- package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-BWFLECmz.js → InjectSystemPromptPanel-6Teoq9Se.js} +1 -1
- package/dist/dashboard/assets/InjectSystemPromptPanel-6Teoq9Se.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-CgoPc5B9.js → LoginPage-CZuI0MAR.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CZuI0MAR.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-D9bFQern.js → ModelPicker-C1hNTlkr.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-C1hNTlkr.js.gz +0 -0
- package/dist/dashboard/assets/{OverviewPage-B_p4tL8k.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-D_0PiZtg.js → ProviderIcon-Ddhm1InL.js} +1 -1
- package/dist/dashboard/assets/ProviderIcon-Ddhm1InL.js.gz +0 -0
- package/dist/dashboard/assets/{ProvidersPage-CLI2bJTK.js → ProvidersPage-C3fYuu6i.js} +1 -1
- 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-DA-a1iSN.js → QuotaPage-D1RV_2BX.js} +1 -1
- package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-CctTga2C.js → SettingsPage-BzZAvYSX.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-BzZAvYSX.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-febLU4r1.js → SetupPage--s-EfEIL.js} +1 -1
- package/dist/dashboard/assets/SetupPage--s-EfEIL.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-M6fvR5Wz.js → StudioPage-BjXFGSK4.js} +1 -1
- package/dist/dashboard/assets/StudioPage-BjXFGSK4.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-nyeuRqz9.js → UsagePage-CP8F8BN7.js} +1 -1
- package/dist/dashboard/assets/UsagePage-CP8F8BN7.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-BFOlvrJC.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-CqTLw5e7.js → assert-BMcj1xZE.js} +1 -1
- package/dist/dashboard/assets/assert-BMcj1xZE.js.gz +0 -0
- package/dist/dashboard/assets/{badge-C83EqLB0.js → badge-DkRUuVfz.js} +1 -1
- package/dist/dashboard/assets/badge-DkRUuVfz.js.gz +0 -0
- package/dist/dashboard/assets/{banner-D1xn7pIe.js → banner-CDjW841Q.js} +1 -1
- package/dist/dashboard/assets/banner-CDjW841Q.js.gz +0 -0
- package/dist/dashboard/assets/{card-DU3Lq5Lm.js → card-Bto5UlL9.js} +1 -1
- package/dist/dashboard/assets/card-Bto5UlL9.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-Dw1U_drK.js → cli-tools-DaH6g9hL.js} +1 -1
- package/dist/dashboard/assets/cli-tools-DaH6g9hL.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-CEL5Sg_J.js → clipboard-button-BYKBCzjk.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-BYKBCzjk.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-BjFR5UHy.js → dialog-DL0Zftwv.js} +1 -1
- package/dist/dashboard/assets/dialog-DL0Zftwv.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-HvBW0AjI.js → drawer-Dpx_UXaS.js} +1 -1
- package/dist/dashboard/assets/drawer-Dpx_UXaS.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CECVELyy.js → entry-BV7-1NoM.js} +1 -1
- package/dist/dashboard/assets/entry-BV7-1NoM.js.gz +0 -0
- package/dist/dashboard/assets/{entry-DYYofQ7W.js → entry-CGvdNU6r.js} +1 -1
- package/dist/dashboard/assets/entry-CGvdNU6r.js.gz +0 -0
- package/dist/dashboard/assets/{entry-BSu3gFlt.js → entry-DScKYvZj.js} +3 -3
- package/dist/dashboard/assets/entry-DScKYvZj.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-DLW4i8zK.js → formatters-BS3DZlZj.js} +1 -1
- package/dist/dashboard/assets/formatters-BS3DZlZj.js.gz +0 -0
- package/dist/dashboard/assets/{index-1-fCqe64.js → index-SQnvvTWX.js} +2 -2
- package/dist/dashboard/assets/index-SQnvvTWX.js.gz +0 -0
- package/dist/dashboard/assets/{inline-DGvU2oLK.js → inline-Dl-cuLK0.js} +1 -1
- package/dist/dashboard/assets/inline-Dl-cuLK0.js.gz +0 -0
- package/dist/dashboard/assets/{input-iNnO1X2K.js → input-Bfx0EzRc.js} +1 -1
- package/dist/dashboard/assets/input-Bfx0EzRc.js.gz +0 -0
- package/dist/dashboard/assets/{layout-0q8UGypb.js → layout-BxhbQ_JS.js} +1 -1
- package/dist/dashboard/assets/layout-BxhbQ_JS.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-C0w2bQjJ.js → list-row-D49Y4ptn.js} +1 -1
- package/dist/dashboard/assets/list-row-D49Y4ptn.js.gz +0 -0
- package/dist/dashboard/assets/{live-M5aJ_xCa.js → live-CbVTdX9V.js} +1 -1
- package/dist/dashboard/assets/live-CbVTdX9V.js.gz +0 -0
- package/dist/dashboard/assets/{network-BKeQGT3K.js → network-JGvDveof.js} +1 -1
- package/dist/dashboard/assets/network-JGvDveof.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-CivAk3da.js → numbers-Dw6kiew-.js} +1 -1
- package/dist/dashboard/assets/numbers-Dw6kiew-.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-CpzvI5ep.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/{routing-BFQX2VBc.js → routing-8jZr-3WE.js} +1 -1
- package/dist/dashboard/assets/routing-8jZr-3WE.js.gz +0 -0
- package/dist/dashboard/assets/{select-CXbck0jG.js → select-oO1FxMVb.js} +1 -1
- package/dist/dashboard/assets/select-oO1FxMVb.js.gz +0 -0
- package/dist/dashboard/assets/{settings-C_XvMHh9.js → settings-CLr3kdIx.js} +1 -1
- package/dist/dashboard/assets/settings-CLr3kdIx.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DggRiCxE.js → stack-Y3pDteZr.js} +1 -1
- package/dist/dashboard/assets/stack-Y3pDteZr.js.gz +0 -0
- package/dist/dashboard/assets/{state-ClFzfbYp.js → state-CArX_Iak.js} +1 -1
- package/dist/dashboard/assets/state-CArX_Iak.js.gz +0 -0
- package/dist/dashboard/assets/{switch-CTIFxKqS.js → switch-DwlwmDO-.js} +1 -1
- package/dist/dashboard/assets/switch-DwlwmDO-.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BClG3yWj.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 +51 -51
- package/migrations/full-postgres/0047_add_provider_device_flow_proxy.sql +17 -0
- package/migrations/lite-sqlite3/0047_add_provider_device_flow_proxy.sql +19 -0
- package/package.json +1 -1
- package/src/boot/app.ts +1 -1
- package/src/boot/dependencies.ts +4 -6
- package/src/config/providers.ts +6 -5
- package/src/config/spec.ts +8 -8
- package/src/console/backup/nine-router.ts +71 -10
- package/src/console/backup/service.ts +8 -1
- package/src/console/console-router.ts +1 -1
- package/src/console/domain-registration.ts +7 -2
- package/src/console/inferhub/client.ts +1 -1
- package/src/console/observability/live.ts +2 -2
- 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 +2 -2
- package/src/console/providers/catalog/store.ts +1914 -1907
- 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-tenant.ts +5 -5
- package/src/console/quota/account-test-egress.ts +2 -2
- package/src/console/quota/daily-growth-job.ts +1 -1
- package/src/console/routing/pools/contracts.ts +1 -1
- package/src/console/routing/pools/operations.ts +1 -1
- 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/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/{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} +7 -7
- 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 +2 -2
- package/src/network/ssrf.ts +1 -1
- package/src/network/{retry.ts → util/retry.ts} +1 -1
- package/src/observability/telemetry-buffer.ts +1 -1
- package/src/persistence/schema.ts +9 -0
- package/src/persistence/ttl-cache.ts +1 -1
- package/src/protocol/codecs.ts +58 -1
- package/src/protocol/request/messages.ts +31 -18
- package/src/protocol/response/messages.ts +34 -7
- 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 +5 -0
- package/src/providers/authentication/oauth-client.ts +40 -6
- package/src/providers/authentication/oauth-device-flow.ts +7 -3
- package/src/providers/authentication/oauth-flow-store.ts +82 -1
- package/src/providers/authentication/oauth-refresh-service.ts +766 -739
- package/src/providers/default-registry.ts +76 -0
- package/src/providers/discovery/discovery-types.ts +7 -0
- package/src/providers/discovery/probe-phases.ts +6 -2
- package/src/providers/discovery/probing-service.ts +7 -0
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +11 -0
- package/src/providers/integrations/buddy/account.ts +969 -0
- package/src/providers/integrations/buddy/capabilities.ts +10 -10
- package/src/providers/integrations/buddy/{buddy-catalog-shared.ts → catalog.ts} +198 -136
- package/src/providers/integrations/buddy/{buddy-chat-shared.ts → chat.ts} +5 -5
- package/src/providers/integrations/buddy/codebuddy/index.ts +429 -0
- package/src/providers/integrations/buddy/codebuddy/shared.ts +4 -4
- package/src/providers/integrations/buddy/{buddy-identity.ts → headers.ts} +138 -140
- 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/cline/cline-oauth.ts +10 -5
- package/src/providers/integrations/cline/cline.ts +3 -4
- 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/cursor/cursor-auth.ts +1 -1
- 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-shared.ts +1 -1
- package/src/providers/integrations/cursor/cursor.ts +485 -70
- 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/factory/factory-oauth.ts +16 -7
- package/src/providers/integrations/freebuff/freebuff.ts +2 -2
- 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 +4 -4
- package/src/providers/integrations/hermes/hermes-oauth.ts +3 -3
- package/src/providers/integrations/hermes/hermes.ts +34 -5
- 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-oauth.ts +2 -2
- package/src/providers/integrations/muse/muse-oauth.ts +5 -5
- package/src/providers/integrations/opencode/fingerprint.ts +1 -1
- package/src/providers/integrations/openrouter/provider.ts +1 -1
- package/src/providers/integrations/perch/perch-protocol.ts +0 -3
- package/src/providers/integrations/perch/perch.ts +3 -3
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +1 -1
- package/src/providers/integrations/qoder/qoder-adapter.ts +19 -113
- package/src/providers/integrations/qoder/qoder-auth.ts +8 -1
- package/src/providers/integrations/qoder/qoder-catalog.ts +1 -1
- package/src/providers/integrations/qoder/qoder-chat.ts +25 -8
- package/src/providers/integrations/qoder/qoder-checkin.ts +1 -1
- 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 -25
- package/src/providers/integrations/sensenova/provider.ts +144 -0
- package/src/providers/integrations/trae/trae-quota.ts +15 -1
- 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/zro/zro.ts +2 -1
- package/src/providers/operations/account-refresh-status.ts +14 -0
- package/src/providers/operations/client-versions.ts +95 -30
- package/src/providers/operations/oauth-refresh-lead.ts +46 -22
- package/src/providers/operations/provider-caches.ts +1 -1
- package/src/providers/operations/provider-credential-service.ts +22 -25
- package/src/providers/operations/session-resolution.ts +17 -1
- package/src/providers/provider-contracts.ts +2 -2
- package/src/providers/provider-metadata.ts +30 -0
- package/src/providers/quota/quota-contracts.ts +2 -0
- package/src/providers/quota/refresh.ts +25 -3
- package/src/security/ip-boundary.ts +1 -1
- package/src/transport/dispatch/attempt-finalize.ts +1 -1
- package/src/transport/dispatch/attempt-loop.ts +3 -3
- package/src/transport/dispatch/handler-deps.ts +1 -1
- package/src/transport/dispatch/leases.ts +1 -1
- package/src/transport/dispatch/retry-policy.ts +1 -1
- package/src/transport/dispatch/streaming/finalize.ts +2 -2
- package/src/transport/dispatch/streaming/stream.ts +1 -1
- package/src/transport/dispatch/upstream.ts +1 -1
- package/src/transport/shutdown-notice.ts +1 -1
- 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-Cc0vKpMG.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js +0 -8
- package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js +0 -9
- package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BFOlvrJC.js.gz +0 -0
- package/dist/dashboard/assets/app-Dx2QgsNj.css +0 -2
- package/dist/dashboard/assets/app-Dx2QgsNj.css.gz +0 -0
- package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
- package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
- package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
- package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-CEL5Sg_J.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
- package/dist/dashboard/assets/drawer-HvBW0AjI.js.gz +0 -0
- package/dist/dashboard/assets/entry-BSu3gFlt.js.gz +0 -0
- package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
- package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
- package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
- package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
- package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
- package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
- package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
- package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
- package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
- package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
- package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +0 -0
- package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
- package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
- package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
- package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
- package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
- package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BClG3yWj.js.gz +0 -0
- package/src/console/providers/catalog/discover-constants.ts +0 -6
- package/src/network/pool-health.ts +0 -198
- 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-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 -188
- package/src/providers/integrations/buddy/codebuddy/oauth.ts +0 -47
- package/src/providers/integrations/buddy/codebuddy/provider.ts +0 -179
- 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 -870
- package/src/providers/integrations/qoder/qoder-ide.ts +0 -481
- /package/dist/dashboard/assets/{app-DIXLjojg.js → app-C0gTXEoN.js} +0 -0
- /package/dist/dashboard/assets/{app-DIXLjojg.js.gz → app-C0gTXEoN.js.gz} +0 -0
- /package/src/network/{http2-bidi.ts → bidi.ts} +0 -0
- /package/src/network/pool/{byte-accounting.ts → accounting.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
- /package/src/network/{rate-limit-verdict.ts → verdict.ts} +0 -0
|
@@ -29,7 +29,7 @@ import { resolveImageSource, splitDataUrl } from "../../../protocol/codecs";
|
|
|
29
29
|
import { IMAGE_CLAMP_MAX_BYTES } from "../../../protocol/image-clamp";
|
|
30
30
|
import type { ProviderAdapter, ProviderDispatchContext, ProviderDispatchTarget } from "../../provider-contracts";
|
|
31
31
|
import { readCredentialSecret } from "../../provider-contracts";
|
|
32
|
-
import type { Http2BidiEndReason
|
|
32
|
+
import type { Http2BidiEndReason } from "../../../network/bidi";
|
|
33
33
|
import type { ProviderContinuationScope } from "../../provider-contracts";
|
|
34
34
|
import { CURSOR_PROVIDER_ID, RUN_IDLE_TTL_MS, isAllowedAgentHost } from "./cursor-shared";
|
|
35
35
|
import {
|
|
@@ -54,7 +54,22 @@ import {
|
|
|
54
54
|
type CursorImageInput,
|
|
55
55
|
type CursorToolDescriptor,
|
|
56
56
|
} from "./cursor-request";
|
|
57
|
-
import {
|
|
57
|
+
import { decodeServerMessages, isRetryableOpenFailure, openRetryDelayMs, openCursorRunWithFallback } from "./cursor-http2";
|
|
58
|
+
import type { CursorRunTransport } from "./cursor-http2";
|
|
59
|
+
import {
|
|
60
|
+
classifyCursorFailure,
|
|
61
|
+
createCheckpointState,
|
|
62
|
+
createRunCompletion,
|
|
63
|
+
cursorQuietError,
|
|
64
|
+
drainInFlightDispatches,
|
|
65
|
+
frameKindOf,
|
|
66
|
+
frameShapeOf,
|
|
67
|
+
reconcileToolCount,
|
|
68
|
+
wireTraceEnabled,
|
|
69
|
+
CURSOR_WIRE_TRACE_MAX,
|
|
70
|
+
type CursorCheckpointState,
|
|
71
|
+
type CursorFrameShape,
|
|
72
|
+
} from "./cursor-hardening";
|
|
58
73
|
import { cursorRunSessions } from "./cursor-session-manager";
|
|
59
74
|
import type { CursorContinuationScope, CursorRunSession } from "./cursor-session-manager";
|
|
60
75
|
import { fetchCursorRunOrigin } from "./cursor-catalog";
|
|
@@ -76,6 +91,34 @@ import { TtlCache } from "../../../persistence/ttl-cache";
|
|
|
76
91
|
*/
|
|
77
92
|
const CURSOR_HEARTBEAT_INTERVAL_MS = 5_000;
|
|
78
93
|
|
|
94
|
+
/** Bounded open retries for connection/deadline failures (donor policy). */
|
|
95
|
+
const CURSOR_MAX_OPEN_RETRIES = 5;
|
|
96
|
+
|
|
97
|
+
/** Heartbeats with no semantic frame before a Run is declared quiet. */
|
|
98
|
+
const CURSOR_QUIET_BEATS = 48; // 48 × 5s = 240s (donor quiet window)
|
|
99
|
+
|
|
100
|
+
/** Optional client-version override for transport tests and live probes. */
|
|
101
|
+
const clientVersionOverride = (() => {
|
|
102
|
+
const raw = process.env.CARTETHYIA_CURSOR_CLIENT_VERSION?.trim();
|
|
103
|
+
return raw === undefined || raw === "" ? undefined : raw;
|
|
104
|
+
})();
|
|
105
|
+
|
|
106
|
+
/** Abortable open-retry backoff. */
|
|
107
|
+
function sleepBeforeOpenRetry(ms: number, signal: AbortSignal): Promise<void> {
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
const timer = setTimeout(resolve, ms);
|
|
110
|
+
timer.unref?.();
|
|
111
|
+
signal.addEventListener(
|
|
112
|
+
"abort",
|
|
113
|
+
() => {
|
|
114
|
+
clearTimeout(timer);
|
|
115
|
+
reject(new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃"));
|
|
116
|
+
},
|
|
117
|
+
{ once: true },
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
79
122
|
/**
|
|
80
123
|
* How long one account's model roster is reused for `parameters` lookups.
|
|
81
124
|
*
|
|
@@ -363,11 +406,27 @@ function resultImages(part: Extract<ContentPart, { kind: "toolResult" }>): Resul
|
|
|
363
406
|
|
|
364
407
|
/** One Run's live state while this adapter is pumping it. */
|
|
365
408
|
interface RunHandle {
|
|
366
|
-
readonly
|
|
409
|
+
readonly transport: CursorRunTransport;
|
|
367
410
|
readonly write: (message: Uint8Array) => Promise<void>;
|
|
368
411
|
close(reason?: string): void;
|
|
369
412
|
}
|
|
370
413
|
|
|
414
|
+
/** The donor's per-Run stream state, kept across a Run's request legs. */
|
|
415
|
+
interface CursorRunFrameState {
|
|
416
|
+
readonly completion: ReturnType<typeof createRunCompletion>;
|
|
417
|
+
readonly checkpoint: CursorCheckpointState;
|
|
418
|
+
/** Frame kinds since the last checkpoint, for the "went quiet" error. */
|
|
419
|
+
kindsSinceCheckpoint: string[];
|
|
420
|
+
/** Heartbeats since the last semantic frame; a quiet Run must not hang. */
|
|
421
|
+
quietBeats: number;
|
|
422
|
+
/** `tool_call_completed` frames the edge reported for this step. */
|
|
423
|
+
serverToolCount: number;
|
|
424
|
+
/** The transport this Run was opened on. */
|
|
425
|
+
transport: "http2" | "http1";
|
|
426
|
+
/** Attempt number, for the structured terminal error. */
|
|
427
|
+
attempt: number;
|
|
428
|
+
}
|
|
429
|
+
|
|
371
430
|
/** One decoded server frame, ready for pump consumption. */
|
|
372
431
|
type RunFrame = Record<string, unknown>;
|
|
373
432
|
|
|
@@ -618,9 +677,10 @@ class CursorAdapter implements ProviderAdapter {
|
|
|
618
677
|
let sequence = 0;
|
|
619
678
|
const next = (): number => (sequence += 1);
|
|
620
679
|
try {
|
|
621
|
-
|
|
622
|
-
const
|
|
623
|
-
|
|
680
|
+
try {
|
|
681
|
+
for (const item of pending) {
|
|
682
|
+
const entry = cursorRunSessions.resolveCall(item.callId, scope);
|
|
683
|
+
if (entry === undefined) continue;
|
|
624
684
|
await handle.write(
|
|
625
685
|
buildMcpResult({
|
|
626
686
|
execId: entry.call.execId,
|
|
@@ -630,7 +690,25 @@ class CursorAdapter implements ProviderAdapter {
|
|
|
630
690
|
...(item.images.length > 0 ? { images: item.images } : {}),
|
|
631
691
|
}),
|
|
632
692
|
);
|
|
633
|
-
|
|
693
|
+
// Cleared only after the write succeeded: a half-delivered result
|
|
694
|
+
// must stay pending so a retry answers the same call, never a new one.
|
|
695
|
+
cursorRunSessions.clearPendingCall(runId, item.callId);
|
|
696
|
+
}
|
|
697
|
+
} catch (error) {
|
|
698
|
+
// The Run's transport rejected a result that the model is waiting on.
|
|
699
|
+
// The call was never delivered: terminate the held Run and rebase the
|
|
700
|
+
// whole transcript into a fresh Run (donor rule — a lost owner rebuilds
|
|
701
|
+
// Chat history on a fresh Run; a result is never replayed into an
|
|
702
|
+
// uncertain transport twice). Failing the request here instead left the
|
|
703
|
+
// client loop stuck: every resend hit the same dead handle.
|
|
704
|
+
cursorRunSessions.terminate(
|
|
705
|
+
runId,
|
|
706
|
+
`Cursor Run rejected a tool result: ${error instanceof Error ? error.message : String(error)}`,
|
|
707
|
+
);
|
|
708
|
+
this.#handles.delete(runId);
|
|
709
|
+
this.cleanupReader(runId);
|
|
710
|
+
yield* this.#startRun(request, candidate, context);
|
|
711
|
+
return;
|
|
634
712
|
}
|
|
635
713
|
yield* this.#pump({
|
|
636
714
|
request,
|
|
@@ -660,13 +738,23 @@ class CursorAdapter implements ProviderAdapter {
|
|
|
660
738
|
request: CanonicalRequest,
|
|
661
739
|
candidate: ProviderDispatchTarget,
|
|
662
740
|
context: ProviderDispatchContext,
|
|
741
|
+
retry?: {
|
|
742
|
+
readonly attempt: number;
|
|
743
|
+
readonly requestId: string;
|
|
744
|
+
readonly conversationId: string;
|
|
745
|
+
readonly checkpoint?: Uint8Array;
|
|
746
|
+
readonly forceH1?: boolean;
|
|
747
|
+
readonly rotateConversation?: boolean;
|
|
748
|
+
},
|
|
663
749
|
): AsyncIterable<CanonicalEvent> {
|
|
750
|
+
const attempt = retry?.attempt ?? 0;
|
|
664
751
|
const outboundHttp2 = context.outbound_http2;
|
|
665
|
-
|
|
752
|
+
const outboundFetch = context.outbound_fetch as unknown as typeof fetch | undefined;
|
|
753
|
+
if (outboundHttp2 === undefined && outboundFetch === undefined) {
|
|
666
754
|
throw new GatewayError(
|
|
667
755
|
"admission_unavailable",
|
|
668
756
|
503,
|
|
669
|
-
"Cursor requires validated
|
|
757
|
+
"Cursor requires validated egress for its Run transport (request {requestId}); none is configured for this route. No bridge to cross — configure a pool first 🏗️",
|
|
670
758
|
{ providerId: CURSOR_PROVIDER_ID },
|
|
671
759
|
);
|
|
672
760
|
}
|
|
@@ -690,28 +778,66 @@ class CursorAdapter implements ProviderAdapter {
|
|
|
690
778
|
const images = await collectImages(request, context);
|
|
691
779
|
const descriptors = toolDescriptorsFor(request.tools);
|
|
692
780
|
const systemPrompt = systemPromptFrom(request);
|
|
693
|
-
const conversationId = crypto.randomUUID();
|
|
781
|
+
const conversationId = retry?.conversationId ?? crypto.randomUUID();
|
|
694
782
|
const runId = crypto.randomUUID();
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
}
|
|
783
|
+
// Bounded open retries with the donor's backoff: connection and deadline
|
|
784
|
+
// failures retry; auth, quota, and request-shape refusals do not. Inside
|
|
785
|
+
// each attempt the transport policy applies (HTTP/2 first, HTTP/1.1 when
|
|
786
|
+
// HTTP/2 is blocked here), so a network that cannot carry HTTP/2 still
|
|
787
|
+
// serves Cursor without the caller ever seeing a transport error.
|
|
788
|
+
let opened: { transport: CursorRunTransport; requestId: string } | undefined;
|
|
789
|
+
let openError: unknown;
|
|
790
|
+
for (let openAttempt = 0; openAttempt <= CURSOR_MAX_OPEN_RETRIES; openAttempt += 1) {
|
|
791
|
+
if (context.abort_signal.aborted) {
|
|
792
|
+
throw new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
|
|
793
|
+
}
|
|
794
|
+
try {
|
|
795
|
+
opened = await openCursorRunWithFallback({
|
|
796
|
+
origin,
|
|
797
|
+
credential,
|
|
798
|
+
outboundHttp2,
|
|
799
|
+
outboundFetch,
|
|
800
|
+
signal: context.abort_signal,
|
|
801
|
+
...(retry?.requestId === undefined ? {} : { requestId: retry.requestId }),
|
|
802
|
+
...(retry?.forceH1 === true ? { forceH1: true } : {}),
|
|
803
|
+
...(clientVersionOverride === undefined ? {} : { clientVersion: clientVersionOverride }),
|
|
804
|
+
});
|
|
805
|
+
break;
|
|
806
|
+
} catch (error) {
|
|
807
|
+
openError = error;
|
|
808
|
+
if (!isRetryableOpenFailure(error) || openAttempt === CURSOR_MAX_OPEN_RETRIES) throw error;
|
|
809
|
+
await sleepBeforeOpenRetry(openRetryDelayMs(openAttempt), context.abort_signal);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (opened === undefined) throw openError;
|
|
813
|
+
const transport = opened.transport;
|
|
701
814
|
let closed = false;
|
|
702
815
|
const handle: RunHandle = {
|
|
703
|
-
|
|
704
|
-
write:
|
|
816
|
+
transport,
|
|
817
|
+
write: (message) => transport.write(message),
|
|
705
818
|
close: (reason) => {
|
|
706
819
|
if (closed) return;
|
|
707
820
|
closed = true;
|
|
708
821
|
if (reason !== undefined && reason.length > 0) {
|
|
709
|
-
void
|
|
822
|
+
void transport.write(buildCancelAction(reason)).catch(() => undefined);
|
|
710
823
|
}
|
|
711
|
-
|
|
824
|
+
transport.abort();
|
|
712
825
|
},
|
|
713
826
|
};
|
|
714
827
|
this.#handles.set(runId, handle);
|
|
828
|
+
this.#frameState.set(runId, {
|
|
829
|
+
completion: createRunCompletion(),
|
|
830
|
+
checkpoint: createCheckpointState(),
|
|
831
|
+
kindsSinceCheckpoint: [],
|
|
832
|
+
quietBeats: 0,
|
|
833
|
+
serverToolCount: 0,
|
|
834
|
+
transport: transport.mode,
|
|
835
|
+
attempt,
|
|
836
|
+
});
|
|
837
|
+
if (retry?.checkpoint !== undefined) {
|
|
838
|
+
const state = this.#frameState.get(runId);
|
|
839
|
+
if (state !== undefined) state.checkpoint.bytes = retry.checkpoint;
|
|
840
|
+
}
|
|
715
841
|
// Reader before the run request: frames can only arrive after it, but the
|
|
716
842
|
// reader must own them from the first byte, across every serving request.
|
|
717
843
|
this.#startReader(runId, handle);
|
|
@@ -728,7 +854,7 @@ class CursorAdapter implements ProviderAdapter {
|
|
|
728
854
|
let sequence = 0;
|
|
729
855
|
const next = (): number => (sequence += 1);
|
|
730
856
|
try {
|
|
731
|
-
await
|
|
857
|
+
await handle.write(
|
|
732
858
|
buildRunRequest({
|
|
733
859
|
text: liveUserText(request),
|
|
734
860
|
modelId,
|
|
@@ -740,7 +866,10 @@ class CursorAdapter implements ProviderAdapter {
|
|
|
740
866
|
// A fresh Run always rebases the client's history into its user text:
|
|
741
867
|
// Cursor's own checkpoint cannot be reconstructed after a restart, and
|
|
742
868
|
// `root_prompt_messages_json` would suppress the RequestContext rules.
|
|
869
|
+
// A retry with a checkpoint resumes instead of replaying the turn.
|
|
743
870
|
history: historyFromRequest(request),
|
|
871
|
+
...(retry?.checkpoint === undefined ? {} : { conversationState: retry.checkpoint }),
|
|
872
|
+
...(retry?.checkpoint === undefined ? {} : { action: "resume" as const }),
|
|
744
873
|
}),
|
|
745
874
|
);
|
|
746
875
|
// Registered before the first frame is pumped: a tool call can arrive on
|
|
@@ -762,9 +891,54 @@ class CursorAdapter implements ProviderAdapter {
|
|
|
762
891
|
continuation: false,
|
|
763
892
|
});
|
|
764
893
|
} catch (error) {
|
|
765
|
-
|
|
894
|
+
const state = this.#frameState.get(runId);
|
|
895
|
+
const failure = error instanceof GatewayError
|
|
896
|
+
? error
|
|
897
|
+
: new GatewayError(
|
|
898
|
+
"platform_unavailable",
|
|
899
|
+
502,
|
|
900
|
+
`Cursor Run failed (request {requestId}): ${error instanceof Error ? error.message : String(error)}. The upstream came back empty-handed — retry 🌙`,
|
|
901
|
+
{ providerId: CURSOR_PROVIDER_ID },
|
|
902
|
+
"upstream",
|
|
903
|
+
);
|
|
904
|
+
const classification = classifyCursorFailure(
|
|
905
|
+
failure,
|
|
906
|
+
state?.transport ?? transport.mode,
|
|
907
|
+
);
|
|
908
|
+
// Only a Run that produced no observable event can be safely replayed:
|
|
909
|
+
// after the first content_delta/tool_call the client has already seen
|
|
910
|
+
// half an answer. This is the donor's replaySafe rule. A checkpoint
|
|
911
|
+
// carried across the retry stands for the work already done.
|
|
912
|
+
const replaySafe = sequence === 0;
|
|
913
|
+
if (
|
|
914
|
+
replaySafe &&
|
|
915
|
+
classification.retryable &&
|
|
916
|
+
(state?.attempt ?? 0) < CURSOR_MAX_OPEN_RETRIES &&
|
|
917
|
+
!context.abort_signal.aborted
|
|
918
|
+
) {
|
|
919
|
+
const nextAttempt = (state?.attempt ?? 0) + 1;
|
|
920
|
+
const checkpoint = state?.checkpoint.bytes;
|
|
921
|
+
await this.#drainDispatches(runId, context.abort_signal);
|
|
922
|
+
cursorRunSessions.terminate(runId, failure.message);
|
|
923
|
+
this.#handles.delete(runId);
|
|
924
|
+
this.cleanupReader(runId);
|
|
925
|
+
this.#frameState.delete(runId);
|
|
926
|
+
this.#dispatches.delete(runId);
|
|
927
|
+
this.#wireTraces.delete(runId);
|
|
928
|
+
await sleepBeforeOpenRetry(openRetryDelayMs(nextAttempt - 1), context.abort_signal);
|
|
929
|
+
yield* this.#startRun(request, candidate, context, {
|
|
930
|
+
attempt: nextAttempt,
|
|
931
|
+
requestId: opened.requestId,
|
|
932
|
+
conversationId: classification.rotateConversation ? crypto.randomUUID() : conversationId,
|
|
933
|
+
...(classification.fallbackH1 ? { forceH1: true } : {}),
|
|
934
|
+
...(checkpoint === undefined || classification.rotateConversation ? {} : { checkpoint }),
|
|
935
|
+
});
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
await this.#drainDispatches(runId, context.abort_signal);
|
|
939
|
+
cursorRunSessions.terminate(runId, failure.message);
|
|
766
940
|
this.#handles.delete(runId);
|
|
767
|
-
throw
|
|
941
|
+
throw structuredCursorFailure(failure, classification, state, runId);
|
|
768
942
|
} finally {
|
|
769
943
|
if (cursorRunSessions.get(runId) === undefined) {
|
|
770
944
|
this.#handles.delete(runId);
|
|
@@ -779,6 +953,9 @@ class CursorAdapter implements ProviderAdapter {
|
|
|
779
953
|
cursorRunSessions.terminate(runId);
|
|
780
954
|
this.#handles.delete(runId);
|
|
781
955
|
this.cleanupReader(runId);
|
|
956
|
+
this.#frameState.delete(runId);
|
|
957
|
+
this.#dispatches.delete(runId);
|
|
958
|
+
this.#wireTraces.delete(runId);
|
|
782
959
|
}
|
|
783
960
|
}
|
|
784
961
|
}
|
|
@@ -787,6 +964,35 @@ class CursorAdapter implements ProviderAdapter {
|
|
|
787
964
|
readonly #handles = new Map<string, RunHandle>();
|
|
788
965
|
/** One shared frame reader per live Run, surviving its requests. */
|
|
789
966
|
readonly #readers = new Map<string, RunFrameStream>();
|
|
967
|
+
/** Checkpoint/exec accounting per Run: survives every pump leg. */
|
|
968
|
+
readonly #frameState = new Map<string, CursorRunFrameState>();
|
|
969
|
+
/** Async exec handlers decoded from a chunk, drained on both pump exits. */
|
|
970
|
+
readonly #dispatches = new Map<string, Set<Promise<void>>>();
|
|
971
|
+
/** Opt-in shape-only traces of failed Runs. */
|
|
972
|
+
readonly #wireTraces = new Map<string, CursorFrameShape[]>();
|
|
973
|
+
|
|
974
|
+
/** Records one frame's shape (opt-in, never payload text/ids). */
|
|
975
|
+
#trace(runId: string, message: RunFrame): void {
|
|
976
|
+
if (!wireTraceEnabled()) return;
|
|
977
|
+
const trace = this.#wireTraces.get(runId) ?? [];
|
|
978
|
+
if (trace.length < CURSOR_WIRE_TRACE_MAX) trace.push(frameShapeOf(message));
|
|
979
|
+
this.#wireTraces.set(runId, trace);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/** Tracks one async exec handler; it removes itself when it settles. */
|
|
983
|
+
#trackDispatch(runId: string, dispatch: Promise<void>): void {
|
|
984
|
+
const dispatches = this.#dispatches.get(runId) ?? new Set<Promise<void>>();
|
|
985
|
+
dispatches.add(dispatch);
|
|
986
|
+
void dispatch.finally(() => dispatches.delete(dispatch));
|
|
987
|
+
this.#dispatches.set(runId, dispatches);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
/** Drains every still-running exec handler before a Run is finalized. */
|
|
991
|
+
async #drainDispatches(runId: string, signal?: AbortSignal): Promise<void> {
|
|
992
|
+
const dispatches = this.#dispatches.get(runId);
|
|
993
|
+
if (dispatches === undefined) return;
|
|
994
|
+
await drainInFlightDispatches(dispatches, signal);
|
|
995
|
+
}
|
|
790
996
|
|
|
791
997
|
/**
|
|
792
998
|
* Opens the Run's single frame reader.
|
|
@@ -804,11 +1010,16 @@ class CursorAdapter implements ProviderAdapter {
|
|
|
804
1010
|
this.#readers.set(runId, frames);
|
|
805
1011
|
// The client-heartbeat cadence belongs to the reader: the Cursor edge
|
|
806
1012
|
// expects a keepalive while the Run stays open, including the window
|
|
807
|
-
// between a tool call and the request that carries its result.
|
|
1013
|
+
// between a tool call and the request that carries its result. A write
|
|
1014
|
+
// that failed means the transport died; stop the cadence rather than
|
|
1015
|
+
// spinning on a dead stream until the idle TTL fires.
|
|
808
1016
|
let state = "open";
|
|
809
1017
|
const beat = (): void => {
|
|
810
1018
|
if (state !== "open") return;
|
|
811
|
-
void handle.write(buildHeartbeat()).catch(() =>
|
|
1019
|
+
void handle.write(buildHeartbeat()).catch(() => {
|
|
1020
|
+
state = "failed";
|
|
1021
|
+
clearInterval(heartbeatTimer);
|
|
1022
|
+
});
|
|
812
1023
|
};
|
|
813
1024
|
const heartbeatTimer = setInterval(
|
|
814
1025
|
beat,
|
|
@@ -822,16 +1033,35 @@ class CursorAdapter implements ProviderAdapter {
|
|
|
822
1033
|
// Stream failures do not always surface as a throw from the body iterator;
|
|
823
1034
|
// the terminal event is the authoritative signal (auth revoked, quota,
|
|
824
1035
|
// RST_STREAM). `local-close` is our own abort: the session manager already
|
|
825
|
-
// decided the outcome, so the pump just sees a clean end.
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
1036
|
+
// decided the outcome, so the pump just sees a clean end. Normalized over
|
|
1037
|
+
// both transports: HTTP/1.1 reports the same terminal kinds.
|
|
1038
|
+
const unsubscribeTerminal = handle.transport.onTerminal((terminal) => {
|
|
1039
|
+
if (terminal.kind === "local-close") return;
|
|
1040
|
+
const endReason: Http2BidiEndReason =
|
|
1041
|
+
terminal.kind === "remote-error"
|
|
1042
|
+
? { kind: "remote-error", error: terminal.failure ?? new Error("Cursor Run transport failed") }
|
|
1043
|
+
: { kind: terminal.kind };
|
|
1044
|
+
// A clean close carries no failure of its own: the pump decides whether
|
|
1045
|
+
// a turn without `turn_ended` is complete (text/step/tool batch) or
|
|
1046
|
+
// quiet. Inventing "ended before turn_ended" here made every held tool
|
|
1047
|
+
// round fail before its call could be handed to the client.
|
|
1048
|
+
const terminalFailure =
|
|
1049
|
+
terminal.failure ??
|
|
1050
|
+
(terminal.kind === "remote-error"
|
|
1051
|
+
? new GatewayError(
|
|
1052
|
+
"platform_unavailable",
|
|
1053
|
+
502,
|
|
1054
|
+
"Cursor Run transport failed (request {requestId}). The upstream came back empty-handed — retry 🌙",
|
|
1055
|
+
{},
|
|
1056
|
+
"network",
|
|
1057
|
+
)
|
|
1058
|
+
: undefined);
|
|
1059
|
+
frames.end(endReason, terminalFailure);
|
|
830
1060
|
unsubscribeTerminal();
|
|
831
1061
|
});
|
|
832
1062
|
void (async () => {
|
|
833
1063
|
try {
|
|
834
|
-
for await (const message of decodeServerMessages(handle.
|
|
1064
|
+
for await (const message of decodeServerMessages(handle.transport.body)) {
|
|
835
1065
|
frames.push(message);
|
|
836
1066
|
}
|
|
837
1067
|
// The body ended without a recorded terminal: treat it as a clean
|
|
@@ -942,54 +1172,150 @@ class CursorAdapter implements ProviderAdapter {
|
|
|
942
1172
|
let terminalUsage: UsageRecord | undefined;
|
|
943
1173
|
let stopReason: CanonicalStopReason = "stop";
|
|
944
1174
|
let failure: GatewayError | undefined;
|
|
1175
|
+
const state = this.#frameState.get(runId) ?? {
|
|
1176
|
+
completion: createRunCompletion(),
|
|
1177
|
+
checkpoint: createCheckpointState(),
|
|
1178
|
+
kindsSinceCheckpoint: [],
|
|
1179
|
+
quietBeats: 0,
|
|
1180
|
+
serverToolCount: 0,
|
|
1181
|
+
transport: handle.transport.mode,
|
|
1182
|
+
attempt: 0,
|
|
1183
|
+
};
|
|
1184
|
+
this.#frameState.set(runId, state);
|
|
1185
|
+
const pendingToolCalls: Array<{ callId: string; toolName: string; arguments: string }> = [];
|
|
945
1186
|
const frames = this.#readers.get(runId) ?? this.#startReader(runId, handle);
|
|
946
1187
|
try {
|
|
947
1188
|
const messages = frames.subscribe();
|
|
948
1189
|
for await (const item of messages) {
|
|
949
1190
|
if (isRunStreamEnd(item)) {
|
|
950
|
-
|
|
1191
|
+
// The reader can end while an exec handler decoded from its last
|
|
1192
|
+
// chunk is still registering a call: drain before deciding.
|
|
1193
|
+
await this.#drainDispatches(runId, context.abort_signal);
|
|
1194
|
+
if (pendingToolCalls.length > 0) {
|
|
1195
|
+
for (const pending of pendingToolCalls.splice(0)) {
|
|
1196
|
+
yield {
|
|
1197
|
+
type: "tool_call_delta",
|
|
1198
|
+
sequence_number: input.next(),
|
|
1199
|
+
call_id: pending.callId,
|
|
1200
|
+
name: pending.toolName,
|
|
1201
|
+
arguments_delta: pending.arguments,
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
sawToolCall = true;
|
|
1205
|
+
cursorRunSessions.touch(runId);
|
|
1206
|
+
yield {
|
|
1207
|
+
type: "terminal",
|
|
1208
|
+
sequence_number: input.next(),
|
|
1209
|
+
state: "complete",
|
|
1210
|
+
stop_reason: "tool_use" as const,
|
|
1211
|
+
usage_deferred: true,
|
|
1212
|
+
};
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
if (item.failure !== undefined && item.__streamEnd.kind !== "remote-clean-close") {
|
|
1216
|
+
failure = item.failure;
|
|
1217
|
+
}
|
|
1218
|
+
state.completion.settle(item.failure === undefined ? "end" : "error");
|
|
951
1219
|
break;
|
|
952
1220
|
}
|
|
953
1221
|
const message = item;
|
|
1222
|
+
this.#trace(runId, message);
|
|
954
1223
|
if (context.abort_signal.aborted) {
|
|
955
1224
|
failure = new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
|
|
1225
|
+
state.completion.settle("timeout");
|
|
956
1226
|
break;
|
|
957
1227
|
}
|
|
1228
|
+
// Checkpoint retention: bytes are opaque and are never decoded here.
|
|
1229
|
+
// A checkpoint with no semantic frame since the previous one is a
|
|
1230
|
+
// repeated one: the streak is what the "went quiet" error names.
|
|
1231
|
+
const checkpointBytes = message["conversation_checkpoint_update"];
|
|
1232
|
+
if (checkpointBytes instanceof Uint8Array && checkpointBytes.byteLength > 0) {
|
|
1233
|
+
state.checkpoint.bytes = new Uint8Array(checkpointBytes);
|
|
1234
|
+
state.checkpoint.index += 1;
|
|
1235
|
+
state.checkpoint.step += 1;
|
|
1236
|
+
state.checkpoint.streak = state.kindsSinceCheckpoint.length === 0 ? state.checkpoint.streak + 1 : 1;
|
|
1237
|
+
state.checkpoint.kinds = state.kindsSinceCheckpoint.slice(-8);
|
|
1238
|
+
state.kindsSinceCheckpoint = [];
|
|
1239
|
+
state.quietBeats = 0;
|
|
1240
|
+
continue;
|
|
1241
|
+
}
|
|
1242
|
+
const kind = frameKindOf(message);
|
|
1243
|
+
state.kindsSinceCheckpoint.push(kind);
|
|
1244
|
+
if (state.kindsSinceCheckpoint.length > 16) state.kindsSinceCheckpoint.shift();
|
|
1245
|
+
|
|
958
1246
|
const execServer = recordOf(message["exec_server_message"]);
|
|
959
1247
|
if (execServer !== undefined) {
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1248
|
+
// Exec handlers are async and may outlive the chunk that spawned
|
|
1249
|
+
// them. Track the dispatch so BOTH exits drain it before terminal.
|
|
1250
|
+
const dispatch = (async () => {
|
|
1251
|
+
const handled = await this.#handleExecServerMessage(execServer, session, handle);
|
|
1252
|
+
if (handled.kind !== "tool_call") return;
|
|
1253
|
+
// Only MCP arms are relayed to the client for execution (client-
|
|
1254
|
+
// declared tools). Native tools are answered in-band by the executor.
|
|
1255
|
+
const callId = `call_cursor_${runId}_${handled.execId}`;
|
|
1256
|
+
const scope = context.continuation_scope;
|
|
1257
|
+
if (scope === undefined) {
|
|
1258
|
+
// Without a scope, no later request could be authorized to answer
|
|
1259
|
+
// this call, so the Run must not be held open for one.
|
|
1260
|
+
await handle.write(buildMcpRejected(handled.execId, "tool call is not answerable", handled.execIdString));
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
if (state.checkpoint.completedCallIds.has(callId)) {
|
|
1264
|
+
// The server already counted this call (donor rule): answering it
|
|
1265
|
+
// twice is what made tool rounds fail with duplicate results.
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
if (!cursorRunSessions.registerPendingCall(runId, {
|
|
1269
|
+
callId,
|
|
1270
|
+
execId: handled.execId,
|
|
1271
|
+
...(handled.execIdString === undefined ? {} : { execIdString: handled.execIdString }),
|
|
1272
|
+
toolName: handled.toolName,
|
|
1273
|
+
})) {
|
|
1274
|
+
await handle.write(buildMcpRejected(handled.execId, "tool call is not answerable", handled.execIdString));
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
state.checkpoint.pendingCallIds.add(callId);
|
|
1278
|
+
state.checkpoint.dispatchedCount += 1;
|
|
1279
|
+
sawToolCall = true;
|
|
1280
|
+
// The yield happens on the pump's own loop, not inside this async
|
|
1281
|
+
// dispatch: the queue below carries the call out to the client.
|
|
1282
|
+
pendingToolCalls.push({
|
|
1283
|
+
callId,
|
|
1284
|
+
toolName: handled.toolName,
|
|
1285
|
+
arguments: handled.arguments,
|
|
1286
|
+
});
|
|
1287
|
+
})().catch((error) => {
|
|
1288
|
+
failure = error instanceof GatewayError
|
|
1289
|
+
? error
|
|
1290
|
+
: new GatewayError(
|
|
1291
|
+
"platform_unavailable",
|
|
1292
|
+
502,
|
|
1293
|
+
`Cursor exec handler failed (request {requestId}): ${error instanceof Error ? error.message : String(error)}`,
|
|
1294
|
+
{ providerId: CURSOR_PROVIDER_ID },
|
|
1295
|
+
"upstream",
|
|
1296
|
+
);
|
|
1297
|
+
});
|
|
1298
|
+
this.#trackDispatch(runId, dispatch);
|
|
1299
|
+
continue;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// Drain handlers before surfacing a tool call: a second exec decoded
|
|
1303
|
+
// from the same chunk may still be registering its call, and the
|
|
1304
|
+
// client's interim terminal must carry the whole batch (donor park).
|
|
1305
|
+
if (pendingToolCalls.length > 0) {
|
|
1306
|
+
await this.#drainDispatches(runId, context.abort_signal);
|
|
1307
|
+
for (const pending of pendingToolCalls.splice(0)) {
|
|
1308
|
+
yield {
|
|
1309
|
+
type: "tool_call_delta",
|
|
1310
|
+
sequence_number: input.next(),
|
|
1311
|
+
call_id: pending.callId,
|
|
1312
|
+
name: pending.toolName,
|
|
1313
|
+
arguments_delta: pending.arguments,
|
|
1314
|
+
};
|
|
980
1315
|
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
sequence_number: input.next(),
|
|
985
|
-
call_id: callId,
|
|
986
|
-
name: handled.toolName,
|
|
987
|
-
arguments_delta: handled.arguments,
|
|
988
|
-
};
|
|
989
|
-
// Interim terminal the moment the call is registered and answered.
|
|
990
|
-
// The Run stays open for the result leg; this request must not wait
|
|
991
|
-
// for `turn_ended`, which only arrives after the client answers —
|
|
992
|
-
// holding it open here is exactly the deadlock that broke tool calling.
|
|
1316
|
+
// Interim terminal the moment the calls are registered: the Run stays
|
|
1317
|
+
// open for the result leg; waiting for `turn_ended` here deadlocked
|
|
1318
|
+
// the original tool loop.
|
|
993
1319
|
cursorRunSessions.touch(runId);
|
|
994
1320
|
yield {
|
|
995
1321
|
type: "terminal",
|
|
@@ -1042,12 +1368,45 @@ class CursorAdapter implements ProviderAdapter {
|
|
|
1042
1368
|
}
|
|
1043
1369
|
continue;
|
|
1044
1370
|
}
|
|
1045
|
-
// `tool_call_started` / `tool_call_completed` are display notifications
|
|
1046
|
-
//
|
|
1047
|
-
//
|
|
1371
|
+
// `tool_call_started` / `tool_call_completed` are display notifications,
|
|
1372
|
+
// but their count is authoritative for the step boundary: a step whose
|
|
1373
|
+
// calls are all counted is done without waiting out its heartbeats
|
|
1374
|
+
// (magpie 0.2.35 / oh-my-pi StepCalls rule).
|
|
1375
|
+
const listed = recordOf(interaction["tool_requests_listed"]);
|
|
1376
|
+
if (listed !== undefined) {
|
|
1377
|
+
const count = typeof listed["call_count"] === "number" ? listed["call_count"] : 0;
|
|
1378
|
+
state.checkpoint.expectedCallCount = count;
|
|
1379
|
+
continue;
|
|
1380
|
+
}
|
|
1381
|
+
const completed = recordOf(interaction["tool_call_completed"]);
|
|
1382
|
+
if (completed !== undefined) {
|
|
1383
|
+
state.serverToolCount += 1;
|
|
1384
|
+
const callId = typeof completed["call_id"] === "string" ? completed["call_id"] : "";
|
|
1385
|
+
if (callId.length > 0) state.checkpoint.completedCallIds.add(callId);
|
|
1386
|
+
const reconciled = reconcileToolCount(state.checkpoint, state.serverToolCount);
|
|
1387
|
+
if (reconciled.endStep) {
|
|
1388
|
+
state.checkpoint.step = 0;
|
|
1389
|
+
state.quietBeats = 0;
|
|
1390
|
+
}
|
|
1391
|
+
continue;
|
|
1392
|
+
}
|
|
1393
|
+
const heartbeat = recordOf(interaction["heartbeat"]);
|
|
1394
|
+
if (heartbeat !== undefined) {
|
|
1395
|
+
state.quietBeats += 1;
|
|
1396
|
+
// A Run that keeps heartbeating after a checkpoint with no semantic
|
|
1397
|
+
// frame is "quiet": name the checkpoint instead of hanging forever.
|
|
1398
|
+
if (state.quietBeats >= CURSOR_QUIET_BEATS) {
|
|
1399
|
+
failure = cursorQuietError(state.checkpoint);
|
|
1400
|
+
state.completion.settle("timeout");
|
|
1401
|
+
break;
|
|
1402
|
+
}
|
|
1403
|
+
continue;
|
|
1404
|
+
}
|
|
1405
|
+
// `turn_ended` is authoritative for the turn's end and its usage.
|
|
1048
1406
|
const turnEnded = recordOf(interaction["turn_ended"]);
|
|
1049
1407
|
if (turnEnded !== undefined) {
|
|
1050
1408
|
terminalUsage = usageFromTurnEnded(turnEnded);
|
|
1409
|
+
state.completion.settle("end");
|
|
1051
1410
|
break;
|
|
1052
1411
|
}
|
|
1053
1412
|
}
|
|
@@ -1062,18 +1421,47 @@ class CursorAdapter implements ProviderAdapter {
|
|
|
1062
1421
|
{ providerId: CURSOR_PROVIDER_ID },
|
|
1063
1422
|
"upstream",
|
|
1064
1423
|
);
|
|
1424
|
+
state.completion.settle("error");
|
|
1425
|
+
}
|
|
1426
|
+
// BOTH exits drain: an exec handler decoded from the last chunk can still
|
|
1427
|
+
// be running when the transport fails (donor L1198-L1201).
|
|
1428
|
+
await this.#drainDispatches(runId, context.abort_signal);
|
|
1429
|
+
// A dispatch that settled during the drain may have queued a call. Hand it
|
|
1430
|
+
// to the client before any terminal event, so a call is never lost.
|
|
1431
|
+
if (pendingToolCalls.length > 0) {
|
|
1432
|
+
for (const pending of pendingToolCalls.splice(0)) {
|
|
1433
|
+
yield {
|
|
1434
|
+
type: "tool_call_delta",
|
|
1435
|
+
sequence_number: input.next(),
|
|
1436
|
+
call_id: pending.callId,
|
|
1437
|
+
name: pending.toolName,
|
|
1438
|
+
arguments_delta: pending.arguments,
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
sawToolCall = true;
|
|
1442
|
+
cursorRunSessions.touch(runId);
|
|
1443
|
+
yield {
|
|
1444
|
+
type: "terminal",
|
|
1445
|
+
sequence_number: input.next(),
|
|
1446
|
+
state: "complete",
|
|
1447
|
+
stop_reason: "tool_use" as const,
|
|
1448
|
+
usage_deferred: true,
|
|
1449
|
+
};
|
|
1450
|
+
return;
|
|
1065
1451
|
}
|
|
1066
1452
|
|
|
1067
1453
|
if (failure !== undefined) {
|
|
1068
|
-
|
|
1454
|
+
const classification = classifyCursorFailure(failure, state.transport);
|
|
1455
|
+
const structured = structuredCursorFailure(failure, classification, state, runId);
|
|
1456
|
+
cursorRunSessions.terminate(runId, structured.message);
|
|
1069
1457
|
yield {
|
|
1070
1458
|
type: "terminal",
|
|
1071
1459
|
sequence_number: input.next(),
|
|
1072
1460
|
state: "failed",
|
|
1073
1461
|
stop_reason: "error",
|
|
1074
|
-
// The refusal text, so the caller sees
|
|
1462
|
+
// The refusal text, so the caller sees the checkpoint/transport reason
|
|
1075
1463
|
// instead of a terminal with no reason attached.
|
|
1076
|
-
stop_details: { code:
|
|
1464
|
+
stop_details: { code: structured.code, status: structured.status, message: structured.message },
|
|
1077
1465
|
...(terminalUsage === undefined ? {} : { usage: terminalUsage }),
|
|
1078
1466
|
};
|
|
1079
1467
|
return;
|
|
@@ -1313,6 +1701,33 @@ class CursorAdapter implements ProviderAdapter {
|
|
|
1313
1701
|
}
|
|
1314
1702
|
}
|
|
1315
1703
|
|
|
1704
|
+
/** Attaches the donor's structured failure shape to a terminal GatewayError. */
|
|
1705
|
+
function structuredCursorFailure(
|
|
1706
|
+
failure: GatewayError,
|
|
1707
|
+
classification: ReturnType<typeof classifyCursorFailure>,
|
|
1708
|
+
state: CursorRunFrameState | undefined,
|
|
1709
|
+
runId: string,
|
|
1710
|
+
): GatewayError {
|
|
1711
|
+
const checkpoint = state?.checkpoint;
|
|
1712
|
+
const quiet = classification.kind === "quiet" ? cursorQuietError(checkpoint) : undefined;
|
|
1713
|
+
const detail = quiet?.message ?? failure.message;
|
|
1714
|
+
return new GatewayError(
|
|
1715
|
+
failure.code,
|
|
1716
|
+
failure.status,
|
|
1717
|
+
`Cursor ${classification.kind}: ${detail} (attempt ${(state?.attempt ?? 0) + 1}/${CURSOR_MAX_OPEN_RETRIES + 1}, ` +
|
|
1718
|
+
`transport ${state?.transport ?? "unknown"}, checkpoint ${checkpoint?.index ?? 0}, run ${runId})`,
|
|
1719
|
+
{
|
|
1720
|
+
...failure.details,
|
|
1721
|
+
providerId: CURSOR_PROVIDER_ID,
|
|
1722
|
+
cursorFailureKind: classification.kind,
|
|
1723
|
+
retryable: classification.retryable,
|
|
1724
|
+
checkpointIndex: checkpoint?.index ?? 0,
|
|
1725
|
+
checkpointStreak: checkpoint?.streak ?? 0,
|
|
1726
|
+
},
|
|
1727
|
+
failure.origin,
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1316
1731
|
/** RequestContext Cursor receives in reply to a context probe. */
|
|
1317
1732
|
function probeContextFor(descriptors: readonly CursorToolDescriptor[]): Record<string, unknown> {
|
|
1318
1733
|
return {
|