@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,220 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { readFileSync } from "node:fs";
3
+ import type { CatalogModel } from "../../codex/catalog";
4
+ import { catalogModelSlug, invalidateCodexModelsCache, nativeModelRows, uniqueCatalogModelsForPublicList } from "../../codex/catalog";
5
+ import {
6
+ DEFAULT_SUBAGENT_MODELS,
7
+ codexAutoStartEnabled,
8
+ hasOwnProvider,
9
+ isValidProviderName,
10
+ multiAgentGuidanceEnabled,
11
+ providerBaseUrlConfigError,
12
+ providerHeadersConfigError,
13
+ saveConfigPreservingClaudeCode,
14
+ } from "../../config";
15
+ import {
16
+ clearLoginState,
17
+ getLoginStatus,
18
+ isPublicOAuthProvider,
19
+ listOAuthProviders,
20
+ startLoginFlow,
21
+ submitManualLoginCode,
22
+ upsertOAuthProvider,
23
+ } from "../../oauth";
24
+ import { removeCredential } from "../../oauth/store";
25
+ import { providerDestinationResolvedError } from "../../lib/destination-policy";
26
+ import { enrichProviderFromCatalog, listKeyLoginProviders } from "../../oauth/key-providers";
27
+ import { deriveProviderPresets } from "../../providers/derive";
28
+ import { providerCodexAccountMode } from "../../providers/registry";
29
+ import { routedSlug, slugEquals } from "../../providers/slug-codec";
30
+ import { clearProviderQuotaCache, fetchProviderQuotaReports } from "../../providers/quota";
31
+ import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
32
+ import {
33
+ CODEX_ACCOUNT_NAMESPACE_COMBO_ALIAS_COLLISION_ERROR,
34
+ codexAccountNamespaceForModel,
35
+ } from "../../codex/account-namespace-match";
36
+ import { clearThreadAccountMap } from "../../codex/routing";
37
+ import { primeCodexPoolQuotas } from "../../codex/auth-api";
38
+ import { DEFAULT_PROVIDER_CONTEXT_CAP, globalContextCapValue, providerContextCap, providerContextCaps, setAllProviderContextCaps, setGlobalContextCapValue, setProviderContextCap } from "../../providers/context-cap";
39
+ import { resolveCodexHomeDir } from "../../codex/home";
40
+ import { readUsageEntries } from "../../usage/log";
41
+ import { getUsageDebugLogEntries } from "../../usage/debug";
42
+ import { parseRange, parseUsageSurface, summarizeUsage } from "../../usage/summary";
43
+ import { stripCodexRuntimeProviderFields } from "../../codex/auth-context";
44
+ import { getProviderRegistryEntry } from "../../providers/registry";
45
+ import { getDebugLogEntries } from "../../lib/debug-log-buffer";
46
+ import { getInjectionDebugLogEntries } from "../../lib/injection-debug-log";
47
+ import {
48
+ clearDebugSettings,
49
+ clearDebugSetting,
50
+ getDebugSettings,
51
+ setDebugSettings,
52
+ type DebugFlag,
53
+ } from "../../lib/debug-settings";
54
+ import type { OcxClaudeCodeConfig, OcxConfig, OcxCustomModel, OcxProviderConfig } from "../../types";
55
+ import { drainAndShutdown } from "../lifecycle";
56
+ import { reconcileLiveStateStores } from "../../lib/state-store-registrations";
57
+ import { filterRequestLogs, getRequestLogEntries, type RequestLogEntry } from "../request-log";
58
+ import { estimateComboCost, estimateRequestCost, normalizeCostTokens, tokensPerSecond } from "../../usage/cost";
59
+ import type { PersistedUsageAttempt } from "../../usage/log";
60
+ import { isAllowedRequestOrigin, jsonResponse, providerManagementConfigError, publicProviderBaseUrl, safeConfigDTO } from "../auth-cors";
61
+ import { applySystemEnvToggle } from "../system-env";
62
+
63
+ import { isPlainRecord, parseDebugLogQuery, tokPerSecondResult, unavailableCostReason, costResult, requestLogDto, stripRegistryOnlyStaticHeaders, fetchAllModels } from "./shared";
64
+ import type { MetricUnavailableReason, TokPerSecondResult, CostEstimateReason, CostResult, MetricSource } from "./shared";
65
+ import type { ManagementContext } from "./context";
66
+ import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
67
+
68
+ export async function handleComboRoutes(ctx: ManagementContext): Promise<Response | null> {
69
+ const { req, url, config, deps, refreshCodexCatalogBestEffort, syncClaudeAgentDefsBestEffort } = ctx;
70
+
71
+ if (url.pathname === "/api/combos" && req.method === "GET") {
72
+ const { comboPublicModelId, getCombo, listComboIds } = await import("../../combos");
73
+ return jsonResponse({ combos: listComboIds(config).map(id => {
74
+ const combo = getCombo(config, id)!;
75
+ return {
76
+ id,
77
+ model: comboPublicModelId(id, combo),
78
+ ...combo,
79
+ };
80
+ }) });
81
+ }
82
+
83
+ if (url.pathname === "/api/combos" && req.method === "PUT") {
84
+ let rawBody: unknown;
85
+ try { rawBody = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
86
+ if (!isPlainRecord(rawBody)) {
87
+ return jsonResponse({ error: "request body must be an object" }, 400);
88
+ }
89
+ const body = rawBody;
90
+ if (typeof body.id !== "string" || !body.id.trim()) {
91
+ return jsonResponse({ error: "id is required and must be a string" }, 400);
92
+ }
93
+ const id = body.id.trim();
94
+ let renameFrom: string | undefined;
95
+ if (body.renameFrom !== undefined) {
96
+ if (typeof body.renameFrom !== "string" || !body.renameFrom.trim()) {
97
+ return jsonResponse({ error: "renameFrom must be a non-empty string" }, 400);
98
+ }
99
+ renameFrom = body.renameFrom.trim();
100
+ if (renameFrom === id) {
101
+ return jsonResponse({ error: "renameFrom must differ from id" }, 400);
102
+ }
103
+ if (!Object.hasOwn(config.combos ?? {}, renameFrom)) {
104
+ return jsonResponse({ error: `combo "${renameFrom}" does not exist` }, 400);
105
+ }
106
+ if (Object.hasOwn(config.combos ?? {}, id)) {
107
+ return jsonResponse({ error: `combo "${id}" already exists` }, 400);
108
+ }
109
+ }
110
+ const {
111
+ clearComboSelectionState,
112
+ clearComboTargetCooldowns,
113
+ comboConfigError,
114
+ comboModelId,
115
+ comboPublicModelId,
116
+ normalizeComboConfig,
117
+ } = await import("../../combos");
118
+ const error = comboConfigError(id, body.combo, config.providers, {
119
+ requireEnabledTarget: true,
120
+ combos: config.combos,
121
+ excludeComboId: renameFrom ?? id,
122
+ });
123
+ if (error) return jsonResponse({ error }, 400);
124
+ const normalized = normalizeComboConfig(body.combo as import("../../types").OcxComboConfig);
125
+ const stored: import("../../types").OcxComboConfig = normalized.alias === null
126
+ ? (({ alias: _alias, ...rest }) => rest)(normalized)
127
+ : normalized;
128
+ const sourceId = renameFrom ?? id;
129
+ const previous = config.combos?.[sourceId];
130
+ const oldPublicModel = previous ? comboPublicModelId(sourceId, previous) : null;
131
+ const newPublicModel = comboPublicModelId(id, normalized);
132
+ if (codexAccountNamespaceForModel(config.codexAccountNamespaces, newPublicModel)) {
133
+ return jsonResponse({ error: CODEX_ACCOUNT_NAMESPACE_COMBO_ALIAS_COLLISION_ERROR }, 409);
134
+ }
135
+ const nextCombos = { ...(config.combos ?? {}) };
136
+ if (renameFrom) delete nextCombos[renameFrom];
137
+ nextCombos[id] = stored;
138
+ config.combos = nextCombos;
139
+ let shouldSyncClaudeAgentDefs = false;
140
+ const migratedModels = new Set<string>();
141
+ if (oldPublicModel && oldPublicModel !== newPublicModel) {
142
+ migratedModels.add(oldPublicModel);
143
+ }
144
+ if (renameFrom) migratedModels.add(comboModelId(renameFrom));
145
+ if (migratedModels.size > 0) {
146
+ const migrateReference = (model: string): string => (
147
+ migratedModels.has(model) ? newPublicModel : model
148
+ );
149
+ const migrateAgentReference = (model: string): string => {
150
+ const migrated = migrateReference(model);
151
+ if (migrated !== model) shouldSyncClaudeAgentDefs = true;
152
+ return migrated;
153
+ };
154
+ const migrateReferences = (models: string[]): string[] => [
155
+ ...new Set(models.map(migrateReference)),
156
+ ];
157
+ if (config.disabledModels) {
158
+ config.disabledModels = migrateReferences(config.disabledModels);
159
+ }
160
+ if (config.subagentModels) {
161
+ config.subagentModels = [...new Set(config.subagentModels.map(migrateAgentReference))];
162
+ }
163
+ if (config.injectionModel && migratedModels.has(config.injectionModel)) {
164
+ config.injectionModel = newPublicModel;
165
+ }
166
+ if (config.shadowCallIntercept?.model && migratedModels.has(config.shadowCallIntercept.model)) {
167
+ config.shadowCallIntercept = {
168
+ ...config.shadowCallIntercept,
169
+ model: newPublicModel,
170
+ };
171
+ }
172
+ if (config.claudeCode) {
173
+ const claudeCode = { ...config.claudeCode };
174
+ for (const field of ["model", "smallFastModel"] as const) {
175
+ if (claudeCode[field]) claudeCode[field] = migrateAgentReference(claudeCode[field]);
176
+ }
177
+ if (claudeCode.tierModels) {
178
+ claudeCode.tierModels = Object.fromEntries(
179
+ Object.entries(claudeCode.tierModels).map(([tier, model]) => [tier, migrateAgentReference(model)]),
180
+ );
181
+ }
182
+ if (claudeCode.modelMap) {
183
+ claudeCode.modelMap = Object.fromEntries(
184
+ Object.entries(claudeCode.modelMap).map(([source, model]) => [source, migrateAgentReference(model)]),
185
+ );
186
+ }
187
+ config.claudeCode = claudeCode;
188
+ }
189
+ }
190
+ saveConfigPreservingClaudeCode(config);
191
+ reconcileLiveStateStores();
192
+ clearComboSelectionState(id);
193
+ clearComboTargetCooldowns(id);
194
+ if (renameFrom) {
195
+ clearComboSelectionState(renameFrom);
196
+ clearComboTargetCooldowns(renameFrom);
197
+ }
198
+ await refreshCodexCatalogBestEffort();
199
+ if (shouldSyncClaudeAgentDefs) await syncClaudeAgentDefsBestEffort();
200
+ return jsonResponse({ success: true, id, model: newPublicModel, combo: normalized });
201
+ }
202
+
203
+ if (url.pathname === "/api/combos" && req.method === "DELETE") {
204
+ const id = url.searchParams.get("id")?.trim();
205
+ if (!id) return jsonResponse({ error: "id query param is required" }, 400);
206
+ if (!Object.hasOwn(config.combos ?? {}, id)) {
207
+ return jsonResponse({ error: "unknown combo" }, 404);
208
+ }
209
+ const { clearComboSelectionState, clearComboTargetCooldowns } = await import("../../combos");
210
+ delete config.combos![id];
211
+ if (Object.keys(config.combos!).length === 0) delete config.combos;
212
+ saveConfigPreservingClaudeCode(config);
213
+ reconcileLiveStateStores();
214
+ clearComboSelectionState(id);
215
+ clearComboTargetCooldowns(id);
216
+ await refreshCodexCatalogBestEffort();
217
+ return jsonResponse({ success: true, id });
218
+ }
219
+ return null;
220
+ }
@@ -0,0 +1,422 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { readFileSync } from "node:fs";
3
+ import type { CatalogModel } from "../../codex/catalog";
4
+ import { catalogModelSlug, invalidateCodexModelsCache, nativeModelRows, uniqueCatalogModelsForPublicList } from "../../codex/catalog";
5
+ import {
6
+ DEFAULT_SUBAGENT_MODELS,
7
+ codexAutoStartEnabled,
8
+ hasOwnProvider,
9
+ isValidProviderName,
10
+ multiAgentGuidanceEnabled,
11
+ providerBaseUrlConfigError,
12
+ providerHeadersConfigError,
13
+ saveConfigPreservingClaudeCode,
14
+ } from "../../config";
15
+ import {
16
+ clearLoginState,
17
+ getLoginStatus,
18
+ isPublicOAuthProvider,
19
+ listOAuthProviders,
20
+ startLoginFlow,
21
+ submitManualLoginCode,
22
+ upsertOAuthProvider,
23
+ } from "../../oauth";
24
+ import { removeCredential } from "../../oauth/store";
25
+ import { providerDestinationResolvedError } from "../../lib/destination-policy";
26
+ import { isStreamMode } from "../../lib/bun-stream-caps";
27
+ import { shadowSourceModels } from "../../lib/shadow-call";
28
+ import {
29
+ configureAppOwnedMemoryBudget,
30
+ enforceAppOwnedMemoryBudget,
31
+ MAX_APP_OWNED_MEMORY_BUDGET_MB,
32
+ MIN_APP_OWNED_MEMORY_BUDGET_MB,
33
+ resolveAppOwnedMemoryBudgetBytes,
34
+ } from "../../lib/app-owned-memory";
35
+ import { enrichProviderFromCatalog, listKeyLoginProviders } from "../../oauth/key-providers";
36
+ import { deriveProviderPresets } from "../../providers/derive";
37
+ import { providerCodexAccountMode } from "../../providers/registry";
38
+ import { routedSlug, slugEquals } from "../../providers/slug-codec";
39
+ import { clearProviderQuotaCache, fetchProviderQuotaReports } from "../../providers/quota";
40
+ import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
41
+ import { clearThreadAccountMap } from "../../codex/routing";
42
+ import { primeCodexPoolQuotas } from "../../codex/auth-api";
43
+ import { DEFAULT_PROVIDER_CONTEXT_CAP, globalContextCapValue, providerContextCap, providerContextCaps, setAllProviderContextCaps, setGlobalContextCapValue, setProviderContextCap } from "../../providers/context-cap";
44
+ import { resolveCodexHomeDir } from "../../codex/home";
45
+ import { readUsageEntries } from "../../usage/log";
46
+ import { getUsageDebugLogEntries } from "../../usage/debug";
47
+ import { parseRange, parseUsageSurface, summarizeUsage } from "../../usage/summary";
48
+ import { stripCodexRuntimeProviderFields } from "../../codex/auth-context";
49
+ import { getProviderRegistryEntry } from "../../providers/registry";
50
+ import { getDebugLogEntries } from "../../lib/debug-log-buffer";
51
+ import { getInjectionDebugLogEntries } from "../../lib/injection-debug-log";
52
+ import {
53
+ clearDebugSettings,
54
+ clearDebugSetting,
55
+ getDebugSettings,
56
+ setDebugSettings,
57
+ type DebugFlag,
58
+ } from "../../lib/debug-settings";
59
+ import type { OcxClaudeCodeConfig, OcxConfig, OcxCustomModel, OcxProviderConfig } from "../../types";
60
+ import { drainAndShutdown } from "../lifecycle";
61
+ import { filterRequestLogs, getRequestLogEntries, type RequestLogEntry } from "../request-log";
62
+ import { estimateComboCost, estimateRequestCost, normalizeCostTokens, tokensPerSecond } from "../../usage/cost";
63
+ import type { PersistedUsageAttempt } from "../../usage/log";
64
+ import { isAllowedRequestOrigin, jsonResponse, providerManagementConfigError, publicProviderBaseUrl, safeConfigDTO } from "../auth-cors";
65
+ import { applySystemEnvToggle } from "../system-env";
66
+ import { getCachedStartupHealth, invalidateStartupHealthCache } from "../startup-health-cache";
67
+ import { runWindowsTrayAction } from "../windows-tray-control";
68
+ import { runStartupInstallAction, type StartupInstallAction } from "../startup-action-control";
69
+ import { displayCodexRuntimePath, effortClampAppliesToRuntime, loadLastEffortClamp, resolveCodexRuntime } from "../../codex/runtime";
70
+
71
+ import { isPlainRecord, parseDebugLogQuery, tokPerSecondResult, unavailableCostReason, costResult, requestLogDto, stripRegistryOnlyStaticHeaders, fetchAllModels } from "./shared";
72
+ import type { MetricUnavailableReason, TokPerSecondResult, CostEstimateReason, CostResult, MetricSource } from "./shared";
73
+ import type { ManagementContext } from "./context";
74
+ import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
75
+
76
+ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Response | null> {
77
+ const { req, url, config, deps, refreshCodexCatalogBestEffort, syncClaudeAgentDefsBestEffort } = ctx;
78
+ if (url.pathname === "/api/config" && req.method === "GET") {
79
+ return jsonResponse(safeConfigDTO(config));
80
+ }
81
+
82
+ if (url.pathname === "/api/config" && req.method === "PUT") {
83
+ return jsonResponse({ error: "Full config PUT is disabled. Use /api/providers POST for provider changes." }, 405);
84
+ }
85
+
86
+ if (url.pathname === "/api/settings" && req.method === "GET") {
87
+ let resolved: ReturnType<typeof resolveCodexRuntime>;
88
+ try {
89
+ // Full alternative discovery (memoized) so newerAvailable warnings work.
90
+ resolved = resolveCodexRuntime();
91
+ } catch {
92
+ resolved = {
93
+ runtime: { command: "codex", version: null, source: "fallback" },
94
+ failures: [],
95
+ };
96
+ }
97
+ const lastClamp = loadLastEffortClamp();
98
+ const clampActive = effortClampAppliesToRuntime(lastClamp, resolved.runtime);
99
+ const warningParts: string[] = [];
100
+ if (resolved.replacedConfigured) {
101
+ warningParts.push(
102
+ `Preferred Codex runtime is unavailable; using ${displayCodexRuntimePath(resolved.runtime.command)} instead.`,
103
+ );
104
+ } else if (
105
+ resolved.runtime.source === "fallback"
106
+ && resolved.failures.length > 0
107
+ && !resolved.runtime.version
108
+ ) {
109
+ warningParts.push("No validated Codex runtime found; falling back to `codex`.");
110
+ }
111
+ if (clampActive) {
112
+ const clampVersion = lastClamp?.runtimeVersion ?? resolved.runtime.version ?? "an older binary";
113
+ warningParts.push(
114
+ `Some reasoning effort options were hidden because OpenCodex used Codex ${clampVersion}.${resolved.newerAvailable ? " A newer Codex installation is available." : ""}`,
115
+ );
116
+ } else if (resolved.newerAvailable) {
117
+ warningParts.push(
118
+ `OpenCodex is using an older Codex binary (${resolved.runtime.version ?? "unknown"}). A newer Codex installation is available.`,
119
+ );
120
+ }
121
+ return jsonResponse({
122
+ // The dashboard renders request-log timestamps. Without this it formats them in the
123
+ // BROWSER's zone, so a KST proxy viewed from a UTC browser reports every request nine
124
+ // hours off (#725). Carried on settings rather than /api/logs because that route's
125
+ // array response has four consumers that would have to change with it.
126
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
127
+ codexAutoStart: codexAutoStartEnabled(config),
128
+ port: config.port,
129
+ hostname: config.hostname ?? "127.0.0.1",
130
+ streamMode: config.streamMode ?? "auto",
131
+ appOwnedMemoryBudgetMb: config.appOwnedMemoryBudgetMb ?? 256,
132
+ startupHealth: await getCachedStartupHealth(config),
133
+ codexRuntime: {
134
+ path: displayCodexRuntimePath(resolved.runtime.command),
135
+ version: resolved.runtime.version,
136
+ source: resolved.runtime.source,
137
+ newerAvailable: resolved.newerAvailable
138
+ ? {
139
+ path: displayCodexRuntimePath(resolved.newerAvailable.command),
140
+ version: resolved.newerAvailable.version,
141
+ }
142
+ : null,
143
+ catalogClamp: {
144
+ active: clampActive,
145
+ removedEfforts: clampActive ? (lastClamp?.removedEfforts ?? []) : [],
146
+ runtimeVersion: clampActive ? (lastClamp?.runtimeVersion ?? null) : null,
147
+ },
148
+ warning: warningParts.length > 0 ? warningParts.join(" ") : null,
149
+ },
150
+ });
151
+ }
152
+
153
+ if (url.pathname === "/api/startup-health" && req.method === "GET") {
154
+ return jsonResponse(await getCachedStartupHealth(config));
155
+ }
156
+
157
+ if (url.pathname === "/api/startup-action" && req.method === "POST") {
158
+ let body: { action?: unknown; repair?: unknown };
159
+ try { body = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
160
+ if (!body || !["install-service", "install-shim"].includes(String(body.action))) {
161
+ return jsonResponse({ error: "action must be install-service or install-shim" }, 400);
162
+ }
163
+ if (body.repair !== undefined && typeof body.repair !== "boolean") {
164
+ return jsonResponse({ error: "repair must be a boolean when provided" }, 400);
165
+ }
166
+ try {
167
+ const action = body.action as StartupInstallAction;
168
+ const repair = body.repair === true;
169
+ const result = await (deps.runStartupInstallAction ?? runStartupInstallAction)(action, { repair });
170
+ invalidateStartupHealthCache();
171
+ return jsonResponse({ ok: true, action, repair, message: result.message });
172
+ } catch (error) {
173
+ return jsonResponse({ error: error instanceof Error ? error.message : String(error) }, 500);
174
+ }
175
+ }
176
+
177
+ if (url.pathname === "/api/windows-tray" && req.method === "GET") {
178
+ if (process.platform !== "win32") return jsonResponse({ supported: false, installed: false, running: false, stale: false, summary: `unsupported on ${process.platform}` });
179
+ try {
180
+ return jsonResponse(await runWindowsTrayAction("status"));
181
+ } catch (error) {
182
+ return jsonResponse({ error: error instanceof Error ? error.message : String(error) }, 500);
183
+ }
184
+ }
185
+
186
+ if (url.pathname === "/api/windows-tray" && req.method === "POST") {
187
+ let body: { action?: unknown };
188
+ try { body = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
189
+ if (!body || !["install", "start", "stop", "uninstall"].includes(String(body.action))) {
190
+ return jsonResponse({ error: "action must be install, start, stop, or uninstall" }, 400);
191
+ }
192
+ if (process.platform !== "win32") return jsonResponse({ error: "Windows tray is only supported on Windows" }, 400);
193
+ try {
194
+ const status = await runWindowsTrayAction(body.action as "install" | "start" | "stop" | "uninstall");
195
+ return jsonResponse({ ok: true, status });
196
+ } catch (error) {
197
+ return jsonResponse({ error: error instanceof Error ? error.message : String(error) }, 500);
198
+ }
199
+ }
200
+
201
+ if (url.pathname === "/api/settings" && req.method === "PUT") {
202
+ // Each field is optional but at least one must be present; fields are
203
+ // validated when present. streamMode-only PUTs must work: Windows/macOS
204
+ // memory troubleshooting can use this persisted stream-shape escape hatch
205
+ // (a Windows service does not inherit shell env). A stream-shape
206
+ // change applies to NEW turns only — the config object is shared by
207
+ // reference with the request handlers, no restart needed.
208
+ let body: { codexAutoStart?: unknown; streamMode?: unknown; appOwnedMemoryBudgetMb?: unknown };
209
+ try { body = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
210
+ if (body.codexAutoStart === undefined && body.streamMode === undefined && body.appOwnedMemoryBudgetMb === undefined) {
211
+ return jsonResponse({ error: "provide codexAutoStart, streamMode, or appOwnedMemoryBudgetMb" }, 400);
212
+ }
213
+ if (body.codexAutoStart !== undefined && typeof body.codexAutoStart !== "boolean") {
214
+ return jsonResponse({ error: "codexAutoStart boolean is required" }, 400);
215
+ }
216
+ if (body.streamMode !== undefined && !isStreamMode(body.streamMode)) {
217
+ return jsonResponse({ error: "streamMode must be auto, legacy-tee, or eager-relay" }, 400);
218
+ }
219
+ if (body.appOwnedMemoryBudgetMb !== undefined && (
220
+ typeof body.appOwnedMemoryBudgetMb !== "number"
221
+ || !Number.isInteger(body.appOwnedMemoryBudgetMb)
222
+ || body.appOwnedMemoryBudgetMb < MIN_APP_OWNED_MEMORY_BUDGET_MB
223
+ || body.appOwnedMemoryBudgetMb > MAX_APP_OWNED_MEMORY_BUDGET_MB
224
+ )) {
225
+ return jsonResponse({ error: `appOwnedMemoryBudgetMb must be an integer from ${MIN_APP_OWNED_MEMORY_BUDGET_MB} to ${MAX_APP_OWNED_MEMORY_BUDGET_MB}` }, 400);
226
+ }
227
+ if (typeof body.codexAutoStart === "boolean") {
228
+ config.codexAutoStart = body.codexAutoStart;
229
+ }
230
+ if (body.streamMode !== undefined) {
231
+ if (body.streamMode === "auto") {
232
+ delete config.streamMode;
233
+ } else {
234
+ config.streamMode = body.streamMode as "legacy-tee" | "eager-relay";
235
+ }
236
+ }
237
+ if (typeof body.appOwnedMemoryBudgetMb === "number") {
238
+ config.appOwnedMemoryBudgetMb = body.appOwnedMemoryBudgetMb;
239
+ }
240
+ saveConfigPreservingClaudeCode(config);
241
+ if (typeof body.appOwnedMemoryBudgetMb === "number") {
242
+ configureAppOwnedMemoryBudget(resolveAppOwnedMemoryBudgetBytes(body.appOwnedMemoryBudgetMb));
243
+ enforceAppOwnedMemoryBudget();
244
+ }
245
+ invalidateStartupHealthCache();
246
+ return jsonResponse({
247
+ ok: true,
248
+ codexAutoStart: codexAutoStartEnabled(config),
249
+ streamMode: config.streamMode ?? "auto",
250
+ appOwnedMemoryBudgetMb: config.appOwnedMemoryBudgetMb ?? 256,
251
+ startupHealth: await getCachedStartupHealth(config),
252
+ });
253
+ }
254
+
255
+ if (url.pathname === "/api/diagnostics/project-config" && req.method === "GET") {
256
+ const { getCachedProjectConfigDiagnostics } = await import("../../codex/project-config-warnings");
257
+ const { warnings, grouped } = getCachedProjectConfigDiagnostics();
258
+ return jsonResponse({ warnings, grouped });
259
+ }
260
+
261
+ if (url.pathname === "/api/sync" && req.method === "POST") {
262
+ const { syncModelsToCodex } = await import("../../codex/sync");
263
+ const { attachStaleAppServerHint } = await import("../../codex/app-server-processes");
264
+ const result = await syncModelsToCodex(undefined, config, null);
265
+ return jsonResponse({
266
+ ...attachStaleAppServerHint(result),
267
+ ...(result.ok ? {} : { error: result.message }),
268
+ }, result.ok ? 200 : 500);
269
+ }
270
+
271
+ if (url.pathname === "/api/update/check" && req.method === "GET") {
272
+ const { checkForUpdate, normalizeUpdateChannel } = await import("../../update/job");
273
+ const rawTag = url.searchParams.get("tag");
274
+ if (rawTag && rawTag !== "latest" && rawTag !== "preview") {
275
+ return jsonResponse({ error: "tag must be latest or preview" }, 400);
276
+ }
277
+ return jsonResponse(checkForUpdate(normalizeUpdateChannel(rawTag)));
278
+ }
279
+
280
+ if (url.pathname === "/api/update/run" && req.method === "POST") {
281
+ const { normalizeUpdateChannel, startUpdateJob, UpdateJobError } = await import("../../update/job");
282
+ let body: { tag?: unknown; restart?: unknown };
283
+ try { body = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
284
+ if (body.tag !== undefined && body.tag !== "latest" && body.tag !== "preview") {
285
+ return jsonResponse({ error: "tag must be latest or preview" }, 400);
286
+ }
287
+ if (body.restart !== undefined && typeof body.restart !== "boolean") {
288
+ return jsonResponse({ error: "restart boolean is required" }, 400);
289
+ }
290
+ try {
291
+ return jsonResponse({ ok: true, job: startUpdateJob(normalizeUpdateChannel(body.tag as string | undefined), body.restart !== false) });
292
+ } catch (err) {
293
+ if (err instanceof UpdateJobError) {
294
+ return jsonResponse({ error: err.message, code: err.code }, err.status);
295
+ }
296
+ return jsonResponse({ error: err instanceof Error ? err.message : String(err) }, 500);
297
+ }
298
+ }
299
+
300
+ if (url.pathname === "/api/update/status" && req.method === "GET") {
301
+ const { readUpdateJob } = await import("../../update/job");
302
+ const job = readUpdateJob(url.searchParams.get("jobId"));
303
+ if (!job) return jsonResponse({ error: "update job not found" }, 404);
304
+ return jsonResponse({ ok: true, job });
305
+ }
306
+
307
+ if (url.pathname === "/api/sidecar-settings" && req.method === "GET") {
308
+ const ws = config.webSearchSidecar ?? {};
309
+ const vs = config.visionSidecar ?? {};
310
+ return jsonResponse({
311
+ webSearch: { model: ws.model ?? "gpt-5.6-luna", backend: ws.backend },
312
+ vision: {
313
+ model: vs.model ?? "gpt-5.6-luna",
314
+ backend: vs.backend,
315
+ maxDescriptionsPerTurn: vs.maxDescriptionsPerTurn,
316
+ },
317
+ });
318
+ }
319
+
320
+ if (url.pathname === "/api/sidecar-settings" && req.method === "PUT") {
321
+ let raw: unknown;
322
+ try { raw = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
323
+ // Strict shape (review F2): reject non-object bodies and non-object sections instead of throwing
324
+ // on `null` or silently accepting arrays/strings as no-op updates.
325
+ if (!isPlainRecord(raw)) return jsonResponse({ error: "body must be a JSON object" }, 400);
326
+ if (raw.webSearch !== undefined && !isPlainRecord(raw.webSearch)) return jsonResponse({ error: "webSearch must be an object" }, 400);
327
+ if (raw.vision !== undefined && !isPlainRecord(raw.vision)) return jsonResponse({ error: "vision must be an object" }, 400);
328
+ const body = raw as {
329
+ webSearch?: { model?: unknown; backend?: unknown; reasoning?: unknown };
330
+ vision?: { model?: unknown; backend?: unknown; maxDescriptionsPerTurn?: unknown };
331
+ };
332
+ if (body.webSearch && body.webSearch.backend !== undefined && body.webSearch.backend !== null
333
+ && body.webSearch.backend !== "openai" && body.webSearch.backend !== "anthropic") {
334
+ return jsonResponse({ error: "webSearch.backend must be openai, anthropic, or null" }, 400);
335
+ }
336
+ if (body.vision && body.vision.backend !== undefined
337
+ && body.vision.backend !== null && body.vision.backend !== "openai" && body.vision.backend !== "anthropic") {
338
+ return jsonResponse({ error: "vision.backend must be openai, anthropic, or null" }, 400);
339
+ }
340
+ if (body.vision && body.vision.maxDescriptionsPerTurn !== undefined
341
+ && (typeof body.vision.maxDescriptionsPerTurn !== "number"
342
+ || !Number.isInteger(body.vision.maxDescriptionsPerTurn)
343
+ || body.vision.maxDescriptionsPerTurn <= 0)) {
344
+ return jsonResponse({ error: "vision.maxDescriptionsPerTurn must be a positive integer" }, 400);
345
+ }
346
+ if (body.webSearch) {
347
+ config.webSearchSidecar = { ...config.webSearchSidecar };
348
+ if (typeof body.webSearch.model === "string") {
349
+ if (body.webSearch.model === "") delete config.webSearchSidecar.model;
350
+ else config.webSearchSidecar.model = body.webSearch.model;
351
+ }
352
+ if (body.webSearch.backend === null) delete config.webSearchSidecar.backend;
353
+ else if (body.webSearch.backend === "openai" || body.webSearch.backend === "anthropic") {
354
+ config.webSearchSidecar.backend = body.webSearch.backend;
355
+ }
356
+ if (typeof body.webSearch.reasoning === "string") config.webSearchSidecar.reasoning = body.webSearch.reasoning;
357
+ }
358
+ if (body.vision) {
359
+ config.visionSidecar = { ...config.visionSidecar };
360
+ if (typeof body.vision.model === "string") {
361
+ if (body.vision.model === "") delete config.visionSidecar.model;
362
+ else config.visionSidecar.model = body.vision.model;
363
+ }
364
+ if (body.vision.backend === null) delete config.visionSidecar.backend;
365
+ else if (body.vision.backend === "openai" || body.vision.backend === "anthropic") {
366
+ config.visionSidecar.backend = body.vision.backend;
367
+ }
368
+ if (typeof body.vision.maxDescriptionsPerTurn === "number") {
369
+ config.visionSidecar.maxDescriptionsPerTurn = body.vision.maxDescriptionsPerTurn;
370
+ }
371
+ }
372
+ saveConfigPreservingClaudeCode(config);
373
+ const ws = config.webSearchSidecar ?? {};
374
+ const vs = config.visionSidecar ?? {};
375
+ return jsonResponse({
376
+ ok: true,
377
+ webSearch: { model: ws.model ?? "gpt-5.6-luna", backend: ws.backend },
378
+ vision: {
379
+ model: vs.model ?? "gpt-5.6-luna",
380
+ backend: vs.backend,
381
+ maxDescriptionsPerTurn: vs.maxDescriptionsPerTurn,
382
+ },
383
+ });
384
+ }
385
+
386
+ if (url.pathname === "/api/shadow-call-settings" && req.method === "GET") {
387
+ const sci = config.shadowCallIntercept ?? {};
388
+ return jsonResponse({
389
+ enabled: sci.enabled === true,
390
+ model: sci.model ?? "",
391
+ sourceModels: shadowSourceModels(sci.sourceModels),
392
+ });
393
+ }
394
+
395
+ if (url.pathname === "/api/shadow-call-settings" && req.method === "PUT") {
396
+ let raw: unknown;
397
+ try { raw = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
398
+ if (!isPlainRecord(raw)) return jsonResponse({ error: "body must be a JSON object" }, 400);
399
+ const body = raw as { enabled?: unknown; model?: unknown };
400
+ if (body.enabled !== undefined && typeof body.enabled !== "boolean") {
401
+ return jsonResponse({ error: "enabled must be a boolean" }, 400);
402
+ }
403
+ if (body.model !== undefined && typeof body.model !== "string") {
404
+ return jsonResponse({ error: "model must be a string" }, 400);
405
+ }
406
+ config.shadowCallIntercept = { ...config.shadowCallIntercept };
407
+ if (typeof body.enabled === "boolean") config.shadowCallIntercept.enabled = body.enabled;
408
+ if (typeof body.model === "string") {
409
+ if (body.model === "") delete config.shadowCallIntercept.model;
410
+ else config.shadowCallIntercept.model = body.model;
411
+ }
412
+ saveConfigPreservingClaudeCode(config);
413
+ const sci = config.shadowCallIntercept;
414
+ return jsonResponse({
415
+ ok: true,
416
+ enabled: sci.enabled === true,
417
+ model: sci.model ?? "",
418
+ sourceModels: shadowSourceModels(sci.sourceModels),
419
+ });
420
+ }
421
+ return null;
422
+ }
@@ -0,0 +1,31 @@
1
+ import type { OcxConfig } from "../../types";
2
+ import type { StartupInstallAction } from "../startup-action-control";
3
+
4
+ export interface ManagementApiDeps {
5
+ toggleCodexMultiAgentV2?: (enabled: boolean) => void;
6
+ refreshCodexCatalog?: () => Promise<void>;
7
+ /**
8
+ * Persistence seam for route-level tests. Production leaves this unset and uses
9
+ * `saveConfigPreservingClaudeCode`; tests that pass an in-memory fixture config
10
+ * MUST inject a no-op/spy so the fixture can never overwrite the user's real
11
+ * OPENCODEX_HOME (incident: devlog 260730.../070).
12
+ */
13
+ saveConfigPreservingClaudeCode?: (config: OcxConfig) => void;
14
+ clearThreadAccountMap?: () => void;
15
+ clearProviderQuotaCache?: () => void;
16
+ primeCodexPoolQuotas?: (config: OcxConfig, reason: string) => Promise<void> | void;
17
+ runStartupInstallAction?: (
18
+ action: StartupInstallAction,
19
+ options?: { repair?: boolean },
20
+ ) => Promise<{ message: string }>;
21
+ }
22
+
23
+
24
+ export interface ManagementContext {
25
+ req: Request;
26
+ url: URL;
27
+ config: OcxConfig;
28
+ deps: ManagementApiDeps;
29
+ refreshCodexCatalogBestEffort: () => Promise<void>;
30
+ syncClaudeAgentDefsBestEffort: () => Promise<void>;
31
+ }