@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,620 @@
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, applyJawcodeCatalogMetadata, applyMultiAgentMode, applyNativeOpenAiContextOverride, catalogModelSlug, ensureCatalogBackup, ensureStrictCatalogFields, findNativeTemplate, isRoutedModelCompatibilityExcluded, normalizeRoutedCatalogEntry, normalizeServiceTiers, readCatalog, readCatalogBackup, readCodexCatalogPath, readNativeBaseline } from "./parsing";
35
+ import type { CatalogModel, MultiAgentMode, RawEntry } from "./parsing";
36
+ import { applyNativeVisibility, disabledNativeSlugs, isUnsupportedOpenAiNativeSlug, nativeOpenAiSlugs, shouldUpgradeToUpstreamEntry, upstreamNativeEntry } from "./metadata";
37
+ import { loadCatalogForSync, resetBundledCatalogCacheForTests } from "./bundled";
38
+ import { isMultiAgentV2Enabled } from "../features";
39
+ import { applyCatalogModelMetadata, applyReasoningLevels, catalogEntryEfforts, clampCatalogModelsToCodexSupport, ensureGpt56ReasoningLevels, ensureUltraReasoningLevel, isGpt56NativeSlug } from "./effort";
40
+ import { clearGatherRoutedModelsInflight, filterCatalogVisibleModels, gatherRoutedModels, lastDropWarnSignature } from "./provider-fetch";
41
+ import { clearLastComboCatalogOmissions, comboCatalogWarningSignatures, comboMasqueradeCollisionWarnings, exactComboCatalogSlugs, openAiApiCollisionWarnings, resolveSlugAliasCollisions, slugAliasCollisionWarnings, warnComboMasqueradeCollisionOnce } from "./aggregation";
42
+ import type { ComboCatalogOmission } from "./aggregation";
43
+
44
+ export const MAX_SPAWN_AGENT_MODEL_OVERRIDES = 5;
45
+
46
+ export type SpawnAgentSurface = "v1" | "v2";
47
+
48
+ export type SubagentRosterExclusionReason =
49
+ | "missing_catalog_entry"
50
+ | "picker_hidden"
51
+ | "surface_incompatible"
52
+ | "outside_display_limit";
53
+
54
+ /**
55
+ * Whether a catalog entry may be offered as a V2 subagent model.
56
+ *
57
+ * Upstream (codex-rs 92938d880) requires `multi_agent_version === "v2"` exactly,
58
+ * because upstream assumes a single backend serves every model. opencodex routes
59
+ * many providers, so that equality would reject the cross-provider spawns this
60
+ * proxy exists to enable.
61
+ *
62
+ * Decision (option B, devlog 260730_codex_rs_upstream_v2_live_handoff/060): any
63
+ * model opencodex actually routes is eligible. An entry pinned to a DIFFERENT
64
+ * multi-agent backend (`v1`) stays excluded, because that pin is a real capability
65
+ * statement rather than an absence of information. An unpinned entry (null or
66
+ * absent) is a routed or unpinned-native model and is allowed. The three-way
67
+ * distinction is the substance; do not flatten it into a truthiness check.
68
+ */
69
+ export function isEligibleV2SubagentEntry(entry: RawEntry): boolean {
70
+ const pinned = entry.multi_agent_version;
71
+ return pinned === "v2" || pinned === null || pinned === undefined;
72
+ }
73
+
74
+ export interface EffectiveSubagentModel {
75
+ model: string;
76
+ efforts: string[];
77
+ }
78
+
79
+ export interface SubagentRosterExclusion {
80
+ configured: string;
81
+ reason: SubagentRosterExclusionReason;
82
+ catalogModel?: string;
83
+ }
84
+
85
+ export interface EffectiveSubagentRoster {
86
+ candidates: EffectiveSubagentModel[];
87
+ advertised: EffectiveSubagentModel[];
88
+ excluded: SubagentRosterExclusion[];
89
+ }
90
+
91
+ export function configuredCatalogEntry(entries: RawEntry[], configured: string): RawEntry | undefined {
92
+ return entries.find(entry => entry.slug === configured)
93
+ ?? entries.find(entry => typeof entry.slug === "string" && slugsEquivalent(configured, entry.slug));
94
+ }
95
+
96
+ export function effectiveSubagentRoster(
97
+ configuredModels: readonly string[],
98
+ surface: SpawnAgentSurface,
99
+ ): EffectiveSubagentRoster {
100
+ const configured = configuredModels
101
+ .filter(model => model.trim().length > 0)
102
+ .filter((model, index, all) =>
103
+ !all.slice(0, index).some(previous => slugsEquivalent(previous, model))
104
+ );
105
+ const entries = readCatalog(readCodexCatalogPath())?.models ?? [];
106
+ const ordered = entries
107
+ .map((entry, index) => ({ entry, index }))
108
+ .filter(({ entry }) => typeof entry.slug === "string")
109
+ .filter(({ entry }) => entry.visibility === "list")
110
+ .filter(({ entry }) => surface !== "v2" || isEligibleV2SubagentEntry(entry))
111
+ .sort((left, right) => {
112
+ const leftPriority = typeof left.entry.priority === "number" && Number.isFinite(left.entry.priority)
113
+ ? left.entry.priority : Number.MAX_SAFE_INTEGER;
114
+ const rightPriority = typeof right.entry.priority === "number" && Number.isFinite(right.entry.priority)
115
+ ? right.entry.priority : Number.MAX_SAFE_INTEGER;
116
+ return leftPriority - rightPriority || left.index - right.index;
117
+ })
118
+ .slice(0, MAX_SPAWN_AGENT_MODEL_OVERRIDES);
119
+
120
+ const candidates = ordered.map(({ entry }) => ({
121
+ model: entry.slug as string,
122
+ efforts: catalogEntryEfforts(entry),
123
+ }));
124
+ const advertised = candidates.filter(candidate =>
125
+ configured.some(model => slugsEquivalent(model, candidate.model))
126
+ );
127
+ const excluded = configured.flatMap((model): SubagentRosterExclusion[] => {
128
+ const entry = configuredCatalogEntry(entries, model);
129
+ if (!entry) return [{ configured: model, reason: "missing_catalog_entry" }];
130
+ const catalogModel = entry.slug as string;
131
+ if (entry.visibility !== "list") {
132
+ return [{ configured: model, catalogModel, reason: "picker_hidden" }];
133
+ }
134
+ if (surface === "v2" && !isEligibleV2SubagentEntry(entry)) {
135
+ return [{ configured: model, catalogModel, reason: "surface_incompatible" }];
136
+ }
137
+ if (!candidates.some(candidate => candidate.model === catalogModel)) {
138
+ return [{ configured: model, catalogModel, reason: "outside_display_limit" }];
139
+ }
140
+ return [];
141
+ });
142
+ return { candidates, advertised, excluded };
143
+ }
144
+
145
+ export function finishUpstreamNativeEntry(clone: RawEntry, priority: number): RawEntry {
146
+ if (priority !== 9) clone.priority = priority;
147
+ applyNativeOpenAiContextOverride(clone);
148
+ // GPT-5.6 natives keep their exact upstream ladders (e.g. luna has max but no ultra).
149
+ // Older natives (gpt-5.5 / 5.4 / 5.4-mini / 5.3-codex-spark) get mock max + ultra
150
+ // (wire-clamped to xhigh). Ultra is always advertised regardless of v2 toggle.
151
+ if (!isGpt56NativeSlug(String(clone.slug ?? ""))) ensureUltraReasoningLevel(clone);
152
+ return ensureStrictCatalogFields(normalizeServiceTiers(clone));
153
+ }
154
+
155
+ export function isExactComboCatalogModel(
156
+ model: CatalogModel | undefined,
157
+ exactComboSlugs: ReadonlySet<string>,
158
+ ): boolean {
159
+ return model !== undefined && exactComboSlugs.has(catalogModelSlug(model));
160
+ }
161
+
162
+ export function deriveEntry(
163
+ template: RawEntry | null,
164
+ slug: string,
165
+ desc: string,
166
+ priority: number,
167
+ model?: CatalogModel,
168
+ exactComboSlugs: ReadonlySet<string> = new Set(),
169
+ ): RawEntry {
170
+ const preserveExact = isExactComboCatalogModel(model, exactComboSlugs);
171
+ const isRouted = model !== undefined;
172
+ if (!isRouted && !slug.includes("/")) {
173
+ // Supported native slug covered by the upstream snapshot: use the REAL entry (exact
174
+ // reasoning ladder — e.g. luna has no ultra — default effort, identity, model_messages)
175
+ // instead of cloning an older template.
176
+ const upstream = upstreamNativeEntry(slug);
177
+ if (upstream) return finishUpstreamNativeEntry(upstream, priority);
178
+ }
179
+ if (template) {
180
+ const e = JSON.parse(JSON.stringify(template)) as RawEntry;
181
+ e.slug = slug;
182
+ e.display_name = slug;
183
+ e.description = desc;
184
+ e.priority = priority;
185
+ e.visibility = "list";
186
+ if ("upgrade" in e) e.upgrade = null;
187
+ delete e.availability_nux; // don't replay another model's "now available" NUX
188
+ // Routed (namespaced) models inherit the gpt template — correct its OpenAI/GPT identity
189
+ // and advertise the reasoning ladder Codex accepts.
190
+ if (isRouted) {
191
+ // Native id for identity text + metadata lookups — the slug may be an encoded
192
+ // alias (`provider/vendor-model`); the model object carries the native id.
193
+ const modelName = model?.id ?? slug.slice(slug.indexOf("/") + 1);
194
+ if (typeof e.base_instructions === "string") {
195
+ // Proxy-neutral: keep the GPT-5/OpenAI disclaimer but never advertise the opencodex proxy
196
+ // (leaking that into base_instructions is a non-first-party signature → ToS risk).
197
+ e.base_instructions = e.base_instructions.replace(
198
+ CODEX_GPT5_IDENTITY_LINE,
199
+ `You are a coding agent powered by the ${modelName} model. Do not claim to be GPT-5 or made by OpenAI.`,
200
+ );
201
+ }
202
+ applyReasoningLevels(e, model?.reasoningEfforts, model?.defaultReasoningEffort, preserveExact);
203
+ normalizeRoutedCatalogEntry(e, model?.parallelToolCalls === true);
204
+ if (model) applyJawcodeCatalogMetadata(e, model.provider, model.id, model.contextCap);
205
+ applyCatalogModelMetadata(e, model);
206
+ } else {
207
+ applyNativeOpenAiContextOverride(e);
208
+ if (isGpt56NativeSlug(slug)) ensureGpt56ReasoningLevels(e);
209
+ else ensureUltraReasoningLevel(e);
210
+ // Non-5.6 natives (5.5, 5.4, 5.4-mini, spark) do not support responses-lite;
211
+ // the template may carry the flag from a 5.6 entry — strip it so codex-rs does
212
+ // not inject reasoning.context: "all_turns" for models that reject it.
213
+ if (!isGpt56NativeSlug(slug)) {
214
+ // Spark NEEDS use_responses_lite: true — it controls the tool delivery format
215
+ // (AdditionalTools in input vs top-level tools). The reasoning params that
216
+ // use_responses_lite triggers (context: "all_turns", summary) are stripped
217
+ // separately in the passthrough adapter (stripUnsupportedReasoningParams).
218
+ if (!slug.includes("codex-spark")) delete e.use_responses_lite;
219
+ delete e.supports_websockets;
220
+ }
221
+ }
222
+ return ensureStrictCatalogFields(normalizeServiceTiers(e), {
223
+ preserveExactInputModalities: preserveExact,
224
+ isRouted,
225
+ });
226
+ }
227
+ // Fallback when no template is available (best-effort; strict parser may need more).
228
+ const entry: RawEntry = {
229
+ slug, display_name: slug, description: desc,
230
+ shell_type: "shell_command", visibility: "list", supported_in_api: true,
231
+ priority, base_instructions: "You are a helpful coding assistant.",
232
+ ...(isRouted ? { web_search_tool_type: "text_and_image", supports_search_tool: true } : {}),
233
+ };
234
+ if (isRouted) {
235
+ applyReasoningLevels(entry, model?.reasoningEfforts, model?.defaultReasoningEffort, preserveExact);
236
+ }
237
+ else {
238
+ applyReasoningLevels(entry, isGpt56NativeSlug(slug) ? undefined : ["low", "medium", "high", "xhigh"]);
239
+ if (isGpt56NativeSlug(slug)) ensureGpt56ReasoningLevels(entry);
240
+ }
241
+ if (model && isRouted) applyJawcodeCatalogMetadata(entry, model.provider, model.id, model.contextCap);
242
+ applyCatalogModelMetadata(entry, model);
243
+ if (!isRouted) applyNativeOpenAiContextOverride(entry);
244
+ return ensureStrictCatalogFields(normalizeServiceTiers(entry), {
245
+ preserveExactInputModalities: preserveExact,
246
+ isRouted,
247
+ });
248
+ }
249
+
250
+ export function buildCatalogEntries(
251
+ template: RawEntry | null,
252
+ gptSlugs: string[],
253
+ goModels: CatalogModel[],
254
+ featured?: string[],
255
+ wsEnabled = false,
256
+ multiAgentMode: MultiAgentMode = "default",
257
+ exactComboSlugs: ReadonlySet<string> = new Set(),
258
+ ): RawEntry[] {
259
+ // Codex's models-manager sorts by `priority` ASC and advertises the first 5 picker-visible
260
+ // models to spawn_agent (sort_by_key(priority) + MAX_MODEL_OVERRIDES_IN_SPAWN_AGENT=5). Catalog
261
+ // ARRAY order is discarded — so "featuring" a model = giving it the LOWEST priority (0..N-1) so
262
+ // it sorts to the front. This works for native gpt slugs AND routed slugs alike.
263
+ const rank = new Map((featured ?? []).map((slug, i) => [slug, i] as const));
264
+ const out: RawEntry[] = [];
265
+ const collisionSkipped = resolveSlugAliasCollisions(goModels);
266
+ const comboPublicSlugs = new Set(goModels
267
+ .filter(model => model.provider === COMBO_NAMESPACE)
268
+ .map(catalogModelSlug));
269
+ for (const slug of gptSlugs) {
270
+ const e = deriveEntry(template, slug, "OpenAI native model (Codex OAuth passthrough).", 9);
271
+ if (rank.has(slug)) e.priority = rank.get(slug)!;
272
+ out.push(e);
273
+ }
274
+ for (const m of goModels) {
275
+ if (collisionSkipped.has(m)) continue;
276
+ const slug = catalogModelSlug(m);
277
+ if (m.provider !== COMBO_NAMESPACE && comboPublicSlugs.has(slug)) {
278
+ warnComboMasqueradeCollisionOnce(slug);
279
+ continue;
280
+ }
281
+ // Provider rows use the one-slash slug codec; combo aliases intentionally override that
282
+ // public slug and may be bare.
283
+ const e = deriveEntry(
284
+ template,
285
+ slug,
286
+ `Routed via opencodex → ${m.provider} (${m.owned_by ?? m.provider}).`,
287
+ 5,
288
+ m,
289
+ exactComboSlugs,
290
+ );
291
+ // Featured picks may be stored raw (legacy) or encoded — honor both.
292
+ const rankHit = rank.get(slug) ?? rank.get(`${m.provider}/${m.id}`);
293
+ if (rankHit !== undefined) e.priority = rankHit;
294
+ out.push(e);
295
+ }
296
+ // Central capability override (phase 120.4): the advertised flag must match the implemented WS
297
+ // endpoint. Overrides both the routed strip (normalizeRoutedCatalogEntry) and any native template
298
+ // leak (deriveEntry clones the template as-is for native slugs).
299
+ for (const entry of out) {
300
+ if (wsEnabled) entry.supports_websockets = true;
301
+ else {
302
+ delete entry.supports_websockets;
303
+ // Snapshot-backed native entries carry prefer_websockets: never advertise a preference
304
+ // for an endpoint ocx has disabled.
305
+ delete entry.prefer_websockets;
306
+ }
307
+ }
308
+ return applyMultiAgentMode(out, multiAgentMode, isMultiAgentV2Enabled());
309
+ }
310
+
311
+ export function resetCatalogRuntimeStateForTests(): void {
312
+ resetBundledCatalogCacheForTests();
313
+ lastDropWarnSignature.clear();
314
+ openAiApiCollisionWarnings.clear();
315
+ comboCatalogWarningSignatures.clear();
316
+ slugAliasCollisionWarnings.clear();
317
+ comboMasqueradeCollisionWarnings.clear();
318
+ clearLastComboCatalogOmissions();
319
+ clearModelCache();
320
+ clearGatherRoutedModelsInflight();
321
+ }
322
+
323
+ export function orderForSubagents(goModels: CatalogModel[], featured?: string[]): CatalogModel[] {
324
+ if (!featured || featured.length === 0) return goModels;
325
+ const rank = new Map(featured.map((id, i) => [id, i]));
326
+ // Featured picks may be stored raw (legacy) or encoded — match both forms.
327
+ const rankOf = (m: CatalogModel) =>
328
+ (m.alias ? rank.get(m.alias) : undefined)
329
+ ?? rank.get(`${m.provider}/${m.id}`)
330
+ ?? rank.get(routedSlug(m.provider, m.id))
331
+ ?? Number.MAX_SAFE_INTEGER;
332
+ return [...goModels].sort((a, b) => {
333
+ return rankOf(a) - rankOf(b);
334
+ });
335
+ }
336
+
337
+ /**
338
+ * True when an existing catalog row was authored by OpenCodex routing (#855).
339
+ * Every generated routed row — current full-slug form, the June–July 2026
340
+ * provider-name form, and legacy combo aliases — carries the stable
341
+ * description prefix `Routed via opencodex → `; foreign rows from Cursor or
342
+ * user tooling do not. `owned_by` cannot serve as the signal (upstream
343
+ * ownership), and `comp_hash` defaults to "opencodex" for every normalized
344
+ * row.
345
+ */
346
+ function isOcxAuthoredRoutedEntry(entry: RawEntry): boolean {
347
+ const desc = typeof entry.description === "string" ? entry.description : "";
348
+ const slug = typeof entry.slug === "string" ? entry.slug : "";
349
+ return slug.includes("/") && desc.startsWith("Routed via opencodex → ");
350
+ }
351
+
352
+ export function mergeCatalogEntriesForSync(
353
+ catalogModels: RawEntry[],
354
+ routedEntries: RawEntry[],
355
+ baseline: Map<string, number>,
356
+ featured: string[],
357
+ wsEnabled: boolean,
358
+ goIds: Set<string> = new Set(),
359
+ template: RawEntry | null = null,
360
+ disabledNative: Set<string> = new Set(),
361
+ gatheredProviderNames: Set<string> = new Set(routedEntries.flatMap(entry => {
362
+ const slug = typeof entry.slug === "string" ? entry.slug : "";
363
+ const slash = slug.indexOf("/");
364
+ return slash > 0 ? [slug.slice(0, slash)] : [];
365
+ })),
366
+ multiAgentMode: MultiAgentMode = "default",
367
+ exactComboSlugs: ReadonlySet<string> = new Set(),
368
+ hasPhysicalComboProvider = false,
369
+ includeNativeOpenAi = true,
370
+ ): RawEntry[] {
371
+ const rank = new Map(featured.map((slug, i) => [slug, i] as const));
372
+ const native = includeNativeOpenAi
373
+ ? catalogModels
374
+ .filter(m => typeof m.slug === "string"
375
+ && !(m.slug as string).includes("/")
376
+ && m.owned_by !== COMBO_NAMESPACE
377
+ && !goIds.has(m.slug as string)
378
+ && !isUnsupportedOpenAiNativeSlug(m.slug as string))
379
+ .map(m => {
380
+ const slug = m.slug as string;
381
+ // Featured models rank first (rank order); non-featured natives are pushed below the featured
382
+ // block when any model is featured, else keep their pristine baseline priority.
383
+ const baselinePriority = baseline.get(slug) ?? (m.priority as number);
384
+ const priority = rank.has(slug)
385
+ ? rank.get(slug)!
386
+ : featured.length > 0
387
+ ? Math.max(typeof baselinePriority === "number" ? baselinePriority : 9, featured.length + 100)
388
+ : baselinePriority;
389
+ // Fallback-quality entries (ocx synthesis / codex-rs model_info fallback: display_name
390
+ // stamped with the bare slug) are upgraded to the pinned upstream snapshot entry so a
391
+ // previously synthesized ladder (e.g. luna advertising ultra) self-heals on sync. A
392
+ // genuine catalog entry (real display name) is preserved untouched.
393
+ if (shouldUpgradeToUpstreamEntry(m)) {
394
+ const upstream = upstreamNativeEntry(slug)!;
395
+ const upgradePriority = rank.has(slug)
396
+ ? rank.get(slug)!
397
+ : featured.length > 0
398
+ ? Math.max(typeof upstream.priority === "number" ? upstream.priority : 9, featured.length + 100)
399
+ : typeof upstream.priority === "number" ? upstream.priority : priority;
400
+ const finished = finishUpstreamNativeEntry(upstream, 9);
401
+ finished.priority = upgradePriority;
402
+ return finished;
403
+ }
404
+ const preserved = normalizeServiceTiers({ ...m, priority });
405
+ // Older natives kept from disk still need the mock top tiers (max + ultra always
406
+ // for subagent max spawns; wire-clamped to the model's real top rung).
407
+ if (!isGpt56NativeSlug(slug)) ensureUltraReasoningLevel(preserved);
408
+ return preserved;
409
+ })
410
+ : [];
411
+
412
+ // Backfill any native OpenAI slug that the on-disk catalog is missing (e.g. gpt-5.5), so a
413
+ // routed provider exposing the same id can never delete the native OpenAI/Codex base row.
414
+ // Skip when no enabled canonical openai provider exists (#636) — bare gpt-* would 404.
415
+ const nativeSlugs = new Set(native.flatMap(m => typeof m.slug === "string" ? [m.slug] : []));
416
+ if (includeNativeOpenAi) {
417
+ for (const slug of nativeOpenAiSlugs()) {
418
+ if (nativeSlugs.has(slug)) continue;
419
+ nativeSlugs.add(slug);
420
+ const priority = rank.has(slug)
421
+ ? rank.get(slug)!
422
+ : featured.length > 0
423
+ ? featured.length + 100
424
+ : 9;
425
+ native.push(deriveEntry(template ? JSON.parse(JSON.stringify(template)) : null, slug, "OpenAI native model (Codex OAuth passthrough).", priority));
426
+ }
427
+ }
428
+
429
+ const freshSlugs = new Set(
430
+ routedEntries.flatMap(entry => typeof entry.slug === "string" ? [entry.slug] : []),
431
+ );
432
+ let finalRoutedEntries = routedEntries;
433
+ const preservingExistingRouted = routedEntries.length === 0
434
+ && catalogModels.some(m => typeof m.slug === "string" && (m.slug as string).includes("/"));
435
+ if (preservingExistingRouted) {
436
+ // #855: transient-fetch protection keeps existing rows, but rows OpenCodex
437
+ // itself authored for a provider that is no longer configured are ghosts,
438
+ // not protected foreign entries.
439
+ finalRoutedEntries = catalogModels.filter(m => {
440
+ if (typeof m.slug !== "string" || !(m.slug as string).includes("/")) return false;
441
+ const provider = (m.slug as string).slice(0, (m.slug as string).indexOf("/"));
442
+ return !(isOcxAuthoredRoutedEntry(m) && !gatheredProviderNames.has(provider));
443
+ });
444
+ } else {
445
+ const preservedForeignRouted = catalogModels.filter(m => {
446
+ if (typeof m.slug !== "string" || !m.slug.includes("/")) return false;
447
+ const provider = m.slug.slice(0, m.slug.indexOf("/"));
448
+ if (gatheredProviderNames.has(provider) || freshSlugs.has(m.slug)) return false;
449
+ // #855: an OpenCodex-authored row whose provider was deleted is a ghost;
450
+ // only genuinely foreign rows (Cursor, user tooling) are preserved.
451
+ return !isOcxAuthoredRoutedEntry(m);
452
+ });
453
+ finalRoutedEntries = [...routedEntries, ...preservedForeignRouted];
454
+ }
455
+ if (!hasPhysicalComboProvider) {
456
+ finalRoutedEntries = finalRoutedEntries.filter(entry => {
457
+ const slug = typeof entry.slug === "string" ? entry.slug : "";
458
+ const comboOwned = slug.startsWith(`${COMBO_NAMESPACE}/`) || entry.owned_by === COMBO_NAMESPACE;
459
+ return !comboOwned || freshSlugs.has(slug);
460
+ });
461
+ }
462
+ finalRoutedEntries = finalRoutedEntries.filter(entry => {
463
+ const slug = typeof entry.slug === "string" ? entry.slug : "";
464
+ return !exactComboSlugs.has(slug)
465
+ || (Array.isArray(entry.input_modalities) && entry.input_modalities.length > 0);
466
+ });
467
+ // Reapply final catalog policy to rows preserved from disk. Those rows bypass
468
+ // gatherRoutedModels, so filtering only the freshly gathered list can resurrect an excluded id.
469
+ finalRoutedEntries = finalRoutedEntries.filter(entry =>
470
+ typeof entry.slug !== "string" || !isRoutedModelCompatibilityExcluded(entry.slug)
471
+ );
472
+ if (preservingExistingRouted) {
473
+ console.warn(`[opencodex] catalog sync: routed model fetch returned empty; preserving ${finalRoutedEntries.length} existing routed entr${finalRoutedEntries.length === 1 ? "y" : "ies"} on disk.`);
474
+ }
475
+
476
+ const mergedEntries = [...native, ...finalRoutedEntries].map(m => {
477
+ const normalized = normalizeServiceTiers(m);
478
+ applyNativeOpenAiContextOverride(normalized);
479
+ const exactCombo = typeof m.slug === "string" && exactComboSlugs.has(m.slug);
480
+ const e = ensureStrictCatalogFields(normalized, {
481
+ preserveExactInputModalities: exactCombo,
482
+ isRouted: finalRoutedEntries.includes(m),
483
+ });
484
+ // Mock-max universality (260709): preserved routed entries from disk may predate
485
+ // the max rung — ensure it here so subagent max spawns validate on every
486
+ // reasoning-capable entry. max only: 5.6 exact ladders (luna: no ultra) stay intact.
487
+ if (!exactCombo) {
488
+ const levels = Array.isArray(e.supported_reasoning_levels)
489
+ ? e.supported_reasoning_levels as Array<{ effort?: string }>
490
+ : [];
491
+ if (levels.length > 0 && !levels.some(level => level.effort === "max")) {
492
+ levels.push(CODEX_REASONING_LEVELS.find(level => level.effort === "max")
493
+ ?? { effort: "max", description: "Maximum reasoning depth for the hardest problems" });
494
+ e.supported_reasoning_levels = levels;
495
+ }
496
+ }
497
+ if (wsEnabled) e.supports_websockets = true;
498
+ else {
499
+ delete e.supports_websockets;
500
+ // Match buildCatalogEntries: never advertise a websocket preference while WS is off.
501
+ delete e.prefer_websockets;
502
+ }
503
+ return e;
504
+ });
505
+ // Native enable/disable (single choke point: bare slugs in `disabledModels`). Runs as the
506
+ // LAST pass so the upstream-upgrade branch above can never clobber a hide flag back to list.
507
+ return applyMultiAgentMode(applyNativeVisibility(mergedEntries, disabledNative), multiAgentMode, isMultiAgentV2Enabled());
508
+ }
509
+
510
+ export async function syncCatalogModels(config: OcxConfig): Promise<{
511
+ added: number;
512
+ path: string;
513
+ catalogWritten: boolean;
514
+ comboOmissions: ComboCatalogOmission[];
515
+ }> {
516
+ const catalogPath = readCodexCatalogPath();
517
+ const catalog = loadCatalogForSync(catalogPath);
518
+ if (!catalog) return { added: 0, path: catalogPath, catalogWritten: false, comboOmissions: [] };
519
+
520
+ // The bundled catalog is a reliable native template on the default path, but it is not the
521
+ // merge source. Preservation must inspect the file that this sync is about to overwrite;
522
+ // otherwise an empty/partial provider gather cannot see routed or user-native rows on disk.
523
+ const onDiskCatalog = readCatalog(catalogPath);
524
+ const catalogModelsForMerge = onDiskCatalog?.models ?? catalog.models ?? [];
525
+
526
+ const template = findNativeTemplate(catalog);
527
+
528
+ const comboOmissions: ComboCatalogOmission[] = [];
529
+ const goModels = await gatherRoutedModels(config, { comboOmissions });
530
+ try {
531
+ // Once-only: preserve the PRISTINE pre-opencodex catalog as the native-priority baseline
532
+ // (later syncs would otherwise overwrite it with featured-modified priorities).
533
+ ensureCatalogBackup(catalogPath, catalog);
534
+ } catch { /* backup best-effort */ }
535
+
536
+ // Hide disabled models from Codex, then feature the chosen subagent models (native OR routed)
537
+ // by giving them the lowest priority — see buildCatalogEntries for why priority, not array order.
538
+ const enabledGo = filterCatalogVisibleModels(goModels, config);
539
+ const featured = config.subagentModels ?? [];
540
+ const orderedGoModels = orderForSubagents(enabledGo, featured); // stable tie-break among equal priorities
541
+ const multiAgentMode: MultiAgentMode = config.multiAgentMode === "v1" || config.multiAgentMode === "v2" ? config.multiAgentMode : "default";
542
+ const exactComboSlugs = exactComboCatalogSlugs(config);
543
+ const hasPhysicalComboProvider = Object.hasOwn(config.providers, COMBO_NAMESPACE);
544
+ const goEntries = buildCatalogEntries(template ? JSON.parse(JSON.stringify(template)) : null, [], orderedGoModels, featured, websocketsEnabled(config), multiAgentMode, exactComboSlugs);
545
+ // Keep genuine native entries (gpt-*, codex-*) with their real per-model fields and append
546
+ // routed providers as namespaced slugs. Cursor and other adopted providers can expose model ids
547
+ // like `gpt-5.5`; those must not delete the native OpenAI/Codex base row.
548
+ const baseline = readNativeBaseline(catalogPath);
549
+ const goIds = new Set(enabledGo.map(m => m.id));
550
+ const gatheredProviderNames = new Set(
551
+ Object.entries(config.providers ?? {})
552
+ .filter(([, prov]) => prov.disabled !== true)
553
+ .map(([name]) => name),
554
+ );
555
+ // Central WS capability override on the FINAL on-disk catalog (the file Codex reads). Applies to
556
+ // native AND routed so the advertised flag matches the implemented endpoint (phase 120.4) and a
557
+ // native template can never leak supports_websockets while the flag is off.
558
+ const wsEnabled = websocketsEnabled(config);
559
+ const enabledProviders = Object.entries(config.providers ?? {})
560
+ .filter(([, prov]) => prov.disabled !== true);
561
+ const hasCanonicalOpenai = enabledProviders.some(([name, prov]) =>
562
+ name === "openai" && isCanonicalOpenAiForwardProvider(prov),
563
+ );
564
+ // #636: when the user only configured non-OpenAI providers (e.g. kimi), do not advertise
565
+ // bare gpt-* rows that hard-404 via NoEnabledOpenAiProviderError. Keep natives when no
566
+ // providers are configured yet (fresh install / catalog bootstrap tests).
567
+ const includeNativeOpenAi = enabledProviders.length === 0 || hasCanonicalOpenai;
568
+ catalog.models = mergeCatalogEntriesForSync(catalogModelsForMerge, goEntries, baseline, featured, wsEnabled, goIds, template, disabledNativeSlugs(config), gatheredProviderNames, multiAgentMode, exactComboSlugs, hasPhysicalComboProvider, includeNativeOpenAi);
569
+ clampCatalogModelsToCodexSupport(catalog.models);
570
+
571
+ atomicWriteFile(catalogPath, JSON.stringify(catalog, null, 2) + "\n");
572
+ return { added: goEntries.length, path: catalogPath, catalogWritten: true, comboOmissions };
573
+ }
574
+
575
+ export function restoreCodexCatalog(): { removed: number; kept: number; path: string } {
576
+ const catalogPath = readCodexCatalogPath();
577
+ const catalog = readCatalog(catalogPath);
578
+ if (!catalog || !Array.isArray(catalog.models)) return { removed: 0, kept: 0, path: catalogPath };
579
+ const backup = readCatalogBackup(catalogPath);
580
+ if (backup && Array.isArray(backup.models)) {
581
+ const removed = (catalog.models ?? []).filter(m => typeof m.slug === "string" && m.slug.includes("/")).length;
582
+ const backupSlugs = new Set(backup.models.flatMap(m => typeof m.slug === "string" ? [m.slug] : []));
583
+ const userNativeAdditions = (catalog.models ?? []).filter(m =>
584
+ typeof m.slug === "string" && !m.slug.includes("/") && !backupSlugs.has(m.slug)
585
+ );
586
+ const restored = {
587
+ ...backup,
588
+ models: [...backup.models, ...userNativeAdditions],
589
+ };
590
+ atomicWriteFile(catalogPath, JSON.stringify(restored, null, 2) + "\n");
591
+ return { removed, kept: restored.models.length, path: catalogPath };
592
+ }
593
+ const before = catalog.models.length;
594
+ const native = catalog.models.filter(m => !(typeof m.slug === "string" && m.slug.includes("/")));
595
+ const removed = before - native.length;
596
+ if (removed > 0) {
597
+ catalog.models = native;
598
+ atomicWriteFile(catalogPath, JSON.stringify(catalog, null, 2) + "\n");
599
+ }
600
+ return { removed, kept: native.length, path: catalogPath };
601
+ }
602
+
603
+ /** Force Codex's models_cache stale from the on-disk catalog. Returns whether a cache write occurred. */
604
+ export function invalidateCodexModelsCache(): boolean {
605
+ try {
606
+ const catalogPath = readCodexCatalogPath();
607
+ if (!existsSync(catalogPath)) return false;
608
+ const catalog = JSON.parse(readFileSync(catalogPath, "utf8"));
609
+ const models = catalog.models ?? catalog;
610
+ const wrapper = {
611
+ fetched_at: "2000-01-01T00:00:00Z",
612
+ client_version: "0.0.0",
613
+ models,
614
+ };
615
+ atomicWriteFile(activeCodexModelsCachePath(), JSON.stringify(wrapper, null, 2) + "\n");
616
+ return true;
617
+ } catch {
618
+ return false;
619
+ }
620
+ }
@@ -0,0 +1,12 @@
1
+ // AUTO-SPLIT facade: original catalog.ts body moved into ./catalog/* modules.
2
+ // Public surface preserved exactly; importers keep using "src/codex/catalog".
3
+ export { isMediaGenerationModelId, shouldExposeRoutedModel, readCodexCatalogPath, readCatalog, normalizeRoutedCatalogEntry, catalogModelSlug, filterSupportedNativeSlugs, catalogModelSupportsReasoningSummaries } from "./catalog/parsing";
4
+ export type { CatalogModel, MultiAgentMode } from "./catalog/parsing";
5
+ export { NATIVE_OPENAI_MODELS, nativeOpenAiContextWindow, disabledNativeSlugs, visibleNativeSlugs, desktopVisibleNativeSlugs, nativeModelRows, applyNativeVisibility, upstreamNativeEntry, nativeOpenAiSlugs, listCatalogNativeSlugs, nativeReasoningEfforts, nativeDefaultReasoningEffort } from "./catalog/metadata";
6
+ export { isSpawnableCodexCandidate, codexExecInvocation, loadBundledCodexCatalog, materializeBundledCodexCatalog, loadCatalogTemplate } from "./catalog/bundled";
7
+ export { nativeEffortClamp, shouldApplyNativeEffortClamp, catalogModelEfforts, codexSupportedReasoningEfforts, clampedDefaultEffort, clampEntryToCodexSupportedEfforts, clampCatalogModelsToCodexSupport } from "./catalog/effort";
8
+ export { applyProviderConfigHints, isDatedVariantId, filterCatalogVisibleModels, gatherRoutedModels, clearGatherRoutedModelsInflight, augmentRoutedModelsWithRegistryOpenAiApiRows, augmentRoutedModelsWithJawcodeMetadata } from "./catalog/provider-fetch";
9
+ export { deriveComboCatalogModel, exactComboCatalogSlugs, getLastComboCatalogOmissions, resetOpenAiApiCatalogWarningStateForTests, uniqueCatalogModelsForPublicList, uniqueCatalogModelsForRawPublicList, buildComboCatalogOmission, comboCatalogOmissionReason, summarizeComboCatalogOmissions } from "./catalog/aggregation";
10
+ export type { ComboCatalogOmission, ComboCatalogOmissionReason } from "./catalog/aggregation";
11
+ export { MAX_SPAWN_AGENT_MODEL_OVERRIDES, effectiveSubagentRoster, buildCatalogEntries, resetCatalogRuntimeStateForTests, orderForSubagents, mergeCatalogEntriesForSync, syncCatalogModels, restoreCodexCatalog, invalidateCodexModelsCache } from "./catalog/sync";
12
+ export type { SpawnAgentSurface, SubagentRosterExclusionReason, EffectiveSubagentModel, SubagentRosterExclusion, EffectiveSubagentRoster } from "./catalog/sync";