@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,378 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync } from "node:fs";
4
+ import { delimiter, dirname, join, resolve } from "node:path";
5
+ import { atomicWriteFile, expandUserPath, getConfigDir, websocketsEnabled } from "../../config";
6
+ import { CODEX_CONFIG_PATH, CODEX_MODELS_CACHE_PATH, DEFAULT_CATALOG_PATH, readRootTomlString, resolveCodexConfigPath } from "../paths";
7
+ import { clearModelCache, DEFAULT_MODEL_CACHE_TTL_MS, getFreshCached, getStaleCached, isModelsFetchCoolingDown, markModelsFetchFailure, setCached } from "../model-cache";
8
+ import { buildModelsRequest, resolveModelsAuthToken } from "../../oauth";
9
+ import type { OcxConfig, OcxProviderConfig } from "../../types";
10
+ import { modelInList } from "../../types";
11
+ import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, modelRecordValue, sanitizeCodexReasoningEfforts } from "../../reasoning-effort";
12
+ import { getJawcodeModelMetadata, getJawcodeModelMetadataCaseInsensitive, listJawcodeModelMetadata, resolveJawcodeProvider } from "../../generated/jawcode-model-metadata";
13
+ import { enrichProviderFromRegistry, shouldCaseFoldMetadataModelId } from "../../providers/derive";
14
+ import { getProviderRegistryEntry } from "../../providers/registry";
15
+ import { applyProviderContextCap, providerContextCap } from "../../providers/context-cap";
16
+ import { routedSlug, slugEquals, slugsEquivalent } from "../../providers/slug-codec";
17
+ import { CODEX_GPT5_IDENTITY_LINE } from "../../adapters/identity";
18
+ import { filterCursorConfiguredModelsByLiveDiscovery } from "../../adapters/cursor/discovery";
19
+ import { fetchCursorUsableModels } from "../../adapters/cursor/live-models";
20
+ import { isCanonicalOpenAiForwardProvider, OPENAI_API_PROVIDER_ID, OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
21
+ import {
22
+ COMBO_NAMESPACE,
23
+ comboModelId,
24
+ getCombo,
25
+ listComboIds,
26
+ targetKey,
27
+ } from "../../combos";
28
+ import type { NormalizedComboConfig } from "../../combos/types";
29
+ import { providerDestinationResolvedError } from "../../lib/destination-policy";
30
+ import { redactSecretString } from "../../lib/redact";
31
+ import upstreamModelsSnapshot from "../data/upstream-models.json";
32
+
33
+
34
+ import { catalogModelSlug } from "./parsing";
35
+ import type { CatalogModel } from "./parsing";
36
+
37
+ export const openAiApiCollisionWarnings = new Set<string>();
38
+
39
+ export const comboCatalogWarningSignatures = new Map<string, string>();
40
+
41
+ /**
42
+ * Why `deriveComboCatalogModel` rejected a configured combo (#484 / #516).
43
+ * Distinguishes unresolved/incomplete member metadata from a complete but empty
44
+ * modality intersection so diagnostics do not send operators to the wrong fix.
45
+ */
46
+ export type ComboCatalogOmissionReason =
47
+ | "incomplete_metadata"
48
+ | "incompatible_modalities";
49
+
50
+ /** Combos omitted from the catalog during the most recent `gatherRoutedModels` call (#484). */
51
+ export interface ComboCatalogOmission {
52
+ id: string;
53
+ targets: string[];
54
+ reason: ComboCatalogOmissionReason;
55
+ message: string;
56
+ }
57
+
58
+ let lastComboCatalogOmissions: ComboCatalogOmission[] = [];
59
+
60
+ export function clearLastComboCatalogOmissions(): void {
61
+ lastComboCatalogOmissions = [];
62
+ }
63
+
64
+ export function getLastComboCatalogOmissions(): readonly ComboCatalogOmission[] {
65
+ return lastComboCatalogOmissions;
66
+ }
67
+
68
+ export function replaceLastComboCatalogOmissions(items: readonly ComboCatalogOmission[]): void {
69
+ lastComboCatalogOmissions = [...items];
70
+ }
71
+
72
+ export function intersectStrings(values: readonly string[][]): string[] {
73
+ if (values.length === 0) return [];
74
+ const rest = values.slice(1).map(value => new Set(value));
75
+ return [...new Set(values[0])].filter(value => rest.every(set => set.has(value)));
76
+ }
77
+
78
+ export function effectiveComboDefault(
79
+ configured: string | null | undefined,
80
+ common: readonly string[],
81
+ ): string | undefined {
82
+ if (!configured) return undefined;
83
+ if (configured && common.includes(configured)) return configured;
84
+ const requestedRank = codexEffortRank(configured);
85
+ const ranked = common
86
+ .map(effort => ({ effort, rank: codexEffortRank(effort) }))
87
+ .filter(item => item.rank >= 0)
88
+ .sort((a, b) => a.rank - b.rank);
89
+ if (ranked.length === 0) return undefined;
90
+ const atOrBelow = ranked.filter(item => item.rank <= requestedRank);
91
+ return atOrBelow.at(-1)?.effort ?? ranked[0]!.effort;
92
+ }
93
+
94
+ /**
95
+ * Classify a combo derivation failure. Returns `null` when the combo would catalog.
96
+ * Callers that already know derivation failed can map the reason into diagnostics.
97
+ */
98
+ export function comboCatalogOmissionReason(
99
+ combo: NormalizedComboConfig,
100
+ members: readonly CatalogModel[],
101
+ ): ComboCatalogOmissionReason | null {
102
+ if (combo.targets.length === 0) return "incomplete_metadata";
103
+ if (new Set(combo.targets.map(targetKey)).size !== combo.targets.length) {
104
+ return "incomplete_metadata";
105
+ }
106
+ if (members.length !== combo.targets.length) return "incomplete_metadata";
107
+ if (!members.every((member, index) => (
108
+ `${member.provider}/${member.id}` === targetKey(combo.targets[index]!)
109
+ ))) {
110
+ return "incomplete_metadata";
111
+ }
112
+ const contexts = members.map(member => member.contextWindow);
113
+ if (contexts.some(value => typeof value !== "number" || value <= 0)) {
114
+ return "incomplete_metadata";
115
+ }
116
+
117
+ const inputModalities = intersectStrings(
118
+ members.map(member => member.inputModalities ?? ["text"]),
119
+ );
120
+ if (inputModalities.length === 0) return "incompatible_modalities";
121
+ return null;
122
+ }
123
+
124
+ export function deriveComboCatalogModel(
125
+ id: string,
126
+ combo: NormalizedComboConfig,
127
+ members: readonly CatalogModel[],
128
+ ): CatalogModel | null {
129
+ if (comboCatalogOmissionReason(combo, members) !== null) return null;
130
+
131
+ const inputModalities = intersectStrings(
132
+ members.map(member => member.inputModalities ?? ["text"]),
133
+ );
134
+ const reasoningEfforts = intersectStrings(
135
+ members.map(member => member.reasoningEfforts ?? []),
136
+ );
137
+ const contextWindow = Math.min(...members.map(member => member.contextWindow!));
138
+ const maxInputTokens = Math.min(
139
+ ...members.map(member => member.maxInputTokens ?? member.contextWindow!),
140
+ );
141
+ const defaultReasoningEffort = effectiveComboDefault(
142
+ combo.defaultEffort,
143
+ reasoningEfforts,
144
+ );
145
+
146
+ return {
147
+ provider: COMBO_NAMESPACE,
148
+ id,
149
+ owned_by: COMBO_NAMESPACE,
150
+ contextWindow,
151
+ maxInputTokens,
152
+ inputModalities,
153
+ reasoningEfforts,
154
+ ...(combo.alias ? { alias: combo.alias } : {}),
155
+ ...(defaultReasoningEffort ? { defaultReasoningEffort } : {}),
156
+ ...(members.every(member => member.parallelToolCalls === true)
157
+ ? { parallelToolCalls: true }
158
+ : {}),
159
+ ...(members.some(member => member.supportsReasoningSummaries === false) ? { supportsReasoningSummaries: false } : {}),
160
+ };
161
+ }
162
+
163
+ export function safeCatalogWarningLabel(value: string): string {
164
+ return redactSecretString(value)
165
+ .replace(/[\u0000-\u001f\u007f]/g, "?")
166
+ .slice(0, 200);
167
+ }
168
+
169
+ export function comboCatalogWarningSignature(
170
+ combo: NormalizedComboConfig,
171
+ members: readonly CatalogModel[],
172
+ ): string {
173
+ const discovered = new Map<string, CatalogModel>(members.map(member => [
174
+ `${member.provider}/${member.id}`,
175
+ member,
176
+ ] as const));
177
+ return JSON.stringify(combo.targets.map(target => {
178
+ const key = targetKey(target);
179
+ const member = discovered.get(key);
180
+ return {
181
+ key,
182
+ contextWindow: member?.contextWindow ?? null,
183
+ maxInputTokens: member?.maxInputTokens ?? null,
184
+ inputModalities: [...new Set(member?.inputModalities ?? [])].sort(),
185
+ reasoningEfforts: [...new Set(member?.reasoningEfforts ?? [])].sort(),
186
+ parallelToolCalls: member?.parallelToolCalls === true,
187
+ supportsReasoningSummaries: member?.supportsReasoningSummaries !== false,
188
+ };
189
+ }).sort((a, b) => a.key.localeCompare(b.key)));
190
+ }
191
+
192
+ export function comboCatalogOmissionDetail(reason: ComboCatalogOmissionReason): string {
193
+ return reason === "incompatible_modalities"
194
+ ? "members have no common input modalities"
195
+ : "member capabilities are incomplete";
196
+ }
197
+
198
+ /** One-line sync/CLI summary that respects the actual omission reason(s). */
199
+ export function summarizeComboCatalogOmissions(
200
+ omissions: readonly Pick<ComboCatalogOmission, "reason">[],
201
+ ): string {
202
+ const n = omissions.length;
203
+ const prefix = `${n} combo${n === 1 ? "" : "s"} omitted from the catalog`;
204
+ if (n === 0) return prefix + ".";
205
+ const reasons = new Set(omissions.map(item => item.reason));
206
+ if (reasons.size === 1) {
207
+ return `${prefix} because ${comboCatalogOmissionDetail([...reasons][0]!)}.`;
208
+ }
209
+ return `${prefix}.`;
210
+ }
211
+
212
+ export function buildComboCatalogOmission(
213
+ id: string,
214
+ combo: NormalizedComboConfig,
215
+ members: readonly CatalogModel[],
216
+ ): ComboCatalogOmission {
217
+ const targets = combo.targets
218
+ .map(target => safeCatalogWarningLabel(targetKey(target)))
219
+ .sort((a, b) => a.localeCompare(b));
220
+ const reason = comboCatalogOmissionReason(combo, members) ?? "incomplete_metadata";
221
+ return {
222
+ id,
223
+ targets,
224
+ reason,
225
+ message: `[opencodex] Combo "${safeCatalogWarningLabel(id)}" is omitted from the catalog because ${comboCatalogOmissionDetail(reason)}: ${targets.join(", ")}.`,
226
+ };
227
+ }
228
+
229
+ /**
230
+ * Record a combo omitted from `/v1/models` + on-disk catalog and warn once per signature.
231
+ * Callers that need a race-free list (catalog sync) pass a local `sink` from the same
232
+ * gather invocation instead of reading process-global state afterward (#484 review).
233
+ */
234
+ export function warnUncataloguedComboOnce(
235
+ id: string,
236
+ combo: NormalizedComboConfig,
237
+ members: readonly CatalogModel[],
238
+ sink: ComboCatalogOmission[] = lastComboCatalogOmissions,
239
+ ): ComboCatalogOmission {
240
+ const omission = buildComboCatalogOmission(id, combo, members);
241
+ sink.push(omission);
242
+ const signature = comboCatalogWarningSignature(combo, members);
243
+ if (comboCatalogWarningSignatures.get(id) !== signature) {
244
+ comboCatalogWarningSignatures.set(id, signature);
245
+ console.warn(omission.message);
246
+ }
247
+ return omission;
248
+ }
249
+
250
+ export function exactComboCatalogSlugs(
251
+ config: Pick<OcxConfig, "combos" | "disabledModels">,
252
+ ): Set<string> {
253
+ const disabled = new Set(config.disabledModels ?? []);
254
+ return new Set(listComboIds(config).flatMap(id => {
255
+ const alias = typeof config.combos?.[id]?.alias === "string"
256
+ ? config.combos[id]!.alias!.trim()
257
+ : "";
258
+ const canonical = comboModelId(id);
259
+ const publicSlug = alias || canonical;
260
+ return disabled.has(publicSlug) || disabled.has(canonical) ? [] : [publicSlug];
261
+ }));
262
+ }
263
+
264
+ export function normalizedOpenAiApiSignature(model: CatalogModel): string {
265
+ const normalized = {
266
+ provider: model.provider,
267
+ id: model.id,
268
+ contextWindow: model.contextWindow ?? null,
269
+ maxInputTokens: model.maxInputTokens ?? null,
270
+ inputModalities: [...new Set(model.inputModalities ?? [])].sort(),
271
+ reasoningEfforts: [...new Set(model.reasoningEfforts ?? [])].sort(),
272
+ ownedBy: model.owned_by ?? null,
273
+ };
274
+ return JSON.stringify(normalized);
275
+ }
276
+
277
+ export function resetOpenAiApiCatalogWarningStateForTests(): void {
278
+ openAiApiCollisionWarnings.clear();
279
+ }
280
+
281
+ export const slugAliasCollisionWarnings = new Set<string>();
282
+
283
+ export const comboMasqueradeCollisionWarnings = new Set<string>();
284
+ let lastWarningReconciledGeneration = 0;
285
+
286
+ export function reconcileCatalogWarningMemos(generation: number): number {
287
+ if (generation <= lastWarningReconciledGeneration) return 0;
288
+ const removed = openAiApiCollisionWarnings.size
289
+ + comboCatalogWarningSignatures.size
290
+ + slugAliasCollisionWarnings.size
291
+ + comboMasqueradeCollisionWarnings.size;
292
+ openAiApiCollisionWarnings.clear();
293
+ comboCatalogWarningSignatures.clear();
294
+ slugAliasCollisionWarnings.clear();
295
+ comboMasqueradeCollisionWarnings.clear();
296
+ lastWarningReconciledGeneration = generation;
297
+ return removed;
298
+ }
299
+
300
+ export function warnComboMasqueradeCollisionOnce(slug: string): void {
301
+ if (comboMasqueradeCollisionWarnings.has(slug)) return;
302
+ comboMasqueradeCollisionWarnings.add(slug);
303
+ console.warn(
304
+ `[opencodex] combo alias collision on "${safeCatalogWarningLabel(slug)}": the combo wins and the shadowed provider model is omitted from the catalog.`,
305
+ );
306
+ }
307
+
308
+ export function resolveSlugAliasCollisions(goModels: CatalogModel[]): Set<CatalogModel> {
309
+ const skipped = new Set<CatalogModel>();
310
+ const winnerByAlias = new Map<string, CatalogModel>();
311
+ for (const m of goModels) {
312
+ // Combo aliases have their own collision policy below: they always shadow provider rows.
313
+ if (m.provider === COMBO_NAMESPACE) continue;
314
+ const key = catalogModelSlug(m);
315
+ const winner = winnerByAlias.get(key);
316
+ if (!winner) {
317
+ winnerByAlias.set(key, m);
318
+ continue;
319
+ }
320
+ const winnerIsPlainAlias = !winner.id.includes("/");
321
+ const currentIsPlainAlias = !m.id.includes("/");
322
+ if (currentIsPlainAlias && !winnerIsPlainAlias) {
323
+ skipped.add(winner);
324
+ winnerByAlias.set(key, m);
325
+ } else {
326
+ skipped.add(m);
327
+ }
328
+ if (!slugAliasCollisionWarnings.has(key)) {
329
+ slugAliasCollisionWarnings.add(key);
330
+ console.warn(
331
+ `[opencodex] slug alias collision on "${key}": multiple native ids encode to the same Codex-facing slug; `
332
+ + "the plain-hyphen native id is cataloged, the slash id remains callable via its raw selector.",
333
+ );
334
+ }
335
+ }
336
+ return skipped;
337
+ }
338
+
339
+ export function uniqueCatalogModelsForPublicList(goModels: CatalogModel[]): CatalogModel[] {
340
+ const collisionSkipped = resolveSlugAliasCollisions(goModels);
341
+ const comboPublicSlugs = new Set(goModels
342
+ .filter(model => model.provider === COMBO_NAMESPACE)
343
+ .map(catalogModelSlug));
344
+ const seen = new Set<string>();
345
+ const out: CatalogModel[] = [];
346
+ for (const model of goModels) {
347
+ if (collisionSkipped.has(model)) continue;
348
+ const slug = catalogModelSlug(model);
349
+ if (model.provider !== COMBO_NAMESPACE && comboPublicSlugs.has(slug)) {
350
+ warnComboMasqueradeCollisionOnce(slug);
351
+ continue;
352
+ }
353
+ if (seen.has(slug)) continue;
354
+ seen.add(slug);
355
+ out.push(model);
356
+ }
357
+ return out;
358
+ }
359
+
360
+ export function uniqueCatalogModelsForRawPublicList(goModels: CatalogModel[]): CatalogModel[] {
361
+ const publicId = (model: CatalogModel): string => model.alias ?? `${model.provider}/${model.id}`;
362
+ const comboPublicIds = new Set(goModels
363
+ .filter(model => model.provider === COMBO_NAMESPACE)
364
+ .map(publicId));
365
+ const seen = new Set<string>();
366
+ const out: CatalogModel[] = [];
367
+ for (const model of goModels) {
368
+ const id = publicId(model);
369
+ if (model.provider !== COMBO_NAMESPACE && comboPublicIds.has(id)) {
370
+ warnComboMasqueradeCollisionOnce(id);
371
+ continue;
372
+ }
373
+ if (seen.has(id)) continue;
374
+ seen.add(id);
375
+ out.push(model);
376
+ }
377
+ return out;
378
+ }
@@ -0,0 +1,251 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync } from "node:fs";
4
+ import { delimiter, dirname, join, resolve } from "node:path";
5
+ import { atomicWriteFile, expandUserPath, getConfigDir, websocketsEnabled } from "../../config";
6
+ import { CODEX_CONFIG_PATH, CODEX_MODELS_CACHE_PATH, DEFAULT_CATALOG_PATH, readRootTomlString, resolveCodexConfigPath } from "../paths";
7
+ import { clearModelCache, DEFAULT_MODEL_CACHE_TTL_MS, getFreshCached, getStaleCached, isModelsFetchCoolingDown, markModelsFetchFailure, setCached } from "../model-cache";
8
+ import { buildModelsRequest, resolveModelsAuthToken } from "../../oauth";
9
+ import type { OcxConfig, OcxProviderConfig } from "../../types";
10
+ import { modelInList } from "../../types";
11
+ import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, modelRecordValue, sanitizeCodexReasoningEfforts } from "../../reasoning-effort";
12
+ import { getJawcodeModelMetadata, getJawcodeModelMetadataCaseInsensitive, listJawcodeModelMetadata, resolveJawcodeProvider } from "../../generated/jawcode-model-metadata";
13
+ import { enrichProviderFromRegistry, shouldCaseFoldMetadataModelId } from "../../providers/derive";
14
+ import { getProviderRegistryEntry } from "../../providers/registry";
15
+ import { applyProviderContextCap, providerContextCap } from "../../providers/context-cap";
16
+ import { routedSlug, slugEquals, slugsEquivalent } from "../../providers/slug-codec";
17
+ import { CODEX_GPT5_IDENTITY_LINE } from "../../adapters/identity";
18
+ import { filterCursorConfiguredModelsByLiveDiscovery } from "../../adapters/cursor/discovery";
19
+ import { fetchCursorUsableModels } from "../../adapters/cursor/live-models";
20
+ import { isCanonicalOpenAiForwardProvider, OPENAI_API_PROVIDER_ID, OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
21
+ import {
22
+ COMBO_NAMESPACE,
23
+ comboModelId,
24
+ getCombo,
25
+ listComboIds,
26
+ targetKey,
27
+ } from "../../combos";
28
+ import type { NormalizedComboConfig } from "../../combos/types";
29
+ import { providerDestinationResolvedError } from "../../lib/destination-policy";
30
+ import { redactSecretString } from "../../lib/redact";
31
+ import upstreamModelsSnapshot from "../data/upstream-models.json";
32
+
33
+
34
+ import { activeCodexModelsCachePath, catalogBackupPathFor, findNativeTemplate, isDefaultCatalogPath, legacyCatalogBackupPath, parseCatalogJson, readCatalog, readCatalogBackup, readCodexCatalogPath } from "./parsing";
35
+ import type { RawCatalog, RawEntry } from "./parsing";
36
+ import { codexExecInvocation, isSpawnableCodexCandidate } from "../exec-invocation";
37
+ import { resolveAndPersistCodexRuntime } from "../runtime";
38
+ import type { EffortClampDiagnostic } from "../runtime";
39
+
40
+ export { isSpawnableCodexCandidate, codexExecInvocation } from "../exec-invocation";
41
+
42
+ export const BUNDLED_CATALOG_CACHE_MS = 60_000;
43
+
44
+ export let bundledCatalogCache: {
45
+ /** Selected runtime identity; must change when doctor/sync picks a different binary. */
46
+ key: string;
47
+ expiresAt: number;
48
+ value: RawCatalog | null;
49
+ } | null = null;
50
+
51
+ /** Test-only: clear the bundled-catalog cache (owned here; sync.ts calls this instead of assigning the import). */
52
+ export function resetBundledCatalogCacheForTests(): void {
53
+ bundledCatalogCache = null;
54
+ }
55
+
56
+ /** Drop the process-local bundled catalog memo (e.g. after runtime selection changes). */
57
+ export function invalidateBundledCatalogCache(): void {
58
+ bundledCatalogCache = null;
59
+ }
60
+
61
+ export type ExecFile = (
62
+ file: string,
63
+ args: string[],
64
+ options: {
65
+ encoding: "utf8";
66
+ stdio: ["ignore", "pipe", "ignore"];
67
+ timeout: number;
68
+ windowsHide: boolean;
69
+ shell?: boolean;
70
+ windowsVerbatimArguments?: boolean;
71
+ },
72
+ ) => string;
73
+
74
+ export interface BundledCatalogDeps {
75
+ commandCandidates?: () => string[];
76
+ execFileSync?: ExecFile;
77
+ onEffortClamp?: (diagnostic: EffortClampDiagnostic) => void;
78
+ configDir?: string;
79
+ env?: NodeJS.ProcessEnv;
80
+ platform?: NodeJS.Platform;
81
+ existsSync?: (path: string) => boolean;
82
+ readFileSync?: (path: string, encoding: "utf8") => string;
83
+ now?: () => number;
84
+ discoverAlternatives?: boolean;
85
+ }
86
+
87
+ export function unique(values: string[]): string[] {
88
+ return [...new Set(values.filter(Boolean))];
89
+ }
90
+
91
+ export function codexCommandCandidates(): string[] {
92
+ const envPath = process.env.CODEX_CLI_PATH?.trim();
93
+ const candidates = envPath ? [envPath] : [];
94
+ candidates.push(...codexShimCommandCandidates());
95
+ if (process.platform === "win32") {
96
+ for (const dir of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
97
+ candidates.push(join(dir, "codex.exe"), join(dir, "codex.cmd"));
98
+ }
99
+ }
100
+ candidates.push("codex");
101
+ return unique(candidates);
102
+ }
103
+
104
+ export function codexShimCommandCandidates(): string[] {
105
+ try {
106
+ const state = JSON.parse(readFileSync(join(getConfigDir(), "codex-shim.json"), "utf8")) as {
107
+ wrapperPath?: unknown;
108
+ originalPath?: unknown;
109
+ backupPath?: unknown;
110
+ wrappers?: Array<{ wrapperPath?: unknown; originalPath?: unknown; backupPath?: unknown }>;
111
+ };
112
+ const files = Array.isArray(state.wrappers) && state.wrappers.length > 0 ? state.wrappers : [state];
113
+ const out: string[] = [];
114
+ for (const file of files) {
115
+ for (const value of [file.backupPath, file.originalPath, file.wrapperPath]) {
116
+ if (typeof value !== "string" || value.length === 0) continue;
117
+ if (!isSpawnableCodexCandidate(value)) continue;
118
+ out.push(value);
119
+ }
120
+ }
121
+ return out;
122
+ } catch {
123
+ return [];
124
+ }
125
+ }
126
+
127
+ export function runCodexDebugModels(
128
+ command: string,
129
+ execFile: ExecFile,
130
+ deps: Pick<BundledCatalogDeps, "env" | "platform" | "existsSync"> = {},
131
+ ): string {
132
+ const args = ["debug", "models", "--bundled"];
133
+ const invocation = codexExecInvocation(command, args, deps.platform ?? process.platform, {
134
+ env: deps.env,
135
+ exists: deps.existsSync,
136
+ });
137
+ return execFile(invocation.file, invocation.args, {
138
+ encoding: "utf8" as const,
139
+ stdio: ["ignore", "pipe", "ignore"] as ["ignore", "pipe", "ignore"],
140
+ timeout: 10_000,
141
+ windowsHide: true,
142
+ ...invocation.options,
143
+ });
144
+ }
145
+
146
+ export function loadBundledCodexCatalog(deps: BundledCatalogDeps = {}): RawCatalog | null {
147
+ const useCache = !deps.commandCandidates && !deps.execFileSync && !deps.configDir && !deps.env;
148
+ const execFile = deps.execFileSync ?? (execFileSync as unknown as ExecFile);
149
+ // Prefer the single resolved runtime so sync/clamp never probe a different binary
150
+ // than OpenCodex will launch. Tests may inject commandCandidates to stub probing.
151
+ let cacheKey: string | null = null;
152
+ const candidates = deps.commandCandidates?.() ?? (() => {
153
+ const resolved = resolveAndPersistCodexRuntime({
154
+ // Forward an INJECTED execFileSync only. Passing the real one unconditionally made
155
+ // resolveCacheKey() bail out (it refuses to memoize injected-dep resolves), so every
156
+ // catalog read re-ran the ~1s `codex --version` probe even on a warm cache hit.
157
+ ...(deps.execFileSync ? { execFileSync: deps.execFileSync } : {}),
158
+ configDir: deps.configDir,
159
+ env: deps.env,
160
+ platform: deps.platform,
161
+ existsSync: deps.existsSync,
162
+ readFileSync: deps.readFileSync,
163
+ now: deps.now,
164
+ // Catalog loading only consumes `resolved.runtime.command`, never `newerAvailable`.
165
+ // Full PATH discovery probes every candidate launcher (100+ on a dev machine, ~1.2s),
166
+ // which alone can exceed the 3s budget `ocx claude` allows /api/claude-code. Priority
167
+ // selection is identical either way; callers wanting discovery diagnostics opt in.
168
+ discoverAlternatives: deps.discoverAlternatives ?? false,
169
+ });
170
+ if (useCache) {
171
+ cacheKey = [
172
+ resolved.runtime.command,
173
+ resolved.runtime.version ?? "",
174
+ process.env.OPENCODEX_HOME ?? "",
175
+ ].join("\0");
176
+ }
177
+ return [resolved.runtime.command];
178
+ })();
179
+ if (
180
+ useCache
181
+ && cacheKey
182
+ && bundledCatalogCache
183
+ && bundledCatalogCache.key === cacheKey
184
+ && bundledCatalogCache.expiresAt > Date.now()
185
+ ) {
186
+ return bundledCatalogCache.value;
187
+ }
188
+ for (const command of unique(candidates)) {
189
+ try {
190
+ const catalog = parseCatalogJson(runCodexDebugModels(command, execFile, deps));
191
+ if (catalog && findNativeTemplate(catalog)) {
192
+ if (useCache && cacheKey) {
193
+ bundledCatalogCache = {
194
+ key: cacheKey,
195
+ expiresAt: Date.now() + BUNDLED_CATALOG_CACHE_MS,
196
+ value: catalog,
197
+ };
198
+ }
199
+ return catalog;
200
+ }
201
+ } catch { /* try next candidate */ }
202
+ }
203
+ if (useCache && cacheKey) {
204
+ bundledCatalogCache = {
205
+ key: cacheKey,
206
+ expiresAt: Date.now() + BUNDLED_CATALOG_CACHE_MS,
207
+ value: null,
208
+ };
209
+ }
210
+ return null;
211
+ }
212
+
213
+ export function materializeBundledCodexCatalog(path: string, deps: BundledCatalogDeps = {}): RawCatalog | null {
214
+ const catalog = loadBundledCodexCatalog(deps);
215
+ if (!catalog) return null;
216
+ try {
217
+ mkdirSync(dirname(path), { recursive: true });
218
+ atomicWriteFile(path, JSON.stringify(catalog, null, 2) + "\n");
219
+ } catch {
220
+ return null;
221
+ }
222
+ return catalog;
223
+ }
224
+
225
+ export function loadCatalogForSync(path: string): RawCatalog | null {
226
+ const bundled = isDefaultCatalogPath(path) ? loadBundledCodexCatalog() : null;
227
+ if (bundled) return JSON.parse(JSON.stringify(bundled)) as RawCatalog;
228
+ const catalog = readCatalog(path);
229
+ if (catalog && findNativeTemplate(catalog)) return catalog;
230
+ return readCatalog(catalogBackupPathFor(path))
231
+ ?? (isDefaultCatalogPath(path) ? readCatalog(legacyCatalogBackupPath()) : null)
232
+ ?? readCatalog(activeCodexModelsCachePath())
233
+ ?? materializeBundledCodexCatalog(path)
234
+ ?? catalog;
235
+ }
236
+
237
+ export function readCurrentCatalogOrCache(): RawCatalog | null {
238
+ const path = readCodexCatalogPath();
239
+ return (isDefaultCatalogPath(path) ? loadBundledCodexCatalog() : null)
240
+ ?? readCatalog(path)
241
+ ?? readCatalog(activeCodexModelsCachePath());
242
+ }
243
+
244
+ export function loadCatalogTemplate(): RawEntry | null {
245
+ const catalogPath = readCodexCatalogPath();
246
+ const native = findNativeTemplate(readCatalog(catalogPath))
247
+ ?? findNativeTemplate(readCatalogBackup(catalogPath))
248
+ ?? findNativeTemplate(readCatalog(activeCodexModelsCachePath()))
249
+ ?? findNativeTemplate(loadBundledCodexCatalog());
250
+ return native ? JSON.parse(JSON.stringify(native)) : null;
251
+ }