@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,107 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { getCodexAccountCredential } from "./account-store";
4
+ import { loadConfig } from "../config";
5
+ import { resolveCodexHomeDir } from "./home";
6
+ import { extractAccountId } from "../oauth/chatgpt";
7
+ import { isSelectableCodexPoolAccount } from "./account-id";
8
+
9
+ export interface CodexTokens {
10
+ access_token: string;
11
+ account_id: string;
12
+ id_token?: string;
13
+ }
14
+
15
+ /**
16
+ * Why a read *outcome* and not just `Tokens | null`: callers need to tell a real sign-out apart
17
+ * from a transient read failure. A single null collapsed "file absent", "malformed JSON", and
18
+ * "read error" into one answer, so a half-written `auth.json` looked exactly like a logout and
19
+ * callers destroyed healthy cached account state because of it.
20
+ */
21
+ export type CodexTokenReadResult =
22
+ | { status: "ok"; tokens: CodexTokens }
23
+ | { status: "missing" | "invalid" | "unreadable" };
24
+
25
+ function hasErrnoCode(error: unknown, code: string): boolean {
26
+ return typeof error === "object" && error !== null && "code" in error
27
+ && (error as { code?: unknown }).code === code;
28
+ }
29
+
30
+ /**
31
+ * Reads the Codex CLI credential file and classifies the outcome. Reads once instead of doing an
32
+ * `existsSync` pre-check, so a file replaced between check and read cannot be misread as absent.
33
+ * Never returns or logs the raw error or any token material.
34
+ */
35
+ export function readCodexTokensResult(): CodexTokenReadResult {
36
+ let raw: string;
37
+ try {
38
+ raw = readFileSync(join(resolveCodexHomeDir(), "auth.json"), "utf-8");
39
+ } catch (error) {
40
+ return { status: hasErrnoCode(error, "ENOENT") ? "missing" : "unreadable" };
41
+ }
42
+ try {
43
+ const j = JSON.parse(raw) as {
44
+ tokens?: { access_token?: string; account_id?: string; id_token?: string };
45
+ };
46
+ if (!j?.tokens?.access_token) return { status: "invalid" };
47
+ return {
48
+ status: "ok",
49
+ tokens: {
50
+ access_token: j.tokens.access_token,
51
+ account_id: j.tokens.account_id ?? "",
52
+ id_token: j.tokens.id_token,
53
+ },
54
+ };
55
+ } catch {
56
+ return { status: "invalid" };
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Compatibility wrapper: any usable-token check keeps the original null contract, which keeps
62
+ * request routing fail-closed. Only callers that must distinguish the failure reason should use
63
+ * `readCodexTokensResult()`.
64
+ */
65
+ export function readCodexTokens(): CodexTokens | null {
66
+ const result = readCodexTokensResult();
67
+ return result.status === "ok" ? result.tokens : null;
68
+ }
69
+
70
+ export function getMainChatgptAccountId(): string | null {
71
+ const tokens = readCodexTokens();
72
+ if (!tokens) return null;
73
+ return extractAccountId(tokens.id_token, tokens.access_token) ?? (tokens.account_id || null);
74
+ }
75
+
76
+ function normalizedEmail(email: string | undefined | null): string | null {
77
+ const trimmed = email?.trim().toLowerCase();
78
+ return trimmed || null;
79
+ }
80
+
81
+ function isWorkspacePlan(plan: string | undefined | null): boolean {
82
+ return !!plan && /team|business|enterprise|workspace|edu/i.test(plan);
83
+ }
84
+
85
+ // Main login and managed pool accounts are separate duplicate buckets.
86
+ // Inside the pool, personal and workspace subscriptions are also separate buckets.
87
+ // Within each pool bucket, keep the original ChatGPT account id + email collision guard.
88
+ export function checkAccountIdCollision(
89
+ chatgptAccountId: string,
90
+ email?: string | null,
91
+ plan?: string | null,
92
+ excludeAccountId?: string | null,
93
+ ): { collision: true; reason: string } | { collision: false } {
94
+ const candidateEmail = normalizedEmail(email);
95
+ const candidateWorkspace = isWorkspacePlan(plan);
96
+ for (const account of loadConfig().codexAccounts ?? []) {
97
+ if (excludeAccountId && account.id === excludeAccountId) continue;
98
+ if (!isSelectableCodexPoolAccount(account)) continue;
99
+ if (isWorkspacePlan(account.plan) !== candidateWorkspace) continue;
100
+ const cred = getCodexAccountCredential(account.id);
101
+ const poolEmail = normalizedEmail(account.email);
102
+ if (cred && cred.chatgptAccountId === chatgptAccountId && (!candidateEmail || !poolEmail || poolEmail === candidateEmail)) {
103
+ return { collision: true, reason: `Account is already in the pool (${account.id}).` };
104
+ }
105
+ }
106
+ return { collision: false };
107
+ }
@@ -0,0 +1,352 @@
1
+ import {
2
+ CodexCredentialGenerationConflictError,
3
+ CodexCredentialRefreshLockTimeoutError,
4
+ CodexCredentialRefreshBusyError,
5
+ CodexCredentialRefreshStaleError,
6
+ getValidCodexToken,
7
+ isCodexAccountGenerationLive,
8
+ } from "./account-store";
9
+ import { ConfigMutationLockError } from "../config";
10
+ import { markAccountNeedsReauth } from "./account-runtime-state";
11
+ import { isCodexAccountUsable } from "./account-usability";
12
+ import { reconcileMainCodexAccountRuntimeState } from "./account-lifecycle";
13
+ import { MAIN_CODEX_ACCOUNT_ID, getMainAccountToken } from "./main-account";
14
+ import {
15
+ codexQuotaScopeForModel,
16
+ getCodexQuotaHealthSnapshot,
17
+ releaseCodexQuotaProbeLease,
18
+ releaseCodexQuotaScopeProbeLease,
19
+ tryAcquireCodexQuotaProbeLease,
20
+ tryAcquireCodexQuotaScopeProbeLease,
21
+ pickAlternateCodexAccount,
22
+ resolveCodexAccountForThreadDetailed,
23
+ } from "./routing";
24
+ import type { CodexCooldownSource, CodexQuotaScope } from "./routing";
25
+ import { maskAccountId } from "../lib/privacy";
26
+ import { formatErrorResponse } from "../bridge";
27
+ import { getAccountQuota } from "./quota";
28
+ import type { CodexAccountMode, OcxConfig, OcxProviderConfig } from "../types";
29
+ import { FORWARD_HEADERS } from "../adapters/openai-responses";
30
+ import { captureConfigGeneration } from "../lib/state-store-sweeper";
31
+
32
+ export type CodexAuthContext =
33
+ | { kind: "main"; accountId: null }
34
+ | {
35
+ kind: "pool";
36
+ accountId: string;
37
+ writerGeneration: number;
38
+ generation: number;
39
+ accessToken: string;
40
+ chatgptAccountId: string;
41
+ /**
42
+ * Set when this request was admitted through an active quota cooldown as
43
+ * the account's single probe. Must be echoed into the upstream outcome so
44
+ * only this request can clear the cooldown (#433).
45
+ */
46
+ probeLeaseId?: string;
47
+ /** Native model quota group selected for this request, when known. */
48
+ quotaScope?: CodexQuotaScope;
49
+ /** Scope that owns `probeLeaseId`, when it is a scoped recovery probe. */
50
+ probeQuotaScope?: CodexQuotaScope;
51
+ }
52
+ | {
53
+ // Main Codex account participating in rotation: token injected from ~/.codex/auth.json
54
+ // (Option A). Distinct from "main" (passthrough fallback that forwards the client token).
55
+ kind: "main-pool";
56
+ accountId: string;
57
+ writerGeneration: number;
58
+ accessToken: string;
59
+ chatgptAccountId: string;
60
+ /** See `pool.probeLeaseId`. */
61
+ probeLeaseId?: string;
62
+ quotaScope?: CodexQuotaScope;
63
+ probeQuotaScope?: CodexQuotaScope;
64
+ };
65
+
66
+ /** Probe lease carried by this context, when it holds one. */
67
+ export function codexProbeLeaseId(ctx: CodexAuthContext | undefined): string | undefined {
68
+ return ctx?.kind === "pool" || ctx?.kind === "main-pool" ? ctx.probeLeaseId : undefined;
69
+ }
70
+
71
+ /** Scope of a lease carried by this context, when it probes a model-specific quota. */
72
+ export function codexProbeQuotaScope(ctx: CodexAuthContext | undefined): CodexQuotaScope | undefined {
73
+ return ctx?.kind === "pool" || ctx?.kind === "main-pool" ? ctx.probeQuotaScope : undefined;
74
+ }
75
+
76
+ /**
77
+ * Hand back a probe lease for a request that will not reach upstream. Safe to
78
+ * call with a context that holds no lease.
79
+ */
80
+ export function releaseCodexAuthContextProbeLease(ctx: CodexAuthContext | undefined): void {
81
+ const leaseId = codexProbeLeaseId(ctx);
82
+ if (!ctx || ctx.kind === "main" || !leaseId) return;
83
+ if (ctx.probeQuotaScope) releaseCodexQuotaScopeProbeLease(ctx.accountId!, ctx.probeQuotaScope, leaseId);
84
+ else releaseCodexQuotaProbeLease(ctx.accountId!, leaseId);
85
+ }
86
+
87
+ export type OcxRuntimeProviderConfig = OcxProviderConfig & {
88
+ _codexAccountOverride?: { accessToken: string; chatgptAccountId: string };
89
+ _codexAccountRequired?: boolean;
90
+ };
91
+
92
+ export class CodexAuthContextError extends Error {
93
+ accountId: string;
94
+
95
+ constructor(accountId: string, cause: unknown) {
96
+ super("Codex pool account auth failed", { cause });
97
+ this.name = "CodexAuthContextError";
98
+ this.accountId = accountId;
99
+ }
100
+ }
101
+
102
+ export class CodexPoolAuthenticationError extends Error {
103
+ constructor() {
104
+ super("OpenAI account pool has no usable account credential");
105
+ this.name = "CodexPoolAuthenticationError";
106
+ }
107
+ }
108
+
109
+ export class CodexDirectAuthenticationError extends Error {
110
+ constructor() {
111
+ super("Codex Direct requires a caller Authorization bearer token");
112
+ this.name = "CodexDirectAuthenticationError";
113
+ }
114
+ }
115
+
116
+ export function hasCallerCodexBearer(headers: Headers): boolean {
117
+ return /^Bearer\s+\S+/i.test(headers.get("authorization")?.trim() ?? "");
118
+ }
119
+
120
+ export class CodexAccountCooldownError extends Error {
121
+ accountId: string;
122
+ cooldownUntil: number;
123
+ cooldownSource?: CodexCooldownSource;
124
+ quotaScope?: CodexQuotaScope;
125
+
126
+ constructor(
127
+ accountId: string,
128
+ cooldownUntil: number,
129
+ cooldownSource?: CodexCooldownSource,
130
+ quotaScope?: CodexQuotaScope,
131
+ ) {
132
+ super("Selected Codex account is cooling down");
133
+ this.name = "CodexAccountCooldownError";
134
+ this.accountId = accountId;
135
+ this.cooldownUntil = cooldownUntil;
136
+ this.cooldownSource = cooldownSource;
137
+ this.quotaScope = quotaScope;
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Human-readable account label for a client-visible error. NEVER the raw id: the proxy
143
+ * supports non-loopback binds (auth-cors.ts `isApiAuthRequired` requires a token there
144
+ * rather than refusing), so data-plane bodies can reach remote authenticated clients.
145
+ * The main login has no secret id, so it renders as the literal alias users type.
146
+ */
147
+ export function cooldownAccountLabel(accountId: string): string {
148
+ return accountId === MAIN_CODEX_ACCOUNT_ID ? "main" : maskAccountId(accountId) ?? "account-…????";
149
+ }
150
+
151
+ /**
152
+ * Actionable message for a cooled-down account: until when, why, and how to escape.
153
+ * Shared by every transport so the WebSocket surface (Codex Desktop) says the same thing
154
+ * as HTTP. The bare "cooling down" string left users with no route but commenting out the
155
+ * injected `openai_base_url` in config.toml.
156
+ */
157
+ export function cooldownErrorMessage(err: CodexAccountCooldownError): string {
158
+ const until = new Date(err.cooldownUntil).toISOString();
159
+ const scope = err.quotaScope === "spark"
160
+ ? "Spark quota"
161
+ : err.quotaScope === "shared"
162
+ ? "shared native quota"
163
+ : null;
164
+ return `Selected Codex account (${cooldownAccountLabel(err.accountId)})${scope ? ` ${scope} is` : " is"} cooling down until ${until}`
165
+ + ` (source: ${err.cooldownSource ?? "default"}).`
166
+ + ` Run 'ocx account list openai' to find the id, then`
167
+ + ` 'ocx account clear-cooldown openai <id>' to lift it, or switch accounts with 'ocx account use openai <id>'.`;
168
+ }
169
+
170
+ /** HTTP form of {@link cooldownErrorMessage}, carrying Retry-After for well-behaved clients. */
171
+ export function cooldownErrorResponse(err: CodexAccountCooldownError, now = Date.now()): Response {
172
+ const res = formatErrorResponse(429, "rate_limit_error", cooldownErrorMessage(err));
173
+ const headers = new Headers(res.headers);
174
+ headers.set("Retry-After", String(Math.max(1, Math.ceil((err.cooldownUntil - now) / 1000))));
175
+ return new Response(res.body, { status: res.status, headers });
176
+ }
177
+
178
+ export class CodexThreadAffinityExpiredError extends Error {
179
+ accountId: string;
180
+
181
+ constructor(accountId: string) {
182
+ super("Codex thread account affinity expired");
183
+ this.name = "CodexThreadAffinityExpiredError";
184
+ this.accountId = accountId;
185
+ }
186
+ }
187
+
188
+ export function shouldMarkAccountNeedsReauthForCodexAuthFailure(cause: unknown): boolean {
189
+ return !(cause instanceof CodexCredentialGenerationConflictError)
190
+ && !(cause instanceof CodexCredentialRefreshLockTimeoutError)
191
+ && !(cause instanceof CodexCredentialRefreshBusyError)
192
+ && !(cause instanceof CodexCredentialRefreshStaleError)
193
+ && !(cause instanceof ConfigMutationLockError);
194
+ }
195
+
196
+ export interface ResolveCodexAuthContextOptions {
197
+ excludeAccountId?: string;
198
+ /** Final native model selected for this request, used to select its quota group. */
199
+ modelId?: string;
200
+ }
201
+
202
+ export async function resolveCodexAuthContext(
203
+ headers: Headers,
204
+ config: OcxConfig,
205
+ mode: CodexAccountMode,
206
+ options: ResolveCodexAuthContextOptions = {},
207
+ ): Promise<CodexAuthContext> {
208
+ const writerGeneration = captureConfigGeneration();
209
+ if (mode === "direct") {
210
+ if (!hasCallerCodexBearer(headers)) throw new CodexDirectAuthenticationError();
211
+ return { kind: "main", accountId: null };
212
+ }
213
+ reconcileMainCodexAccountRuntimeState();
214
+ const threadId = headers.get("x-codex-parent-thread-id");
215
+ const quotaScope = codexQuotaScopeForModel(options.modelId);
216
+ const resolution = options.excludeAccountId
217
+ ? (() => {
218
+ const accountId = pickAlternateCodexAccount(config, options.excludeAccountId!, Date.now(), quotaScope);
219
+ return accountId
220
+ ? { status: "selected" as const, accountId }
221
+ : { status: "none" as const };
222
+ })()
223
+ : resolveCodexAccountForThreadDetailed(threadId, config, Date.now(), quotaScope);
224
+ if (resolution.status === "expired") throw new CodexThreadAffinityExpiredError(resolution.accountId);
225
+ let accountId = resolution.status === "selected" ? resolution.accountId : null;
226
+ if (!accountId) throw new CodexPoolAuthenticationError();
227
+ // Lazy prime: if the selected account has no quota yet, the pool is likely
228
+ // unprimed (dashboard never opened, or startup prime was blocked). Kick a
229
+ // best-effort prime so the NEXT routing decision has real scores. This never
230
+ // blocks the current request, and the helper's single-flight guard collapses
231
+ // repeated triggers into one pass.
232
+ if (!getAccountQuota(accountId)) {
233
+ import("./auth-api")
234
+ .then(({ primeCodexPoolQuotas }) => primeCodexPoolQuotas(config, "pre-route"))
235
+ .catch(() => {});
236
+ }
237
+ // Snapshot (not just the deadline) so a refused request can report WHY it is cooled:
238
+ // a literal Retry-After reads very differently to a user than a reset-derived guess.
239
+ const cooldown = getCodexQuotaHealthSnapshot(accountId, quotaScope);
240
+ const cooldownUntil = cooldown?.cooldownUntil;
241
+ // A cooled-down account never sends traffic, so upstream recovery can never be
242
+ // observed and the cooldown outlives the real limit. Admit one probe per
243
+ // interval; its outcome decides whether the cooldown ends (#433).
244
+ let probeLeaseId: string | undefined;
245
+ let probeQuotaScope: CodexQuotaScope | undefined;
246
+ if (cooldownUntil) {
247
+ probeQuotaScope = cooldown?.quotaScope;
248
+ probeLeaseId = probeQuotaScope
249
+ ? tryAcquireCodexQuotaScopeProbeLease(accountId, probeQuotaScope) ?? undefined
250
+ : tryAcquireCodexQuotaProbeLease(accountId) ?? undefined;
251
+ if (!probeLeaseId) {
252
+ throw new CodexAccountCooldownError(accountId, cooldownUntil, cooldown?.cooldownSource, cooldown?.quotaScope);
253
+ }
254
+ }
255
+
256
+ if (accountId === MAIN_CODEX_ACCOUNT_ID) {
257
+ // Main account in rotation: inject the read-only auth.json token and fail closed if it vanished.
258
+ const token = getMainAccountToken();
259
+ if (!token) {
260
+ // Nothing will reach upstream, so give the probe back instead of burning it.
261
+ if (probeLeaseId && probeQuotaScope) releaseCodexQuotaScopeProbeLease(accountId, probeQuotaScope, probeLeaseId);
262
+ else if (probeLeaseId) releaseCodexQuotaProbeLease(accountId, probeLeaseId);
263
+ throw new CodexPoolAuthenticationError();
264
+ }
265
+ return {
266
+ kind: "main-pool",
267
+ accountId,
268
+ writerGeneration,
269
+ accessToken: token.accessToken,
270
+ chatgptAccountId: token.chatgptAccountId,
271
+ ...(quotaScope ? { quotaScope } : {}),
272
+ ...(probeLeaseId ? { probeLeaseId } : {}),
273
+ ...(probeQuotaScope ? { probeQuotaScope } : {}),
274
+ };
275
+ }
276
+
277
+ try {
278
+ const token = await getValidCodexToken(accountId);
279
+ return {
280
+ kind: "pool",
281
+ accountId,
282
+ writerGeneration,
283
+ generation: token.generation,
284
+ accessToken: token.accessToken,
285
+ chatgptAccountId: token.chatgptAccountId,
286
+ ...(quotaScope ? { quotaScope } : {}),
287
+ ...(probeLeaseId ? { probeLeaseId } : {}),
288
+ ...(probeQuotaScope ? { probeQuotaScope } : {}),
289
+ };
290
+ } catch (cause) {
291
+ if (probeLeaseId && probeQuotaScope) releaseCodexQuotaScopeProbeLease(accountId, probeQuotaScope, probeLeaseId);
292
+ else if (probeLeaseId) releaseCodexQuotaProbeLease(accountId, probeLeaseId);
293
+ if (shouldMarkAccountNeedsReauthForCodexAuthFailure(cause)) {
294
+ markAccountNeedsReauth(accountId, writerGeneration);
295
+ }
296
+ throw new CodexAuthContextError(accountId, cause);
297
+ }
298
+ }
299
+
300
+ export function assertCodexAuthContextNotCooled(ctx: CodexAuthContext | undefined): void {
301
+ if (ctx?.kind !== "pool" && ctx?.kind !== "main-pool") return;
302
+ // A context holding the probe lease was deliberately admitted through the cooldown.
303
+ if (ctx.probeLeaseId) return;
304
+ const cooldown = getCodexQuotaHealthSnapshot(ctx.accountId, ctx.quotaScope);
305
+ if (cooldown?.cooldownUntil) {
306
+ throw new CodexAccountCooldownError(ctx.accountId, cooldown.cooldownUntil, cooldown.cooldownSource, cooldown.quotaScope);
307
+ }
308
+ }
309
+
310
+ export function applyCodexAuthContextToProvider(
311
+ provider: OcxProviderConfig,
312
+ ctx: CodexAuthContext,
313
+ mode: CodexAccountMode | undefined,
314
+ ): OcxRuntimeProviderConfig {
315
+ if (mode !== "pool" || (ctx.kind !== "pool" && ctx.kind !== "main-pool") || provider.authMode !== "forward") return provider;
316
+ return {
317
+ ...provider,
318
+ _codexAccountOverride: {
319
+ accessToken: ctx.accessToken,
320
+ chatgptAccountId: ctx.chatgptAccountId,
321
+ },
322
+ _codexAccountRequired: true,
323
+ };
324
+ }
325
+
326
+ export function headersForCodexAuthContext(headers: Headers, ctx: CodexAuthContext): Headers {
327
+ const selected = new Headers();
328
+ for (const name of FORWARD_HEADERS) {
329
+ const value = headers.get(name);
330
+ if (value) selected.set(name, value);
331
+ }
332
+ if (ctx.kind === "pool" || ctx.kind === "main-pool") {
333
+ selected.set("authorization", `Bearer ${ctx.accessToken}`);
334
+ selected.set("chatgpt-account-id", ctx.chatgptAccountId);
335
+ }
336
+ return selected;
337
+ }
338
+
339
+ export function isCodexAuthContextUsable(ctx: CodexAuthContext, config: OcxConfig): boolean {
340
+ if (ctx.kind === "main") return true;
341
+ if (ctx.kind === "main-pool") return isCodexAccountUsable(config, ctx.accountId);
342
+ return isCodexAccountUsable(config, ctx.accountId) && isCodexAccountGenerationLive(ctx.accountId, ctx.generation);
343
+ }
344
+
345
+ export function stripCodexRuntimeProviderFields(provider: OcxProviderConfig): OcxProviderConfig {
346
+ const {
347
+ _codexAccountOverride: _override,
348
+ _codexAccountRequired: _required,
349
+ ...safeProvider
350
+ } = provider as OcxRuntimeProviderConfig;
351
+ return safeProvider;
352
+ }
@@ -0,0 +1,149 @@
1
+ import { codexAutoStartEnabled } from "../config";
2
+ import { diagnoseService, type ServiceDiagnostic } from "../service";
3
+ import type { OcxConfig } from "../types";
4
+ import { getCodexRoutingKind, type CodexRoutingKind } from "./inject";
5
+ import { diagnoseCodexShim, type CodexShimDiagnostic } from "./shim";
6
+
7
+ export type StartupProtection = "service" | "shim" | "none";
8
+ export type StartupHealthStatus = "native" | "protected" | "at-risk";
9
+ export type ShimCoverage = "full" | "cli-only" | "none";
10
+
11
+ export interface StartupHealthInputs {
12
+ routingKind: CodexRoutingKind;
13
+ autostartEnabled: boolean;
14
+ serviceInstalled: boolean;
15
+ serviceViable: boolean;
16
+ serviceEnabled: boolean;
17
+ serviceRunning: boolean;
18
+ serviceStale: boolean;
19
+ serviceConflict: boolean;
20
+ serviceSupported: boolean;
21
+ shimInstalled: boolean;
22
+ shimHealthy: boolean;
23
+ platform: NodeJS.Platform;
24
+ diagnosticStale?: boolean;
25
+ }
26
+
27
+ export interface StartupHealth {
28
+ status: StartupHealthStatus;
29
+ routingKind: CodexRoutingKind;
30
+ routingInjected: boolean;
31
+ localRoutingDependency: boolean;
32
+ autostartEnabled: boolean;
33
+ rebootSafe: boolean;
34
+ protection: StartupProtection;
35
+ serviceInstalled: boolean;
36
+ serviceViable: boolean;
37
+ serviceEnabled: boolean;
38
+ serviceRunning: boolean;
39
+ serviceStale: boolean;
40
+ serviceConflict: boolean;
41
+ shimInstalled: boolean;
42
+ shimHealthy: boolean;
43
+ shimCoverage: ShimCoverage;
44
+ serviceSupported: boolean;
45
+ platform: NodeJS.Platform;
46
+ diagnosticStale: boolean;
47
+ recommendedCommand: string | null;
48
+ commands: {
49
+ installService: string;
50
+ installShim: string;
51
+ restoreNative: string;
52
+ };
53
+ }
54
+
55
+ const COMMANDS = {
56
+ installService: "ocx service install",
57
+ installShim: "ocx codex-shim install",
58
+ restoreNative: "ocx restore",
59
+ } as const;
60
+
61
+ export function deriveStartupHealth(inputs: StartupHealthInputs): StartupHealth {
62
+ const shimEffective = inputs.autostartEnabled && inputs.shimHealthy;
63
+ const routingInjected = inputs.routingKind === "opencodex-local";
64
+ const localRoutingDependency = inputs.routingKind === "opencodex-local"
65
+ || inputs.routingKind === "custom-local"
66
+ || inputs.routingKind === "unknown";
67
+ // Script launchers never cover Codex Desktop/app-server surfaces. This is
68
+ // intentionally conservative on every OS and for WSL-shared Codex homes.
69
+ const shimCoverage: ShimCoverage = !shimEffective
70
+ ? "none"
71
+ : "cli-only";
72
+ // We can only credit an opencodex service/shim for routing that opencodex owns.
73
+ // An arbitrary localhost gateway has an independent lifecycle that OCX cannot repair.
74
+ const ownsLocalRouting = inputs.routingKind === "opencodex-local";
75
+ const protection: StartupProtection = ownsLocalRouting && inputs.serviceViable
76
+ ? "service"
77
+ : ownsLocalRouting && shimEffective
78
+ ? "shim"
79
+ : "none";
80
+ const rebootSafe = !localRoutingDependency || (ownsLocalRouting && inputs.serviceViable);
81
+ const status: StartupHealthStatus = !localRoutingDependency
82
+ ? "native"
83
+ : rebootSafe
84
+ ? "protected"
85
+ : "at-risk";
86
+ const recommendedCommand = status !== "at-risk"
87
+ ? null
88
+ : inputs.routingKind === "custom-local" || inputs.routingKind === "unknown"
89
+ ? COMMANDS.restoreNative
90
+ : inputs.serviceSupported
91
+ ? COMMANDS.installService
92
+ : COMMANDS.restoreNative;
93
+ return {
94
+ ...inputs,
95
+ diagnosticStale: inputs.diagnosticStale ?? false,
96
+ routingInjected,
97
+ localRoutingDependency,
98
+ status,
99
+ rebootSafe,
100
+ protection,
101
+ shimCoverage,
102
+ recommendedCommand,
103
+ commands: { ...COMMANDS },
104
+ };
105
+ }
106
+
107
+ export interface StartupHealthDiagnostics {
108
+ routingKind?: CodexRoutingKind;
109
+ service?: ServiceDiagnostic;
110
+ shim?: CodexShimDiagnostic;
111
+ }
112
+
113
+ /** Collect current machine state without mutating config, services, or shims. */
114
+ export function collectStartupHealth(
115
+ config: Pick<OcxConfig, "codexAutoStart">,
116
+ diagnostics: StartupHealthDiagnostics = {},
117
+ ): StartupHealth {
118
+ const shim = diagnostics.shim ?? diagnoseCodexShim();
119
+ const service = diagnostics.service ?? diagnoseService();
120
+ return deriveStartupHealth({
121
+ routingKind: diagnostics.routingKind ?? getCodexRoutingKind(),
122
+ autostartEnabled: codexAutoStartEnabled(config),
123
+ serviceInstalled: service.installed,
124
+ serviceViable: service.viable,
125
+ serviceEnabled: service.enabled,
126
+ serviceRunning: service.running,
127
+ serviceStale: service.stale,
128
+ serviceConflict: service.conflict,
129
+ serviceSupported: service.supported,
130
+ shimInstalled: shim.installed,
131
+ shimHealthy: shim.healthy,
132
+ platform: process.platform,
133
+ });
134
+ }
135
+
136
+ export function startupHealthSummary(health: StartupHealth): string {
137
+ if (health.status === "native") return health.routingKind === "custom-remote"
138
+ ? "custom remote Codex routing (no local restart dependency)"
139
+ : "native Codex routing (no opencodex restart dependency)";
140
+ if (health.protection === "service") return "protected by background service";
141
+ const command = health.recommendedCommand ?? health.commands.restoreNative;
142
+ if (health.routingKind === "unknown") return `AT RISK after restart (Codex routing could not be verified; run '${command}')`;
143
+ if (health.routingKind === "custom-local") return `AT RISK after restart (custom local gateway lifecycle is not managed by opencodex; run '${command}')`;
144
+ if (health.shimCoverage === "cli-only") return `AT RISK for Codex Desktop after restart (launcher shim covers CLI scripts only; run '${command}')`;
145
+ if (health.serviceConflict) return `AT RISK after restart (background service managers conflict; run '${command}')`;
146
+ if (health.serviceStale) return `AT RISK after restart (background service files are stale; run '${command}')`;
147
+ if (health.serviceInstalled && !health.serviceViable) return `AT RISK after restart (installed service is disabled, stopped, or unhealthy; run '${command}')`;
148
+ return `AT RISK after restart (no viable background service; run '${command}')`;
149
+ }