@remodex/rmx 1.0.2
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/AGENTS_INSTALL.md +91 -0
- package/LICENSE +21 -0
- package/README.md +242 -0
- package/assets/architecture.png +0 -0
- package/assets/banner.png +0 -0
- package/assets/claude-code-models.gif +0 -0
- package/assets/codex-app-picker.png +0 -0
- package/bin/ocx.mjs +584 -0
- package/bin/package-main.mjs +9 -0
- package/gui/dist/assets/index-CZqebSPQ.css +1 -0
- package/gui/dist/assets/index-CkETtt7P.js +71 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/fonts/google-sans-cyrillic.woff2 +0 -0
- package/gui/dist/fonts/google-sans-latin.woff2 +0 -0
- package/gui/dist/icons.svg +24 -0
- package/gui/dist/index.html +25 -0
- package/gui/dist/logo.png +0 -0
- package/gui/dist/provider-icons/alibaba-color.svg +1 -0
- package/gui/dist/provider-icons/antigravity-color.svg +1 -0
- package/gui/dist/provider-icons/claude-color.svg +1 -0
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -0
- package/gui/dist/provider-icons/cursor-color.svg +2 -0
- package/gui/dist/provider-icons/deepseek-color.svg +1 -0
- package/gui/dist/provider-icons/discord.svg +1 -0
- package/gui/dist/provider-icons/firepass-color.svg +1 -0
- package/gui/dist/provider-icons/fireworks-color.svg +1 -0
- package/gui/dist/provider-icons/gemini-color.svg +1 -0
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
- package/gui/dist/provider-icons/grok.svg +1 -0
- package/gui/dist/provider-icons/groq-color.svg +1 -0
- package/gui/dist/provider-icons/huggingface-color.svg +1 -0
- package/gui/dist/provider-icons/kimi-color.svg +1 -0
- package/gui/dist/provider-icons/kiro-color.svg +15 -0
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
- package/gui/dist/provider-icons/mistral-color.svg +1 -0
- package/gui/dist/provider-icons/moonshot-color.svg +1 -0
- package/gui/dist/provider-icons/nvidia-color.svg +1 -0
- package/gui/dist/provider-icons/ollama-color.svg +1 -0
- package/gui/dist/provider-icons/openai.svg +1 -0
- package/gui/dist/provider-icons/opencode.svg +2 -0
- package/gui/dist/provider-icons/openrouter-color.svg +1 -0
- package/gui/dist/provider-icons/pi.svg +21 -0
- package/gui/dist/provider-icons/qianfan-color.svg +1 -0
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
- package/gui/dist/provider-icons/telegram.svg +1 -0
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/vllm-color.svg +1 -0
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
- package/package.json +118 -0
- package/src/AGENTS.md +28 -0
- package/src/adapters/anthropic-image-guard.ts +251 -0
- package/src/adapters/anthropic-image-normalize.ts +518 -0
- package/src/adapters/anthropic.ts +1205 -0
- package/src/adapters/azure.ts +36 -0
- package/src/adapters/base.ts +83 -0
- package/src/adapters/client-fingerprint.ts +59 -0
- package/src/adapters/command-code.ts +453 -0
- package/src/adapters/cursor/arg-codec.ts +38 -0
- package/src/adapters/cursor/arg-normalize.ts +104 -0
- package/src/adapters/cursor/cursor-errors.ts +165 -0
- package/src/adapters/cursor/discovery.ts +276 -0
- package/src/adapters/cursor/effort-map.ts +139 -0
- package/src/adapters/cursor/exec-policy.ts +88 -0
- package/src/adapters/cursor/framing.ts +250 -0
- package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
- package/src/adapters/cursor/kv-store.ts +52 -0
- package/src/adapters/cursor/live-models.ts +153 -0
- package/src/adapters/cursor/live-smoke-gate.ts +41 -0
- package/src/adapters/cursor/live-transport.ts +1235 -0
- package/src/adapters/cursor/mcp-config.ts +42 -0
- package/src/adapters/cursor/mcp-manager.ts +333 -0
- package/src/adapters/cursor/message-mapper.ts +49 -0
- package/src/adapters/cursor/native-exec-common.ts +59 -0
- package/src/adapters/cursor/native-exec-desktop.ts +184 -0
- package/src/adapters/cursor/native-exec-fs.ts +332 -0
- package/src/adapters/cursor/native-exec-mcp.ts +153 -0
- package/src/adapters/cursor/native-exec-network.ts +43 -0
- package/src/adapters/cursor/native-exec-shell.ts +548 -0
- package/src/adapters/cursor/native-exec-tools.ts +118 -0
- package/src/adapters/cursor/native-exec.ts +604 -0
- package/src/adapters/cursor/protobuf-events.ts +735 -0
- package/src/adapters/cursor/protobuf-request.ts +719 -0
- package/src/adapters/cursor/request-builder.ts +280 -0
- package/src/adapters/cursor/thread-continuity.ts +67 -0
- package/src/adapters/cursor/tool-definitions.ts +621 -0
- package/src/adapters/cursor/transport-retry.ts +132 -0
- package/src/adapters/cursor/transport.ts +57 -0
- package/src/adapters/cursor/types.ts +59 -0
- package/src/adapters/cursor.ts +196 -0
- package/src/adapters/google-antigravity-replay.ts +520 -0
- package/src/adapters/google-antigravity-wire.ts +140 -0
- package/src/adapters/google-errors.ts +85 -0
- package/src/adapters/google-http.ts +100 -0
- package/src/adapters/google-tool-schema.ts +173 -0
- package/src/adapters/google-truncation.ts +24 -0
- package/src/adapters/google-wire-compiler.ts +232 -0
- package/src/adapters/google.ts +859 -0
- package/src/adapters/identity.ts +77 -0
- package/src/adapters/image.ts +23 -0
- package/src/adapters/kiro-constants.ts +16 -0
- package/src/adapters/kiro-errors.ts +208 -0
- package/src/adapters/kiro-events.ts +197 -0
- package/src/adapters/kiro-images.ts +129 -0
- package/src/adapters/kiro-retry.ts +312 -0
- package/src/adapters/kiro-thinking.ts +104 -0
- package/src/adapters/kiro-tool-fallback.ts +36 -0
- package/src/adapters/kiro-tools.ts +224 -0
- package/src/adapters/kiro-truncation.ts +33 -0
- package/src/adapters/kiro-wire.ts +129 -0
- package/src/adapters/kiro.ts +1924 -0
- package/src/adapters/mimo-free.ts +263 -0
- package/src/adapters/openai-chat.ts +1265 -0
- package/src/adapters/openai-responses.ts +1309 -0
- package/src/adapters/run-turn-queue.ts +114 -0
- package/src/adapters/tool-catalog-nudge.ts +71 -0
- package/src/adapters/upstream-http-error.ts +48 -0
- package/src/android-remote/assets.ts +218 -0
- package/src/android-remote/attachments.ts +168 -0
- package/src/android-remote/auth.ts +237 -0
- package/src/android-remote/cloudflare-provisioning.ts +409 -0
- package/src/android-remote/cloudflare-secret.ts +106 -0
- package/src/android-remote/cloudflare-tunnel.ts +488 -0
- package/src/android-remote/cloudflared.ts +286 -0
- package/src/android-remote/codex-app-server.ts +565 -0
- package/src/android-remote/desktop-history-page.ts +936 -0
- package/src/android-remote/desktop-ipc.ts +3643 -0
- package/src/android-remote/desktop-ownership-store.ts +98 -0
- package/src/android-remote/desktop-project-registration.ts +129 -0
- package/src/android-remote/desktop-session-stream.ts +1110 -0
- package/src/android-remote/desktop-workspace-state.ts +443 -0
- package/src/android-remote/file-change-parser.ts +112 -0
- package/src/android-remote/gateway.ts +9108 -0
- package/src/android-remote/mutation-store.ts +299 -0
- package/src/android-remote/projection.ts +1780 -0
- package/src/android-remote/queued-turn-store.ts +248 -0
- package/src/android-remote/session-command-recovery.ts +1384 -0
- package/src/android-remote/store.ts +466 -0
- package/src/android-remote/thread-reconciliation.ts +133 -0
- package/src/android-remote/thread-source-paths.ts +307 -0
- package/src/android-remote/thread-stream.ts +546 -0
- package/src/android-remote/turn-activity.ts +235 -0
- package/src/android-remote/user-message-identity.ts +94 -0
- package/src/bridge.ts +1793 -0
- package/src/chat/inbound.ts +295 -0
- package/src/chat/outbound.ts +821 -0
- package/src/claude/agents-inject.ts +267 -0
- package/src/claude/alias.ts +149 -0
- package/src/claude/auth-detect.ts +229 -0
- package/src/claude/auth-mode-migration.ts +32 -0
- package/src/claude/auth-mode.ts +62 -0
- package/src/claude/context-windows.ts +189 -0
- package/src/claude/desktop-3p-guard.ts +35 -0
- package/src/claude/desktop-3p-paths.ts +84 -0
- package/src/claude/desktop-3p.ts +601 -0
- package/src/claude/desktop-health.ts +26 -0
- package/src/claude/desktop-profile.ts +263 -0
- package/src/claude/gateway-cache.ts +70 -0
- package/src/claude/inbound-debug.ts +163 -0
- package/src/claude/inbound.ts +519 -0
- package/src/claude/model-info.ts +154 -0
- package/src/claude/outbound.ts +898 -0
- package/src/cli/access.ts +108 -0
- package/src/cli/account-api.ts +296 -0
- package/src/cli/account-auth.ts +250 -0
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +476 -0
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +297 -0
- package/src/cli/agent-driven.ts +70 -0
- package/src/cli/agent.ts +184 -0
- package/src/cli/catalog-prewarm.ts +27 -0
- package/src/cli/claude-desktop.ts +211 -0
- package/src/cli/claude.ts +302 -0
- package/src/cli/codex-shim-autorestore.ts +45 -0
- package/src/cli/codex-shim-readiness.ts +69 -0
- package/src/cli/combo.ts +124 -0
- package/src/cli/config-command.ts +183 -0
- package/src/cli/debug.ts +228 -0
- package/src/cli/desktop-first-run.ts +25 -0
- package/src/cli/doctor.ts +1022 -0
- package/src/cli/export-command.ts +201 -0
- package/src/cli/help.ts +370 -0
- package/src/cli/index.ts +1565 -0
- package/src/cli/init.ts +224 -0
- package/src/cli/integrations.ts +225 -0
- package/src/cli/interactive-confirm.ts +133 -0
- package/src/cli/internal-dispatch.ts +35 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/models-runtime.ts +224 -0
- package/src/cli/models.ts +340 -0
- package/src/cli/observe.ts +170 -0
- package/src/cli/opencode.ts +587 -0
- package/src/cli/provider-runtime.ts +179 -0
- package/src/cli/provider.ts +476 -0
- package/src/cli/ready.ts +301 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +328 -0
- package/src/cli/star-prompt.ts +211 -0
- package/src/cli/status-oauth.ts +78 -0
- package/src/cli/status.ts +321 -0
- package/src/cli/system-command.ts +196 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +205 -0
- package/src/cli/v2.ts +200 -0
- package/src/cli.ts +10 -0
- package/src/clients/config-export.ts +1109 -0
- package/src/codex/account-id.ts +34 -0
- package/src/codex/account-label.ts +34 -0
- package/src/codex/account-lifecycle.ts +172 -0
- package/src/codex/account-namespace-match.ts +63 -0
- package/src/codex/account-namespaces.ts +195 -0
- package/src/codex/account-pause.ts +20 -0
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/account-runtime-state.ts +31 -0
- package/src/codex/account-store.ts +517 -0
- package/src/codex/account-usability.ts +40 -0
- package/src/codex/admission.ts +263 -0
- package/src/codex/app-server-processes.ts +799 -0
- package/src/codex/auth-api.ts +2098 -0
- package/src/codex/auth-collision.ts +107 -0
- package/src/codex/auth-context.ts +480 -0
- package/src/codex/autostart-health.ts +156 -0
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +471 -0
- package/src/codex/catalog/bundled.ts +533 -0
- package/src/codex/catalog/effort.ts +432 -0
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +287 -0
- package/src/codex/catalog/native-models.ts +7 -0
- package/src/codex/catalog/parsing.ts +503 -0
- package/src/codex/catalog/provider-fetch.ts +2267 -0
- package/src/codex/catalog/sync.ts +1606 -0
- package/src/codex/catalog-admission.ts +197 -0
- package/src/codex/catalog-refresh-status.ts +87 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +15 -0
- package/src/codex/codex-write-lock.ts +384 -0
- package/src/codex/convergence-types.ts +593 -0
- package/src/codex/convergence.ts +580 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/data/upstream-models.json +830 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/desktop-client-processes.ts +521 -0
- package/src/codex/exec-invocation.ts +22 -0
- package/src/codex/features.ts +1091 -0
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +347 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-migration-guardian.ts +115 -0
- package/src/codex/history-provider.ts +1075 -0
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +204 -0
- package/src/codex/home.ts +206 -0
- package/src/codex/inject-coordination.ts +257 -0
- package/src/codex/inject.ts +1857 -0
- package/src/codex/injected-marker.ts +79 -0
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +105 -0
- package/src/codex/journal.ts +172 -0
- package/src/codex/main-account-cache.ts +56 -0
- package/src/codex/main-account.ts +40 -0
- package/src/codex/management-convergence.ts +114 -0
- package/src/codex/model-cache.ts +267 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +167 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +348 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +691 -0
- package/src/codex/paths.ts +78 -0
- package/src/codex/plugins-doctor.ts +242 -0
- package/src/codex/pool-rotation.ts +295 -0
- package/src/codex/project-config-warnings.ts +426 -0
- package/src/codex/prompt-journal.ts +311 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/provider-adoption.ts +242 -0
- package/src/codex/quota-rejection.ts +224 -0
- package/src/codex/quota.ts +494 -0
- package/src/codex/refresh.ts +60 -0
- package/src/codex/routing.ts +1855 -0
- package/src/codex/runtime.ts +659 -0
- package/src/codex/shim.ts +1215 -0
- package/src/codex/subagent-defaults.ts +557 -0
- package/src/codex/subagent-model-fallback.ts +560 -0
- package/src/codex/sync.ts +238 -0
- package/src/codex/transition-state.ts +612 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +374 -0
- package/src/codex/warmup.ts +192 -0
- package/src/codex/websocket-registry.ts +100 -0
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +140 -0
- package/src/combos/index.ts +44 -0
- package/src/combos/request.ts +64 -0
- package/src/combos/resolve.ts +232 -0
- package/src/combos/types.ts +392 -0
- package/src/config.ts +3270 -0
- package/src/generated/model-metadata.ts +144 -0
- package/src/github/star-state.ts +203 -0
- package/src/grok/inject.ts +540 -0
- package/src/grok/inspect.ts +45 -0
- package/src/grok/status.ts +127 -0
- package/src/grok/sync.ts +66 -0
- package/src/images/artifacts.ts +516 -0
- package/src/images/fulfill-video.ts +163 -0
- package/src/images/fulfill.ts +149 -0
- package/src/images/index.ts +4 -0
- package/src/images/loop.ts +922 -0
- package/src/images/plan.ts +133 -0
- package/src/images/synthetic-tool.ts +133 -0
- package/src/images/types.ts +41 -0
- package/src/images/xai-client.ts +141 -0
- package/src/images/xai-video-client.ts +163 -0
- package/src/index.ts +22 -0
- package/src/integrations/config-io.ts +151 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +108 -0
- package/src/integrations/serialize.ts +235 -0
- package/src/integrations/state.ts +290 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer.ts +492 -0
- package/src/lib/abort.ts +146 -0
- package/src/lib/admin-secrets.ts +25 -0
- package/src/lib/admission.ts +83 -0
- package/src/lib/app-owned-memory-stores.ts +173 -0
- package/src/lib/app-owned-memory.ts +265 -0
- package/src/lib/bounded-body.ts +242 -0
- package/src/lib/bun-binary-validator.d.mts +3 -0
- package/src/lib/bun-binary-validator.mjs +18 -0
- package/src/lib/bun-runtime.ts +184 -0
- package/src/lib/bun-stream-caps.ts +127 -0
- package/src/lib/config-ownership.ts +438 -0
- package/src/lib/crash-guard.ts +344 -0
- package/src/lib/debug-log-buffer.ts +83 -0
- package/src/lib/debug-settings.ts +108 -0
- package/src/lib/debug.ts +31 -0
- package/src/lib/destination-policy.ts +316 -0
- package/src/lib/errors.ts +364 -0
- package/src/lib/eventstream-decoder.ts +253 -0
- package/src/lib/gcp-adc.ts +341 -0
- package/src/lib/injection-debug-log.ts +58 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/open-url.ts +25 -0
- package/src/lib/pinned-http.ts +182 -0
- package/src/lib/privacy.ts +20 -0
- package/src/lib/process-control.ts +168 -0
- package/src/lib/provider-environment.ts +470 -0
- package/src/lib/provider-outbound.ts +203 -0
- package/src/lib/provider-url.ts +14 -0
- package/src/lib/proxy-env.ts +18 -0
- package/src/lib/redact.ts +510 -0
- package/src/lib/remodex-home.ts +616 -0
- package/src/lib/retry-after.ts +55 -0
- package/src/lib/service-secrets.ts +178 -0
- package/src/lib/shadow-call.ts +54 -0
- package/src/lib/sidecar-tracker.ts +52 -0
- package/src/lib/sse-decoder.ts +364 -0
- package/src/lib/state-store-registrations.ts +109 -0
- package/src/lib/state-store-sweeper.ts +184 -0
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/test-home-guard.ts +98 -0
- package/src/lib/token-estimate.ts +69 -0
- package/src/lib/translator-budget.ts +366 -0
- package/src/lib/upstream-reachability.ts +91 -0
- package/src/lib/upstream-retry.ts +508 -0
- package/src/lib/win-exec.ts +115 -0
- package/src/lib/win-paths.ts +68 -0
- package/src/lib/windows-elevation.ts +705 -0
- package/src/lib/windows-secret-acl.ts +817 -0
- package/src/lib/windows-user-principal.ts +283 -0
- package/src/lib/winsw.ts +402 -0
- package/src/model-sources.ts +73 -0
- package/src/oauth/anthropic-routing.ts +594 -0
- package/src/oauth/anthropic.ts +177 -0
- package/src/oauth/callback-server.ts +294 -0
- package/src/oauth/chatgpt.ts +150 -0
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +231 -0
- package/src/oauth/github-copilot.ts +428 -0
- package/src/oauth/google-antigravity.ts +230 -0
- package/src/oauth/health.ts +443 -0
- package/src/oauth/index.ts +1280 -0
- package/src/oauth/key-providers.ts +128 -0
- package/src/oauth/kimi.ts +213 -0
- package/src/oauth/kiro-credentials.ts +726 -0
- package/src/oauth/kiro.ts +621 -0
- package/src/oauth/local-token-detect.ts +121 -0
- package/src/oauth/log.ts +48 -0
- package/src/oauth/login-cli.ts +163 -0
- package/src/oauth/pkce.ts +15 -0
- package/src/oauth/store.ts +655 -0
- package/src/oauth/token-guardian.ts +309 -0
- package/src/oauth/types.ts +62 -0
- package/src/oauth/xai.ts +241 -0
- package/src/providers/alibaba-region-backup.ts +75 -0
- package/src/providers/alibaba-region-migration.ts +156 -0
- package/src/providers/alibaba-region-startup.ts +36 -0
- package/src/providers/antigravity-models.ts +317 -0
- package/src/providers/api-keys.ts +140 -0
- package/src/providers/base-url-choices.ts +64 -0
- package/src/providers/codex-capacity.ts +288 -0
- package/src/providers/command-code-efforts.ts +85 -0
- package/src/providers/context-cap.ts +73 -0
- package/src/providers/derive.ts +451 -0
- package/src/providers/free-directory.ts +187 -0
- package/src/providers/github-copilot-transport.ts +56 -0
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +271 -0
- package/src/providers/kiro-models.ts +67 -0
- package/src/providers/label.ts +19 -0
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +361 -0
- package/src/providers/openai-sidecar.ts +235 -0
- package/src/providers/openai-tier-startup.ts +27 -0
- package/src/providers/openai-tiers.ts +301 -0
- package/src/providers/openai-virtual-models.ts +83 -0
- package/src/providers/openrouter-routing.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +179 -0
- package/src/providers/quota.ts +1942 -0
- package/src/providers/reasoning-capabilities.ts +336 -0
- package/src/providers/registry.ts +2375 -0
- package/src/providers/slug-codec.ts +74 -0
- package/src/providers/xai-transport.ts +149 -0
- package/src/reasoning-effort.ts +243 -0
- package/src/responses/compaction.ts +124 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/parser.ts +714 -0
- package/src/responses/reasoning-envelope.ts +60 -0
- package/src/responses/reasoning-replay-cache.ts +106 -0
- package/src/responses/schema.ts +159 -0
- package/src/responses/spill-store.ts +431 -0
- package/src/responses/state.ts +1039 -0
- package/src/responses/tool-groups.ts +19 -0
- package/src/router.ts +802 -0
- package/src/routing/analytics.ts +377 -0
- package/src/routing/capability.ts +205 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +444 -0
- package/src/routing/health.ts +401 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +424 -0
- package/src/routing/quota.ts +145 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +686 -0
- package/src/server/adapter-resolve.ts +83 -0
- package/src/server/auth-cors.ts +606 -0
- package/src/server/chat-completions.ts +379 -0
- package/src/server/claude-messages.ts +980 -0
- package/src/server/effort-policy.ts +251 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +152 -0
- package/src/server/image-retry.ts +42 -0
- package/src/server/images.ts +485 -0
- package/src/server/index.ts +1633 -0
- package/src/server/lifecycle.ts +482 -0
- package/src/server/live.ts +609 -0
- package/src/server/management/agent-settings-routes.ts +1180 -0
- package/src/server/management/android-remote-routes.ts +390 -0
- package/src/server/management/api-access.ts +141 -0
- package/src/server/management/api-key-usage.ts +167 -0
- package/src/server/management/body.ts +35 -0
- package/src/server/management/combo-routes.ts +244 -0
- package/src/server/management/config-routes.ts +602 -0
- package/src/server/management/context.ts +88 -0
- package/src/server/management/integration-routes.ts +538 -0
- package/src/server/management/logs-usage-routes.ts +516 -0
- package/src/server/management/model-routes.ts +519 -0
- package/src/server/management/model-rows.ts +143 -0
- package/src/server/management/native-integration-routes.ts +781 -0
- package/src/server/management/oauth-account-routes.ts +573 -0
- package/src/server/management/provider-routes.ts +781 -0
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +384 -0
- package/src/server/management/shared.ts +277 -0
- package/src/server/management/sidebar-routes.ts +106 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +433 -0
- package/src/server/management/system-routes.ts +141 -0
- package/src/server/management/usage-summary-cache.ts +86 -0
- package/src/server/management-api.ts +269 -0
- package/src/server/management-auth.ts +353 -0
- package/src/server/memory-watchdog.ts +156 -0
- package/src/server/port-reclaim.ts +307 -0
- package/src/server/ports.ts +156 -0
- package/src/server/proxy-liveness.ts +326 -0
- package/src/server/proxy-stop.ts +92 -0
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +353 -0
- package/src/server/relay.ts +1179 -0
- package/src/server/request-decompress.ts +132 -0
- package/src/server/request-log-conversation.ts +168 -0
- package/src/server/request-log.ts +1072 -0
- package/src/server/responses/collaboration.ts +409 -0
- package/src/server/responses/compact.ts +710 -0
- package/src/server/responses/core.ts +3561 -0
- package/src/server/responses/encrypted-payload.ts +308 -0
- package/src/server/responses/fetch-helpers.ts +171 -0
- package/src/server/responses/passthrough-error.ts +78 -0
- package/src/server/responses/policy-fallback.ts +152 -0
- package/src/server/responses/terminal-guard.ts +230 -0
- package/src/server/responses/upstream-error.ts +48 -0
- package/src/server/responses-image-gen-repair.ts +132 -0
- package/src/server/responses-item-id-repair.ts +272 -0
- package/src/server/responses-json-events.ts +52 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses.ts +10 -0
- package/src/server/search.ts +181 -0
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +263 -0
- package/src/server/startup-action-control.ts +308 -0
- package/src/server/startup-health-cache.ts +119 -0
- package/src/server/system-env.ts +418 -0
- package/src/server/windows-tcp-drop.ts +184 -0
- package/src/server/windows-tray-control.ts +41 -0
- package/src/server/ws-bridge.ts +470 -0
- package/src/service-manager-probe.ts +824 -0
- package/src/service.ts +3011 -0
- package/src/stall-timeout.ts +20 -0
- package/src/storage/cleanup-job.ts +57 -0
- package/src/storage/cleanup.ts +3085 -0
- package/src/storage/policy-job.ts +457 -0
- package/src/storage/policy-scheduler.ts +40 -0
- package/src/storage/policy-worker.ts +59 -0
- package/src/storage/policy.ts +527 -0
- package/src/storage/restore-job.ts +299 -0
- package/src/storage/restore-worker.ts +58 -0
- package/src/storage/scanner.ts +238 -0
- package/src/storage/storage-mutation-coordinator.ts +139 -0
- package/src/storage/worker-lifecycle.ts +215 -0
- package/src/tray/assets/opencodex-tray-offline.ico +0 -0
- package/src/tray/assets/opencodex-tray-online.ico +0 -0
- package/src/tray/assets/opencodex-tray-warning.ico +0 -0
- package/src/tray/assets/opencodex-tray.png +0 -0
- package/src/tray/windows-tray.ps1 +364 -0
- package/src/tray/windows.ts +738 -0
- package/src/types.ts +1531 -0
- package/src/update/badge.ts +72 -0
- package/src/update/desktop-release.ts +1620 -0
- package/src/update/index.ts +402 -0
- package/src/update/job.ts +1906 -0
- package/src/update/notify.ts +261 -0
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/npm-invocation.d.mts +23 -0
- package/src/update/npm-invocation.mjs +94 -0
- package/src/update/tray-update-plan.d.mts +18 -0
- package/src/update/tray-update-plan.mjs +38 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/debug.ts +97 -0
- package/src/usage/expected-prices.ts +283 -0
- package/src/usage/log.ts +695 -0
- package/src/usage/summary.ts +585 -0
- package/src/usage/totals.ts +14 -0
- package/src/vision/anthropic-describe.ts +185 -0
- package/src/vision/describe.ts +127 -0
- package/src/vision/index.ts +558 -0
- package/src/vision/reasoning.ts +55 -0
- package/src/web-search/anthropic-executor.ts +189 -0
- package/src/web-search/executor.ts +105 -0
- package/src/web-search/format-result.ts +89 -0
- package/src/web-search/index.ts +196 -0
- package/src/web-search/loop.ts +791 -0
- package/src/web-search/parse.ts +235 -0
- package/src/web-search/progress-stream.ts +342 -0
- package/src/web-search/synthetic-tool.ts +47 -0
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
import type { OcxUsage } from "../../types";
|
|
2
|
+
import type { AgentServerMessage, McpArgs, ToolCall } from "./gen/agent_pb";
|
|
3
|
+
import { decodeCursorArgsMap } from "./arg-codec";
|
|
4
|
+
import { normalizeArgKeys } from "./arg-normalize";
|
|
5
|
+
import {
|
|
6
|
+
CODEX_APPLY_PATCH_TOOL,
|
|
7
|
+
CURSOR_MULTI_EDIT_TOOL,
|
|
8
|
+
cursorShellBridgeArgsValid,
|
|
9
|
+
cursorShellBridgeDropError,
|
|
10
|
+
defaultShellBridgeArgNormalizeSchema,
|
|
11
|
+
isCodexShellBridgeToolName,
|
|
12
|
+
isCursorStructuredEditToolName,
|
|
13
|
+
normalizeCursorWireName,
|
|
14
|
+
OCX_RESPONSES_TOOL_PROVIDER,
|
|
15
|
+
resolveShellBridgeAliasKey,
|
|
16
|
+
responsesToolNameFromCursorWire,
|
|
17
|
+
} from "./tool-definitions";
|
|
18
|
+
import type { CursorServerMessage } from "./types";
|
|
19
|
+
import type { TranslatorBudget } from "../../lib/translator-budget";
|
|
20
|
+
|
|
21
|
+
const DEFAULT_CONTEXT_USAGE_MAX_ENTRIES = 200;
|
|
22
|
+
const DEFAULT_CONTEXT_USAGE_TTL_MS = 60 * 60 * 1_000;
|
|
23
|
+
|
|
24
|
+
export interface CursorContextUsageControls {
|
|
25
|
+
/**
|
|
26
|
+
* Last observed absolute context size for the same Cursor conversation and uncompacted context
|
|
27
|
+
* epoch. Used only when the current turn produces output but no checkpoint.
|
|
28
|
+
*/
|
|
29
|
+
carryForwardTokens?: number;
|
|
30
|
+
/** Persist a fresh checkpoint for later turns in this conversation. */
|
|
31
|
+
recordContextTokens?: (tokens: number) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CursorContextUsageTracker {
|
|
35
|
+
controlsForConversation(conversationId: string, options?: { clearPrior?: boolean; storeCheckpoints?: boolean }): CursorContextUsageControls;
|
|
36
|
+
get(conversationId: string): number | undefined;
|
|
37
|
+
record(conversationId: string, tokens: number): void;
|
|
38
|
+
/** Copy numeric carry-forward totals when a conversation id is rotated for replay. */
|
|
39
|
+
rekey(fromConversationId: string, toConversationId: string): void;
|
|
40
|
+
clear(conversationId: string): void;
|
|
41
|
+
clearAll(): void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface CursorContextUsageEntry {
|
|
45
|
+
tokens: number;
|
|
46
|
+
updatedAt: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Bounded numeric-only carry-forward for Cursor context usage. Cursor checkpoints are absolute
|
|
51
|
+
* active-context sizes, but client-tool suspension can end a turn before a checkpoint arrives. Keep
|
|
52
|
+
* only the last known total per provider conversation so a no-checkpoint finalize does not overwrite
|
|
53
|
+
* a real active-context value with the current turn's tiny output delta. Context text, tool args,
|
|
54
|
+
* and model output are never stored here.
|
|
55
|
+
*/
|
|
56
|
+
export function createCursorContextUsageTracker(options: { maxEntries?: number; ttlMs?: number; now?: () => number } = {}): CursorContextUsageTracker {
|
|
57
|
+
const maxEntries = options.maxEntries ?? DEFAULT_CONTEXT_USAGE_MAX_ENTRIES;
|
|
58
|
+
const ttlMs = options.ttlMs ?? DEFAULT_CONTEXT_USAGE_TTL_MS;
|
|
59
|
+
const now = options.now ?? (() => Date.now());
|
|
60
|
+
const entries = new Map<string, CursorContextUsageEntry>();
|
|
61
|
+
|
|
62
|
+
const prune = () => {
|
|
63
|
+
const at = now();
|
|
64
|
+
for (const [conversationId, entry] of entries) {
|
|
65
|
+
if (at - entry.updatedAt > ttlMs) entries.delete(conversationId);
|
|
66
|
+
}
|
|
67
|
+
while (entries.size > maxEntries) {
|
|
68
|
+
const oldest = entries.keys().next().value;
|
|
69
|
+
if (!oldest) break;
|
|
70
|
+
entries.delete(oldest);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const record = (conversationId: string, tokens: number) => {
|
|
75
|
+
if (!Number.isFinite(tokens) || tokens <= 0) return;
|
|
76
|
+
prune();
|
|
77
|
+
const existing = entries.get(conversationId);
|
|
78
|
+
if (existing && existing.tokens >= tokens) {
|
|
79
|
+
entries.delete(conversationId);
|
|
80
|
+
entries.set(conversationId, { tokens: existing.tokens, updatedAt: now() });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
entries.delete(conversationId);
|
|
84
|
+
entries.set(conversationId, { tokens, updatedAt: now() });
|
|
85
|
+
prune();
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const get = (conversationId: string): number | undefined => {
|
|
89
|
+
prune();
|
|
90
|
+
const entry = entries.get(conversationId);
|
|
91
|
+
if (!entry) return undefined;
|
|
92
|
+
entries.delete(conversationId);
|
|
93
|
+
entries.set(conversationId, { tokens: entry.tokens, updatedAt: now() });
|
|
94
|
+
return entry.tokens;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
controlsForConversation(conversationId, requestOptions = {}) {
|
|
99
|
+
if (requestOptions.clearPrior === true) entries.delete(conversationId);
|
|
100
|
+
const storeCheckpoints = requestOptions.storeCheckpoints !== false;
|
|
101
|
+
const carryForwardTokens = storeCheckpoints ? get(conversationId) : undefined;
|
|
102
|
+
return {
|
|
103
|
+
...(carryForwardTokens !== undefined ? { carryForwardTokens } : {}),
|
|
104
|
+
...(storeCheckpoints ? { recordContextTokens: tokens => record(conversationId, tokens) } : {}),
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
get,
|
|
108
|
+
record,
|
|
109
|
+
rekey(fromConversationId, toConversationId) {
|
|
110
|
+
if (!fromConversationId || !toConversationId || fromConversationId === toConversationId) return;
|
|
111
|
+
prune();
|
|
112
|
+
const from = entries.get(fromConversationId);
|
|
113
|
+
if (!from) return;
|
|
114
|
+
const to = entries.get(toConversationId);
|
|
115
|
+
const tokens = Math.max(from.tokens, to?.tokens ?? 0);
|
|
116
|
+
entries.delete(fromConversationId);
|
|
117
|
+
entries.delete(toConversationId);
|
|
118
|
+
entries.set(toConversationId, { tokens, updatedAt: now() });
|
|
119
|
+
prune();
|
|
120
|
+
},
|
|
121
|
+
clear(conversationId) {
|
|
122
|
+
entries.delete(conversationId);
|
|
123
|
+
},
|
|
124
|
+
clearAll() {
|
|
125
|
+
entries.clear();
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface CursorProtobufEventState {
|
|
131
|
+
usage: OcxUsage;
|
|
132
|
+
/**
|
|
133
|
+
* Absolute conversation context size from Cursor's `conversationCheckpointUpdate.usedTokens`
|
|
134
|
+
* (authoritative cumulative context, NOT a per-turn delta). Kept separate from `usage.outputTokens`
|
|
135
|
+
* so it is never folded into the additive per-turn output count. Surfaced as `done.usage.totalTokens`
|
|
136
|
+
* so Codex's `last_token_usage.total_tokens` reflects the real active context. Mirrors the Kiro
|
|
137
|
+
* contextUsagePercentage SOT fix (devlog 142.10): absolute context and additive output must not
|
|
138
|
+
* share one field, or Codex double-counts (e.g. 10000 then 10300 surfacing as 20300).
|
|
139
|
+
*/
|
|
140
|
+
contextTokens?: number;
|
|
141
|
+
/**
|
|
142
|
+
* Request-local input estimate derived from the payload actually sent to Cursor.
|
|
143
|
+
* Used only when neither a checkpoint nor a carry-forward is available — a restart
|
|
144
|
+
* clears the tracker, and reporting inputTokens=0 makes Codex see an almost-empty
|
|
145
|
+
* context (#373). Never written back into the tracker: only real checkpoints are.
|
|
146
|
+
*/
|
|
147
|
+
estimatedInputTokens?: number;
|
|
148
|
+
/**
|
|
149
|
+
* Session-level last-known absolute context size for this Cursor conversation. This is a fallback
|
|
150
|
+
* for no-checkpoint client-tool finalize turns only; any checkpoint observed during the current
|
|
151
|
+
* turn remains authoritative, with monotonic max semantics unless a compaction boundary reset the
|
|
152
|
+
* conversation cache before the turn.
|
|
153
|
+
*/
|
|
154
|
+
contextCarryForwardTokens?: number;
|
|
155
|
+
recordContextTokens?: (tokens: number) => void;
|
|
156
|
+
openToolCalls: Map<string, { name: string; args: string }>;
|
|
157
|
+
completedToolCalls: Set<string>;
|
|
158
|
+
/** Set once a terminal `done`/truncation has been emitted, so post-terminal frames stay inert. */
|
|
159
|
+
terminated?: boolean;
|
|
160
|
+
clientToolNames?: Set<string>;
|
|
161
|
+
parallelToolCalls?: boolean;
|
|
162
|
+
startedClientToolCalls: number;
|
|
163
|
+
/** Tool wire-name → original JSON Schema parameters object, for arg-key normalization. */
|
|
164
|
+
toolSchemas?: Map<string, unknown>;
|
|
165
|
+
/** Cursor wire-name → original Responses/Codex tool name for this request. */
|
|
166
|
+
cursorToolNameMap?: Map<string, string>;
|
|
167
|
+
/**
|
|
168
|
+
* Bare names WE advertised as synthetic structured-edit tools on this request.
|
|
169
|
+
* See structuredEditCallIsOurs: conversion is gated on provenance, not on the name.
|
|
170
|
+
*/
|
|
171
|
+
syntheticStructuredEditToolNames?: ReadonlySet<string>;
|
|
172
|
+
translatorBudget?: TranslatorBudget;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Did WE advertise this bare tool name as a synthetic structured-edit tool on this request?
|
|
178
|
+
*
|
|
179
|
+
* Provenance, not a name test. `edit_file` / `multi_edit` are ordinary names a client or MCP
|
|
180
|
+
* server may legitimately expose, and `cursorStructuredEditTools` already refuses to shadow one
|
|
181
|
+
* that exists. Converting on the name alone would undo that refusal at the other end of the
|
|
182
|
+
* request: the client's own call would be silently re-emitted as `apply_patch`, or dropped with
|
|
183
|
+
* an error naming a conversion the user never asked for.
|
|
184
|
+
*
|
|
185
|
+
* Absent set = we advertised nothing, so nothing converts. Fail-closed in the safe direction:
|
|
186
|
+
* an unconverted structured call is a visible, recoverable failure; a wrongly converted one
|
|
187
|
+
* edits a file.
|
|
188
|
+
*/
|
|
189
|
+
function structuredEditCallIsOurs(
|
|
190
|
+
advertised: ReadonlySet<string> | undefined,
|
|
191
|
+
toolName: string,
|
|
192
|
+
): boolean {
|
|
193
|
+
return advertised?.has(toolName) === true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function createCursorProtobufEventState(options: {
|
|
197
|
+
clientToolNames?: Iterable<string>;
|
|
198
|
+
parallelToolCalls?: boolean;
|
|
199
|
+
toolSchemas?: Map<string, unknown>;
|
|
200
|
+
cursorToolNameMap?: Map<string, string>;
|
|
201
|
+
syntheticStructuredEditToolNames?: Iterable<string>;
|
|
202
|
+
contextUsage?: CursorContextUsageControls;
|
|
203
|
+
/**
|
|
204
|
+
* Request-local input estimate derived from the payload actually sent. Used only
|
|
205
|
+
* when neither a checkpoint nor a carry-forward is available; never recorded into
|
|
206
|
+
* the checkpoint tracker (#373).
|
|
207
|
+
*/
|
|
208
|
+
estimatedInputTokens?: number;
|
|
209
|
+
translatorBudget?: TranslatorBudget;
|
|
210
|
+
} = {}): CursorProtobufEventState {
|
|
211
|
+
return {
|
|
212
|
+
// Cursor provides no authoritative usage frame; token counts are heuristic estimates from
|
|
213
|
+
// checkpoint/delta events, so mark estimated from the start.
|
|
214
|
+
usage: { inputTokens: 0, outputTokens: 0, estimated: true },
|
|
215
|
+
openToolCalls: new Map(),
|
|
216
|
+
completedToolCalls: new Set(),
|
|
217
|
+
...(options.clientToolNames ? { clientToolNames: new Set(options.clientToolNames) } : {}),
|
|
218
|
+
...(options.syntheticStructuredEditToolNames
|
|
219
|
+
? { syntheticStructuredEditToolNames: new Set(options.syntheticStructuredEditToolNames) }
|
|
220
|
+
: {}),
|
|
221
|
+
...(options.parallelToolCalls !== undefined ? { parallelToolCalls: options.parallelToolCalls } : {}),
|
|
222
|
+
startedClientToolCalls: 0,
|
|
223
|
+
...(options.toolSchemas ? { toolSchemas: options.toolSchemas } : {}),
|
|
224
|
+
...(options.cursorToolNameMap ? { cursorToolNameMap: options.cursorToolNameMap } : {}),
|
|
225
|
+
...(options.translatorBudget ? { translatorBudget: options.translatorBudget } : {}),
|
|
226
|
+
...(options.contextUsage?.carryForwardTokens !== undefined ? { contextCarryForwardTokens: options.contextUsage.carryForwardTokens } : {}),
|
|
227
|
+
...(options.contextUsage?.recordContextTokens ? { recordContextTokens: options.contextUsage.recordContextTokens } : {}),
|
|
228
|
+
...(typeof options.estimatedInputTokens === "number"
|
|
229
|
+
&& Number.isFinite(options.estimatedInputTokens)
|
|
230
|
+
&& options.estimatedInputTokens > 0
|
|
231
|
+
? { estimatedInputTokens: options.estimatedInputTokens }
|
|
232
|
+
: {}),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function observeContextTokens(state: CursorProtobufEventState, usedTokens: number): void {
|
|
237
|
+
if (!Number.isFinite(usedTokens) || usedTokens <= 0) return;
|
|
238
|
+
if (usedTokens > (state.contextTokens ?? 0)) state.contextTokens = usedTokens;
|
|
239
|
+
state.recordContextTokens?.(usedTokens);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function reportableContextTokens(state: CursorProtobufEventState): number | undefined {
|
|
243
|
+
const current = state.contextTokens;
|
|
244
|
+
const carry = state.contextCarryForwardTokens;
|
|
245
|
+
if (current === undefined) return carry;
|
|
246
|
+
if (carry === undefined) return current;
|
|
247
|
+
return Math.max(current, carry);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function usageFromContextTokens(state: CursorProtobufEventState, contextTokens: number): OcxUsage {
|
|
251
|
+
return {
|
|
252
|
+
...state.usage,
|
|
253
|
+
inputTokens: Math.max(0, contextTokens - state.usage.outputTokens),
|
|
254
|
+
totalTokens: contextTokens,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Exported for live-transport's client-tool frame classification (finalize revocation). */
|
|
259
|
+
export function mcpArgsFromToolCall(toolCall: ToolCall | undefined): McpArgs | undefined {
|
|
260
|
+
if (toolCall?.tool.case !== "mcpToolCall") return undefined;
|
|
261
|
+
const args = toolCall.tool.value.args;
|
|
262
|
+
return args?.providerIdentifier === OCX_RESPONSES_TOOL_PROVIDER ? args : undefined;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function mcpWireNameFromArgs(args: McpArgs | undefined): string | undefined {
|
|
266
|
+
const raw = args?.toolName || args?.name;
|
|
267
|
+
// Models may call the Cursor-displayed `mcp_<provider>_<tool>` name; fold it to the advertised name.
|
|
268
|
+
return raw && raw.length > 0 ? normalizeCursorWireName(raw) : undefined;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function mcpCursorWireName(toolCall: ToolCall | undefined): string | undefined {
|
|
272
|
+
return mcpWireNameFromArgs(mcpArgsFromToolCall(toolCall));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function decodeMcpArgs(args: McpArgs | undefined): string {
|
|
276
|
+
return JSON.stringify(decodeCursorArgsMap(args?.args));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Resolve an advertised client-tool wire name, including shell_command/exec_command aliases (#399). */
|
|
280
|
+
function resolveAdvertisedClientToolName(
|
|
281
|
+
state: CursorProtobufEventState,
|
|
282
|
+
cursorWireName: string,
|
|
283
|
+
): string | undefined {
|
|
284
|
+
const normalized = normalizeCursorWireName(cursorWireName);
|
|
285
|
+
if (!state.clientToolNames) return normalized;
|
|
286
|
+
return resolveShellBridgeAliasKey(normalized, alias => (state.clientToolNames!.has(alias) ? alias : undefined));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function toolSchemaForWireName(state: CursorProtobufEventState, toolName: string | undefined): unknown | undefined {
|
|
290
|
+
if (!toolName || !state.toolSchemas) return undefined;
|
|
291
|
+
return resolveShellBridgeAliasKey(toolName, alias => state.toolSchemas!.get(alias));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function decodeMcpArgsNormalized(args: McpArgs | undefined, state: CursorProtobufEventState): string {
|
|
295
|
+
const decoded = decodeCursorArgsMap(args?.args);
|
|
296
|
+
const toolName = mcpWireNameFromArgs(args);
|
|
297
|
+
const schema = toolSchemaForWireName(state, toolName);
|
|
298
|
+
if (schema) return JSON.stringify(normalizeArgKeys(decoded, schema));
|
|
299
|
+
return JSON.stringify(decoded);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function hasMcpArgBytes(args: McpArgs | undefined): boolean {
|
|
303
|
+
return Object.keys(args?.args ?? {}).length > 0;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function isCompleteJson(text: string): boolean {
|
|
307
|
+
if (text.length === 0) return false;
|
|
308
|
+
try {
|
|
309
|
+
JSON.parse(text);
|
|
310
|
+
return true;
|
|
311
|
+
} catch {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Schema-normalize a JSON-text argument blob for a named tool, if a schema is known. */
|
|
317
|
+
function normalizeJsonText(text: string, toolName: string | undefined, state: CursorProtobufEventState): string {
|
|
318
|
+
const schema = toolSchemaForWireName(state, toolName);
|
|
319
|
+
if (!schema) return text;
|
|
320
|
+
try {
|
|
321
|
+
const parsed = JSON.parse(text);
|
|
322
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
323
|
+
return JSON.stringify(normalizeArgKeys(parsed as Record<string, unknown>, schema));
|
|
324
|
+
}
|
|
325
|
+
} catch {
|
|
326
|
+
// Not parseable as an object: leave as-is.
|
|
327
|
+
}
|
|
328
|
+
return text;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Resolve the authoritative argument string for a completed client tool call.
|
|
333
|
+
*
|
|
334
|
+
* Cursor sends args two ways: incrementally as `argsTextDelta` (buffered into `open.args`, never
|
|
335
|
+
* streamed onward), and/or as a structured protobuf map on `toolCallCompleted`. We emit the args
|
|
336
|
+
* exactly once, at completion, so they can always be schema-normalized regardless of which form
|
|
337
|
+
* arrived. The completed map wins when present (canonical); otherwise the buffered streamed text is
|
|
338
|
+
* used. Returns an empty string when there are no args (the bridge serializes that as `{}`).
|
|
339
|
+
*/
|
|
340
|
+
function resolveCompletedArgs(buffered: string, args: McpArgs | undefined, state: CursorProtobufEventState): string {
|
|
341
|
+
if (hasMcpArgBytes(args)) return decodeMcpArgsNormalized(args, state);
|
|
342
|
+
const name = mcpWireNameFromArgs(args);
|
|
343
|
+
if (isCompleteJson(buffered)) return normalizeJsonText(buffered, name, state);
|
|
344
|
+
return "";
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const PATCH_BEGIN = "*** Begin Patch";
|
|
348
|
+
const PATCH_END = "*** End Patch";
|
|
349
|
+
|
|
350
|
+
function firstStringArg(args: Record<string, unknown>, keys: readonly string[]): string | undefined {
|
|
351
|
+
for (const key of keys) {
|
|
352
|
+
const value = args[key];
|
|
353
|
+
if (typeof value === "string") return value;
|
|
354
|
+
}
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** Split a replacement into patch lines, ignoring one trailing newline (line-based patch semantics). */
|
|
359
|
+
function patchLines(text: string): string[] {
|
|
360
|
+
const lines = text.split("\n");
|
|
361
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
362
|
+
return lines;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** One `@@` hunk replacing `oldString` with `newString`. */
|
|
366
|
+
function replacementHunk(oldString: string, newString: string): { hunk: string } | { error: string } {
|
|
367
|
+
if (oldString.length === 0) {
|
|
368
|
+
return {
|
|
369
|
+
error:
|
|
370
|
+
"structured edit requires a non-empty old_string to locate the replacement; for new files or insertions without existing text, call apply_patch with an `*** Add File` / context hunk or use the shell bridge",
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
const oldLines = patchLines(oldString);
|
|
374
|
+
const newLines = patchLines(newString);
|
|
375
|
+
// Line-based patch semantics cannot express an edit that only adds or removes the file's
|
|
376
|
+
// final newline, and an old/new pair that normalizes to the same lines is a silent no-op —
|
|
377
|
+
// reject it rather than emitting an empty hunk that apply_patch would drop.
|
|
378
|
+
if (oldLines.length === 0 && newLines.length === 0) {
|
|
379
|
+
return { error: "structured edit requires a non-empty old_string; an empty replacement is not a valid edit" };
|
|
380
|
+
}
|
|
381
|
+
if (oldLines.length === newLines.length && oldLines.every((line, i) => line === newLines[i])) {
|
|
382
|
+
return { error: "structured edit old_string and new_string are identical after line normalization; the replacement is a no-op and was dropped" };
|
|
383
|
+
}
|
|
384
|
+
const removed = oldLines.map(line => `-${line}`);
|
|
385
|
+
const added = newLines.map(line => `+${line}`);
|
|
386
|
+
return { hunk: ["@@", ...removed, ...added].join("\n") };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Convert a completed Cursor structured edit call (`edit_file` / `multi_edit`) into a valid Codex
|
|
391
|
+
* apply_patch freeform payload (#1017). Cursor-trained models cannot emit Codex's freeform patch
|
|
392
|
+
* grammar, so the adapter advertises exact-match replacement tools and performs the grammar here.
|
|
393
|
+
* Returns `{ patch }` for a valid conversion, `{ error }` for a malformed call (which must never be
|
|
394
|
+
* relayed verbatim: Codex would reject it locally after the HTTP 200, the reported failure mode),
|
|
395
|
+
* and `undefined` for tools that are not structured edits.
|
|
396
|
+
*/
|
|
397
|
+
export type StructuredEditTranslation =
|
|
398
|
+
| { patch: string; error?: undefined }
|
|
399
|
+
| { error: string; patch?: undefined };
|
|
400
|
+
|
|
401
|
+
export function translateStructuredEditCall(
|
|
402
|
+
toolName: string,
|
|
403
|
+
argsText: string,
|
|
404
|
+
): StructuredEditTranslation | undefined {
|
|
405
|
+
if (!isCursorStructuredEditToolName(toolName)) return undefined;
|
|
406
|
+
let parsed: unknown;
|
|
407
|
+
try {
|
|
408
|
+
parsed = JSON.parse(argsText);
|
|
409
|
+
} catch {
|
|
410
|
+
return {
|
|
411
|
+
error: `${toolName} arguments were not valid JSON; the call was dropped. ${
|
|
412
|
+
toolName === CURSOR_MULTI_EDIT_TOOL
|
|
413
|
+
? "Use file_path and edits[] (each edit with old_string and new_string)."
|
|
414
|
+
: "Use file_path, old_string and new_string."
|
|
415
|
+
}`,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
419
|
+
return { error: `${toolName} arguments must be a JSON object; the call was dropped.` };
|
|
420
|
+
}
|
|
421
|
+
const args = parsed as Record<string, unknown>;
|
|
422
|
+
const path = firstStringArg(args, ["file_path", "filePath", "path", "filepath", "filename"]);
|
|
423
|
+
if (!path || path.trim().length === 0) {
|
|
424
|
+
return { error: `${toolName} is missing a non-empty file_path; the call was dropped.` };
|
|
425
|
+
}
|
|
426
|
+
const hunks: string[] = [];
|
|
427
|
+
const addReplacement = (record: Record<string, unknown>): StructuredEditTranslation => {
|
|
428
|
+
const oldString = firstStringArg(record, ["old_string", "oldString", "oldtext", "old_text"]);
|
|
429
|
+
const newString = firstStringArg(record, ["new_string", "newString", "newtext", "new_text"]);
|
|
430
|
+
if (oldString === undefined || newString === undefined) {
|
|
431
|
+
return { error: `${toolName} requires old_string and new_string; the call was dropped.` };
|
|
432
|
+
}
|
|
433
|
+
const hunk = replacementHunk(oldString, newString);
|
|
434
|
+
if ("error" in hunk) return { error: hunk.error };
|
|
435
|
+
return { patch: hunk.hunk as string };
|
|
436
|
+
};
|
|
437
|
+
if (toolName === CURSOR_MULTI_EDIT_TOOL) {
|
|
438
|
+
const edits = args.edits;
|
|
439
|
+
if (!Array.isArray(edits) || edits.length === 0) {
|
|
440
|
+
return { error: "multi_edit requires a non-empty edits array; the call was dropped." };
|
|
441
|
+
}
|
|
442
|
+
for (const edit of edits) {
|
|
443
|
+
if (!edit || typeof edit !== "object" || Array.isArray(edit)) {
|
|
444
|
+
return { error: "multi_edit edits entries must be objects with old_string and new_string; the call was dropped." };
|
|
445
|
+
}
|
|
446
|
+
const editResult = addReplacement(edit as Record<string, unknown>);
|
|
447
|
+
if (editResult.error !== undefined) return editResult;
|
|
448
|
+
hunks.push(editResult.patch);
|
|
449
|
+
}
|
|
450
|
+
} else {
|
|
451
|
+
const editResult = addReplacement(args);
|
|
452
|
+
if (editResult.error !== undefined) return editResult;
|
|
453
|
+
hunks.push(editResult.patch);
|
|
454
|
+
}
|
|
455
|
+
return { patch: [PATCH_BEGIN, `*** Update File: ${path}`, ...hunks, PATCH_END].join("\n") };
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export function mapSyntheticMcpExecToToolEvents(
|
|
459
|
+
args: McpArgs,
|
|
460
|
+
fallbackCallId = "cursor_mcp_exec",
|
|
461
|
+
options: { allowEmptyArgs?: boolean; state?: CursorProtobufEventState } = {},
|
|
462
|
+
): CursorServerMessage[] {
|
|
463
|
+
if (args.providerIdentifier !== OCX_RESPONSES_TOOL_PROVIDER) return [];
|
|
464
|
+
if (options.allowEmptyArgs !== true && !hasMcpArgBytes(args)) return [];
|
|
465
|
+
const cursorWireName = mcpWireNameFromArgs(args);
|
|
466
|
+
if (!cursorWireName) return [{ type: "error", message: "Cursor requested a Responses tool without a tool name" }];
|
|
467
|
+
const callId = args.toolCallId || fallbackCallId;
|
|
468
|
+
if (options.state?.completedToolCalls.has(callId)) return [];
|
|
469
|
+
if (options.state) {
|
|
470
|
+
// Native-exec delivers the whole client tool call at once. Record it (no-op if already opened by
|
|
471
|
+
// an earlier started/partial event), then emit the atomic start -> delta -> end unit.
|
|
472
|
+
const out: CursorServerMessage[] = [...recordToolCall(options.state, callId, cursorWireName)];
|
|
473
|
+
if (out.some(event => event.type === "error")) return out;
|
|
474
|
+
const open = options.state.openToolCalls.get(callId);
|
|
475
|
+
const finalArgs = resolveCompletedArgs(open?.args ?? "", args, options.state);
|
|
476
|
+
out.push(...commitToolCall(options.state, callId, finalArgs));
|
|
477
|
+
return out;
|
|
478
|
+
}
|
|
479
|
+
const responsesName = responsesToolNameFromCursorWire(cursorWireName);
|
|
480
|
+
const normSchema = defaultShellBridgeArgNormalizeSchema(responsesName);
|
|
481
|
+
const normalizedArgs = JSON.stringify(normalizeArgKeys(decodeCursorArgsMap(args?.args), normSchema));
|
|
482
|
+
if (!cursorShellBridgeArgsValid(normalizedArgs, responsesName, normSchema)) {
|
|
483
|
+
if (isCodexShellBridgeToolName(responsesName)) {
|
|
484
|
+
return [{ type: "error", message: cursorShellBridgeDropError(responsesName) }];
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
// Stateless fallback (no shared event state): emit a complete, self-contained tool call.
|
|
488
|
+
//
|
|
489
|
+
// No conversion happens here by design (#1036 review). Structured-edit translation is gated on
|
|
490
|
+
// provenance — did WE advertise this bare name on THIS request — and that record lives on the
|
|
491
|
+
// request state, which this branch does not have. Converting anyway would reinstate the exact
|
|
492
|
+
// hazard the gate exists to close: a client or MCP tool legitimately named `edit_file` would be
|
|
493
|
+
// rewritten into an apply_patch it never asked for. The live path always carries state
|
|
494
|
+
// (live-transport seeds it), so this only affects direct/unit callers.
|
|
495
|
+
const emittedName = responsesName;
|
|
496
|
+
const emittedArgs = normalizedArgs;
|
|
497
|
+
return [
|
|
498
|
+
{ type: "tool_call_start", id: callId, name: emittedName },
|
|
499
|
+
...(emittedArgs.length > 2
|
|
500
|
+
? [{ type: "tool_call_delta" as const, arguments: emittedArgs }]
|
|
501
|
+
: []),
|
|
502
|
+
{ type: "tool_call_end", id: callId },
|
|
503
|
+
];
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Record (open) a client tool call WITHOUT emitting `tool_call_start`. The outward start is deferred
|
|
508
|
+
* to completion (see commitToolCall) so each Cursor tool call surfaces to the bridge as one atomic,
|
|
509
|
+
* self-contained start -> delta -> end unit. This lets Cursor open several tool calls in parallel
|
|
510
|
+
* (or interleave their partial-arg streams) without cross-wiring: nothing reaches the single-current-
|
|
511
|
+
* call bridge until a call completes, and completed calls are emitted whole, one after another.
|
|
512
|
+
* Returns an error only for a genuinely unknown (un-advertised) tool name.
|
|
513
|
+
*/
|
|
514
|
+
function recordToolCall(state: CursorProtobufEventState, callId: string, cursorWireName: string): CursorServerMessage[] {
|
|
515
|
+
if (state.completedToolCalls.has(callId)) return [];
|
|
516
|
+
if (state.openToolCalls.has(callId)) return [];
|
|
517
|
+
const advertisedName = resolveAdvertisedClientToolName(state, cursorWireName);
|
|
518
|
+
if (state.clientToolNames && !advertisedName) {
|
|
519
|
+
return [{ type: "error", message: `Cursor requested unknown Responses tool: ${cursorWireName}` }];
|
|
520
|
+
}
|
|
521
|
+
// Prefer the advertised catalog name for Responses mapping so shell_command/exec_command aliases
|
|
522
|
+
// land on the tool Codex actually exposed this turn (#399).
|
|
523
|
+
const mapKey = advertisedName ?? normalizeCursorWireName(cursorWireName);
|
|
524
|
+
state.openToolCalls.set(callId, { name: responsesToolNameFromCursorWire(mapKey, state.cursorToolNameMap), args: "" });
|
|
525
|
+
state.translatorBudget?.openCall(callId);
|
|
526
|
+
state.startedClientToolCalls++;
|
|
527
|
+
return [];
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Emit a completed client tool call as one atomic unit: `tool_call_start` (deferred from open time),
|
|
532
|
+
* the full normalized arguments delta when present, then `tool_call_end`. The call must already be
|
|
533
|
+
* recorded in `openToolCalls`. Because each completion emits a whole non-interleaved unit, the bridge
|
|
534
|
+
* (which tracks a single current tool call) serializes parallel Cursor calls correctly.
|
|
535
|
+
*/
|
|
536
|
+
function dropShellBridgeCall(state: CursorProtobufEventState, callId: string, toolName: string): CursorServerMessage[] {
|
|
537
|
+
state.openToolCalls.delete(callId);
|
|
538
|
+
state.translatorBudget?.closeCall(callId);
|
|
539
|
+
state.completedToolCalls.add(callId);
|
|
540
|
+
return [{ type: "error", message: cursorShellBridgeDropError(toolName) }];
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function dropStructuredEditCall(state: CursorProtobufEventState, callId: string, toolName: string, reason: string): CursorServerMessage[] {
|
|
544
|
+
state.openToolCalls.delete(callId);
|
|
545
|
+
state.translatorBudget?.closeCall(callId);
|
|
546
|
+
state.completedToolCalls.add(callId);
|
|
547
|
+
return [{ type: "error", message: `${toolName} call was not converted to apply_patch: ${reason}` }];
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function commitToolCall(state: CursorProtobufEventState, callId: string, finalArgs: string): CursorServerMessage[] {
|
|
551
|
+
const open = state.openToolCalls.get(callId);
|
|
552
|
+
if (!open) return [];
|
|
553
|
+
const schema = toolSchemaForWireName(state, open.name);
|
|
554
|
+
if (!cursorShellBridgeArgsValid(finalArgs, open.name, schema)) {
|
|
555
|
+
if (isCodexShellBridgeToolName(open.name)) return dropShellBridgeCall(state, callId, open.name);
|
|
556
|
+
}
|
|
557
|
+
// Structured edit calls are converted to apply_patch here so both the interactionUpdate and the
|
|
558
|
+
// native-exec mcpArgs paths emit the same valid freeform payload (#1017).
|
|
559
|
+
const translation = structuredEditCallIsOurs(state.syntheticStructuredEditToolNames, open.name)
|
|
560
|
+
? translateStructuredEditCall(open.name, finalArgs)
|
|
561
|
+
: undefined;
|
|
562
|
+
if (translation?.error !== undefined) {
|
|
563
|
+
return dropStructuredEditCall(state, callId, open.name, translation.error);
|
|
564
|
+
}
|
|
565
|
+
if (finalArgs !== open.args) {
|
|
566
|
+
const previousBytes = Buffer.byteLength(open.args);
|
|
567
|
+
const reservation = state.translatorBudget?.reserveTransient(
|
|
568
|
+
Buffer.byteLength(finalArgs),
|
|
569
|
+
{ kind: "tool_args", callId },
|
|
570
|
+
);
|
|
571
|
+
open.args = finalArgs;
|
|
572
|
+
reservation?.commitRetained();
|
|
573
|
+
state.translatorBudget?.releaseRetained(previousBytes, { kind: "tool_args", callId });
|
|
574
|
+
}
|
|
575
|
+
const emittedName = translation ? CODEX_APPLY_PATCH_TOOL : open.name;
|
|
576
|
+
const emittedArgs = translation ? JSON.stringify({ input: translation.patch }) : finalArgs;
|
|
577
|
+
const out: CursorServerMessage[] = [{ type: "tool_call_start", id: callId, name: emittedName }];
|
|
578
|
+
if (emittedArgs.length > 0) out.push({ type: "tool_call_delta", arguments: emittedArgs });
|
|
579
|
+
out.push(...endToolCall(state, callId));
|
|
580
|
+
return out;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Buffer Cursor's cumulative `argsTextDelta` into the open call WITHOUT emitting a delta. Args are
|
|
585
|
+
* emitted once, normalized, at completion (see resolveCompletedArgs), so a mis-keyed or
|
|
586
|
+
* non-canonical streamed blob can still be repaired before Codex sees it. `argsTextDelta` is
|
|
587
|
+
* cumulative; keep the longest value seen.
|
|
588
|
+
*/
|
|
589
|
+
function bufferToolArgs(state: CursorProtobufEventState, callId: string, cumulative: string): void {
|
|
590
|
+
const open = state.openToolCalls.get(callId);
|
|
591
|
+
if (!open) return;
|
|
592
|
+
if (cumulative.length >= open.args.length) {
|
|
593
|
+
const encoder = new TextEncoder();
|
|
594
|
+
const previousBytes = encoder.encode(open.args).byteLength;
|
|
595
|
+
const reservation = state.translatorBudget?.reserveTransient(
|
|
596
|
+
encoder.encode(cumulative).byteLength,
|
|
597
|
+
{ kind: "tool_args", callId },
|
|
598
|
+
);
|
|
599
|
+
open.args = cumulative;
|
|
600
|
+
reservation?.commitRetained();
|
|
601
|
+
state.translatorBudget?.releaseRetained(previousBytes, { kind: "tool_args", callId });
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function endToolCall(state: CursorProtobufEventState, callId: string): CursorServerMessage[] {
|
|
606
|
+
if (!state.openToolCalls.has(callId)) return [];
|
|
607
|
+
state.openToolCalls.delete(callId);
|
|
608
|
+
state.translatorBudget?.closeCall(callId);
|
|
609
|
+
state.completedToolCalls.add(callId);
|
|
610
|
+
return [{ type: "tool_call_end", id: callId }];
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export function mapCursorProtobufServerMessage(
|
|
614
|
+
serverMessage: AgentServerMessage,
|
|
615
|
+
state: CursorProtobufEventState,
|
|
616
|
+
): CursorServerMessage[] {
|
|
617
|
+
if (state.terminated) return [];
|
|
618
|
+
|
|
619
|
+
if (serverMessage.message.case === "conversationCheckpointUpdate") {
|
|
620
|
+
const usedTokens = serverMessage.message.value.tokenDetails?.usedTokens ?? 0;
|
|
621
|
+
// `usedTokens` is the ABSOLUTE conversation context size, not a per-turn output delta. Track it
|
|
622
|
+
// separately (monotonic max) and surface it as `done.usage.totalTokens`; folding it into
|
|
623
|
+
// `outputTokens` (which also accumulates `tokenDelta`) double-counts in Codex. See contextTokens.
|
|
624
|
+
observeContextTokens(state, usedTokens);
|
|
625
|
+
return [];
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (serverMessage.message.case !== "interactionUpdate") return [];
|
|
629
|
+
const update = serverMessage.message.value.message;
|
|
630
|
+
switch (update.case) {
|
|
631
|
+
case "textDelta":
|
|
632
|
+
return update.value.text ? [{ type: "text", text: update.value.text }] : [];
|
|
633
|
+
case "thinkingDelta":
|
|
634
|
+
return update.value.text ? [{ type: "thinking", thinking: update.value.text }] : [];
|
|
635
|
+
case "toolCallStarted": {
|
|
636
|
+
const name = mcpCursorWireName(update.value.toolCall);
|
|
637
|
+
// Record the open call but defer the outward tool_call_start to completion (atomic emission).
|
|
638
|
+
return name ? recordToolCall(state, update.value.callId, name) : [];
|
|
639
|
+
}
|
|
640
|
+
case "partialToolCall": {
|
|
641
|
+
const out: CursorServerMessage[] = [];
|
|
642
|
+
const name = mcpCursorWireName(update.value.toolCall);
|
|
643
|
+
if (name) out.push(...recordToolCall(state, update.value.callId, name));
|
|
644
|
+
if (out.some(event => event.type === "error")) return out;
|
|
645
|
+
// Buffer cumulative args; do not emit a delta. Args are emitted once, normalized, at completion.
|
|
646
|
+
if (state.openToolCalls.has(update.value.callId)) {
|
|
647
|
+
bufferToolArgs(state, update.value.callId, update.value.argsTextDelta);
|
|
648
|
+
}
|
|
649
|
+
return out;
|
|
650
|
+
}
|
|
651
|
+
case "toolCallDelta":
|
|
652
|
+
// Cursor's typed deltas currently cover native exec internals (shell/task/edit). Client
|
|
653
|
+
// Responses tools return as McpToolCall plus partial args text, so native deltas stay internal.
|
|
654
|
+
return [];
|
|
655
|
+
case "toolCallCompleted": {
|
|
656
|
+
const out: CursorServerMessage[] = [];
|
|
657
|
+
if (state.completedToolCalls.has(update.value.callId)) return [];
|
|
658
|
+
const name = mcpCursorWireName(update.value.toolCall);
|
|
659
|
+
const args = mcpArgsFromToolCall(update.value.toolCall);
|
|
660
|
+
const openBeforeStart = state.openToolCalls.get(update.value.callId);
|
|
661
|
+
// Empty-arg completion handling:
|
|
662
|
+
// - already open with empty args -> wait for the native-exec args path (do not commit yet).
|
|
663
|
+
// - never started + not advertised -> Cursor prelude noise, drop it.
|
|
664
|
+
// - advertised client tool, not yet open -> a legitimate no-arg call: commit it (start+end)
|
|
665
|
+
// so it is not silently dropped; the bridge serializes empty args as "{}".
|
|
666
|
+
if (name && !hasMcpArgBytes(args)) {
|
|
667
|
+
if (openBeforeStart && openBeforeStart.args.length === 0) return [];
|
|
668
|
+
// Only commit a no-arg call when the tool is *explicitly* advertised. Without an advertised
|
|
669
|
+
// tool list we cannot tell a real no-arg call from a Cursor prelude, so we keep dropping it.
|
|
670
|
+
const advertised = state.clientToolNames?.has(name) ?? false;
|
|
671
|
+
if (!openBeforeStart && !advertised) return [];
|
|
672
|
+
}
|
|
673
|
+
// Ensure the call is recorded (covers a completion with no prior started/partial event), then
|
|
674
|
+
// emit it as one atomic start -> delta -> end unit so parallel Cursor calls serialize cleanly.
|
|
675
|
+
if (name) out.push(...recordToolCall(state, update.value.callId, name));
|
|
676
|
+
if (out.some(event => event.type === "error")) return out;
|
|
677
|
+
const open = state.openToolCalls.get(update.value.callId);
|
|
678
|
+
if (open) {
|
|
679
|
+
const finalArgs = resolveCompletedArgs(open.args, args, state);
|
|
680
|
+
out.push(...commitToolCall(state, update.value.callId, finalArgs));
|
|
681
|
+
}
|
|
682
|
+
return out;
|
|
683
|
+
}
|
|
684
|
+
case "tokenDelta":
|
|
685
|
+
state.usage.outputTokens += update.value.tokens;
|
|
686
|
+
return [];
|
|
687
|
+
case "turnEnded":
|
|
688
|
+
return finalizeTurnEvents(state);
|
|
689
|
+
default:
|
|
690
|
+
return [];
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Resolve the usage to report for a turn, in order of trustworthiness: a checkpoint
|
|
696
|
+
* observed this turn, then the session carry-forward, then the request-local estimate,
|
|
697
|
+
* then the raw per-turn counters. Shared with the partial-usage path in live-transport
|
|
698
|
+
* so a failed turn reports the same input side as a clean one (#373).
|
|
699
|
+
*/
|
|
700
|
+
export function resolvedTurnUsage(state: CursorProtobufEventState): OcxUsage {
|
|
701
|
+
const contextTokens = reportableContextTokens(state);
|
|
702
|
+
if (contextTokens !== undefined) return usageFromContextTokens(state, contextTokens);
|
|
703
|
+
const estimate = state.estimatedInputTokens;
|
|
704
|
+
if (estimate !== undefined) {
|
|
705
|
+
return {
|
|
706
|
+
...state.usage,
|
|
707
|
+
inputTokens: estimate,
|
|
708
|
+
totalTokens: estimate + state.usage.outputTokens,
|
|
709
|
+
estimated: true,
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
return { ...state.usage };
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Finalize a Cursor turn. If any client tool call is still open (started but never completed),
|
|
717
|
+
* the stream was truncated and the partial tool call must not reach Codex as a completed call
|
|
718
|
+
* with corrupt/empty arguments. Emit an explicit error instead of done (fail-closed).
|
|
719
|
+
* Mirrors kiro-truncation.ts behavior.
|
|
720
|
+
*/
|
|
721
|
+
export function finalizeTurnEvents(state: CursorProtobufEventState): CursorServerMessage[] {
|
|
722
|
+
state.terminated = true;
|
|
723
|
+
if (state.openToolCalls.size > 0) {
|
|
724
|
+
const openIds = [...state.openToolCalls.keys()].join(", ");
|
|
725
|
+
// Clear so a second turnEnded (should not happen, but defensive) doesn't re-emit.
|
|
726
|
+
state.openToolCalls.clear();
|
|
727
|
+
return [{ type: "error", message: `Cursor stream ended with incomplete tool call(s): ${openIds}. Arguments may be truncated; the call was not committed.` }];
|
|
728
|
+
}
|
|
729
|
+
// Surface the absolute context size (when Cursor reported a checkpoint) as both totalTokens and
|
|
730
|
+
// the estimated input side of Codex's visible `input + output` counter. Codex status lines can
|
|
731
|
+
// render the additive pair instead of total_tokens, so leaving inputTokens at 0 makes a 16k-context
|
|
732
|
+
// first turn display as "9 used". Keep outputTokens as the per-turn delta and clamp the inferred
|
|
733
|
+
// input to 0 in case Cursor reports a checkpoint smaller than the streamed output delta.
|
|
734
|
+
return [{ type: "done", usage: resolvedTurnUsage(state) }];
|
|
735
|
+
}
|