@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,656 @@
1
+ import type {
2
+ OcxAssistantMessage,
3
+ OcxContentPart,
4
+ OcxContext,
5
+ OcxMessage,
6
+ OcxParsedRequest,
7
+ OcxRequestOptions,
8
+ OcxTextContent,
9
+ OcxThinkingContent,
10
+ OcxTool,
11
+ OcxToolCall,
12
+ } from "../types";
13
+ import { namespacedToolName } from "../types";
14
+ import { responsesRequestSchema } from "./schema";
15
+ import { compactionItemToText } from "./compaction";
16
+ import { previousResponseReplayPrefixLength } from "./state";
17
+ import { decodeReasoningEnvelope } from "./reasoning-envelope";
18
+ import { extractHostedWebSearch, WEB_SEARCH_TOOL_NAME } from "../web-search/synthetic-tool";
19
+ import { extractHostedImageGeneration, IMAGE_GEN_TOOL_NAME } from "../images/synthetic-tool";
20
+
21
+ function isObj(v: unknown): v is Record<string, unknown> {
22
+ return typeof v === "object" && v !== null && !Array.isArray(v);
23
+ }
24
+
25
+ type InputBlock =
26
+ | { type: "input_text"; text: string }
27
+ | { type: "text"; text: string }
28
+ | { type: "input_image"; image_url?: string; file_id?: string; detail?: string }
29
+ | { type: "input_file"; file_id?: string; filename?: string; file_data?: string };
30
+
31
+ /** A usable reference string, or undefined. Empty strings and non-strings are not references. */
32
+ function nonEmptyString(value: unknown): string | undefined {
33
+ return typeof value === "string" && value.length > 0 ? value : undefined;
34
+ }
35
+
36
+ function inputContentParts(blocks: unknown): string | OcxContentPart[] {
37
+ if (typeof blocks === "string") return blocks;
38
+ // The catch-all can also hand back a non-array `content` (an object, a number), which would
39
+ // throw at the loop below before any per-block guard runs.
40
+ if (!Array.isArray(blocks)) return [];
41
+ const parts: OcxContentPart[] = [];
42
+ for (const raw of blocks) {
43
+ // A malformed message item fails its strict schema and falls through to inputItemSchema's
44
+ // permissive catch-all, so blocks reaching here are NOT guaranteed to match the declared
45
+ // shape. Validate each field before use, as outputToToolResultContent already does.
46
+ if (!isObj(raw)) continue;
47
+ const block = raw as InputBlock;
48
+ if (block.type === "input_text" || block.type === "text") {
49
+ if (typeof raw.text === "string") parts.push({ type: "text", text: raw.text });
50
+ } else if (block.type === "input_image") {
51
+ const b = block as { image_url?: string; file_id?: string; detail?: string };
52
+ const imageUrl = nonEmptyString(b.image_url);
53
+ const fileId = nonEmptyString(b.file_id);
54
+ const detail = nonEmptyString(b.detail);
55
+ if (imageUrl) {
56
+ // Preserve the image as a structured part — adapters send it as a native image block.
57
+ // NEVER inline the (often base64 data-URL) image_url as text: that explodes the token count.
58
+ parts.push({ type: "image", imageUrl, ...(detail ? { detail: normalizeImageDetail(detail) } : {}) });
59
+ } else if (fileId) {
60
+ parts.push({ type: "text", text: `[image: ${fileId}]` }); // file_id ref → no inline data
61
+ }
62
+ // No usable reference: omit the block. A "[image: ?]" marker would claim an attachment
63
+ // the request never carried, which is worse than dropping malformed input.
64
+ } else if (block.type === "input_file") {
65
+ const b = block as { file_id?: string; filename?: string; file_data?: string };
66
+ const fileId = nonEmptyString(b.file_id);
67
+ const fileData = nonEmptyString(b.file_data);
68
+ const filename = nonEmptyString(b.filename);
69
+ if (fileId) {
70
+ parts.push({ type: "text", text: `[file: ${fileId}]` });
71
+ } else if (fileData) {
72
+ // Inline file_data is often large base64. Preserve only its presence and name, never bytes.
73
+ parts.push({ type: "text", text: filename ? `[file: ${filename}]` : "[file: inline data]" });
74
+ }
75
+ // A bare filename is not a file resource in the Responses schema, so omit it rather than
76
+ // fabricating a "[file: ...]" marker for an attachment that was never sent.
77
+ }
78
+ }
79
+ // Collapse to a plain string only for a single TEXT part; images must stay structured.
80
+ if (parts.length === 1 && parts[0].type === "text") return parts[0].text;
81
+ return parts;
82
+ }
83
+
84
+ type OutputBlock = { type: "output_text"; text: string } | { type: "text"; text: string } | { type: "refusal"; refusal: string };
85
+
86
+ function outputTextOf(blocks: unknown): OcxTextContent[] {
87
+ if (typeof blocks === "string") return blocks.length > 0 ? [{ type: "text", text: blocks }] : [];
88
+ if (!Array.isArray(blocks)) return [];
89
+ const out: OcxTextContent[] = [];
90
+ for (const raw of blocks) {
91
+ // Same catch-all caveat as inputContentParts: validate before use.
92
+ if (!isObj(raw)) continue;
93
+ const b = raw as OutputBlock;
94
+ if (b.type === "output_text" || b.type === "text") {
95
+ if (typeof raw.text === "string") out.push({ type: "text", text: raw.text });
96
+ } else if (b.type === "refusal") {
97
+ if (typeof raw.refusal === "string") out.push({ type: "text", text: `[refusal: ${raw.refusal}]` });
98
+ }
99
+ }
100
+ return out;
101
+ }
102
+
103
+ function mapToolChoice(value: unknown): OcxRequestOptions["toolChoice"] {
104
+ if (value === undefined || value === null) return undefined;
105
+ if (value === "auto" || value === "none" || value === "required") return value;
106
+ if (isObj(value) && "type" in value) {
107
+ const t = (value as { type: string }).type;
108
+ if ((t === "function" || t === "custom") && "name" in value) {
109
+ return { name: (value as { name: string }).name };
110
+ }
111
+ // Hosted image tool types (with or without a name) map to the synthetic image_gen wire name.
112
+ if (t === "image_generation" || t === "image_gen") {
113
+ return { name: IMAGE_GEN_TOOL_NAME };
114
+ }
115
+ if (t === "allowed_tools" && Array.isArray(value.tools)) {
116
+ const names = value.tools
117
+ .map(allowedToolName)
118
+ .filter((name): name is string => Boolean(name));
119
+ return names.length > 0
120
+ ? { allowedTools: [...new Set(names)], mode: value.mode === "required" ? "required" : "auto" }
121
+ : "none";
122
+ }
123
+ return "auto";
124
+ }
125
+ return undefined;
126
+ }
127
+
128
+ function allowedToolName(tool: unknown): string | undefined {
129
+ if (!isObj(tool)) return undefined;
130
+ if (typeof tool.name === "string" && tool.name.length > 0) return tool.name;
131
+ if (tool.type === "web_search" || tool.type === "web_search_preview") return WEB_SEARCH_TOOL_NAME;
132
+ if (tool.type === "image_generation" || tool.type === "image_gen") return IMAGE_GEN_TOOL_NAME;
133
+ if (tool.type === "tool_search") return "tool_search";
134
+ return undefined;
135
+ }
136
+
137
+ function buildTools(tools: unknown[] | undefined): OcxTool[] | undefined {
138
+ if (!tools) return undefined;
139
+ const out: OcxTool[] = [];
140
+ const normalizeParameters = (raw: unknown): Record<string, unknown> => {
141
+ if (isObj(raw) && raw.type === "object") return raw;
142
+ return { ...(isObj(raw) ? raw : {}), type: "object" };
143
+ };
144
+ const pushFn = (t: Record<string, unknown>, namespace?: string) => {
145
+ const tool: OcxTool = {
146
+ name: t.name as string,
147
+ description: (t.description as string) ?? "",
148
+ parameters: normalizeParameters(t.parameters),
149
+ };
150
+ if (t.strict !== undefined) tool.strict = t.strict as boolean;
151
+ if (namespace) tool.namespace = namespace;
152
+ out.push(tool);
153
+ };
154
+ for (const t of tools) {
155
+ if (!isObj(t)) continue;
156
+ if (t.type === "function" && typeof t.name === "string") {
157
+ pushFn(t);
158
+ } else if (t.type === "namespace" && Array.isArray(t.tools)) {
159
+ // MCP tools arrive grouped under a namespace tool; flatten the inner function tools so
160
+ // chat-completions models receive them (round-trip restores the namespace in the bridge).
161
+ const ns = typeof t.name === "string" ? t.name : undefined;
162
+ for (const inner of t.tools as unknown[]) {
163
+ if (isObj(inner) && inner.type === "function" && typeof inner.name === "string") pushFn(inner, ns);
164
+ }
165
+ }
166
+ else if (t.type === "custom" && typeof t.name === "string") {
167
+ // Freeform custom tool (e.g. apply_patch). Chat models can't emit a lark grammar, so expose a
168
+ // function with a single string `input` carrying the raw tool body; the bridge relays the model's
169
+ // call back as a custom_tool_call (Codex's freeform handler rejects a function_call → fatal abort).
170
+ out.push({
171
+ name: t.name,
172
+ description: (t.description as string) ?? "",
173
+ parameters: { type: "object", properties: { input: { type: "string", description: "Raw tool input. For apply_patch, begin exactly with `*** Begin Patch` (no trailing `***`), then use its standard patch envelope." } }, required: ["input"] },
174
+ freeform: true,
175
+ });
176
+ }
177
+ else if (t.type === "tool_search") {
178
+ // Client-executed tool discovery — the gateway to deferred tools (subagents, extra MCP tools).
179
+ // Expose as a function so chat models can call it; the bridge relays it as a tool_search_call.
180
+ out.push({
181
+ name: "tool_search",
182
+ description: (t.description as string) ?? "Search for additional tools to load for the next turn.",
183
+ parameters: (isObj(t.parameters) ? t.parameters : {
184
+ type: "object",
185
+ properties: {
186
+ query: { type: "string", description: "Search query for tools to load." },
187
+ limit: { type: "number", description: "Maximum number of tools to return." },
188
+ },
189
+ required: ["query"],
190
+ }) as Record<string, unknown>,
191
+ toolSearch: true,
192
+ });
193
+ }
194
+ else if (typeof t.name === "string" && t.type !== "web_search" && t.type !== "image_generation") {
195
+ // Any OTHER named tool (e.g. a native/computer-use tool type opencodex doesn't explicitly
196
+ // model) is client-executed — pass it through as a function so the routed model can read and
197
+ // call it naturally; the bridge relays its call as a function_call. Previously such tools were
198
+ // silently dropped, so the model never saw them.
199
+ pushFn(t);
200
+ }
201
+ // Only the OpenAI-hosted server-side tools (web_search, image_generation) are intentionally
202
+ // dropped — they're executed by OpenAI and can't be relayed to a routed chat model.
203
+ }
204
+ return out.length > 0 ? out : undefined;
205
+ }
206
+
207
+ function ensureAssistantPlaceholder(messages: OcxMessage[], modelId: string, now: number): OcxAssistantMessage {
208
+ const last = messages[messages.length - 1];
209
+ if (last && last.role === "assistant") return last;
210
+ const placeholder: OcxAssistantMessage = { role: "assistant", content: [], model: modelId, timestamp: now };
211
+ messages.push(placeholder);
212
+ return placeholder;
213
+ }
214
+
215
+ /**
216
+ * Tool-call output content. Preserves images (e.g. Codex `view_image` returns
217
+ * `input_image` items): returns content parts when any image is present, else a plain joined string.
218
+ * Never inlines an image_url as text (that would explode the token count).
219
+ */
220
+ function outputToToolResultContent(output: string | unknown[] | undefined): string | OcxContentPart[] {
221
+ if (typeof output === "string") return output;
222
+ if (!Array.isArray(output)) return "";
223
+ const parts: OcxContentPart[] = [];
224
+ let hasImage = false;
225
+ for (const raw of output) {
226
+ if (!isObj(raw)) continue;
227
+ if (raw.type === "output_text" || raw.type === "text" || raw.type === "input_text") {
228
+ if (typeof raw.text === "string") parts.push({ type: "text", text: raw.text });
229
+ } else if (raw.type === "refusal" && typeof raw.refusal === "string") {
230
+ parts.push({ type: "text", text: `[refusal: ${raw.refusal}]` });
231
+ } else if (raw.type === "input_image" && typeof raw.image_url === "string") {
232
+ parts.push({ type: "image", imageUrl: raw.image_url, ...(typeof raw.detail === "string" ? { detail: normalizeImageDetail(raw.detail) } : {}) });
233
+ hasImage = true;
234
+ } else if (raw.type === "encrypted_content") {
235
+ // codex-rs FunctionCallOutputContentItem::EncryptedContent — opaque to routed models.
236
+ parts.push({ type: "text", text: "[encrypted content omitted]" });
237
+ }
238
+ }
239
+ if (!hasImage) return parts.map(p => (p.type === "text" ? p.text : "")).join("");
240
+ return parts;
241
+ }
242
+
243
+ function toolOutputContainsEncryptedContent(output: string | unknown[] | undefined): boolean {
244
+ return Array.isArray(output) && output.some(raw => isObj(raw) && raw.type === "encrypted_content");
245
+ }
246
+
247
+ /**
248
+ * codex-rs ImageDetail allows "original", but chat-completions providers only accept
249
+ * auto|low|high on image_url.detail — degrade "original" to "high" (the codex default).
250
+ */
251
+ function normalizeImageDetail(detail: string): string {
252
+ return detail === "original" ? "high" : detail;
253
+ }
254
+
255
+ function findToolById(messages: OcxMessage[], callId: string): { name: string; namespace?: string } {
256
+ for (let i = messages.length - 1; i >= 0; i--) {
257
+ const m = messages[i];
258
+ if (m.role !== "assistant") continue;
259
+ for (const part of m.content) {
260
+ if (part.type === "toolCall" && part.id === callId) return { name: part.name, namespace: part.namespace };
261
+ }
262
+ }
263
+ return { name: "" };
264
+ }
265
+
266
+ const REASONING_EFFORTS = new Set(["none", "minimal", "low", "medium", "high", "xhigh", "max"]);
267
+
268
+ export function parseRequest(body: unknown): OcxParsedRequest {
269
+ const replayedInputPrefixLength = previousResponseReplayPrefixLength(body);
270
+ const parsed = responsesRequestSchema.safeParse(body);
271
+ if (!parsed.success) {
272
+ throw new Error(`responses parse error: ${parsed.error.message}`);
273
+ }
274
+ const data = parsed.data;
275
+ const now = Date.now();
276
+ const messages: OcxMessage[] = [];
277
+ const systemPrompt: string[] = [];
278
+ // Responses reasoning siblings belong to the following assistant, including across call items.
279
+ // Keep them off the message list until that assistant arrives; turn boundaries clear the array.
280
+ const pendingReasoning: Array<{ part: OcxThinkingContent; envelopeSigned: boolean }> = [];
281
+ // Assistant placeholder that first folds any pending reasoning into the same turn (official
282
+ // grok-build preserves reasoning across call items; Anthropic replay requires thinking to
283
+ // precede tool_use inside one assistant message).
284
+ const assistantHolderWithReasoning = (): OcxAssistantMessage => {
285
+ const holder = ensureAssistantPlaceholder(messages, data.model, now);
286
+ if (pendingReasoning.length > 0) {
287
+ holder.content.push(...pendingReasoning.map(entry => entry.part));
288
+ pendingReasoning.length = 0;
289
+ }
290
+ return holder;
291
+ };
292
+ // Tool specs surfaced by a prior tool_search (deferred tools, e.g. subagents). Codex does not
293
+ // re-list these in `tools`, but chat models can only call listed tools — so we re-inject them.
294
+ const loadedToolSpecs: unknown[] = [];
295
+ // Remote compaction v2: the input tail carries `{type:"compaction_trigger"}` and Codex expects a
296
+ // synthetic `{type:"compaction"}` output item (src/responses/compaction.ts). Flagged for the server.
297
+ let compactionRequest = false;
298
+ let contextCompactionBoundary = false;
299
+
300
+ if (typeof data.instructions === "string" && data.instructions.length > 0) {
301
+ systemPrompt.push(data.instructions);
302
+ }
303
+
304
+ if (typeof data.input === "string") {
305
+ messages.push({ role: "user", content: data.input, timestamp: now });
306
+ } else if (data.input) {
307
+ for (let inputIndex = 0; inputIndex < data.input.length; inputIndex++) {
308
+ const item = data.input[inputIndex];
309
+ const effectiveType = (item as { type?: string }).type ?? ("role" in item ? "message" : undefined);
310
+
311
+ if (effectiveType === "compaction_trigger") {
312
+ compactionRequest = true;
313
+ continue;
314
+ }
315
+
316
+ if (effectiveType === "additional_tools") {
317
+ // Codex Desktop responses_lite WS path: tools ride INSIDE input as an
318
+ // `additional_tools` item ({type, role, tools:[...]}) instead of body.tools.
319
+ // Same spec wire shapes (function/namespace/custom/tool_search) — collect and
320
+ // merge through the exact buildTools path so surface detection (collabSurface)
321
+ // and chat-model tool listing see them. The item itself never becomes a message;
322
+ // the native passthrough keeps it verbatim in _rawBody.
323
+ const at = item as { tools?: unknown[] };
324
+ if (Array.isArray(at.tools)) loadedToolSpecs.push(...at.tools);
325
+ continue;
326
+ }
327
+
328
+ if (effectiveType === "compaction" || effectiveType === "compaction_summary" || effectiveType === "context_compaction") {
329
+ // A stored summary from a previous compaction. Decode our ocx1 envelope into plain text so
330
+ // the routed model keeps the compacted context; real OpenAI-encrypted blobs degrade to a note.
331
+ // `context_compaction` (encrypted_content optional) is codex-rs's local-compaction marker;
332
+ // with no payload it is a pure marker (the summary follows as its own user message), so it
333
+ // is dropped silently. It must NOT flag _compactionRequest. Only a marker newly appended in
334
+ // this request starts a provider-private context epoch; markers inside the prefix restored by
335
+ // previous_response_id were already acknowledged on the turn that introduced them.
336
+ if (inputIndex >= replayedInputPrefixLength) contextCompactionBoundary = true;
337
+ const encrypted = (item as { encrypted_content?: unknown }).encrypted_content;
338
+ if (effectiveType === "context_compaction" && typeof encrypted !== "string") continue;
339
+ pendingReasoning.length = 0;
340
+ messages.push({
341
+ role: "user",
342
+ content: compactionItemToText(typeof encrypted === "string" ? encrypted : undefined),
343
+ timestamp: now,
344
+ });
345
+ continue;
346
+ }
347
+
348
+ if (effectiveType === "agent_message") {
349
+ const agentMessage = item as {
350
+ author?: string;
351
+ recipient?: string;
352
+ content?: unknown;
353
+ };
354
+
355
+ const content = inputContentParts(agentMessage.content);
356
+
357
+ const hasContent =
358
+ typeof content === "string"
359
+ ? content.trim().length > 0
360
+ : content.length > 0;
361
+
362
+ // An agent_message is external input delivered to the parent agent.
363
+ // Preserve it as a user-role turn so signed Anthropic thinking blocks
364
+ // on either side are never merged into one modified assistant response.
365
+ pendingReasoning.length = 0;
366
+ messages.push({
367
+ role: "user",
368
+ content: hasContent ? content : "(sub-agent message received)",
369
+ timestamp: now,
370
+ });
371
+
372
+ continue;
373
+ }
374
+
375
+ if (effectiveType === "message") {
376
+ const msg = item as { role?: string; content?: unknown; phase?: "commentary" | "final_answer" };
377
+ switch (msg.role) {
378
+ case "system": {
379
+ pendingReasoning.length = 0;
380
+ const text = inputContentParts(msg.content);
381
+ const flat = typeof text === "string" ? text : text.map(p => (p.type === "text" ? p.text : "")).join("");
382
+ if (flat.length > 0) systemPrompt.push(flat);
383
+ break;
384
+ }
385
+ case "user":
386
+ case "developer": {
387
+ pendingReasoning.length = 0;
388
+ const content = inputContentParts(msg.content);
389
+ messages.push({ role: msg.role, content, timestamp: now });
390
+ break;
391
+ }
392
+ case "assistant": {
393
+ const parts = outputTextOf(msg.content);
394
+ messages.push({
395
+ role: "assistant",
396
+ content: pendingReasoning.length > 0
397
+ ? [...pendingReasoning.map(entry => entry.part), ...parts]
398
+ : parts,
399
+ ...(msg.phase ? { phase: msg.phase } : {}),
400
+ model: data.model,
401
+ timestamp: now,
402
+ });
403
+ pendingReasoning.length = 0;
404
+ break;
405
+ }
406
+ }
407
+ continue;
408
+ }
409
+
410
+ if (effectiveType === "reasoning") {
411
+ const reasoning = item as { id?: string; summary?: { text: string }[]; content?: { text: string }[]; encrypted_content?: string };
412
+ const fromSummary = (reasoning.summary ?? []).map(c => c.text).join("");
413
+ const text = fromSummary || (reasoning.content ?? []).map(c => c.text).join("");
414
+ const envelope = typeof reasoning.encrypted_content === "string"
415
+ ? decodeReasoningEnvelope(reasoning.encrypted_content)
416
+ : null;
417
+ const thinkingText = envelope?.txt || text;
418
+
419
+ // Native/non-ocxr1 encrypted-only reasoning is opaque here. Do not create a detached
420
+ // assistant turn or invent replayable plaintext/signatures from the encrypted payload.
421
+ if (thinkingText.length > 0) {
422
+ const part: OcxThinkingContent = {
423
+ type: "thinking",
424
+ thinking: thinkingText,
425
+ signature: envelope?.sig ?? JSON.stringify(reasoning),
426
+ ...(envelope?.red ? { redacted: envelope.red } : {}),
427
+ ...(reasoning.id ? { itemId: reasoning.id } : {}),
428
+ };
429
+ const envelopeSigned = typeof envelope?.sig === "string";
430
+ const previous = pendingReasoning[pendingReasoning.length - 1];
431
+
432
+ if (!envelopeSigned && previous && !previous.envelopeSigned) {
433
+ previous.part = {
434
+ ...part,
435
+ thinking: `${previous.part.thinking}\n${part.thinking}`,
436
+ };
437
+ } else {
438
+ pendingReasoning.push({ part, envelopeSigned });
439
+ }
440
+ }
441
+ continue;
442
+ }
443
+
444
+ if (effectiveType === "function_call") {
445
+ const call = item as { id?: string; call_id: string; name: string; arguments?: string; namespace?: string };
446
+ // Tolerate empty/non-JSON arguments (e.g. a no-arg tool call serialized as "") instead of
447
+ // throwing — a single poisoned history item would otherwise 400 every subsequent turn.
448
+ let args: Record<string, unknown> = {};
449
+ const rawArgs = call.arguments?.trim();
450
+ if (rawArgs) {
451
+ try {
452
+ const parsed: unknown = JSON.parse(rawArgs);
453
+ if (isObj(parsed)) args = parsed;
454
+ } catch {
455
+ console.warn(`[parser] function_call ${call.call_id} has non-JSON arguments; defaulting to {}`);
456
+ }
457
+ }
458
+ // Do NOT map Responses item `id` (fc_/ctc_/…) onto `thoughtSignature`. That field is
459
+ // reserved for Gemini/Antigravity opaque thought tokens; forwarding item ids as
460
+ // thoughtSignature 400s Antigravity (Base64 / TYPE_BYTES). Continuity for CCA comes from
461
+ // the in-process replay cache (and any already-real signature stored on the tool call).
462
+ const toolCall: OcxToolCall = {
463
+ type: "toolCall", id: call.call_id, name: call.name, arguments: args,
464
+ ...(call.namespace ? { namespace: call.namespace } : {}),
465
+ };
466
+ assistantHolderWithReasoning().content.push(toolCall);
467
+ continue;
468
+ }
469
+
470
+ if (effectiveType === "custom_tool_call") {
471
+ const call = item as { id?: string; call_id: string; name: string; input: string };
472
+ const toolCall: OcxToolCall = {
473
+ type: "toolCall", id: call.call_id, name: call.name,
474
+ arguments: { input: call.input ?? "" },
475
+ customWireName: call.name,
476
+ };
477
+ assistantHolderWithReasoning().content.push(toolCall);
478
+ continue;
479
+ }
480
+
481
+ if (effectiveType === "local_shell_call") {
482
+ // codex-rs LocalShellCall replay: pair it as an assistant toolCall so the subsequent
483
+ // function_call_output (same call_id) doesn't become an orphaned tool result.
484
+ const call = item as { id?: string; call_id?: string; action?: { type?: string; command?: string[] } };
485
+ const callId = call.call_id ?? call.id;
486
+ if (callId) {
487
+ const command = Array.isArray(call.action?.command) ? call.action.command : [];
488
+ assistantHolderWithReasoning().content.push({
489
+ type: "toolCall", id: callId, name: "shell",
490
+ arguments: command.length > 0 ? { command } : {},
491
+ });
492
+ }
493
+ continue;
494
+ }
495
+
496
+ if (effectiveType === "web_search_call") {
497
+ // Replayed hosted web-search evidence has no paired result payload that routed providers can
498
+ // consume. Keep it out of assistant-visible text: the old marker was useful as an internal
499
+ // loop hint, but when no sidecar is available the model can echo it as a fake answer.
500
+ pendingReasoning.length = 0;
501
+ continue;
502
+ }
503
+
504
+ if (effectiveType === "tool_search_call") {
505
+ // Preserve the model's prior tool_search call as an assistant tool call so multi-turn
506
+ // history stays complete (otherwise the model re-issues tool_search forever).
507
+ const call = item as { id?: string; call_id?: string; arguments?: unknown };
508
+ const callId = call.call_id ?? call.id ?? "";
509
+ assistantHolderWithReasoning().content.push({
510
+ type: "toolCall", id: callId, name: "tool_search",
511
+ arguments: isObj(call.arguments) ? call.arguments : {},
512
+ });
513
+ continue;
514
+ }
515
+
516
+ if (effectiveType === "tool_search_output") {
517
+ pendingReasoning.length = 0;
518
+ // Pair the tool_search call with its result so the model sees what was loaded.
519
+ const out = item as { call_id?: string; status?: string; tools?: unknown[] };
520
+ const specs = Array.isArray(out.tools) ? (out.tools as Record<string, unknown>[]) : [];
521
+ loadedToolSpecs.push(...specs);
522
+ // List the EXACT wire names the model must call (flattened for namespaced specs), matching
523
+ // how buildTools exposes them — otherwise the model guesses wrong names (e.g. the bare namespace).
524
+ const wireNames: string[] = [];
525
+ for (const spec of specs) {
526
+ if (spec.type === "namespace" && Array.isArray(spec.tools)) {
527
+ for (const inner of spec.tools as Record<string, unknown>[]) {
528
+ if (typeof inner.name === "string") wireNames.push(namespacedToolName(spec.name as string, inner.name));
529
+ }
530
+ } else if (typeof spec.name === "string") {
531
+ wireNames.push(spec.name);
532
+ }
533
+ }
534
+ const failed = typeof out.status === "string" && out.status !== "completed" && out.status !== "success";
535
+ messages.push({
536
+ role: "toolResult", toolCallId: out.call_id ?? "", toolName: "tool_search",
537
+ content: failed && wireNames.length === 0
538
+ ? `Tool search failed (status: ${out.status}).`
539
+ : wireNames.length
540
+ ? `Tool search loaded these tools — they are now in your available tools. Call one by its EXACT name: ${wireNames.join(", ")}.`
541
+ : "Tool search returned no tools.",
542
+ isError: failed && wireNames.length === 0, timestamp: now,
543
+ });
544
+ continue;
545
+ }
546
+
547
+ if (effectiveType === "function_call_output") {
548
+ pendingReasoning.length = 0;
549
+ const output = item as { call_id: string; output?: string | unknown[] };
550
+ const toolInfo = findToolById(messages, output.call_id);
551
+ messages.push({
552
+ role: "toolResult", toolCallId: output.call_id,
553
+ toolName: toolInfo.name, toolNamespace: toolInfo.namespace,
554
+ content: outputToToolResultContent(output.output), isError: false, timestamp: now,
555
+ ...(toolOutputContainsEncryptedContent(output.output) ? { containsEncryptedContent: true } : {}),
556
+ });
557
+ continue;
558
+ }
559
+
560
+ if (effectiveType === "custom_tool_call_output") {
561
+ pendingReasoning.length = 0;
562
+ const output = item as { call_id: string; output: string | unknown[] };
563
+ const toolInfo = findToolById(messages, output.call_id);
564
+ messages.push({
565
+ role: "toolResult", toolCallId: output.call_id,
566
+ toolName: toolInfo.name, toolNamespace: toolInfo.namespace,
567
+ // Same payload shape as function_call_output (codex-rs FunctionCallOutputPayload):
568
+ // string or content items — normalize arrays instead of leaking raw wire blocks.
569
+ content: outputToToolResultContent(output.output), isError: false, timestamp: now,
570
+ ...(toolOutputContainsEncryptedContent(output.output) ? { containsEncryptedContent: true } : {}),
571
+ });
572
+ }
573
+ }
574
+ }
575
+
576
+ const declaredTools = buildTools(data.tools as unknown[] | undefined) ?? [];
577
+ const loadedTools = buildTools(loadedToolSpecs) ?? [];
578
+ const loadedToolNames = new Set(loadedTools.map(t => namespacedToolName(t.namespace, t.name)));
579
+ const seenTools = new Set<string>();
580
+ const mergedTools = [...declaredTools, ...loadedTools]
581
+ .filter(t => {
582
+ const k = namespacedToolName(t.namespace, t.name);
583
+ if (seenTools.has(k)) return false;
584
+ seenTools.add(k);
585
+ return true;
586
+ })
587
+ .map(t => loadedToolNames.has(namespacedToolName(t.namespace, t.name))
588
+ ? { ...t, loadedFromToolSearch: true }
589
+ : t);
590
+ const context: OcxContext = {
591
+ ...(systemPrompt.length > 0 ? { systemPrompt } : {}),
592
+ messages,
593
+ ...(mergedTools.length > 0 ? { tools: mergedTools } : {}),
594
+ };
595
+
596
+ const options: OcxRequestOptions = {};
597
+ if (data.max_output_tokens !== undefined) options.maxOutputTokens = data.max_output_tokens;
598
+ if (data.temperature !== undefined) options.temperature = data.temperature;
599
+ if (data.top_p !== undefined) options.topP = data.top_p;
600
+ if (data.stop !== undefined && data.stop !== null) {
601
+ options.stopSequences = typeof data.stop === "string" ? [data.stop] : data.stop;
602
+ }
603
+ const tc = mapToolChoice(data.tool_choice);
604
+ if (tc !== undefined) options.toolChoice = tc;
605
+ if (data.parallel_tool_calls !== undefined) options.parallelToolCalls = data.parallel_tool_calls;
606
+ // Upstream codex-rs converts "ultra" to "max" at the inference boundary (core/src/client.rs
607
+ // `reasoning_effort_for_request`), so current clients never send it — but a catalog that
608
+ // advertises ultra plus an older/direct caller can. Degrade it to max like upstream instead of
609
+ // silently dropping reasoning altogether.
610
+ const requestedEffort = data.reasoning?.effort === "ultra" ? "max" : data.reasoning?.effort;
611
+ if (requestedEffort && REASONING_EFFORTS.has(requestedEffort)) {
612
+ options.reasoning = requestedEffort;
613
+ }
614
+ const summaryMode = data.reasoning?.summary;
615
+ if (!summaryMode || summaryMode === "none") options.hideThinkingSummary = true;
616
+ if (data.presence_penalty !== undefined) options.presencePenalty = data.presence_penalty;
617
+ if (data.frequency_penalty !== undefined) options.frequencyPenalty = data.frequency_penalty;
618
+ if (data.service_tier !== undefined) options.serviceTier = data.service_tier;
619
+ if (data.prompt_cache_key !== undefined) options.promptCacheKey = data.prompt_cache_key;
620
+
621
+ // Stash the hosted web_search config (if Codex enabled it) so the proxy can run searches via the
622
+ // gpt-mini sidecar for routed providers. buildTools still drops the hosted tool; the sidecar path
623
+ // re-injects a synthetic function tool only when it will actually handle the call.
624
+ const webSearch = extractHostedWebSearch(data.tools as unknown[] | undefined);
625
+ const imageGen = extractHostedImageGeneration([
626
+ ...(data.tools as unknown[] ?? []),
627
+ ...loadedToolSpecs,
628
+ ]);
629
+ // Detect structured-output mode (Responses `text.format`) so the web-search sidecar can render its
630
+ // tool_result as JSON rather than prose that could corrupt the model's schema-constrained answer.
631
+ const structuredOutput = detectStructuredOutput(data.text);
632
+
633
+ return {
634
+ modelId: data.model,
635
+ ...(data.previous_response_id ? { previousResponseId: data.previous_response_id } : {}),
636
+ context,
637
+ stream: data.stream === true,
638
+ options,
639
+ _rawBody: body,
640
+ ...(replayedInputPrefixLength > 0 ? { _replayPrefixLen: replayedInputPrefixLength } : {}),
641
+ ...(webSearch ? { _webSearch: webSearch } : {}),
642
+ ...(imageGen ? { _imageGeneration: imageGen } : {}),
643
+ ...(structuredOutput ? { _structuredOutput: true } : {}),
644
+ ...(compactionRequest ? { _compactionRequest: true } : {}),
645
+ ...(contextCompactionBoundary ? { _contextCompactionBoundary: true } : {}),
646
+ };
647
+ }
648
+
649
+ /** True when the Responses `text.format` requests structured output (json_schema or json_object). */
650
+ function detectStructuredOutput(text: unknown): boolean {
651
+ if (!isObj(text)) return false;
652
+ const format = (text as { format?: unknown }).format;
653
+ if (!isObj(format)) return false;
654
+ const t = (format as { type?: unknown }).type;
655
+ return t === "json_schema" || t === "json_object";
656
+ }