@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,175 @@
1
+ import { resolveEnvValue } from "../config";
2
+ import {
3
+ headersForCodexAuthContext,
4
+ hasCallerCodexBearer,
5
+ isCodexAuthContextUsable,
6
+ resolveCodexAuthContext,
7
+ type CodexAuthContext,
8
+ } from "../codex/auth-context";
9
+ import { recordCodexUpstreamOutcome, type CodexUpstreamOutcome } from "../codex/routing";
10
+ import { extractAccountId } from "../oauth/chatgpt";
11
+ import { ForwardAdmissionCredentialError, validateForwardAdmissionCredential } from "../server/auth-cors";
12
+ import type { CodexAccountMode, OcxConfig, OcxProviderConfig } from "../types";
13
+ import {
14
+ isCanonicalOpenAiForwardProvider,
15
+ OPENAI_API_PROVIDER_ID,
16
+ OPENAI_CODEX_PROVIDER_ID,
17
+ } from "./openai-tiers";
18
+ import { getProviderRegistryEntry, providerCodexAccountMode } from "./registry";
19
+
20
+ export interface OpenAiForwardSidecarCandidate {
21
+ providerName: typeof OPENAI_CODEX_PROVIDER_ID;
22
+ provider: OcxProviderConfig;
23
+ accountMode: CodexAccountMode;
24
+ }
25
+
26
+ export interface ResolvedOpenAiForwardSidecar extends OpenAiForwardSidecarCandidate {
27
+ authContext: CodexAuthContext;
28
+ headers: Headers;
29
+ recordOutcome?: (outcome: CodexUpstreamOutcome) => void;
30
+ }
31
+
32
+ export interface OpenAiImagesProviderSelection {
33
+ forwardCandidates: OpenAiForwardSidecarCandidate[];
34
+ keyed?: {
35
+ providerName: string;
36
+ provider: OcxProviderConfig;
37
+ apiKey: string;
38
+ };
39
+ error?: string;
40
+ }
41
+
42
+ export function listOpenAiForwardSidecarCandidates(config: OcxConfig): OpenAiForwardSidecarCandidate[] {
43
+ const provider = config.providers[OPENAI_CODEX_PROVIDER_ID];
44
+ if (!provider || provider.disabled === true || !isCanonicalOpenAiForwardProvider(provider)) return [];
45
+ return [{
46
+ providerName: OPENAI_CODEX_PROVIDER_ID,
47
+ provider,
48
+ accountMode: providerCodexAccountMode(OPENAI_CODEX_PROVIDER_ID, provider) ?? "pool",
49
+ }];
50
+ }
51
+
52
+ function directSidecarHeaders(
53
+ incomingHeaders: Headers,
54
+ ): Headers | undefined {
55
+ const bearer = incomingHeaders.get("authorization")?.replace(/^Bearer\s+/i, "").trim();
56
+ if (!bearer) return undefined;
57
+ const derivedAccountId = extractAccountId(undefined, bearer);
58
+ if (!derivedAccountId) return undefined;
59
+ const requestedAccountId = incomingHeaders.get("chatgpt-account-id")?.trim();
60
+ // JWT payloads are decoded locally but not signature-verified. Requiring the caller's
61
+ // explicit account header, and checking it against the token claim, makes forwarding an
62
+ // intentional ChatGPT-auth operation instead of silently reclassifying any JWT-shaped
63
+ // provider credential as a Codex bearer.
64
+ if (!requestedAccountId || requestedAccountId !== derivedAccountId) return undefined;
65
+ const selected = headersForCodexAuthContext(incomingHeaders, { kind: "main", accountId: null });
66
+ return selected;
67
+ }
68
+
69
+ export async function resolveFirstUsableOpenAiSidecar(
70
+ candidates: readonly OpenAiForwardSidecarCandidate[],
71
+ incomingHeaders: Headers,
72
+ config: OcxConfig,
73
+ ): Promise<ResolvedOpenAiForwardSidecar | undefined> {
74
+ let callerBearerMayBeForwarded = true;
75
+ try {
76
+ validateForwardAdmissionCredential(incomingHeaders, config);
77
+ } catch (error) {
78
+ if (!(error instanceof ForwardAdmissionCredentialError)) throw error;
79
+ callerBearerMayBeForwarded = false;
80
+ }
81
+ for (const candidate of candidates) {
82
+ if (candidate.accountMode === "direct") {
83
+ if (!callerBearerMayBeForwarded || !hasCallerCodexBearer(incomingHeaders)) continue;
84
+ const headers = directSidecarHeaders(incomingHeaders);
85
+ if (!headers) continue;
86
+ return {
87
+ ...candidate,
88
+ authContext: { kind: "main", accountId: null },
89
+ headers,
90
+ };
91
+ }
92
+ const authContext = await resolveCodexAuthContext(incomingHeaders, config, candidate.accountMode);
93
+ if (!isCodexAuthContextUsable(authContext, config)) continue;
94
+ return {
95
+ ...candidate,
96
+ authContext,
97
+ headers: headersForCodexAuthContext(incomingHeaders, authContext),
98
+ ...(authContext.kind === "pool" || authContext.kind === "main-pool"
99
+ ? {
100
+ recordOutcome: (outcome: CodexUpstreamOutcome) => recordCodexUpstreamOutcome(
101
+ config,
102
+ authContext.accountId,
103
+ outcome,
104
+ {
105
+ probeLeaseId: authContext.probeLeaseId,
106
+ writerGeneration: authContext.writerGeneration,
107
+ },
108
+ ),
109
+ }
110
+ : {}),
111
+ };
112
+ }
113
+ return undefined;
114
+ }
115
+
116
+ export function selectOpenAiImagesProvider(config: OcxConfig): OpenAiImagesProviderSelection {
117
+ const selection: OpenAiImagesProviderSelection = {
118
+ forwardCandidates: listOpenAiForwardSidecarCandidates(config),
119
+ };
120
+ const provider = config.providers[OPENAI_API_PROVIDER_ID];
121
+ if (
122
+ provider
123
+ && provider.disabled !== true
124
+ && provider.adapter === "openai-responses"
125
+ && provider.authMode !== "forward"
126
+ && provider.baseUrl.replace(/\/+$/, "") === "https://api.openai.com/v1"
127
+ ) {
128
+ const apiKey = resolveEnvValue(provider.apiKey)?.trim();
129
+ if (apiKey) selection.keyed = { providerName: OPENAI_API_PROVIDER_ID, provider, apiKey };
130
+ }
131
+ return selection;
132
+ }
133
+
134
+ /** Resolve an explicit custom Images provider, otherwise preserve the existing OpenAI fallback. */
135
+ export function selectImagesProvider(config: OcxConfig): OpenAiImagesProviderSelection {
136
+ const configuredProvider = config.images?.provider;
137
+ if (configuredProvider === undefined) return selectOpenAiImagesProvider(config);
138
+ if (typeof configuredProvider !== "string" || !configuredProvider.trim()) {
139
+ return { forwardCandidates: [], error: "images.provider must be a nonblank provider name" };
140
+ }
141
+ const providerName = configuredProvider.trim();
142
+
143
+ if (getProviderRegistryEntry(providerName)) {
144
+ return {
145
+ forwardCandidates: [],
146
+ error: `images.provider "${providerName}" must name a custom provider; omit it to use built-in OpenAI tiers`,
147
+ };
148
+ }
149
+
150
+ const provider = Object.prototype.hasOwnProperty.call(config.providers, providerName)
151
+ ? config.providers[providerName]
152
+ : undefined;
153
+ if (!provider) {
154
+ return { forwardCandidates: [], error: `images.provider "${providerName}" is not configured` };
155
+ }
156
+ if (provider.disabled === true) {
157
+ return { forwardCandidates: [], error: `images.provider "${providerName}" is disabled` };
158
+ }
159
+ if (provider.adapter !== "openai-responses" || (provider.authMode !== undefined && provider.authMode !== "key")) {
160
+ return {
161
+ forwardCandidates: [],
162
+ error: `images.provider "${providerName}" must be an API-key openai-responses provider`,
163
+ };
164
+ }
165
+
166
+ const apiKey = resolveEnvValue(provider.apiKey)?.trim();
167
+ if (!apiKey) {
168
+ return { forwardCandidates: [], error: `images.provider "${providerName}" has no usable API key` };
169
+ }
170
+
171
+ return {
172
+ forwardCandidates: [],
173
+ keyed: { providerName, provider, apiKey },
174
+ };
175
+ }
@@ -0,0 +1,27 @@
1
+ import { backupConfigBeforeOpenAiTierMigration, saveConfig } from "../config";
2
+ import type { OcxConfig } from "../types";
3
+ import { projectOpenAiTierMigration } from "./openai-tiers";
4
+
5
+ export interface OpenAiTierStartupDeps {
6
+ project: typeof projectOpenAiTierMigration;
7
+ backup: () => void;
8
+ save: (config: OcxConfig) => void;
9
+ }
10
+
11
+ const DEFAULT_DEPS: OpenAiTierStartupDeps = {
12
+ project: projectOpenAiTierMigration,
13
+ backup: backupConfigBeforeOpenAiTierMigration,
14
+ save: saveConfig,
15
+ };
16
+
17
+ export function runOpenAiTierStartupMigration(
18
+ config: OcxConfig,
19
+ deps: OpenAiTierStartupDeps = DEFAULT_DEPS,
20
+ ): OcxConfig {
21
+ const projection = deps.project(config);
22
+ if (!projection.changed) return projection.config;
23
+ deps.backup();
24
+ deps.save(projection.config);
25
+ for (const warning of projection.warnings) console.warn(`[openai-provider-migration] ${warning}`);
26
+ return projection.config;
27
+ }
@@ -0,0 +1,301 @@
1
+ import type { CodexAccountMode, OcxConfig, OcxProviderConfig } from "../types";
2
+ import { OPENAI_PROVIDER_TIER_VERSION } from "../types";
3
+
4
+ export const OPENAI_CODEX_PROVIDER_ID = "openai";
5
+ export const LEGACY_OPENAI_MULTI_PROVIDER_ID = "openai-multi";
6
+ export const OPENAI_API_PROVIDER_ID = "openai-apikey";
7
+ export const LEGACY_CHATGPT_PROVIDER_ID = "chatgpt";
8
+
9
+ export const CODEX_FORWARD_BASE_URL = "https://chatgpt.com/backend-api/codex";
10
+ const LEGACY_OPENAI_MULTI_PREFIX = `${LEGACY_OPENAI_MULTI_PROVIDER_ID}/`;
11
+
12
+ function canonicalCodexForwardProvider(mode: CodexAccountMode): OcxProviderConfig {
13
+ return {
14
+ adapter: "openai-responses",
15
+ baseUrl: CODEX_FORWARD_BASE_URL,
16
+ authMode: "forward",
17
+ codexAccountMode: mode,
18
+ };
19
+ }
20
+
21
+ function normalizedBaseUrl(value: string): string | undefined {
22
+ try {
23
+ const url = new URL(value.trim());
24
+ if (url.username || url.password || url.search || url.hash) return undefined;
25
+ const path = url.pathname.replace(/\/+$/, "");
26
+ return `${url.origin}${path}`;
27
+ } catch {
28
+ return undefined;
29
+ }
30
+ }
31
+
32
+ export function isCanonicalOpenAiForwardProvider(provider: OcxProviderConfig): boolean {
33
+ return provider.adapter === "openai-responses"
34
+ && provider.authMode === "forward"
35
+ && normalizedBaseUrl(provider.baseUrl) === CODEX_FORWARD_BASE_URL;
36
+ }
37
+
38
+ const OPENAI_API_BASE_URL = "https://api.openai.com/v1";
39
+
40
+ /**
41
+ * Whether this provider can serve `POST /responses/compact`. The canonical ChatGPT
42
+ * backend can, and so can the official OpenAI API — but an arbitrary gateway that
43
+ * merely speaks the Responses wire cannot, and calling it there fails compaction
44
+ * with an unhelpful error instead of falling back to a routed summary (#422).
45
+ */
46
+ export function supportsNativeResponsesCompactEndpoint(
47
+ providerName: string,
48
+ provider: OcxProviderConfig,
49
+ ): boolean {
50
+ if (isCanonicalOpenAiForwardProvider(provider)) return true;
51
+ return providerName === OPENAI_API_PROVIDER_ID
52
+ && provider.adapter === "openai-responses"
53
+ && normalizedBaseUrl(provider.baseUrl) === OPENAI_API_BASE_URL;
54
+ }
55
+
56
+ export interface OpenAiTierMigrationProjection {
57
+ config: OcxConfig;
58
+ changed: boolean;
59
+ resolvedMode: CodexAccountMode;
60
+ warnings: string[];
61
+ }
62
+
63
+ export class OpenAiTierMigrationCollisionError extends Error {
64
+ readonly providerName = LEGACY_OPENAI_MULTI_PROVIDER_ID;
65
+
66
+ constructor() {
67
+ super(`Reserved provider id "${LEGACY_OPENAI_MULTI_PROVIDER_ID}" is already configured with a noncanonical shape`);
68
+ this.name = "OpenAiTierMigrationCollisionError";
69
+ }
70
+ }
71
+
72
+ function managedLegacyMultiOverlay(provider: OcxProviderConfig): Pick<OcxProviderConfig, "disabled" | "selectedModels"> | null {
73
+ const allowed = new Set(["adapter", "authMode", "baseUrl", "disabled", "selectedModels"]);
74
+ if (!Object.keys(provider).every(key => allowed.has(key))) return null;
75
+ if (!isCanonicalOpenAiForwardProvider(provider)) return null;
76
+ if (provider.disabled !== undefined && typeof provider.disabled !== "boolean") return null;
77
+ if (provider.selectedModels !== undefined && (
78
+ !Array.isArray(provider.selectedModels)
79
+ || provider.selectedModels.some(model => typeof model !== "string")
80
+ )) return null;
81
+ return {
82
+ ...(provider.disabled !== undefined ? { disabled: provider.disabled } : {}),
83
+ ...(provider.selectedModels !== undefined ? { selectedModels: [...provider.selectedModels] } : {}),
84
+ };
85
+ }
86
+
87
+ function rewriteLegacyOpenAiSelectedId(value: string): string {
88
+ return value.startsWith(LEGACY_OPENAI_MULTI_PREFIX)
89
+ ? value.slice(LEGACY_OPENAI_MULTI_PREFIX.length)
90
+ : value;
91
+ }
92
+
93
+ function rewriteLegacyOpenAiModelList(values: string[] | undefined): string[] | undefined {
94
+ if (!values) return undefined;
95
+ return [...new Set(values.map(rewriteLegacyOpenAiSelectedId))];
96
+ }
97
+
98
+ function mergeLegacyOpenAiProviderRows(
99
+ openai: OcxProviderConfig | undefined,
100
+ legacyMulti: OcxProviderConfig | undefined,
101
+ mode: CodexAccountMode,
102
+ ): OcxProviderConfig {
103
+ const selectedModels = rewriteLegacyOpenAiModelList([
104
+ ...(openai?.selectedModels ?? []),
105
+ ...(legacyMulti?.selectedModels ?? []),
106
+ ]);
107
+ const formerRows = [openai, legacyMulti].filter((row): row is OcxProviderConfig => row !== undefined);
108
+ const disabled = formerRows.length > 0 && formerRows.every(row => row.disabled === true);
109
+ return {
110
+ ...canonicalCodexForwardProvider(mode),
111
+ ...(disabled ? { disabled: true } : {}),
112
+ ...(selectedModels && selectedModels.length > 0 ? { selectedModels } : {}),
113
+ };
114
+ }
115
+
116
+ function hasKnownLegacyOpenAiReference(config: OcxConfig): boolean {
117
+ const matches = (value: unknown): boolean => typeof value === "string" && value.startsWith(LEGACY_OPENAI_MULTI_PREFIX);
118
+ const matchesList = (value: unknown): boolean => Array.isArray(value) && value.some(matches);
119
+ const claude = config.claudeCode;
120
+ return config.defaultProvider === LEGACY_OPENAI_MULTI_PROVIDER_ID
121
+ || matchesList(config.disabledModels)
122
+ || matchesList(config.subagentModels)
123
+ || matches(config.injectionModel)
124
+ || matches(config.shadowCallIntercept?.model)
125
+ || matches(config.webSearchSidecar?.model)
126
+ || matches(config.visionSidecar?.model)
127
+ || matches(claude?.webSearchSidecar?.model)
128
+ || matches(claude?.visionSidecar?.model)
129
+ || matches(claude?.model)
130
+ || matches(claude?.smallFastModel)
131
+ || Object.values(claude?.tierModels ?? {}).some(matches)
132
+ || Object.values(claude?.modelMap ?? {}).some(matches)
133
+ || matchesList(config.providers[OPENAI_CODEX_PROVIDER_ID]?.selectedModels)
134
+ || matchesList(config.providers[LEGACY_OPENAI_MULTI_PROVIDER_ID]?.selectedModels)
135
+ || config.providerContextCaps?.[LEGACY_OPENAI_MULTI_PROVIDER_ID] !== undefined;
136
+ }
137
+
138
+ function rewriteLegacyOpenAiReferences(config: OcxConfig, warnings: string[]): void {
139
+ config.disabledModels = rewriteLegacyOpenAiModelList(config.disabledModels);
140
+ config.subagentModels = rewriteLegacyOpenAiModelList(config.subagentModels);
141
+ if (config.injectionModel) config.injectionModel = rewriteLegacyOpenAiSelectedId(config.injectionModel);
142
+ if (config.shadowCallIntercept?.model) {
143
+ config.shadowCallIntercept.model = rewriteLegacyOpenAiSelectedId(config.shadowCallIntercept.model);
144
+ }
145
+ if (config.webSearchSidecar?.model) config.webSearchSidecar.model = rewriteLegacyOpenAiSelectedId(config.webSearchSidecar.model);
146
+ if (config.visionSidecar?.model) config.visionSidecar.model = rewriteLegacyOpenAiSelectedId(config.visionSidecar.model);
147
+
148
+ const claude = config.claudeCode;
149
+ if (claude?.webSearchSidecar?.model) claude.webSearchSidecar.model = rewriteLegacyOpenAiSelectedId(claude.webSearchSidecar.model);
150
+ if (claude?.visionSidecar?.model) claude.visionSidecar.model = rewriteLegacyOpenAiSelectedId(claude.visionSidecar.model);
151
+ if (claude?.model) claude.model = rewriteLegacyOpenAiSelectedId(claude.model);
152
+ if (claude?.smallFastModel) claude.smallFastModel = rewriteLegacyOpenAiSelectedId(claude.smallFastModel);
153
+ if (claude?.tierModels) {
154
+ for (const tier of ["opus", "sonnet", "haiku", "fable"] as const) {
155
+ const value = claude.tierModels[tier];
156
+ if (value) claude.tierModels[tier] = rewriteLegacyOpenAiSelectedId(value);
157
+ }
158
+ }
159
+ if (claude?.modelMap) {
160
+ for (const key of Object.keys(claude.modelMap)) {
161
+ claude.modelMap[key] = rewriteLegacyOpenAiSelectedId(claude.modelMap[key]!);
162
+ }
163
+ }
164
+
165
+ const caps = config.providerContextCaps;
166
+ const legacyCap = caps?.[LEGACY_OPENAI_MULTI_PROVIDER_ID];
167
+ if (caps && legacyCap !== undefined) {
168
+ const currentCap = caps[OPENAI_CODEX_PROVIDER_ID];
169
+ if (currentCap !== undefined) {
170
+ caps[OPENAI_CODEX_PROVIDER_ID] = Math.min(currentCap, legacyCap);
171
+ warnings.push("providerContextCaps.openai + providerContextCaps.openai-multi: kept lower positive cap");
172
+ } else {
173
+ caps[OPENAI_CODEX_PROVIDER_ID] = legacyCap;
174
+ }
175
+ delete caps[LEGACY_OPENAI_MULTI_PROVIDER_ID];
176
+ }
177
+ }
178
+
179
+ function isKnownLegacyValuePath(path: readonly string[]): boolean {
180
+ const joined = path.join(".");
181
+ if (joined === "defaultProvider") return true;
182
+ if (/^(disabledModels|subagentModels)\.\d+$/.test(joined)) return true;
183
+ if (/^providers\.(openai|openai-multi)\.selectedModels\.\d+$/.test(joined)) return true;
184
+ return new Set([
185
+ "injectionModel",
186
+ "shadowCallIntercept.model",
187
+ "webSearchSidecar.model",
188
+ "visionSidecar.model",
189
+ "claudeCode.webSearchSidecar.model",
190
+ "claudeCode.visionSidecar.model",
191
+ "claudeCode.model",
192
+ "claudeCode.smallFastModel",
193
+ "claudeCode.tierModels.opus",
194
+ "claudeCode.tierModels.sonnet",
195
+ "claudeCode.tierModels.haiku",
196
+ "claudeCode.tierModels.fable",
197
+ ]).has(joined) || /^claudeCode\.modelMap\..+$/.test(joined);
198
+ }
199
+
200
+ function unknownLegacyOpenAiWarnings(config: OcxConfig): string[] {
201
+ const warnings = new Set<string>();
202
+ const visit = (value: unknown, path: string[]): void => {
203
+ if (typeof value === "string") {
204
+ if (value.includes(LEGACY_OPENAI_MULTI_PROVIDER_ID) && !isKnownLegacyValuePath(path)) {
205
+ warnings.add(`${path.join(".")}: legacy OpenAI provider id left unchanged`);
206
+ }
207
+ return;
208
+ }
209
+ if (!value || typeof value !== "object") return;
210
+ if (Array.isArray(value)) {
211
+ value.forEach((item, index) => visit(item, [...path, String(index)]));
212
+ return;
213
+ }
214
+ for (const [key, item] of Object.entries(value as Record<string, unknown>)) {
215
+ visit(item, [...path, key]);
216
+ }
217
+ };
218
+ visit(config, []);
219
+ return [...warnings];
220
+ }
221
+
222
+ function resolvedOpenAiMode(
223
+ config: OcxConfig,
224
+ openai: OcxProviderConfig | undefined,
225
+ legacyMulti: OcxProviderConfig | undefined,
226
+ knownLegacyReference: boolean,
227
+ ): CodexAccountMode {
228
+ if (openai?.codexAccountMode === "pool" || openai?.codexAccountMode === "direct") {
229
+ return openai.codexAccountMode;
230
+ }
231
+ if (
232
+ (legacyMulti && legacyMulti.disabled !== true)
233
+ || config.defaultProvider === LEGACY_OPENAI_MULTI_PROVIDER_ID
234
+ || knownLegacyReference
235
+ ) return "pool";
236
+ if (config.openaiProviderTierVersion === 1 && openai) return "direct";
237
+ if (config.openaiProviderTierVersion === 1 && legacyMulti) return "pool";
238
+ return "pool";
239
+ }
240
+
241
+ export function projectOpenAiTierMigration(config: OcxConfig): OpenAiTierMigrationProjection {
242
+ const projected = structuredClone(config);
243
+ const legacyMulti = projected.providers[LEGACY_OPENAI_MULTI_PROVIDER_ID];
244
+ if (legacyMulti && !managedLegacyMultiOverlay(legacyMulti)) {
245
+ throw new OpenAiTierMigrationCollisionError();
246
+ }
247
+
248
+ const openai = projected.providers[OPENAI_CODEX_PROVIDER_ID];
249
+ const knownLegacyReference = hasKnownLegacyOpenAiReference(projected);
250
+ const resolvedMode = resolvedOpenAiMode(projected, openai, legacyMulti, knownLegacyReference);
251
+ if (
252
+ projected.openaiProviderTierVersion === OPENAI_PROVIDER_TIER_VERSION
253
+ && !legacyMulti
254
+ && !Object.hasOwn(projected.providers, LEGACY_CHATGPT_PROVIDER_ID)
255
+ && projected.defaultProvider !== LEGACY_OPENAI_MULTI_PROVIDER_ID
256
+ && !knownLegacyReference
257
+ ) {
258
+ return { config: projected, changed: false, resolvedMode, warnings: [] };
259
+ }
260
+
261
+ const warnings = unknownLegacyOpenAiWarnings(projected);
262
+ const referencesCodexForward = !!openai
263
+ || !!legacyMulti
264
+ || Object.hasOwn(projected.providers, LEGACY_CHATGPT_PROVIDER_ID)
265
+ || projected.defaultProvider === OPENAI_CODEX_PROVIDER_ID
266
+ || projected.defaultProvider === LEGACY_OPENAI_MULTI_PROVIDER_ID
267
+ || projected.defaultProvider === LEGACY_CHATGPT_PROVIDER_ID
268
+ || knownLegacyReference;
269
+ const mergedOpenAi = referencesCodexForward
270
+ ? mergeLegacyOpenAiProviderRows(openai, legacyMulti, resolvedMode)
271
+ : undefined;
272
+
273
+ const nextProviders: Array<[string, OcxProviderConfig]> = [];
274
+ let inserted = false;
275
+ for (const [name, provider] of Object.entries(projected.providers)) {
276
+ if (name === OPENAI_CODEX_PROVIDER_ID) {
277
+ if (mergedOpenAi && !inserted) nextProviders.push([OPENAI_CODEX_PROVIDER_ID, mergedOpenAi]);
278
+ inserted = true;
279
+ continue;
280
+ }
281
+ if (name === LEGACY_OPENAI_MULTI_PROVIDER_ID || name === LEGACY_CHATGPT_PROVIDER_ID) {
282
+ if (mergedOpenAi && !inserted && !openai) {
283
+ nextProviders.push([OPENAI_CODEX_PROVIDER_ID, mergedOpenAi]);
284
+ inserted = true;
285
+ }
286
+ continue;
287
+ }
288
+ nextProviders.push([name, provider]);
289
+ }
290
+ if (mergedOpenAi && !inserted) nextProviders.push([OPENAI_CODEX_PROVIDER_ID, mergedOpenAi]);
291
+ projected.providers = Object.fromEntries(nextProviders);
292
+
293
+ if (
294
+ projected.defaultProvider === LEGACY_OPENAI_MULTI_PROVIDER_ID
295
+ || projected.defaultProvider === LEGACY_CHATGPT_PROVIDER_ID
296
+ ) projected.defaultProvider = OPENAI_CODEX_PROVIDER_ID;
297
+
298
+ rewriteLegacyOpenAiReferences(projected, warnings);
299
+ projected.openaiProviderTierVersion = OPENAI_PROVIDER_TIER_VERSION;
300
+ return { config: projected, changed: true, resolvedMode, warnings: [...new Set(warnings)] };
301
+ }
@@ -0,0 +1,82 @@
1
+ import { PROVIDER_REGISTRY } from "./registry";
2
+ import { OPENAI_API_PROVIDER_ID } from "./openai-tiers";
3
+ import type { OcxParsedRequest } from "../types";
4
+ import type { RouteResult } from "../router";
5
+ import type { RequestLogContext } from "../server/request-log";
6
+
7
+ export interface OpenAiVirtualModelResolution {
8
+ selectedModelId: string;
9
+ wireModelId: string;
10
+ reasoningMode: "pro";
11
+ }
12
+
13
+ export class InvalidOpenAiVirtualModelRegistryError extends Error {
14
+ constructor(selectedModelId: string) {
15
+ super(`Invalid OpenAI virtual model registry definition: ${selectedModelId}`);
16
+ this.name = "InvalidOpenAiVirtualModelRegistryError";
17
+ }
18
+ }
19
+
20
+ export function validateOpenAiVirtualModelDefinition(
21
+ selectedModelId: string,
22
+ definition: unknown,
23
+ ): OpenAiVirtualModelResolution {
24
+ if (!definition || typeof definition !== "object" || Array.isArray(definition)) {
25
+ throw new InvalidOpenAiVirtualModelRegistryError(selectedModelId);
26
+ }
27
+ const raw = definition as { wireModelId?: unknown; reasoningMode?: unknown };
28
+ if (
29
+ typeof raw.wireModelId !== "string"
30
+ || raw.wireModelId.trim() !== raw.wireModelId
31
+ || raw.wireModelId.length === 0
32
+ || raw.wireModelId.includes("/")
33
+ || raw.wireModelId === selectedModelId
34
+ || raw.reasoningMode !== "pro"
35
+ ) {
36
+ throw new InvalidOpenAiVirtualModelRegistryError(selectedModelId);
37
+ }
38
+ return { selectedModelId, wireModelId: raw.wireModelId, reasoningMode: "pro" };
39
+ }
40
+
41
+ export function resolveOpenAiVirtualModel(
42
+ providerName: string,
43
+ selectedModelId: string,
44
+ ): OpenAiVirtualModelResolution | undefined {
45
+ if (providerName !== OPENAI_API_PROVIDER_ID) return undefined;
46
+ const entry = PROVIDER_REGISTRY.find(row => row.id === OPENAI_API_PROVIDER_ID);
47
+ if (!entry?.virtualModels || !Object.hasOwn(entry.virtualModels, selectedModelId)) return undefined;
48
+ const definition = entry.virtualModels[selectedModelId];
49
+ return validateOpenAiVirtualModelDefinition(selectedModelId, definition);
50
+ }
51
+
52
+ export function applyOpenAiVirtualModel(
53
+ parsed: OcxParsedRequest,
54
+ route: RouteResult,
55
+ logCtx: RequestLogContext,
56
+ ): OpenAiVirtualModelResolution | undefined {
57
+ const selectedModelId = logCtx.model && logCtx.model !== route.modelId ? logCtx.model : route.modelId;
58
+ const resolution = resolveOpenAiVirtualModel(route.providerName, selectedModelId);
59
+ if (!resolution) return undefined;
60
+
61
+ logCtx.model = resolution.selectedModelId;
62
+ logCtx.resolvedModel = resolution.wireModelId;
63
+ route.modelId = resolution.wireModelId;
64
+ parsed.modelId = resolution.wireModelId;
65
+
66
+ if (parsed._rawBody && typeof parsed._rawBody === "object" && !Array.isArray(parsed._rawBody)) {
67
+ const raw = parsed._rawBody as Record<string, unknown>;
68
+ raw.model = resolution.wireModelId;
69
+ const existing = raw.reasoning;
70
+ raw.reasoning = existing && typeof existing === "object" && !Array.isArray(existing)
71
+ ? { ...(existing as Record<string, unknown>), mode: resolution.reasoningMode }
72
+ : { mode: resolution.reasoningMode };
73
+ }
74
+ return resolution;
75
+ }
76
+
77
+ export function resolveOpenAiCompactModel(
78
+ providerName: string,
79
+ selectedModelId: string,
80
+ ): OpenAiVirtualModelResolution | undefined {
81
+ return resolveOpenAiVirtualModel(providerName, selectedModelId);
82
+ }
@@ -0,0 +1,102 @@
1
+ import type { OcxProviderConfig, OpenRouterProviderRouting } from "../types";
2
+
3
+ const ROUTING_KEYS = new Set(["order", "only", "allowFallbacks"]);
4
+ const MAX_PROVIDER_SLUGS = 64;
5
+
6
+ function isPlainRecord(value: unknown): value is Record<string, unknown> {
7
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
8
+ const prototype = Object.getPrototypeOf(value);
9
+ return prototype === Object.prototype || prototype === null;
10
+ }
11
+
12
+ export function isCanonicalOpenRouterTarget(baseUrl: string): boolean {
13
+ try {
14
+ const url = new URL(baseUrl);
15
+ return url.origin === "https://openrouter.ai"
16
+ && !url.username
17
+ && !url.password
18
+ && !url.search
19
+ && !url.hash
20
+ && url.pathname.replace(/\/+$/, "") === "/api/v1";
21
+ } catch {
22
+ return false;
23
+ }
24
+ }
25
+
26
+ function routingPreferenceError(value: unknown, field: string): string | null {
27
+ if (!isPlainRecord(value)) return `${field} must be a plain object`;
28
+ const unknown = Object.keys(value).find(key => !ROUTING_KEYS.has(key));
29
+ if (unknown) return `${field} contains unknown field "${unknown}"`;
30
+
31
+ for (const listField of ["order", "only"] as const) {
32
+ const list = value[listField];
33
+ if (list === undefined) continue;
34
+ if (!Array.isArray(list) || list.length === 0 || list.length > MAX_PROVIDER_SLUGS) {
35
+ return `${field}.${listField} must contain 1-${MAX_PROVIDER_SLUGS} provider slugs`;
36
+ }
37
+ const seen = new Set<string>();
38
+ for (const slug of list) {
39
+ if (typeof slug !== "string" || !slug.trim() || slug !== slug.trim() || slug.length > 128) {
40
+ return `${field}.${listField} must contain nonblank trimmed provider slugs up to 128 characters`;
41
+ }
42
+ if (seen.has(slug)) return `${field}.${listField} must not contain duplicate provider slugs`;
43
+ seen.add(slug);
44
+ }
45
+ }
46
+ if (value.allowFallbacks !== undefined && typeof value.allowFallbacks !== "boolean") {
47
+ return `${field}.allowFallbacks must be a boolean`;
48
+ }
49
+ if (value.order === undefined && value.only === undefined && value.allowFallbacks === undefined) {
50
+ return `${field} must define order, only, or allowFallbacks`;
51
+ }
52
+ return null;
53
+ }
54
+
55
+ export function openRouterRoutingConfigError(provider: OcxProviderConfig): string | null {
56
+ const hasDefault = provider.openRouterRouting !== undefined;
57
+ const hasModels = provider.modelOpenRouterRouting !== undefined;
58
+ if (!hasDefault && !hasModels) return null;
59
+ if (provider.adapter !== "openai-chat") {
60
+ return "OpenRouter routing preferences require the openai-chat adapter";
61
+ }
62
+ if (!isCanonicalOpenRouterTarget(provider.baseUrl)) {
63
+ return "OpenRouter routing preferences require the canonical https://openrouter.ai/api/v1 baseUrl";
64
+ }
65
+ if (hasDefault) {
66
+ const error = routingPreferenceError(provider.openRouterRouting, "openRouterRouting");
67
+ if (error) return error;
68
+ }
69
+ if (hasModels) {
70
+ const routes = provider.modelOpenRouterRouting;
71
+ if (!isPlainRecord(routes)) return "modelOpenRouterRouting must be a plain object";
72
+ for (const [modelId, preference] of Object.entries(routes)) {
73
+ if (!modelId.trim() || modelId !== modelId.trim()) {
74
+ return "modelOpenRouterRouting keys must be nonblank trimmed model ids";
75
+ }
76
+ const error = routingPreferenceError(preference, `modelOpenRouterRouting.${modelId}`);
77
+ if (error) return error;
78
+ }
79
+ }
80
+ return null;
81
+ }
82
+
83
+ export function resolveOpenRouterRouting(
84
+ provider: OcxProviderConfig,
85
+ modelId: string,
86
+ ): OpenRouterProviderRouting | undefined {
87
+ if (!isCanonicalOpenRouterTarget(provider.baseUrl)) return undefined;
88
+ const modelRoutes = provider.modelOpenRouterRouting;
89
+ return modelRoutes && Object.hasOwn(modelRoutes, modelId)
90
+ ? modelRoutes[modelId]
91
+ : provider.openRouterRouting;
92
+ }
93
+
94
+ export function openRouterProviderPayload(
95
+ preference: OpenRouterProviderRouting,
96
+ ): Record<string, unknown> {
97
+ return {
98
+ ...(preference.order ? { order: [...preference.order] } : {}),
99
+ ...(preference.only ? { only: [...preference.only] } : {}),
100
+ ...(preference.allowFallbacks !== undefined ? { allow_fallbacks: preference.allowFallbacks } : {}),
101
+ };
102
+ }