@iislee/opencodex 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (476) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +225 -0
  3. package/assets/architecture.png +0 -0
  4. package/assets/banner.png +0 -0
  5. package/assets/claude-code-models.gif +0 -0
  6. package/assets/codex-app-picker.png +0 -0
  7. package/bin/ocx.mjs +451 -0
  8. package/bin/package-main.mjs +9 -0
  9. package/gui/dist/assets/index-DTpMHS4F.js +67 -0
  10. package/gui/dist/assets/index-ZNVDE3C7.css +1 -0
  11. package/gui/dist/favicon.png +0 -0
  12. package/gui/dist/icons.svg +24 -0
  13. package/gui/dist/index.html +25 -0
  14. package/gui/dist/logo.png +0 -0
  15. package/gui/dist/provider-icons/alibaba-color.svg +1 -0
  16. package/gui/dist/provider-icons/antigravity-color.svg +1 -0
  17. package/gui/dist/provider-icons/antigravity.svg +1 -0
  18. package/gui/dist/provider-icons/claude-color.svg +1 -0
  19. package/gui/dist/provider-icons/claude.svg +1 -0
  20. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
  21. package/gui/dist/provider-icons/copilot-color.svg +1 -0
  22. package/gui/dist/provider-icons/copilot.svg +1 -0
  23. package/gui/dist/provider-icons/cursor-color.svg +2 -0
  24. package/gui/dist/provider-icons/cursor.svg +2 -0
  25. package/gui/dist/provider-icons/deepseek-color.svg +1 -0
  26. package/gui/dist/provider-icons/discord.svg +1 -0
  27. package/gui/dist/provider-icons/firepass-color.svg +1 -0
  28. package/gui/dist/provider-icons/fireworks-color.svg +1 -0
  29. package/gui/dist/provider-icons/gemini-color.svg +1 -0
  30. package/gui/dist/provider-icons/gemini.svg +1 -0
  31. package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
  32. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
  33. package/gui/dist/provider-icons/grok-color.svg +1 -0
  34. package/gui/dist/provider-icons/grok.svg +1 -0
  35. package/gui/dist/provider-icons/groq-color.svg +1 -0
  36. package/gui/dist/provider-icons/huggingface-color.svg +1 -0
  37. package/gui/dist/provider-icons/kimi-color.svg +1 -0
  38. package/gui/dist/provider-icons/kiro-color.svg +15 -0
  39. package/gui/dist/provider-icons/kiro.svg +14 -0
  40. package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
  41. package/gui/dist/provider-icons/mistral-color.svg +1 -0
  42. package/gui/dist/provider-icons/moonshot-color.svg +1 -0
  43. package/gui/dist/provider-icons/nvidia-color.svg +1 -0
  44. package/gui/dist/provider-icons/ollama-color.svg +1 -0
  45. package/gui/dist/provider-icons/openai.svg +1 -0
  46. package/gui/dist/provider-icons/opencode.svg +1 -0
  47. package/gui/dist/provider-icons/openrouter-color.svg +1 -0
  48. package/gui/dist/provider-icons/pi.svg +21 -0
  49. package/gui/dist/provider-icons/qianfan-color.svg +1 -0
  50. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
  51. package/gui/dist/provider-icons/telegram.svg +1 -0
  52. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
  53. package/gui/dist/provider-icons/vllm-color.svg +1 -0
  54. package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
  55. package/package.json +102 -0
  56. package/src/AGENTS.md +28 -0
  57. package/src/adapters/anthropic-image-guard.ts +251 -0
  58. package/src/adapters/anthropic-image-normalize.ts +518 -0
  59. package/src/adapters/anthropic.ts +1003 -0
  60. package/src/adapters/azure.ts +36 -0
  61. package/src/adapters/base.ts +72 -0
  62. package/src/adapters/client-fingerprint.ts +59 -0
  63. package/src/adapters/cursor/arg-codec.ts +38 -0
  64. package/src/adapters/cursor/arg-normalize.ts +104 -0
  65. package/src/adapters/cursor/cursor-errors.ts +165 -0
  66. package/src/adapters/cursor/discovery.ts +276 -0
  67. package/src/adapters/cursor/effort-map.ts +127 -0
  68. package/src/adapters/cursor/exec-policy.ts +88 -0
  69. package/src/adapters/cursor/framing.ts +211 -0
  70. package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
  71. package/src/adapters/cursor/kv-store.ts +52 -0
  72. package/src/adapters/cursor/live-models.ts +153 -0
  73. package/src/adapters/cursor/live-smoke-gate.ts +41 -0
  74. package/src/adapters/cursor/live-transport.ts +1214 -0
  75. package/src/adapters/cursor/mcp-config.ts +42 -0
  76. package/src/adapters/cursor/mcp-manager.ts +333 -0
  77. package/src/adapters/cursor/message-mapper.ts +49 -0
  78. package/src/adapters/cursor/native-exec-common.ts +55 -0
  79. package/src/adapters/cursor/native-exec-desktop.ts +184 -0
  80. package/src/adapters/cursor/native-exec-fs.ts +329 -0
  81. package/src/adapters/cursor/native-exec-mcp.ts +153 -0
  82. package/src/adapters/cursor/native-exec-network.ts +43 -0
  83. package/src/adapters/cursor/native-exec-shell.ts +548 -0
  84. package/src/adapters/cursor/native-exec-tools.ts +118 -0
  85. package/src/adapters/cursor/native-exec.ts +576 -0
  86. package/src/adapters/cursor/protobuf-events.ts +563 -0
  87. package/src/adapters/cursor/protobuf-request.ts +714 -0
  88. package/src/adapters/cursor/request-builder.ts +255 -0
  89. package/src/adapters/cursor/thread-continuity.ts +67 -0
  90. package/src/adapters/cursor/tool-definitions.ts +505 -0
  91. package/src/adapters/cursor/transport-retry.ts +132 -0
  92. package/src/adapters/cursor/transport.ts +57 -0
  93. package/src/adapters/cursor/types.ts +52 -0
  94. package/src/adapters/cursor.ts +196 -0
  95. package/src/adapters/google-antigravity-replay.ts +303 -0
  96. package/src/adapters/google-antigravity-wire.ts +108 -0
  97. package/src/adapters/google-errors.ts +85 -0
  98. package/src/adapters/google-http.ts +100 -0
  99. package/src/adapters/google-tool-schema.ts +173 -0
  100. package/src/adapters/google-truncation.ts +13 -0
  101. package/src/adapters/google-wire-compiler.ts +232 -0
  102. package/src/adapters/google.ts +758 -0
  103. package/src/adapters/identity.ts +44 -0
  104. package/src/adapters/image.ts +23 -0
  105. package/src/adapters/kiro-constants.ts +16 -0
  106. package/src/adapters/kiro-errors.ts +197 -0
  107. package/src/adapters/kiro-events.ts +179 -0
  108. package/src/adapters/kiro-images.ts +129 -0
  109. package/src/adapters/kiro-retry.ts +312 -0
  110. package/src/adapters/kiro-thinking.ts +96 -0
  111. package/src/adapters/kiro-tool-fallback.ts +36 -0
  112. package/src/adapters/kiro-tools.ts +215 -0
  113. package/src/adapters/kiro-truncation.ts +33 -0
  114. package/src/adapters/kiro-wire.ts +129 -0
  115. package/src/adapters/kiro.ts +1898 -0
  116. package/src/adapters/mimo-free.ts +263 -0
  117. package/src/adapters/openai-chat.ts +1005 -0
  118. package/src/adapters/openai-responses.ts +1137 -0
  119. package/src/adapters/run-turn-queue.ts +114 -0
  120. package/src/adapters/tool-catalog-nudge.ts +71 -0
  121. package/src/adapters/upstream-http-error.ts +48 -0
  122. package/src/bridge.ts +1619 -0
  123. package/src/chat/inbound.ts +295 -0
  124. package/src/chat/outbound.ts +765 -0
  125. package/src/claude/agents-inject.ts +243 -0
  126. package/src/claude/alias.ts +149 -0
  127. package/src/claude/auth-detect.ts +229 -0
  128. package/src/claude/auth-mode-migration.ts +32 -0
  129. package/src/claude/auth-mode.ts +62 -0
  130. package/src/claude/context-windows.ts +189 -0
  131. package/src/claude/desktop-3p-guard.ts +35 -0
  132. package/src/claude/desktop-3p-paths.ts +84 -0
  133. package/src/claude/desktop-3p.ts +381 -0
  134. package/src/claude/desktop-health.ts +26 -0
  135. package/src/claude/desktop-profile.ts +263 -0
  136. package/src/claude/gateway-cache.ts +70 -0
  137. package/src/claude/inbound-debug.ts +163 -0
  138. package/src/claude/inbound.ts +509 -0
  139. package/src/claude/model-info.ts +151 -0
  140. package/src/claude/outbound.ts +872 -0
  141. package/src/cli/access.ts +108 -0
  142. package/src/cli/account-api.ts +268 -0
  143. package/src/cli/account-auth.ts +223 -0
  144. package/src/cli/account-extended.ts +350 -0
  145. package/src/cli/account.ts +275 -0
  146. package/src/cli/agent-driven.ts +70 -0
  147. package/src/cli/agent.ts +184 -0
  148. package/src/cli/catalog-prewarm.ts +27 -0
  149. package/src/cli/claude-desktop.ts +188 -0
  150. package/src/cli/claude.ts +286 -0
  151. package/src/cli/codex-shim-autorestore.ts +45 -0
  152. package/src/cli/combo.ts +119 -0
  153. package/src/cli/config-command.ts +145 -0
  154. package/src/cli/debug.ts +228 -0
  155. package/src/cli/doctor.ts +930 -0
  156. package/src/cli/export-command.ts +187 -0
  157. package/src/cli/help.ts +354 -0
  158. package/src/cli/index.ts +1113 -0
  159. package/src/cli/init.ts +224 -0
  160. package/src/cli/integrations.ts +142 -0
  161. package/src/cli/interactive-confirm.ts +133 -0
  162. package/src/cli/internal-dispatch.ts +20 -0
  163. package/src/cli/models-runtime.ts +212 -0
  164. package/src/cli/models.ts +336 -0
  165. package/src/cli/observe.ts +117 -0
  166. package/src/cli/opencode.ts +586 -0
  167. package/src/cli/pi.ts +188 -0
  168. package/src/cli/provider-runtime.ts +162 -0
  169. package/src/cli/provider.ts +463 -0
  170. package/src/cli/runtime-api.ts +325 -0
  171. package/src/cli/star-prompt.ts +155 -0
  172. package/src/cli/status-oauth.ts +78 -0
  173. package/src/cli/status.ts +321 -0
  174. package/src/cli/sync-cloud.ts +283 -0
  175. package/src/cli/system-command.ts +112 -0
  176. package/src/cli/tray-proxy.ts +52 -0
  177. package/src/cli/v2.ts +173 -0
  178. package/src/cli.ts +10 -0
  179. package/src/clients/config-export.ts +377 -0
  180. package/src/clients/effective-status.ts +385 -0
  181. package/src/clients/probes/agy.ts +55 -0
  182. package/src/clients/probes/cc-switch.ts +110 -0
  183. package/src/clients/probes/claude.ts +90 -0
  184. package/src/clients/probes/codex.ts +125 -0
  185. package/src/clients/probes/grok.ts +29 -0
  186. package/src/clients/probes/opencode.ts +109 -0
  187. package/src/clients/probes/paseo.ts +55 -0
  188. package/src/clients/probes/pi.ts +55 -0
  189. package/src/cloud/onedrive-auth.ts +666 -0
  190. package/src/cloud/onedrive-graph.ts +108 -0
  191. package/src/cloud/settings.ts +75 -0
  192. package/src/cloud/sync.ts +212 -0
  193. package/src/cloud/types.ts +56 -0
  194. package/src/cloud/vault.ts +89 -0
  195. package/src/codex/account-id.ts +34 -0
  196. package/src/codex/account-label.ts +34 -0
  197. package/src/codex/account-lifecycle.ts +55 -0
  198. package/src/codex/account-namespace-match.ts +63 -0
  199. package/src/codex/account-namespaces.ts +149 -0
  200. package/src/codex/account-pause.ts +20 -0
  201. package/src/codex/account-runtime-state.ts +31 -0
  202. package/src/codex/account-store.ts +517 -0
  203. package/src/codex/account-usability.ts +20 -0
  204. package/src/codex/app-server-processes.ts +756 -0
  205. package/src/codex/auth-api.ts +1540 -0
  206. package/src/codex/auth-collision.ts +107 -0
  207. package/src/codex/auth-context.ts +352 -0
  208. package/src/codex/autostart-health.ts +149 -0
  209. package/src/codex/catalog/aggregation.ts +378 -0
  210. package/src/codex/catalog/bundled.ts +251 -0
  211. package/src/codex/catalog/effort.ts +355 -0
  212. package/src/codex/catalog/metadata.ts +180 -0
  213. package/src/codex/catalog/parsing.ts +456 -0
  214. package/src/codex/catalog/provider-fetch.ts +902 -0
  215. package/src/codex/catalog/sync.ts +620 -0
  216. package/src/codex/catalog.ts +12 -0
  217. package/src/codex/data/upstream-models.json +830 -0
  218. package/src/codex/exec-invocation.ts +22 -0
  219. package/src/codex/features.ts +969 -0
  220. package/src/codex/history-migration-guardian.ts +102 -0
  221. package/src/codex/history-provider.ts +776 -0
  222. package/src/codex/home.ts +206 -0
  223. package/src/codex/inject.ts +799 -0
  224. package/src/codex/injected-marker.ts +72 -0
  225. package/src/codex/journal.ts +163 -0
  226. package/src/codex/main-account-cache.ts +32 -0
  227. package/src/codex/main-account.ts +40 -0
  228. package/src/codex/model-cache.ts +227 -0
  229. package/src/codex/paths.ts +65 -0
  230. package/src/codex/plugins-doctor.ts +242 -0
  231. package/src/codex/pool-rotation.ts +225 -0
  232. package/src/codex/project-config-warnings.ts +411 -0
  233. package/src/codex/quota.ts +411 -0
  234. package/src/codex/refresh.ts +53 -0
  235. package/src/codex/routing.ts +1477 -0
  236. package/src/codex/runtime.ts +538 -0
  237. package/src/codex/shim.ts +1189 -0
  238. package/src/codex/subagent-defaults.ts +550 -0
  239. package/src/codex/subagent-model-fallback.ts +469 -0
  240. package/src/codex/sync.ts +130 -0
  241. package/src/codex/warmup.ts +192 -0
  242. package/src/codex/websocket-registry.ts +100 -0
  243. package/src/combos/failover.ts +140 -0
  244. package/src/combos/index.ts +41 -0
  245. package/src/combos/request.ts +62 -0
  246. package/src/combos/resolve.ts +232 -0
  247. package/src/combos/types.ts +326 -0
  248. package/src/config.ts +2356 -0
  249. package/src/generated/jawcode-model-metadata.ts +104 -0
  250. package/src/github/star-state.ts +203 -0
  251. package/src/grok/inject.ts +545 -0
  252. package/src/grok/status.ts +121 -0
  253. package/src/grok/sync.ts +103 -0
  254. package/src/grok/usage-hook/report.mjs +348 -0
  255. package/src/grok/usage-hook.ts +278 -0
  256. package/src/images/artifacts.ts +516 -0
  257. package/src/images/fulfill-video.ts +163 -0
  258. package/src/images/fulfill.ts +149 -0
  259. package/src/images/index.ts +4 -0
  260. package/src/images/loop.ts +829 -0
  261. package/src/images/plan.ts +133 -0
  262. package/src/images/synthetic-tool.ts +133 -0
  263. package/src/images/types.ts +41 -0
  264. package/src/images/xai-client.ts +141 -0
  265. package/src/images/xai-video-client.ts +163 -0
  266. package/src/index.ts +22 -0
  267. package/src/lib/abort.ts +146 -0
  268. package/src/lib/admin-secrets.ts +25 -0
  269. package/src/lib/admission.ts +83 -0
  270. package/src/lib/app-owned-memory-stores.ts +173 -0
  271. package/src/lib/app-owned-memory.ts +265 -0
  272. package/src/lib/bounded-body.ts +202 -0
  273. package/src/lib/bun-binary-validator.d.mts +3 -0
  274. package/src/lib/bun-binary-validator.mjs +18 -0
  275. package/src/lib/bun-runtime.ts +71 -0
  276. package/src/lib/bun-stream-caps.ts +126 -0
  277. package/src/lib/config-ownership.ts +360 -0
  278. package/src/lib/crash-guard.ts +344 -0
  279. package/src/lib/debug-log-buffer.ts +83 -0
  280. package/src/lib/debug-settings.ts +108 -0
  281. package/src/lib/debug.ts +31 -0
  282. package/src/lib/destination-policy.ts +316 -0
  283. package/src/lib/errors.ts +364 -0
  284. package/src/lib/eventstream-decoder.ts +253 -0
  285. package/src/lib/gcp-adc.ts +341 -0
  286. package/src/lib/injection-debug-log.ts +58 -0
  287. package/src/lib/open-url.ts +25 -0
  288. package/src/lib/pinned-http.ts +151 -0
  289. package/src/lib/privacy.ts +20 -0
  290. package/src/lib/process-control.ts +165 -0
  291. package/src/lib/provider-outbound.ts +170 -0
  292. package/src/lib/provider-url.ts +14 -0
  293. package/src/lib/proxy-env.ts +18 -0
  294. package/src/lib/redact.ts +105 -0
  295. package/src/lib/retry-after.ts +55 -0
  296. package/src/lib/service-secrets.ts +25 -0
  297. package/src/lib/shadow-call.ts +30 -0
  298. package/src/lib/sidecar-tracker.ts +52 -0
  299. package/src/lib/sse-decoder.ts +323 -0
  300. package/src/lib/state-store-registrations.ts +109 -0
  301. package/src/lib/state-store-sweeper.ts +184 -0
  302. package/src/lib/test-home-guard.ts +90 -0
  303. package/src/lib/token-estimate.ts +69 -0
  304. package/src/lib/translator-budget.ts +356 -0
  305. package/src/lib/upstream-retry.ts +239 -0
  306. package/src/lib/win-exec.ts +115 -0
  307. package/src/lib/win-paths.ts +68 -0
  308. package/src/lib/windows-elevation.ts +705 -0
  309. package/src/lib/windows-secret-acl.ts +514 -0
  310. package/src/lib/winsw.ts +375 -0
  311. package/src/oauth/anthropic-routing.ts +594 -0
  312. package/src/oauth/anthropic.ts +177 -0
  313. package/src/oauth/callback-server.ts +294 -0
  314. package/src/oauth/chatgpt.ts +150 -0
  315. package/src/oauth/cursor.ts +211 -0
  316. package/src/oauth/github-copilot.ts +428 -0
  317. package/src/oauth/google-antigravity.ts +230 -0
  318. package/src/oauth/health.ts +399 -0
  319. package/src/oauth/index.ts +1174 -0
  320. package/src/oauth/key-providers.ts +108 -0
  321. package/src/oauth/kimi.ts +213 -0
  322. package/src/oauth/kiro-credentials.ts +726 -0
  323. package/src/oauth/kiro.ts +577 -0
  324. package/src/oauth/local-token-detect.ts +121 -0
  325. package/src/oauth/log.ts +48 -0
  326. package/src/oauth/login-cli.ts +163 -0
  327. package/src/oauth/pkce.ts +15 -0
  328. package/src/oauth/store.ts +630 -0
  329. package/src/oauth/token-guardian.ts +303 -0
  330. package/src/oauth/types.ts +62 -0
  331. package/src/oauth/xai.ts +241 -0
  332. package/src/pi/extensions.ts +72 -0
  333. package/src/pi/home.ts +42 -0
  334. package/src/pi/index.ts +40 -0
  335. package/src/pi/models.ts +278 -0
  336. package/src/pi/packages.ts +219 -0
  337. package/src/pi/settings.ts +365 -0
  338. package/src/pi/status.ts +68 -0
  339. package/src/pi/sync.ts +75 -0
  340. package/src/providers/alibaba-region-backup.ts +75 -0
  341. package/src/providers/alibaba-region-migration.ts +156 -0
  342. package/src/providers/alibaba-region-startup.ts +36 -0
  343. package/src/providers/antigravity-models.ts +205 -0
  344. package/src/providers/api-keys.ts +140 -0
  345. package/src/providers/base-url-choices.ts +64 -0
  346. package/src/providers/context-cap.ts +65 -0
  347. package/src/providers/derive.ts +339 -0
  348. package/src/providers/free-directory.ts +184 -0
  349. package/src/providers/github-copilot-transport.ts +56 -0
  350. package/src/providers/key-failover.ts +203 -0
  351. package/src/providers/kiro-models.ts +67 -0
  352. package/src/providers/label.ts +19 -0
  353. package/src/providers/model-discovery.ts +356 -0
  354. package/src/providers/openai-sidecar.ts +175 -0
  355. package/src/providers/openai-tier-startup.ts +27 -0
  356. package/src/providers/openai-tiers.ts +301 -0
  357. package/src/providers/openai-virtual-models.ts +82 -0
  358. package/src/providers/openrouter-routing.ts +102 -0
  359. package/src/providers/provider-id-rewrite.ts +150 -0
  360. package/src/providers/quota.ts +1260 -0
  361. package/src/providers/registry.ts +1600 -0
  362. package/src/providers/slug-codec.ts +67 -0
  363. package/src/providers/xai-transport.ts +141 -0
  364. package/src/reasoning-effort.ts +135 -0
  365. package/src/responses/compaction.ts +117 -0
  366. package/src/responses/parser.ts +656 -0
  367. package/src/responses/reasoning-envelope.ts +52 -0
  368. package/src/responses/schema.ts +159 -0
  369. package/src/responses/spill-store.ts +394 -0
  370. package/src/responses/state.ts +895 -0
  371. package/src/responses/tool-groups.ts +19 -0
  372. package/src/router.ts +425 -0
  373. package/src/server/adapter-resolve.ts +80 -0
  374. package/src/server/auth-cors.ts +530 -0
  375. package/src/server/chat-completions.ts +368 -0
  376. package/src/server/claude-messages.ts +914 -0
  377. package/src/server/effort-policy.ts +172 -0
  378. package/src/server/gui-static.ts +123 -0
  379. package/src/server/image-retry.ts +42 -0
  380. package/src/server/images.ts +476 -0
  381. package/src/server/index.ts +1126 -0
  382. package/src/server/lifecycle.ts +227 -0
  383. package/src/server/live.ts +598 -0
  384. package/src/server/management/agent-settings-routes.ts +1169 -0
  385. package/src/server/management/api-access.ts +141 -0
  386. package/src/server/management/api-key-usage.ts +167 -0
  387. package/src/server/management/body.ts +35 -0
  388. package/src/server/management/clients-routes.ts +63 -0
  389. package/src/server/management/cloud-sync-routes.ts +266 -0
  390. package/src/server/management/combo-routes.ts +220 -0
  391. package/src/server/management/config-routes.ts +422 -0
  392. package/src/server/management/context.ts +31 -0
  393. package/src/server/management/logs-usage-routes.ts +707 -0
  394. package/src/server/management/model-routes.ts +525 -0
  395. package/src/server/management/oauth-account-routes.ts +563 -0
  396. package/src/server/management/provider-routes.ts +556 -0
  397. package/src/server/management/shared.ts +277 -0
  398. package/src/server/management/sidebar-routes.ts +90 -0
  399. package/src/server/management/system-restart.ts +179 -0
  400. package/src/server/management/system-routes.ts +117 -0
  401. package/src/server/management/usage-summary-cache.ts +86 -0
  402. package/src/server/management-api.ts +215 -0
  403. package/src/server/management-auth.ts +267 -0
  404. package/src/server/memory-watchdog.ts +156 -0
  405. package/src/server/port-reclaim.ts +307 -0
  406. package/src/server/ports.ts +116 -0
  407. package/src/server/proxy-liveness.ts +201 -0
  408. package/src/server/relay-eager.ts +313 -0
  409. package/src/server/relay.ts +1049 -0
  410. package/src/server/request-decompress.ts +132 -0
  411. package/src/server/request-log-conversation.ts +168 -0
  412. package/src/server/request-log.ts +1046 -0
  413. package/src/server/responses/collaboration.ts +354 -0
  414. package/src/server/responses/compact.ts +384 -0
  415. package/src/server/responses/core.ts +2758 -0
  416. package/src/server/responses/encrypted-payload.ts +308 -0
  417. package/src/server/responses/fetch-helpers.ts +157 -0
  418. package/src/server/responses/passthrough-error.ts +78 -0
  419. package/src/server/responses/terminal-guard.ts +230 -0
  420. package/src/server/responses/upstream-error.ts +48 -0
  421. package/src/server/responses-image-gen-repair.ts +132 -0
  422. package/src/server/responses-item-id-repair.ts +224 -0
  423. package/src/server/responses.ts +9 -0
  424. package/src/server/search.ts +136 -0
  425. package/src/server/sse-payload-rewrite.ts +175 -0
  426. package/src/server/startup-action-control.ts +308 -0
  427. package/src/server/startup-health-cache.ts +113 -0
  428. package/src/server/system-env.ts +413 -0
  429. package/src/server/windows-tcp-drop.ts +184 -0
  430. package/src/server/windows-tray-control.ts +41 -0
  431. package/src/server/ws-bridge.ts +471 -0
  432. package/src/service.ts +2554 -0
  433. package/src/stall-timeout.ts +20 -0
  434. package/src/storage/cleanup-job.ts +57 -0
  435. package/src/storage/cleanup.ts +3085 -0
  436. package/src/storage/policy-job.ts +457 -0
  437. package/src/storage/policy-scheduler.ts +40 -0
  438. package/src/storage/policy-worker.ts +59 -0
  439. package/src/storage/policy.ts +527 -0
  440. package/src/storage/restore-job.ts +299 -0
  441. package/src/storage/restore-worker.ts +58 -0
  442. package/src/storage/scanner.ts +238 -0
  443. package/src/storage/storage-mutation-coordinator.ts +139 -0
  444. package/src/storage/worker-lifecycle.ts +215 -0
  445. package/src/tray/assets/opencodex-tray-offline.ico +0 -0
  446. package/src/tray/assets/opencodex-tray-online.ico +0 -0
  447. package/src/tray/assets/opencodex-tray-warning.ico +0 -0
  448. package/src/tray/assets/opencodex-tray.png +0 -0
  449. package/src/tray/windows-tray.ps1 +290 -0
  450. package/src/tray/windows.ts +730 -0
  451. package/src/types.ts +1237 -0
  452. package/src/update/badge.ts +72 -0
  453. package/src/update/index.ts +407 -0
  454. package/src/update/job.ts +1520 -0
  455. package/src/update/notify.ts +257 -0
  456. package/src/update/npm-invocation.d.mts +23 -0
  457. package/src/update/npm-invocation.mjs +94 -0
  458. package/src/update/tray-update-plan.d.mts +18 -0
  459. package/src/update/tray-update-plan.mjs +38 -0
  460. package/src/usage/cost.ts +0 -0
  461. package/src/usage/debug.ts +97 -0
  462. package/src/usage/expected-prices.ts +164 -0
  463. package/src/usage/log.ts +658 -0
  464. package/src/usage/summary.ts +585 -0
  465. package/src/usage/totals.ts +14 -0
  466. package/src/vision/anthropic-describe.ts +185 -0
  467. package/src/vision/describe.ts +125 -0
  468. package/src/vision/index.ts +467 -0
  469. package/src/web-search/anthropic-executor.ts +189 -0
  470. package/src/web-search/executor.ts +105 -0
  471. package/src/web-search/format-result.ts +89 -0
  472. package/src/web-search/index.ts +196 -0
  473. package/src/web-search/loop.ts +664 -0
  474. package/src/web-search/parse.ts +220 -0
  475. package/src/web-search/progress-stream.ts +342 -0
  476. package/src/web-search/synthetic-tool.ts +47 -0
@@ -0,0 +1,42 @@
1
+ import type { OcxProviderConfig } from "../../types";
2
+
3
+ /**
4
+ * One MCP server opencodex starts/connects and exposes to the Cursor agent as callable tools.
5
+ * Either `command` (stdio: opencodex spawns the server as a child process) or `url`
6
+ * (streamable-http: opencodex connects to a remote MCP server) must be set.
7
+ */
8
+ export interface CursorMcpServerConfig {
9
+ /** stdio: executable to spawn (e.g. "npx", "node", "uvx"). */
10
+ command?: string;
11
+ /** stdio: arguments for the spawned command. */
12
+ args?: string[];
13
+ /** stdio: extra environment variables for the child process. */
14
+ env?: Record<string, string>;
15
+ /** stdio: working directory for the child process. */
16
+ cwd?: string;
17
+ /** streamable-http: remote MCP server URL (alternative to `command`). */
18
+ url?: string;
19
+ /** streamable-http: extra headers for the remote connection. */
20
+ headers?: Record<string, string>;
21
+ /** Set false to keep the server in config but not connect. Default true. */
22
+ enabled?: boolean;
23
+ /** Optional namespace prepended to advertised tool names to avoid collisions. */
24
+ toolPrefix?: string;
25
+ }
26
+
27
+ export interface ResolvedMcpServer extends CursorMcpServerConfig {
28
+ serverName: string;
29
+ }
30
+
31
+ /**
32
+ * Resolve the enabled, connectable MCP servers from a provider config. A server is
33
+ * connectable only if it declares either a `command` (stdio) or a `url` (http).
34
+ */
35
+ export function resolveMcpServers(provider: OcxProviderConfig): ResolvedMcpServer[] {
36
+ const raw = provider.mcpServers;
37
+ if (!raw) return [];
38
+ return Object.entries(raw)
39
+ .map(([serverName, cfg]) => ({ serverName, ...cfg }))
40
+ .filter(server => server.enabled !== false)
41
+ .filter(server => Boolean(server.command || server.url));
42
+ }
@@ -0,0 +1,333 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
4
+ import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
5
+ import type { ResolvedMcpServer } from "./mcp-config";
6
+
7
+ const DEFAULT_CONNECT_TIMEOUT_MS = 15_000;
8
+ const DEFAULT_CALL_TIMEOUT_MS = 120_000;
9
+ const CURSOR_MCP_MAX_SERVERS = 32;
10
+ const CURSOR_MCP_MAX_TOOLS = 512;
11
+ const CURSOR_MCP_MAX_RESOURCES = 1_024;
12
+ const CURSOR_MCP_MAX_SCHEMA_BYTES = 256 * 1024;
13
+ const CURSOR_MCP_MAX_CATALOG_BYTES = 4 * 1024 * 1024;
14
+ const CURSOR_MCP_MAX_RESULT_BYTES = 8 * 1024 * 1024;
15
+ const utf8 = new TextEncoder();
16
+
17
+ export class McpCatalogLimitError extends Error {
18
+ readonly code = "MCP_CATALOG_LIMIT";
19
+ constructor() { super("Cursor MCP catalog limit exceeded"); this.name = "McpCatalogLimitError"; }
20
+ }
21
+ export class McpPayloadTooLargeError extends Error {
22
+ readonly code = "MCP_PAYLOAD_TOO_LARGE";
23
+ constructor(readonly kind: "result" | "resource") { super(`Cursor MCP ${kind} too large`); this.name = "McpPayloadTooLargeError"; }
24
+ }
25
+ /** Backward-compatible export for existing integrations. */
26
+ export const CursorMcpPayloadTooLargeError = McpPayloadTooLargeError;
27
+
28
+ function stableJson(value: unknown): string {
29
+ return JSON.stringify(value, (_key, nested) => nested && typeof nested === "object" && !Array.isArray(nested)
30
+ ? Object.fromEntries(Object.entries(nested as Record<string, unknown>).sort(([left], [right]) => left.localeCompare(right)))
31
+ : nested);
32
+ }
33
+
34
+ function payloadBytes(value: unknown): number {
35
+ return utf8.encode(stableJson(value)).byteLength;
36
+ }
37
+
38
+ /** A tool discovered on a connected MCP server, with its opencodex-advertised name. */
39
+ export interface McpToolHandle {
40
+ serverName: string;
41
+ toolName: string;
42
+ /** Name advertised to the Cursor agent (toolPrefix applied). */
43
+ advertisedName: string;
44
+ description: string;
45
+ inputSchema: unknown;
46
+ }
47
+
48
+ /** Normalized MCP tool-call result (SDK-shape-agnostic). */
49
+ export interface McpCallResult {
50
+ isError: boolean;
51
+ content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;
52
+ }
53
+
54
+ export interface McpResourceListing {
55
+ uri: string;
56
+ name?: string;
57
+ description?: string;
58
+ mimeType?: string;
59
+ server: string;
60
+ }
61
+
62
+ export interface McpResourceContent {
63
+ uri: string;
64
+ mimeType?: string;
65
+ text?: string;
66
+ blob?: Uint8Array;
67
+ }
68
+
69
+ export interface CursorMcpManagerOptions {
70
+ connectTimeoutMs?: number;
71
+ callTimeoutMs?: number;
72
+ /** Test seam: provide a transport factory instead of spawning real processes. */
73
+ transportFactory?: (server: ResolvedMcpServer) => Transport;
74
+ log?: (message: string) => void;
75
+ maxTools?: number;
76
+ maxSchemaBytes?: number;
77
+ maxResultBytes?: number;
78
+ }
79
+
80
+ interface ConnectedServer {
81
+ server: ResolvedMcpServer;
82
+ client: Client;
83
+ }
84
+
85
+ /**
86
+ * Owns the lifecycle of MCP client connections for one Cursor stream. Lazily connects to the
87
+ * configured servers, discovers their tools/resources, and executes tool/resource calls.
88
+ *
89
+ * Connection failures are isolated per-server: one unreachable server never blocks the others
90
+ * and never throws out of `ensureConnected`. Tool-level errors from a server resolve as
91
+ * `{ isError: true }` (they do not throw); only protocol/transport failures throw from
92
+ * `callTool`/`listResources`/`readResource`, and callers are expected to map those to typed
93
+ * protobuf error results.
94
+ */
95
+ export class CursorMcpManager {
96
+ private readonly connectTimeoutMs: number;
97
+ private readonly callTimeoutMs: number;
98
+ private readonly maxTools: number;
99
+ private readonly maxSchemaBytes: number;
100
+ private readonly maxResultBytes: number;
101
+ private connected?: Promise<void>;
102
+ private readonly servers = new Map<string, ConnectedServer>();
103
+ /** advertisedName -> { serverName, original toolName } */
104
+ private readonly toolIndex = new Map<string, { serverName: string; toolName: string; handle: McpToolHandle }>();
105
+ private toolCatalogBytes = 0;
106
+
107
+ constructor(
108
+ private readonly resolved: ResolvedMcpServer[],
109
+ private readonly options: CursorMcpManagerOptions = {},
110
+ ) {
111
+ if (resolved.length > CURSOR_MCP_MAX_SERVERS) throw new McpCatalogLimitError();
112
+ this.connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
113
+ this.callTimeoutMs = options.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS;
114
+ this.maxTools = options.maxTools ?? CURSOR_MCP_MAX_TOOLS;
115
+ this.maxSchemaBytes = options.maxSchemaBytes ?? CURSOR_MCP_MAX_SCHEMA_BYTES;
116
+ this.maxResultBytes = options.maxResultBytes ?? CURSOR_MCP_MAX_RESULT_BYTES;
117
+ }
118
+
119
+ /** Idempotent, lazy connect + tool discovery across all servers. Never throws. */
120
+ ensureConnected(): Promise<void> {
121
+ if (!this.connected) this.connected = this.connectAll();
122
+ return this.connected;
123
+ }
124
+
125
+ private async connectAll(): Promise<void> {
126
+ const staged: Array<{ connection: ConnectedServer; tools: Array<[string, { serverName: string; toolName: string; handle: McpToolHandle }]>; bytes: number }> = [];
127
+ try {
128
+ for (const server of this.resolved) {
129
+ const connected = await this.connectOne(server);
130
+ if (!connected) continue;
131
+ staged.push(connected);
132
+ const toolCount = staged.reduce((sum, row) => sum + row.tools.length, 0);
133
+ const catalogBytes = staged.reduce((sum, row) => sum + row.bytes, 0);
134
+ if (toolCount > this.maxTools || catalogBytes > CURSOR_MCP_MAX_CATALOG_BYTES) throw new McpCatalogLimitError();
135
+ }
136
+ for (const row of staged) {
137
+ this.servers.set(row.connection.server.serverName, row.connection);
138
+ for (const [name, entry] of row.tools) this.toolIndex.set(name, entry);
139
+ this.toolCatalogBytes += row.bytes;
140
+ }
141
+ } catch (error) {
142
+ await Promise.all(staged.map(row => row.connection.client.close().catch(() => {})));
143
+ this.servers.clear();
144
+ this.toolIndex.clear();
145
+ this.toolCatalogBytes = 0;
146
+ throw error;
147
+ }
148
+ }
149
+
150
+ private async connectOne(server: ResolvedMcpServer): Promise<{ connection: ConnectedServer; tools: Array<[string, { serverName: string; toolName: string; handle: McpToolHandle }]>; bytes: number } | null> {
151
+ let client: Client | undefined;
152
+ try {
153
+ const transport = this.options.transportFactory
154
+ ? this.options.transportFactory(server)
155
+ : this.createTransport(server);
156
+ client = new Client({ name: "opencodex", version: "1.0.0" });
157
+ await this.withTimeout(client.connect(transport), this.connectTimeoutMs, `connect ${server.serverName}`);
158
+ const indexed = await this.indexTools(server, client);
159
+ return { connection: { server, client }, tools: indexed.tools, bytes: indexed.bytes };
160
+ } catch (err) {
161
+ if (client) await client.close().catch(() => {});
162
+ this.options.log?.(`[cursor-mcp] server "${server.serverName}" failed to connect: ${errText(err)}`);
163
+ if (err instanceof McpCatalogLimitError || err instanceof McpPayloadTooLargeError) throw err;
164
+ return null;
165
+ }
166
+ }
167
+
168
+ private createTransport(server: ResolvedMcpServer): Transport {
169
+ if (server.command) {
170
+ return new StdioClientTransport({
171
+ command: server.command,
172
+ args: server.args ?? [],
173
+ env: server.env,
174
+ cwd: server.cwd,
175
+ });
176
+ }
177
+ if (server.url) {
178
+ return new StreamableHTTPClientTransport(new URL(server.url), {
179
+ requestInit: server.headers ? { headers: server.headers } : undefined,
180
+ });
181
+ }
182
+ throw new Error(`MCP server "${server.serverName}" has neither command nor url`);
183
+ }
184
+
185
+ private async indexTools(server: ResolvedMcpServer, client: Client): Promise<{ tools: Array<[string, { serverName: string; toolName: string; handle: McpToolHandle }]>; bytes: number }> {
186
+ const prefix = server.toolPrefix ?? "";
187
+ const { tools } = await this.withTimeout(client.listTools(), this.connectTimeoutMs, `listTools ${server.serverName}`);
188
+ const additions: Array<[string, { serverName: string; toolName: string; handle: McpToolHandle }]> = [];
189
+ let catalogBytes = 0;
190
+ for (const tool of tools ?? []) {
191
+ if (additions.length >= this.maxTools) throw new McpCatalogLimitError();
192
+ const advertisedName = `${prefix}${tool.name}`;
193
+ const schema = tool.inputSchema ?? {};
194
+ const schemaBytes = utf8.encode(stableJson(schema)).byteLength;
195
+ if (schemaBytes > this.maxSchemaBytes) throw new McpCatalogLimitError();
196
+ catalogBytes += utf8.encode(advertisedName).byteLength
197
+ + utf8.encode(tool.description ?? "").byteLength
198
+ + schemaBytes;
199
+ if (catalogBytes > CURSOR_MCP_MAX_CATALOG_BYTES) throw new McpCatalogLimitError();
200
+ const handle: McpToolHandle = {
201
+ serverName: server.serverName,
202
+ toolName: tool.name,
203
+ advertisedName,
204
+ description: tool.description ?? "",
205
+ inputSchema: schema,
206
+ };
207
+ additions.push([advertisedName, { serverName: server.serverName, toolName: tool.name, handle }]);
208
+ }
209
+ return { tools: additions, bytes: catalogBytes };
210
+ }
211
+
212
+ async listToolHandles(): Promise<McpToolHandle[]> {
213
+ await this.ensureConnected();
214
+ return [...this.toolIndex.values()].map(entry => entry.handle);
215
+ }
216
+
217
+ async resolveTool(advertisedName: string): Promise<McpToolHandle | undefined> {
218
+ await this.ensureConnected();
219
+ return this.toolIndex.get(advertisedName)?.handle;
220
+ }
221
+
222
+ async toolNames(): Promise<string[]> {
223
+ await this.ensureConnected();
224
+ return [...this.toolIndex.keys()];
225
+ }
226
+
227
+ /** Throws only on protocol/transport failure or unknown tool; tool-level errors resolve. */
228
+ async callTool(advertisedName: string, args: Record<string, unknown>): Promise<McpCallResult> {
229
+ await this.ensureConnected();
230
+ const entry = this.toolIndex.get(advertisedName);
231
+ if (!entry) throw new Error(`MCP tool not found: ${advertisedName}`);
232
+ const conn = this.servers.get(entry.serverName);
233
+ if (!conn) throw new Error(`MCP server not connected: ${entry.serverName}`);
234
+ const result = await this.withTimeout(
235
+ conn.client.callTool({ name: entry.toolName, arguments: args }),
236
+ this.callTimeoutMs,
237
+ `callTool ${advertisedName}`,
238
+ );
239
+ if (payloadBytes(result) > this.maxResultBytes) throw new McpPayloadTooLargeError("result");
240
+ return {
241
+ isError: Boolean((result as { isError?: boolean }).isError),
242
+ content: normalizeContent((result as { content?: unknown[] }).content),
243
+ };
244
+ }
245
+
246
+ async listResources(server?: string): Promise<McpResourceListing[]> {
247
+ await this.ensureConnected();
248
+ const targets = server ? [this.servers.get(server)].filter(Boolean) as ConnectedServer[] : [...this.servers.values()];
249
+ const out: McpResourceListing[] = [];
250
+ let catalogBytes = 0;
251
+ for (const conn of targets) {
252
+ const { resources } = await this.withTimeout(conn.client.listResources(), this.callTimeoutMs, `listResources ${conn.server.serverName}`);
253
+ catalogBytes += payloadBytes(resources ?? []);
254
+ if ((resources?.length ?? 0) + out.length > CURSOR_MCP_MAX_RESOURCES
255
+ || catalogBytes > CURSOR_MCP_MAX_CATALOG_BYTES) {
256
+ throw new McpCatalogLimitError();
257
+ }
258
+ for (const r of resources ?? []) {
259
+ out.push({ uri: r.uri, name: r.name, description: r.description, mimeType: r.mimeType, server: conn.server.serverName });
260
+ }
261
+ }
262
+ return out;
263
+ }
264
+
265
+ async readResource(server: string, uri: string): Promise<McpResourceContent> {
266
+ await this.ensureConnected();
267
+ const conn = this.servers.get(server);
268
+ if (!conn) throw new Error(`MCP server not connected: ${server}`);
269
+ const result = await this.withTimeout(conn.client.readResource({ uri }), this.callTimeoutMs, `readResource ${uri}`);
270
+ const rawBytes = payloadBytes(result);
271
+ if (rawBytes > this.maxResultBytes) throw new McpPayloadTooLargeError("resource");
272
+ const first = (result.contents ?? [])[0] as { uri?: string; mimeType?: string; text?: string; blob?: string } | undefined;
273
+ if (!first) return { uri, mimeType: undefined, text: "" };
274
+ const decodedBytes = typeof first.blob === "string" ? decodedBase64Length(first.blob) : 0;
275
+ if (rawBytes + decodedBytes > this.maxResultBytes) throw new McpPayloadTooLargeError("resource");
276
+ return {
277
+ uri: first.uri ?? uri,
278
+ mimeType: first.mimeType,
279
+ text: typeof first.text === "string" ? first.text : undefined,
280
+ blob: typeof first.blob === "string" ? Uint8Array.from(Buffer.from(first.blob, "base64")) : undefined,
281
+ };
282
+ }
283
+
284
+ assertDecodedResultBudget(value: unknown, decodedBytes: number): void {
285
+ if (payloadBytes(value) + decodedBytes > this.maxResultBytes) throw new McpPayloadTooLargeError("result");
286
+ }
287
+
288
+ async dispose(): Promise<void> {
289
+ const conns = [...this.servers.values()];
290
+ this.servers.clear();
291
+ this.toolIndex.clear();
292
+ this.toolCatalogBytes = 0;
293
+ await Promise.all(conns.map(async conn => {
294
+ try {
295
+ await conn.client.close();
296
+ } catch (err) {
297
+ this.options.log?.(`[cursor-mcp] dispose "${conn.server.serverName}": ${errText(err)}`);
298
+ }
299
+ }));
300
+ }
301
+
302
+ private async withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
303
+ let timer: ReturnType<typeof setTimeout> | undefined;
304
+ try {
305
+ return await Promise.race([
306
+ promise,
307
+ new Promise<never>((_, reject) => {
308
+ timer = setTimeout(() => reject(new Error(`MCP ${label} timed out after ${ms}ms`)), ms);
309
+ }),
310
+ ]);
311
+ } finally {
312
+ if (timer) clearTimeout(timer);
313
+ }
314
+ }
315
+ }
316
+
317
+ export function decodedBase64Length(value: string): number {
318
+ if (!value) return 0;
319
+ const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
320
+ return Math.max(0, Math.floor(value.length * 3 / 4) - padding);
321
+ }
322
+
323
+ function normalizeContent(content: unknown[] | undefined): McpCallResult["content"] {
324
+ if (!Array.isArray(content)) return [];
325
+ return content.map(item => {
326
+ const block = item as { type?: string; text?: string; data?: string; mimeType?: string };
327
+ return { type: block.type ?? "text", text: block.text, data: block.data, mimeType: block.mimeType };
328
+ });
329
+ }
330
+
331
+ function errText(err: unknown): string {
332
+ return err instanceof Error ? err.message : String(err);
333
+ }
@@ -0,0 +1,49 @@
1
+ import type { AdapterEvent } from "../../types";
2
+ import { cursorExecResult } from "./exec-policy";
3
+ import type { CursorClientMessage, CursorServerMessage } from "./types";
4
+ import type { CursorKvStore } from "./kv-store";
5
+
6
+ export interface CursorMessageMapperState {
7
+ kv: CursorKvStore;
8
+ writeClient(message: CursorClientMessage): void;
9
+ }
10
+
11
+ export function mapCursorServerMessage(
12
+ message: CursorServerMessage,
13
+ state: CursorMessageMapperState,
14
+ ): AdapterEvent[] {
15
+ switch (message.type) {
16
+ case "text":
17
+ return [{ type: "text_delta", text: message.text }];
18
+ case "thinking":
19
+ return [{ type: "thinking_delta", thinking: message.thinking }];
20
+ case "tool_call_start":
21
+ return [{ type: "tool_call_start", id: message.id, name: message.name }];
22
+ case "tool_call_delta":
23
+ return [{ type: "tool_call_delta", arguments: message.arguments }];
24
+ case "tool_call_end":
25
+ return [{ type: "tool_call_end" }];
26
+ case "done":
27
+ return [{ type: "done", usage: message.usage }];
28
+ case "error":
29
+ return [{ type: "error", message: message.message, ...(message.usage ? { usage: message.usage } : {}) }];
30
+ case "heartbeat":
31
+ // Liveness only: keeps the bridge's stall watchdog from tripping upstream_stall_timeout while
32
+ // Cursor silently assembles (parallel) tool calls. The bridge resets stallTicks on any adapter
33
+ // event and ignores unknown event types, so this emits no Responses protocol event.
34
+ return [{ type: "heartbeat" }];
35
+ case "kv_get":
36
+ state.writeClient({ type: "kv_value", key: message.key, value: state.kv.get(message.key) });
37
+ return [];
38
+ case "kv_set":
39
+ state.kv.set(message.key, message.value);
40
+ state.writeClient({ type: "kv_stored", key: message.key });
41
+ return [];
42
+ case "exec":
43
+ state.writeClient(cursorExecResult(message.requestId, message.execCase));
44
+ return [];
45
+ case "local_side_effect":
46
+ // Internal retry-safety signal only; keep the bridge alive without producing protocol output.
47
+ return [{ type: "heartbeat" }];
48
+ }
49
+ }
@@ -0,0 +1,55 @@
1
+ import { create, toBinary } from "@bufbuild/protobuf";
2
+ import {
3
+ AgentClientMessageSchema,
4
+ ExecClientControlMessageSchema,
5
+ ExecClientMessageSchema,
6
+ ExecClientStreamCloseSchema,
7
+ type ExecClientMessage,
8
+ type ExecServerMessage,
9
+ } from "./gen/agent_pb";
10
+
11
+ export const textDecoder = new TextDecoder();
12
+ export const textEncoder = new TextEncoder();
13
+
14
+ export function clientBytes(message: Parameters<typeof create<typeof AgentClientMessageSchema>>[1]): Uint8Array {
15
+ return toBinary(AgentClientMessageSchema, create(AgentClientMessageSchema, message));
16
+ }
17
+
18
+ export function execBytes(execMsg: ExecServerMessage, messageCase: ExecClientMessage["message"]["case"], value: unknown): Uint8Array {
19
+ return clientBytes({
20
+ message: {
21
+ case: "execClientMessage",
22
+ value: create(ExecClientMessageSchema, {
23
+ id: execMsg.id,
24
+ execId: execMsg.execId,
25
+ message: { case: messageCase, value: value as never },
26
+ }),
27
+ },
28
+ });
29
+ }
30
+
31
+ /**
32
+ * Exec-channel stream close acknowledgement (`execClientControlMessage.streamClose`). Cursor keeps
33
+ * a streamed exec (e.g. `shellStreamArgs`) — and with it the whole turn — pending until the client
34
+ * closes the exec stream; stream deltas and even the `exit` event alone are not treated as
35
+ * completion. Mirrors jawcode `sendExecClientStreamClose`.
36
+ */
37
+ export function execStreamCloseBytes(execMsg: ExecServerMessage): Uint8Array {
38
+ return clientBytes({
39
+ message: {
40
+ case: "execClientControlMessage",
41
+ value: create(ExecClientControlMessageSchema, {
42
+ message: { case: "streamClose", value: create(ExecClientStreamCloseSchema, { id: execMsg.id }) },
43
+ }),
44
+ },
45
+ });
46
+ }
47
+
48
+ export function errorText(err: unknown): string {
49
+ return err instanceof Error ? err.message : String(err);
50
+ }
51
+
52
+ export function lineCount(text: string): number {
53
+ if (text.length === 0) return 0;
54
+ return text.split(/\r\n|\r|\n/).length;
55
+ }
@@ -0,0 +1,184 @@
1
+ import { spawn } from "node:child_process";
2
+ import { shellInvocation } from "../../lib/win-exec";
3
+ import { create } from "@bufbuild/protobuf";
4
+ import {
5
+ ComputerUseErrorSchema,
6
+ ComputerUseResultSchema,
7
+ ComputerUseSuccessSchema,
8
+ RecordScreenDiscardSuccessSchema,
9
+ RecordScreenFailureSchema,
10
+ RecordScreenResultSchema,
11
+ RecordScreenSaveSuccessSchema,
12
+ RecordScreenStartSuccessSchema,
13
+ type ComputerUseArgs,
14
+ type ComputerUseResult,
15
+ type RecordScreenArgs,
16
+ type RecordScreenResult,
17
+ } from "./gen/agent_pb";
18
+ import { errorText } from "./native-exec-common";
19
+ import type { CursorNativeToolDeps } from "./native-exec-tools";
20
+
21
+ const DEFAULT_DESKTOP_TIMEOUT_MS = 30_000;
22
+
23
+ /**
24
+ * Opt-in external executor for computer-use / record-screen. opencodex is a headless proxy and
25
+ * cannot drive a screen itself; set these commands only when running on a host that can. Each
26
+ * command receives the request as JSON on stdin and must print a JSON result on stdout.
27
+ */
28
+ export interface DesktopExecutorConfig {
29
+ /** Command (run via the platform shell) handling computer-use. Receives `{toolCallId, actions}` on stdin. */
30
+ computerUseCommand?: string;
31
+ /** Command handling record-screen. Receives `{mode, toolCallId, saveAsFilename?}` on stdin. */
32
+ recordScreenCommand?: string;
33
+ cwd?: string;
34
+ env?: Record<string, string>;
35
+ /** Max time to wait for the external process. Default 30s. */
36
+ timeoutMs?: number;
37
+ }
38
+
39
+ /**
40
+ * Build `computerUse` / `recordScreen` deps from external executor commands. Returns `{}` when no
41
+ * command is configured (the dispatcher then falls back to the honest "not supported" default).
42
+ * Every method maps results to protobuf and NEVER throws — a throw would propagate into the
43
+ * stream loop and fail the conversation.
44
+ */
45
+ export function desktopDepsFromConfig(config?: DesktopExecutorConfig): CursorNativeToolDeps {
46
+ if (!config?.computerUseCommand && !config?.recordScreenCommand) return {};
47
+ const deps: CursorNativeToolDeps = {};
48
+ if (config.computerUseCommand) {
49
+ deps.computerUse = (args: ComputerUseArgs) => runComputerUse(config, args);
50
+ }
51
+ if (config.recordScreenCommand) {
52
+ deps.recordScreen = (args: RecordScreenArgs) => runRecordScreen(config, args);
53
+ }
54
+ return deps;
55
+ }
56
+
57
+ async function runComputerUse(config: DesktopExecutorConfig, args: ComputerUseArgs): Promise<ComputerUseResult> {
58
+ const actionCount = args.actions.length;
59
+ try {
60
+ const out = await runExternalJson(config.computerUseCommand!, {
61
+ toolCallId: args.toolCallId,
62
+ actions: args.actions,
63
+ }, config);
64
+ if (out && typeof out === "object" && "error" in out) {
65
+ return computerUseError(String((out as { error: unknown }).error), actionCount);
66
+ }
67
+ const result = out as { screenshot?: string; screenshotPath?: string; durationMs?: number; log?: string };
68
+ return create(ComputerUseResultSchema, {
69
+ result: { case: "success", value: create(ComputerUseSuccessSchema, {
70
+ actionCount,
71
+ durationMs: typeof result?.durationMs === "number" ? result.durationMs : 0,
72
+ screenshot: result?.screenshot,
73
+ screenshotPath: result?.screenshotPath,
74
+ log: result?.log,
75
+ }) },
76
+ });
77
+ } catch (err) {
78
+ return computerUseError(errorText(err), actionCount);
79
+ }
80
+ }
81
+
82
+ async function runRecordScreen(config: DesktopExecutorConfig, args: RecordScreenArgs): Promise<RecordScreenResult> {
83
+ try {
84
+ const out = await runExternalJson(config.recordScreenCommand!, {
85
+ mode: args.mode,
86
+ toolCallId: args.toolCallId,
87
+ saveAsFilename: args.saveAsFilename,
88
+ }, config) as Record<string, unknown>;
89
+ if (out?.startSuccess) {
90
+ const s = out.startSuccess as { wasPriorRecordingCancelled?: boolean; wasSaveAsFilenameIgnored?: boolean };
91
+ return create(RecordScreenResultSchema, { result: { case: "startSuccess", value: create(RecordScreenStartSuccessSchema, {
92
+ wasPriorRecordingCancelled: Boolean(s.wasPriorRecordingCancelled),
93
+ wasSaveAsFilenameIgnored: Boolean(s.wasSaveAsFilenameIgnored),
94
+ }) } });
95
+ }
96
+ if (out?.saveSuccess) {
97
+ const s = out.saveSuccess as { path?: string; recordingDurationMs?: number };
98
+ return create(RecordScreenResultSchema, { result: { case: "saveSuccess", value: create(RecordScreenSaveSuccessSchema, {
99
+ path: String(s.path ?? ""),
100
+ recordingDurationMs: BigInt(Math.trunc(s.recordingDurationMs ?? 0)),
101
+ }) } });
102
+ }
103
+ if (out?.discardSuccess) {
104
+ return create(RecordScreenResultSchema, { result: { case: "discardSuccess", value: create(RecordScreenDiscardSuccessSchema, {}) } });
105
+ }
106
+ const failure = out?.failure as { error?: unknown } | undefined;
107
+ return recordScreenFailure(failure?.error ? String(failure.error) : "record-screen executor returned no recognized result");
108
+ } catch (err) {
109
+ return recordScreenFailure(errorText(err));
110
+ }
111
+ }
112
+
113
+ function computerUseError(error: string, actionCount: number): ComputerUseResult {
114
+ return create(ComputerUseResultSchema, {
115
+ result: { case: "error", value: create(ComputerUseErrorSchema, { error, actionCount, durationMs: 0 }) },
116
+ });
117
+ }
118
+
119
+ function recordScreenFailure(error: string): RecordScreenResult {
120
+ return create(RecordScreenResultSchema, {
121
+ result: { case: "failure", value: create(RecordScreenFailureSchema, { error }) },
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Spawn `command` via the platform shell (sh -c on POSIX, cmd.exe /d /s /c on win32 —
127
+ * the configured command is platform-native shell syntax; devlog
128
+ * 260715_cross_platform_audit/020), write `payload` as JSON to stdin, return parsed stdout JSON.
129
+ */
130
+ function runExternalJson(command: string, payload: unknown, config: DesktopExecutorConfig): Promise<unknown> {
131
+ const timeoutMs = config.timeoutMs ?? DEFAULT_DESKTOP_TIMEOUT_MS;
132
+ return new Promise((resolve, reject) => {
133
+ const inv = shellInvocation(command);
134
+ const child = spawn(inv.file, inv.args, {
135
+ cwd: config.cwd,
136
+ env: config.env ? { ...process.env, ...config.env } : process.env,
137
+ stdio: ["pipe", "pipe", "pipe"],
138
+ ...inv.options,
139
+ });
140
+ let stdout = "";
141
+ let stderr = "";
142
+ let settled = false;
143
+ const timer = setTimeout(() => {
144
+ if (settled) return;
145
+ settled = true;
146
+ child.kill("SIGKILL");
147
+ reject(new Error(`desktop executor timed out after ${timeoutMs}ms`));
148
+ }, timeoutMs);
149
+
150
+ child.stdout.on("data", chunk => { stdout += chunk.toString(); });
151
+ child.stderr.on("data", chunk => { stderr += chunk.toString(); });
152
+ child.on("error", err => {
153
+ if (settled) return;
154
+ settled = true;
155
+ clearTimeout(timer);
156
+ reject(err);
157
+ });
158
+ child.on("close", code => {
159
+ if (settled) return;
160
+ settled = true;
161
+ clearTimeout(timer);
162
+ if (code !== 0) {
163
+ reject(new Error(`desktop executor exited with code ${code}${stderr ? `: ${stderr.trim()}` : ""}`));
164
+ return;
165
+ }
166
+ try {
167
+ resolve(JSON.parse(stdout.trim()));
168
+ } catch {
169
+ reject(new Error(`desktop executor produced invalid JSON: ${stdout.slice(0, 200)}`));
170
+ }
171
+ });
172
+
173
+ try {
174
+ child.stdin.write(JSON.stringify(payload));
175
+ child.stdin.end();
176
+ } catch (err) {
177
+ if (!settled) {
178
+ settled = true;
179
+ clearTimeout(timer);
180
+ reject(err);
181
+ }
182
+ }
183
+ });
184
+ }