@iislee/opencodex 2.11.0 → 2.35.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 (733) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +117 -22
  3. package/bin/ocx.mjs +245 -88
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-Be1sDpWw.css +1 -0
  6. package/gui/dist/assets/index-D4OKgUV4.js +114 -0
  7. package/gui/dist/index.html +2 -2
  8. package/gui/dist/provider-icons/alibaba-color.svg +1 -1
  9. package/gui/dist/provider-icons/antigravity-color.svg +1 -1
  10. package/gui/dist/provider-icons/claude-color.svg +1 -1
  11. package/gui/dist/provider-icons/cline-color.svg +16 -0
  12. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
  13. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  14. package/gui/dist/provider-icons/copilot-color.svg +1 -1
  15. package/gui/dist/provider-icons/cursor-color.svg +1 -1
  16. package/gui/dist/provider-icons/deepseek-color.svg +1 -1
  17. package/gui/dist/provider-icons/firepass-color.svg +1 -1
  18. package/gui/dist/provider-icons/fireworks-color.svg +1 -1
  19. package/gui/dist/provider-icons/gemini-color.svg +1 -1
  20. package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
  21. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
  22. package/gui/dist/provider-icons/grok.svg +1 -1
  23. package/gui/dist/provider-icons/groq-color.svg +1 -1
  24. package/gui/dist/provider-icons/huggingface-color.svg +1 -1
  25. package/gui/dist/provider-icons/kimi-color.svg +1 -1
  26. package/gui/dist/provider-icons/kiro-color.svg +2 -2
  27. package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
  28. package/gui/dist/provider-icons/mistral-color.svg +1 -1
  29. package/gui/dist/provider-icons/moonshot-color.svg +1 -1
  30. package/gui/dist/provider-icons/nvidia-color.svg +1 -1
  31. package/gui/dist/provider-icons/ollama-color.svg +1 -1
  32. package/gui/dist/provider-icons/openai.svg +1 -1
  33. package/gui/dist/provider-icons/opencode.svg +2 -1
  34. package/gui/dist/provider-icons/openrouter-color.svg +1 -1
  35. package/gui/dist/provider-icons/pi.svg +2 -2
  36. package/gui/dist/provider-icons/qianfan-color.svg +1 -1
  37. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
  38. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
  39. package/gui/dist/provider-icons/vllm-color.svg +1 -1
  40. package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
  41. package/package.json +21 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +475 -62
  44. package/src/adapters/base.ts +82 -7
  45. package/src/adapters/client-fingerprint.ts +18 -12
  46. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  47. package/src/adapters/command-code.ts +637 -0
  48. package/src/adapters/cursor/call-id.ts +44 -0
  49. package/src/adapters/cursor/catalog.ts +541 -0
  50. package/src/adapters/cursor/checkpoint-store.ts +308 -0
  51. package/src/adapters/cursor/cursor-errors.ts +144 -6
  52. package/src/adapters/cursor/discovery.ts +122 -14
  53. package/src/adapters/cursor/effort-map.ts +106 -4
  54. package/src/adapters/cursor/envelope-echo.ts +290 -0
  55. package/src/adapters/cursor/framing.ts +39 -0
  56. package/src/adapters/cursor/h2-pool.ts +123 -0
  57. package/src/adapters/cursor/http1-bidi.ts +361 -0
  58. package/src/adapters/cursor/images.ts +704 -0
  59. package/src/adapters/cursor/live-models.ts +180 -59
  60. package/src/adapters/cursor/live-transport.ts +623 -170
  61. package/src/adapters/cursor/message-mapper.ts +4 -1
  62. package/src/adapters/cursor/native-exec-common.ts +23 -2
  63. package/src/adapters/cursor/native-exec-desktop.ts +23 -0
  64. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  65. package/src/adapters/cursor/native-exec-network.ts +1 -1
  66. package/src/adapters/cursor/native-exec-shell.ts +5 -3
  67. package/src/adapters/cursor/native-exec.ts +121 -14
  68. package/src/adapters/cursor/protobuf-events.ts +829 -11
  69. package/src/adapters/cursor/protobuf-request.ts +447 -75
  70. package/src/adapters/cursor/request-builder.ts +271 -35
  71. package/src/adapters/cursor/tool-definitions.ts +252 -12
  72. package/src/adapters/cursor/tool-result-normalize.ts +115 -0
  73. package/src/adapters/cursor/transport.ts +22 -0
  74. package/src/adapters/cursor/types.ts +48 -1
  75. package/src/adapters/cursor.ts +336 -39
  76. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  77. package/src/adapters/google-antigravity-replay.ts +713 -41
  78. package/src/adapters/google-antigravity-wire.ts +48 -10
  79. package/src/adapters/google-errors.ts +44 -12
  80. package/src/adapters/google-http.ts +38 -10
  81. package/src/adapters/google-tool-schema.ts +80 -15
  82. package/src/adapters/google-truncation.ts +11 -0
  83. package/src/adapters/google.ts +742 -86
  84. package/src/adapters/identity.ts +39 -6
  85. package/src/adapters/image.ts +1 -1
  86. package/src/adapters/kiro-constants.ts +27 -0
  87. package/src/adapters/kiro-errors.ts +11 -0
  88. package/src/adapters/kiro-events.ts +19 -1
  89. package/src/adapters/kiro-thinking.ts +18 -2
  90. package/src/adapters/kiro-tools.ts +65 -17
  91. package/src/adapters/kiro.ts +270 -73
  92. package/src/adapters/mimo-free.ts +17 -0
  93. package/src/adapters/openai-chat-url.ts +11 -0
  94. package/src/adapters/openai-chat.ts +1377 -344
  95. package/src/adapters/openai-responses-url.ts +16 -0
  96. package/src/adapters/openai-responses.ts +1164 -64
  97. package/src/adapters/registry.ts +175 -0
  98. package/src/adapters/responses-tool-schema.ts +67 -0
  99. package/src/adapters/run-turn-queue.ts +36 -1
  100. package/src/adapters/tool-call-id.ts +119 -0
  101. package/src/adapters/tool-catalog-nudge.ts +105 -21
  102. package/src/adapters/xai-tool-schema.ts +436 -0
  103. package/src/adapters/xai-web-search.ts +186 -0
  104. package/src/bridge.ts +512 -92
  105. package/src/chat/inbound.ts +57 -20
  106. package/src/chat/outbound.ts +97 -33
  107. package/src/claude/agents-inject.ts +40 -10
  108. package/src/claude/context-windows.ts +37 -14
  109. package/src/claude/desktop-3p.ts +243 -9
  110. package/src/claude/gateway-cache.ts +41 -4
  111. package/src/claude/inbound.ts +72 -3
  112. package/src/claude/model-info.ts +38 -15
  113. package/src/claude/outbound.ts +88 -21
  114. package/src/cli/access.ts +46 -3
  115. package/src/cli/account-api.ts +150 -19
  116. package/src/cli/account-auth.ts +33 -6
  117. package/src/cli/account-catalog-refresh.ts +14 -0
  118. package/src/cli/account-extended.ts +648 -28
  119. package/src/cli/account-main.ts +317 -0
  120. package/src/cli/account.ts +97 -19
  121. package/src/cli/agent.ts +53 -2
  122. package/src/cli/alias.ts +66 -0
  123. package/src/cli/capabilities-command.ts +94 -0
  124. package/src/cli/capabilities.ts +496 -0
  125. package/src/cli/claude-agent-startup-sync.ts +73 -0
  126. package/src/cli/claude-desktop.ts +60 -15
  127. package/src/cli/claude.ts +129 -35
  128. package/src/cli/codex-log-guard-doctor.ts +103 -0
  129. package/src/cli/codex-shim-autorestore.ts +2 -0
  130. package/src/cli/codex-shim-readiness.ts +76 -0
  131. package/src/cli/combo.ts +8 -0
  132. package/src/cli/config-command.ts +74 -10
  133. package/src/cli/dispatch.ts +821 -0
  134. package/src/cli/doctor.ts +440 -56
  135. package/src/cli/ensure-desired-integrations.ts +152 -0
  136. package/src/cli/export-command.ts +46 -20
  137. package/src/cli/help.ts +34 -274
  138. package/src/cli/index.ts +417 -528
  139. package/src/cli/init.ts +5 -17
  140. package/src/cli/inspect.ts +230 -0
  141. package/src/cli/integrations.ts +120 -2
  142. package/src/cli/lab.ts +607 -0
  143. package/src/cli/launcher-context.ts +77 -0
  144. package/src/cli/minimax.ts +497 -0
  145. package/src/cli/models-runtime.ts +130 -2
  146. package/src/cli/models.ts +118 -16
  147. package/src/cli/observe.ts +123 -11
  148. package/src/cli/opencode.ts +4 -2
  149. package/src/cli/provider-runtime.ts +34 -3
  150. package/src/cli/provider.ts +25 -3
  151. package/src/cli/ready.ts +301 -0
  152. package/src/cli/registry.ts +476 -0
  153. package/src/cli/root.ts +86 -0
  154. package/src/cli/route-policy.ts +92 -0
  155. package/src/cli/runtime-api.ts +57 -10
  156. package/src/cli/star-prompt.ts +71 -15
  157. package/src/cli/status.ts +51 -4
  158. package/src/cli/storage.ts +234 -0
  159. package/src/cli/system-command.ts +16 -0
  160. package/src/cli/system-restart-client.ts +146 -0
  161. package/src/cli/tray-proxy.ts +153 -6
  162. package/src/cli/usage-report.ts +184 -0
  163. package/src/cli/v2.ts +137 -18
  164. package/src/cli/version-skew.ts +46 -0
  165. package/src/cli.ts +1 -1
  166. package/src/clients/config-export.ts +1442 -23
  167. package/src/clients/effective-status.ts +30 -2
  168. package/src/clients/probes/cline.ts +81 -0
  169. package/src/codex/account-label.ts +35 -1
  170. package/src/codex/account-lifecycle.ts +130 -13
  171. package/src/codex/account-namespaces.ts +49 -3
  172. package/src/codex/account-pause.ts +2 -1
  173. package/src/codex/account-priority.ts +84 -0
  174. package/src/codex/account-store.ts +29 -2
  175. package/src/codex/account-usability.ts +25 -2
  176. package/src/codex/admission.ts +256 -0
  177. package/src/codex/affinity-debug.ts +162 -0
  178. package/src/codex/app-server-processes.ts +570 -107
  179. package/src/codex/app-server-restart-service.ts +232 -0
  180. package/src/codex/auth-api.ts +893 -246
  181. package/src/codex/auth-collision.ts +5 -3
  182. package/src/codex/auth-context.ts +348 -32
  183. package/src/codex/autostart-health.ts +24 -1
  184. package/src/codex/catalog/account-models.ts +67 -0
  185. package/src/codex/catalog/aggregation.ts +86 -10
  186. package/src/codex/catalog/bundled.ts +331 -33
  187. package/src/codex/catalog/effort.ts +138 -32
  188. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  189. package/src/codex/catalog/kinds.ts +2 -0
  190. package/src/codex/catalog/metadata.ts +555 -45
  191. package/src/codex/catalog/native-models.ts +75 -0
  192. package/src/codex/catalog/parsing.ts +269 -30
  193. package/src/codex/catalog/provider-fetch.ts +1626 -144
  194. package/src/codex/catalog/sync.ts +1576 -187
  195. package/src/codex/catalog-admission.ts +199 -0
  196. package/src/codex/catalog-refresh-status.ts +105 -0
  197. package/src/codex/catalog-write-serialization.ts +242 -0
  198. package/src/codex/catalog.ts +6 -3
  199. package/src/codex/codex-write-lock.ts +386 -0
  200. package/src/codex/convergence-types.ts +614 -0
  201. package/src/codex/convergence.ts +676 -0
  202. package/src/codex/coordinator-doctor.ts +332 -0
  203. package/src/codex/custom-model-catalog-migration.ts +176 -0
  204. package/src/codex/desired-state.ts +230 -0
  205. package/src/codex/desktop-app-restart.ts +355 -0
  206. package/src/codex/features.ts +636 -39
  207. package/src/codex/generation.ts +202 -0
  208. package/src/codex/history-job.ts +436 -0
  209. package/src/codex/history-lock.ts +242 -0
  210. package/src/codex/history-manifest.ts +112 -0
  211. package/src/codex/history-migration-guardian.ts +30 -24
  212. package/src/codex/history-provider.ts +1016 -235
  213. package/src/codex/history-transition.ts +105 -0
  214. package/src/codex/history-worker.ts +223 -0
  215. package/src/codex/inject-coordination.ts +373 -0
  216. package/src/codex/inject.ts +1114 -152
  217. package/src/codex/injected-marker.ts +37 -3
  218. package/src/codex/integration-record.ts +266 -0
  219. package/src/codex/internal/catalog-writer.ts +203 -0
  220. package/src/codex/internal/history-writer.ts +81 -0
  221. package/src/codex/journal.ts +66 -4
  222. package/src/codex/log-guard/inspect.ts +524 -0
  223. package/src/codex/log-guard/lock.ts +150 -0
  224. package/src/codex/log-guard/maintenance.ts +403 -0
  225. package/src/codex/log-guard/path-safety.ts +88 -0
  226. package/src/codex/log-guard/policy.ts +44 -0
  227. package/src/codex/log-guard/processes.ts +205 -0
  228. package/src/codex/log-guard/protection.ts +489 -0
  229. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  230. package/src/codex/main-account-cache.ts +24 -0
  231. package/src/codex/main-account.ts +29 -1
  232. package/src/codex/management-convergence.ts +167 -0
  233. package/src/codex/model-cache.ts +56 -10
  234. package/src/codex/model-entitlements.ts +360 -0
  235. package/src/codex/native-main-admission.ts +47 -0
  236. package/src/codex/native-main-auth-temp.ts +187 -0
  237. package/src/codex/native-main-claim.ts +178 -0
  238. package/src/codex/native-main-lock-file.ts +162 -0
  239. package/src/codex/native-main-owner.ts +329 -0
  240. package/src/codex/native-profile-api.ts +247 -0
  241. package/src/codex/native-profile-manager.ts +1531 -0
  242. package/src/codex/native-profile-processes.ts +121 -0
  243. package/src/codex/native-profile-recovery.ts +99 -0
  244. package/src/codex/native-profile-stage-store.ts +387 -0
  245. package/src/codex/native-profile-startup.ts +622 -0
  246. package/src/codex/native-profile-store.ts +855 -0
  247. package/src/codex/native-profile-types.ts +120 -0
  248. package/src/codex/native-residue.ts +675 -0
  249. package/src/codex/paths.ts +80 -1
  250. package/src/codex/plan-from-token.ts +140 -0
  251. package/src/codex/plan.ts +40 -0
  252. package/src/codex/plugins-doctor.ts +1 -1
  253. package/src/codex/pool-rotation.ts +74 -4
  254. package/src/codex/project-config-warnings.ts +20 -6
  255. package/src/codex/prompt-journal.ts +352 -0
  256. package/src/codex/prompt-layers.ts +1313 -0
  257. package/src/codex/prompt-lock.ts +143 -0
  258. package/src/codex/prompt-text-probe.ts +238 -0
  259. package/src/codex/quota-rejection.ts +298 -0
  260. package/src/codex/quota.ts +264 -21
  261. package/src/codex/refresh.ts +11 -2
  262. package/src/codex/reset-credit-recovery.ts +1044 -0
  263. package/src/codex/routing.ts +514 -94
  264. package/src/codex/runtime.ts +159 -38
  265. package/src/codex/shim.ts +1065 -31
  266. package/src/codex/subagent-model-fallback.ts +437 -43
  267. package/src/codex/sync.ts +191 -2
  268. package/src/codex/transition-state.ts +720 -0
  269. package/src/codex/upstream-host-health.ts +368 -0
  270. package/src/codex/user-identity.ts +557 -0
  271. package/src/codex/warmup.ts +187 -81
  272. package/src/codex/write-coordination.ts +114 -0
  273. package/src/combos/failover.ts +47 -0
  274. package/src/combos/index.ts +4 -0
  275. package/src/combos/request.ts +32 -0
  276. package/src/combos/types.ts +91 -9
  277. package/src/compatibility/index.ts +26 -0
  278. package/src/compatibility/manifest.ts +253 -0
  279. package/src/compatibility/openai-responses.ts +121 -0
  280. package/src/config/atomic-write.ts +219 -0
  281. package/src/config/paths.ts +40 -0
  282. package/src/config/process-state.ts +309 -0
  283. package/src/config/provider-name.ts +24 -0
  284. package/src/config/provider-validation.ts +177 -0
  285. package/src/config/rebase-provenance.ts +68 -0
  286. package/src/config.ts +1713 -709
  287. package/src/generated/compatibility-version.json +3324 -0
  288. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  289. package/src/grok/inject.ts +16 -5
  290. package/src/grok/inspect.ts +45 -0
  291. package/src/grok/sync.ts +2 -2
  292. package/src/images/loop.ts +157 -33
  293. package/src/images/plan.ts +24 -13
  294. package/src/integrations/config-io.ts +269 -0
  295. package/src/integrations/journal.ts +315 -0
  296. package/src/integrations/merge.ts +135 -0
  297. package/src/integrations/mutation-flight.ts +71 -0
  298. package/src/integrations/native/ownership-preflight.ts +202 -0
  299. package/src/integrations/omp-yaml-source.ts +358 -0
  300. package/src/integrations/owned-refresh.ts +74 -0
  301. package/src/integrations/ownership-policy.ts +160 -0
  302. package/src/integrations/ownership.ts +155 -0
  303. package/src/integrations/registry.ts +166 -0
  304. package/src/integrations/serialize.ts +314 -0
  305. package/src/integrations/state.ts +433 -0
  306. package/src/integrations/store.ts +103 -0
  307. package/src/integrations/writer-lock.ts +98 -0
  308. package/src/integrations/writer.ts +715 -0
  309. package/src/lab/artifacts/sanitize.ts +586 -0
  310. package/src/lab/artifacts/secure-fs.ts +475 -0
  311. package/src/lab/artifacts/store.ts +310 -0
  312. package/src/lab/automation/budgets.ts +78 -0
  313. package/src/lab/automation/config-persistence.ts +256 -0
  314. package/src/lab/automation/constants.ts +39 -0
  315. package/src/lab/automation/cooldown.ts +103 -0
  316. package/src/lab/automation/dispatch.ts +211 -0
  317. package/src/lab/automation/index.ts +13 -0
  318. package/src/lab/automation/orchestrator.ts +499 -0
  319. package/src/lab/automation/persistence.ts +512 -0
  320. package/src/lab/automation/planner.ts +371 -0
  321. package/src/lab/automation/policy.ts +136 -0
  322. package/src/lab/automation/queue.ts +191 -0
  323. package/src/lab/automation/recovery.ts +24 -0
  324. package/src/lab/automation/route-context.ts +21 -0
  325. package/src/lab/automation/run-key.ts +44 -0
  326. package/src/lab/automation/runs-query.ts +34 -0
  327. package/src/lab/automation/types.ts +160 -0
  328. package/src/lab/conformance/assertion.ts +325 -0
  329. package/src/lab/conformance/digest.ts +22 -0
  330. package/src/lab/conformance/executor.ts +741 -0
  331. package/src/lab/conformance/fixture-provider.ts +27 -0
  332. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  333. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  334. package/src/lab/conformance/harness-budget.ts +47 -0
  335. package/src/lab/conformance/index.ts +5 -0
  336. package/src/lab/conformance/jcs.ts +64 -0
  337. package/src/lab/conformance/json-pointer.ts +39 -0
  338. package/src/lab/conformance/manifest.ts +180 -0
  339. package/src/lab/conformance/mcp-stub.ts +179 -0
  340. package/src/lab/conformance/negative-controls.ts +164 -0
  341. package/src/lab/conformance/observation.ts +355 -0
  342. package/src/lab/conformance/runner.ts +68 -0
  343. package/src/lab/conformance/sse-normalize.ts +59 -0
  344. package/src/lab/conformance/suite-manifest.ts +78 -0
  345. package/src/lab/conformance/types.ts +214 -0
  346. package/src/lab/constants.ts +126 -0
  347. package/src/lab/digest.ts +64 -0
  348. package/src/lab/events/errors.ts +9 -0
  349. package/src/lab/events/limits.ts +117 -0
  350. package/src/lab/events/types.ts +229 -0
  351. package/src/lab/events/validate.ts +781 -0
  352. package/src/lab/fabric/constants.ts +40 -0
  353. package/src/lab/fabric/executor.ts +492 -0
  354. package/src/lab/fabric/index.ts +80 -0
  355. package/src/lab/fabric/manifest.ts +222 -0
  356. package/src/lab/fabric/observe.ts +489 -0
  357. package/src/lab/fabric/patch.ts +79 -0
  358. package/src/lab/fabric/producer-child.ts +139 -0
  359. package/src/lab/fabric/producer-isolate.ts +276 -0
  360. package/src/lab/fabric/producer-protocol.ts +61 -0
  361. package/src/lab/fabric/scratch.ts +439 -0
  362. package/src/lab/fabric/subject.ts +106 -0
  363. package/src/lab/fabric/types.ts +134 -0
  364. package/src/lab/fabric/verifier.ts +98 -0
  365. package/src/lab/index.ts +54 -0
  366. package/src/lab/ledger/artifact-refs.ts +127 -0
  367. package/src/lab/ledger/invalidation.ts +136 -0
  368. package/src/lab/ledger/purge.ts +310 -0
  369. package/src/lab/ledger/store.ts +532 -0
  370. package/src/lab/live/credential-lease.ts +53 -0
  371. package/src/lab/live/destination.ts +155 -0
  372. package/src/lab/live/executor.ts +336 -0
  373. package/src/lab/live/inert-tools.ts +56 -0
  374. package/src/lab/live/manifest.ts +85 -0
  375. package/src/lab/live/mcp-loopback.ts +57 -0
  376. package/src/lab/live/runner.ts +19 -0
  377. package/src/lab/live/sandbox.ts +61 -0
  378. package/src/lab/live/suite-manifest.ts +41 -0
  379. package/src/lab/live/transport.ts +118 -0
  380. package/src/lab/live/types.ts +197 -0
  381. package/src/lab/observe/from-conformance.ts +301 -0
  382. package/src/lab/observe/from-live.ts +117 -0
  383. package/src/lab/paths.ts +153 -0
  384. package/src/lab/projection/rebuild.ts +495 -0
  385. package/src/lab/projection/schema.ts +135 -0
  386. package/src/lab/projection/verdicts.ts +474 -0
  387. package/src/lab/projection/verification.ts +412 -0
  388. package/src/lab/public/bundle.ts +217 -0
  389. package/src/lab/public/community-authority.ts +175 -0
  390. package/src/lab/public/community-files.ts +29 -0
  391. package/src/lab/public/community.ts +479 -0
  392. package/src/lab/public/file-safety.ts +155 -0
  393. package/src/lab/public/ids.ts +26 -0
  394. package/src/lab/public/index.ts +16 -0
  395. package/src/lab/public/mutation-lock.ts +424 -0
  396. package/src/lab/public/operator.ts +353 -0
  397. package/src/lab/public/origin-purge.ts +79 -0
  398. package/src/lab/public/origin.ts +203 -0
  399. package/src/lab/public/privacy.ts +143 -0
  400. package/src/lab/public/private-file.ts +261 -0
  401. package/src/lab/public/project.ts +124 -0
  402. package/src/lab/public/purge-test-fault.ts +21 -0
  403. package/src/lab/public/purge.ts +223 -0
  404. package/src/lab/public/registry.ts +44 -0
  405. package/src/lab/public/revocation.ts +252 -0
  406. package/src/lab/public/signature.ts +243 -0
  407. package/src/lab/public/storage.ts +105 -0
  408. package/src/lab/public/strict-json.ts +206 -0
  409. package/src/lab/public/time.ts +26 -0
  410. package/src/lab/public/types.ts +172 -0
  411. package/src/lab/public/validate.ts +391 -0
  412. package/src/lab/query/catalog.ts +101 -0
  413. package/src/lab/query/connection.ts +107 -0
  414. package/src/lab/query/constants.ts +4 -0
  415. package/src/lab/query/cursor.ts +132 -0
  416. package/src/lab/query/dto-map.ts +277 -0
  417. package/src/lab/query/errors.ts +22 -0
  418. package/src/lab/query/freshness.ts +53 -0
  419. package/src/lab/query/index.ts +45 -0
  420. package/src/lab/query/latest-observation.ts +59 -0
  421. package/src/lab/query/passive-production.ts +159 -0
  422. package/src/lab/query/queries.ts +444 -0
  423. package/src/lab/query/types.ts +266 -0
  424. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  425. package/src/lab/subject/installation-salt.ts +112 -0
  426. package/src/lab/subject/protocol-subject.ts +80 -0
  427. package/src/lab/subject/route-subject.ts +74 -0
  428. package/src/lib/admin-secrets.ts +24 -0
  429. package/src/lib/app-owned-memory-stores.ts +22 -0
  430. package/src/lib/bounded-body.ts +167 -11
  431. package/src/lib/bun-runtime.ts +125 -12
  432. package/src/lib/bun-stream-caps.ts +13 -9
  433. package/src/lib/codex-restart-contract.ts +120 -0
  434. package/src/lib/config-ownership.ts +26 -2
  435. package/src/lib/destination-policy.ts +65 -1
  436. package/src/lib/errors.ts +80 -5
  437. package/src/lib/fabric-task-execution-authority.ts +7 -0
  438. package/src/lib/fabric-task-host.ts +29 -0
  439. package/src/lib/lab-activation.ts +223 -0
  440. package/src/lib/lab-live-execution-authority.ts +13 -0
  441. package/src/lib/lab-live-host.ts +30 -0
  442. package/src/lib/lab-live-pinned-sender.ts +56 -0
  443. package/src/lib/lab-live-route-production.ts +130 -0
  444. package/src/lib/lab-passive-linker-registration.ts +26 -0
  445. package/src/lib/local-management-attestation.ts +51 -0
  446. package/src/lib/local-management-capability.ts +100 -0
  447. package/src/lib/local-provider-reload-contract.ts +100 -0
  448. package/src/lib/optional-shutdown-hooks.ts +57 -0
  449. package/src/lib/package-tree-integrity.ts +101 -0
  450. package/src/lib/pinned-http.ts +145 -26
  451. package/src/lib/process-control.ts +6 -2
  452. package/src/lib/provider-outbound.ts +49 -9
  453. package/src/lib/redact.ts +419 -3
  454. package/src/lib/self-launch-argv.ts +15 -0
  455. package/src/lib/server-resource-ownership.ts +71 -0
  456. package/src/lib/service-secrets.ts +15 -0
  457. package/src/lib/shadow-call.ts +35 -4
  458. package/src/lib/sse-decoder.ts +41 -0
  459. package/src/lib/state-store-registrations.ts +10 -2
  460. package/src/lib/system-restart-contract.ts +73 -0
  461. package/src/lib/token-estimate.ts +19 -2
  462. package/src/lib/tool-argument-integers.ts +253 -0
  463. package/src/lib/translator-budget.ts +44 -0
  464. package/src/lib/upstream-http-version.ts +57 -0
  465. package/src/lib/upstream-reachability.ts +95 -0
  466. package/src/lib/upstream-retry.ts +156 -3
  467. package/src/lib/windows-atomic-replace.ts +155 -0
  468. package/src/lib/windows-elevation.ts +70 -2
  469. package/src/lib/windows-secret-acl.ts +409 -69
  470. package/src/lib/windows-service-wrappers.ts +72 -0
  471. package/src/lib/windows-text.ts +106 -0
  472. package/src/lib/windows-user-principal.ts +341 -0
  473. package/src/lib/winsw.ts +33 -5
  474. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  475. package/src/oauth/account-import/index.ts +15 -0
  476. package/src/oauth/account-import/parser.ts +83 -0
  477. package/src/oauth/account-import/registry.ts +18 -0
  478. package/src/oauth/account-import/service.ts +75 -0
  479. package/src/oauth/account-import/types.ts +91 -0
  480. package/src/oauth/anthropic.ts +12 -1
  481. package/src/oauth/callback-server.ts +30 -4
  482. package/src/oauth/chatgpt.ts +12 -1
  483. package/src/oauth/cline.ts +203 -0
  484. package/src/oauth/command-code.ts +239 -0
  485. package/src/oauth/cursor.ts +46 -5
  486. package/src/oauth/generic-account-failover.ts +231 -0
  487. package/src/oauth/google-antigravity.ts +35 -3
  488. package/src/oauth/health.ts +20 -12
  489. package/src/oauth/index.ts +462 -71
  490. package/src/oauth/key-providers.ts +25 -0
  491. package/src/oauth/kimi.ts +25 -3
  492. package/src/oauth/kiro.ts +95 -6
  493. package/src/oauth/local-token-detect.ts +13 -2
  494. package/src/oauth/log.ts +3 -1
  495. package/src/oauth/login-cli.ts +88 -28
  496. package/src/oauth/nous.ts +798 -0
  497. package/src/oauth/open-browser-choice.ts +26 -0
  498. package/src/oauth/store.ts +133 -26
  499. package/src/oauth/token-guardian.ts +9 -3
  500. package/src/oauth/types.ts +15 -0
  501. package/src/pi/models.ts +2 -2
  502. package/src/providers/alibaba-region-migration.ts +1 -1
  503. package/src/providers/antigravity-models.ts +521 -31
  504. package/src/providers/auto-compact-budget.ts +65 -0
  505. package/src/providers/base-url-choices.ts +10 -0
  506. package/src/providers/codex-capacity.ts +292 -0
  507. package/src/providers/command-code-efforts.ts +176 -0
  508. package/src/providers/context-cap.ts +26 -8
  509. package/src/providers/cursor-pool.ts +72 -0
  510. package/src/providers/default-aliases.ts +65 -0
  511. package/src/providers/derive.ts +281 -6
  512. package/src/providers/fastwire.ts +507 -0
  513. package/src/providers/free-directory.ts +10 -7
  514. package/src/providers/google-vertex-location.ts +14 -0
  515. package/src/providers/key-failover.ts +71 -3
  516. package/src/providers/label.ts +35 -2
  517. package/src/providers/model-discovery-limits.ts +16 -0
  518. package/src/providers/model-discovery.ts +115 -22
  519. package/src/providers/model-presets.ts +119 -0
  520. package/src/providers/model-rename-migration.ts +255 -0
  521. package/src/providers/model-rename-startup.ts +28 -0
  522. package/src/providers/new-model-policy.ts +146 -0
  523. package/src/providers/openai-sidecar.ts +72 -4
  524. package/src/providers/openai-tier-startup.ts +31 -2
  525. package/src/providers/openai-tiers.ts +119 -4
  526. package/src/providers/openai-virtual-models.ts +1 -0
  527. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  528. package/src/providers/provider-id-rewrite.ts +30 -0
  529. package/src/providers/quota.ts +1379 -40
  530. package/src/providers/registry.ts +1545 -112
  531. package/src/providers/request-pacing.ts +310 -0
  532. package/src/providers/service-tier.ts +277 -0
  533. package/src/providers/slug-codec.ts +94 -6
  534. package/src/providers/static-model-discovery.ts +86 -0
  535. package/src/providers/xai-responses-opt-in.ts +15 -0
  536. package/src/providers/xai-transport.ts +32 -4
  537. package/src/reasoning-effort.ts +68 -3
  538. package/src/responses/apply-patch-envelope.ts +63 -0
  539. package/src/responses/code-mode-helper-compat.ts +50 -0
  540. package/src/responses/compaction.ts +26 -1
  541. package/src/responses/custom-tool-compat.ts +384 -0
  542. package/src/responses/hosted-tool-policy.ts +9 -0
  543. package/src/responses/namespace-tool-compat.ts +435 -0
  544. package/src/responses/parser.ts +225 -38
  545. package/src/responses/provider-continuation.ts +98 -0
  546. package/src/responses/provider-opaque-metadata.ts +73 -0
  547. package/src/responses/reasoning-envelope.ts +9 -1
  548. package/src/responses/reasoning-replay-cache.ts +504 -0
  549. package/src/responses/schema.ts +12 -2
  550. package/src/responses/spill-store.ts +75 -10
  551. package/src/responses/state.ts +659 -31
  552. package/src/responses/thought-signature-replay.ts +364 -0
  553. package/src/responses/tool-search-compat.ts +301 -0
  554. package/src/responses/truncated-stop-reason.ts +60 -0
  555. package/src/responses/turn-termination.ts +107 -0
  556. package/src/router.ts +405 -31
  557. package/src/routing/analytics.ts +379 -0
  558. package/src/routing/capability.ts +244 -0
  559. package/src/routing/compatibility/assemble.ts +73 -0
  560. package/src/routing/compatibility/behavior.ts +278 -0
  561. package/src/routing/compatibility/catalog.ts +99 -0
  562. package/src/routing/compatibility/endpoint.ts +52 -0
  563. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  564. package/src/routing/compatibility/policy.ts +181 -0
  565. package/src/routing/compatibility/provider-slot.ts +56 -0
  566. package/src/routing/compatibility/reader.ts +110 -0
  567. package/src/routing/compatibility/subject.ts +191 -0
  568. package/src/routing/compatibility/types.ts +64 -0
  569. package/src/routing/compatibility/version.ts +104 -0
  570. package/src/routing/cost.ts +77 -0
  571. package/src/routing/evaluator.ts +495 -0
  572. package/src/routing/health.ts +412 -0
  573. package/src/routing/history/cursor.ts +43 -0
  574. package/src/routing/history/indexer.ts +605 -0
  575. package/src/routing/history/schema.ts +72 -0
  576. package/src/routing/profile-namespace.ts +15 -0
  577. package/src/routing/profile.ts +547 -0
  578. package/src/routing/quota.ts +155 -0
  579. package/src/routing/request-evidence.ts +45 -0
  580. package/src/routing/trace.ts +776 -0
  581. package/src/server/adapter-resolve.ts +2 -29
  582. package/src/server/auth-cors.ts +289 -48
  583. package/src/server/background-lifecycle.ts +182 -0
  584. package/src/server/chat-completions.ts +154 -72
  585. package/src/server/chat-native-sse.ts +331 -0
  586. package/src/server/chat-native.ts +445 -0
  587. package/src/server/claude-messages.ts +160 -44
  588. package/src/server/direct-local-http.ts +347 -0
  589. package/src/server/effort-policy.ts +18 -0
  590. package/src/server/github-copilot-responses-repair.ts +338 -0
  591. package/src/server/gui-static.ts +39 -10
  592. package/src/server/images.ts +94 -12
  593. package/src/server/index.ts +1021 -190
  594. package/src/server/lifecycle.ts +336 -14
  595. package/src/server/live.ts +136 -17
  596. package/src/server/local-management-read-client.ts +90 -0
  597. package/src/server/local-provider-reload-client.ts +137 -0
  598. package/src/server/management/agent-settings-routes.ts +433 -116
  599. package/src/server/management/api-key-usage.ts +31 -5
  600. package/src/server/management/body.ts +6 -0
  601. package/src/server/management/codex-prompt-routes.ts +570 -0
  602. package/src/server/management/combo-routes.ts +64 -25
  603. package/src/server/management/config-routes.ts +511 -51
  604. package/src/server/management/context.ts +89 -2
  605. package/src/server/management/integration-routes.ts +498 -0
  606. package/src/server/management/lab-automation-routes.ts +206 -0
  607. package/src/server/management/lab-routes.ts +563 -0
  608. package/src/server/management/logs-usage-routes.ts +127 -52
  609. package/src/server/management/model-routes.ts +461 -133
  610. package/src/server/management/model-rows.ts +167 -0
  611. package/src/server/management/native-integration-routes.ts +770 -0
  612. package/src/server/management/oauth-account-routes.ts +90 -8
  613. package/src/server/management/provider-capability-config.ts +48 -0
  614. package/src/server/management/provider-routes.ts +836 -159
  615. package/src/server/management/request-history-routes.ts +191 -0
  616. package/src/server/management/route-registry.ts +332 -0
  617. package/src/server/management/routing-analytics-routes.ts +74 -0
  618. package/src/server/management/routing-profile-routes.ts +380 -0
  619. package/src/server/management/shared.ts +27 -11
  620. package/src/server/management/sidebar-routes.ts +47 -31
  621. package/src/server/management/storage-log-guard-routes.ts +186 -0
  622. package/src/server/management/sync-response.ts +69 -0
  623. package/src/server/management/system-restart.ts +289 -33
  624. package/src/server/management/system-routes.ts +77 -0
  625. package/src/server/management/usage-summary-cache.ts +9 -1
  626. package/src/server/management/vision-sidecar-options.ts +167 -0
  627. package/src/server/management/web-search-sidecar-options.ts +120 -0
  628. package/src/server/management-api.ts +126 -23
  629. package/src/server/management-auth.ts +220 -5
  630. package/src/server/passive-route-linker.ts +66 -0
  631. package/src/server/port-reclaim.ts +1 -1
  632. package/src/server/ports.ts +41 -1
  633. package/src/server/proxy-liveness.ts +161 -10
  634. package/src/server/readiness.ts +99 -0
  635. package/src/server/relay-eager.ts +221 -54
  636. package/src/server/relay.ts +479 -87
  637. package/src/server/request-decompress.ts +113 -6
  638. package/src/server/request-log-conversation.ts +51 -0
  639. package/src/server/request-log.ts +298 -31
  640. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  641. package/src/server/responses/agent-task-recovery.ts +465 -0
  642. package/src/server/responses/codex-auth-error.ts +55 -0
  643. package/src/server/responses/collaboration.ts +239 -36
  644. package/src/server/responses/combo-stream-preflight.ts +178 -0
  645. package/src/server/responses/compact.ts +444 -72
  646. package/src/server/responses/core.ts +3579 -436
  647. package/src/server/responses/empty-completion-guard.ts +333 -0
  648. package/src/server/responses/encrypted-payload.ts +62 -39
  649. package/src/server/responses/fetch-helpers.ts +89 -99
  650. package/src/server/responses/input-admission.ts +187 -0
  651. package/src/server/responses/pacing-overload.ts +13 -0
  652. package/src/server/responses/passthrough-error.ts +33 -9
  653. package/src/server/responses/policy-fallback.ts +178 -0
  654. package/src/server/responses/responses-field-backfill.ts +343 -0
  655. package/src/server/responses/terminal-guard.ts +26 -5
  656. package/src/server/responses/upstream-error.ts +5 -0
  657. package/src/server/responses/ws-upstream.ts +381 -0
  658. package/src/server/responses-custom-tool-repair.ts +363 -0
  659. package/src/server/responses-item-id-repair.ts +54 -6
  660. package/src/server/responses-json-events.ts +90 -0
  661. package/src/server/responses-model-rewrite.ts +29 -0
  662. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  663. package/src/server/responses-snapshot-repair.ts +621 -0
  664. package/src/server/responses-terminal-repair.ts +363 -0
  665. package/src/server/responses-tool-search-repair.ts +267 -0
  666. package/src/server/responses-undeclared-tool-guard.ts +376 -0
  667. package/src/server/responses.ts +18 -2
  668. package/src/server/search.ts +78 -13
  669. package/src/server/sse-frame-buffer.ts +319 -0
  670. package/src/server/sse-payload-rewrite.ts +110 -22
  671. package/src/server/startup-action-control.ts +8 -1
  672. package/src/server/startup-health-cache.ts +19 -1
  673. package/src/server/system-env.ts +80 -9
  674. package/src/server/ws-bridge.ts +53 -40
  675. package/src/service-manager-probe.ts +892 -0
  676. package/src/service.ts +1153 -94
  677. package/src/sidecar/auth.ts +92 -0
  678. package/src/sidecar/candidates.ts +83 -0
  679. package/src/storage/cleanup.ts +2 -2
  680. package/src/storage/policy-job.ts +14 -4
  681. package/src/storage/policy.ts +88 -23
  682. package/src/storage/scanner.ts +1 -1
  683. package/src/storage/worker-lifecycle.ts +14 -14
  684. package/src/tray/windows-tray.ps1 +83 -9
  685. package/src/tray/windows.ts +43 -16
  686. package/src/types/accounts.ts +37 -0
  687. package/src/types/config.ts +911 -0
  688. package/src/types/provider.ts +614 -0
  689. package/src/types/request.ts +397 -0
  690. package/src/types/tools.ts +236 -0
  691. package/src/types/wire.ts +80 -0
  692. package/src/types.ts +107 -1237
  693. package/src/update/index.ts +37 -23
  694. package/src/update/job.ts +445 -68
  695. package/src/update/notify.ts +12 -6
  696. package/src/update/npm-cache-preflight.d.mts +47 -0
  697. package/src/update/npm-cache-preflight.mjs +201 -0
  698. package/src/update/transactional-install.d.mts +22 -0
  699. package/src/update/transactional-install.mjs +266 -0
  700. package/src/usage/cost.ts +0 -0
  701. package/src/usage/expected-prices.ts +287 -16
  702. package/src/usage/log.ts +666 -50
  703. package/src/usage/summary.ts +388 -17
  704. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  705. package/src/usage/user-cost-overlays.ts +314 -0
  706. package/src/vision/anthropic-describe.ts +10 -6
  707. package/src/vision/backends.ts +97 -0
  708. package/src/vision/describe.ts +27 -16
  709. package/src/vision/eligibility.ts +250 -0
  710. package/src/vision/index.ts +238 -24
  711. package/src/vision/reasoning.ts +55 -0
  712. package/src/vision/routed-describe.ts +175 -0
  713. package/src/vision/timeout-bounds.ts +9 -0
  714. package/src/web-search/anthropic-executor.ts +13 -7
  715. package/src/web-search/backends.ts +108 -0
  716. package/src/web-search/exa-executor.ts +119 -0
  717. package/src/web-search/executor.ts +21 -6
  718. package/src/web-search/gemini-executor.ts +141 -0
  719. package/src/web-search/index.ts +150 -15
  720. package/src/web-search/loop.ts +284 -54
  721. package/src/web-search/parse.ts +125 -30
  722. package/src/web-search/sources.ts +60 -0
  723. package/src/web-search/xai-executor.ts +219 -0
  724. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  725. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  726. package/gui/dist/provider-icons/antigravity.svg +0 -1
  727. package/gui/dist/provider-icons/claude.svg +0 -1
  728. package/gui/dist/provider-icons/copilot.svg +0 -1
  729. package/gui/dist/provider-icons/cursor.svg +0 -2
  730. package/gui/dist/provider-icons/gemini.svg +0 -1
  731. package/gui/dist/provider-icons/grok-color.svg +0 -1
  732. package/gui/dist/provider-icons/kiro.svg +0 -14
  733. package/src/cli/internal-dispatch.ts +0 -20
@@ -0,0 +1,255 @@
1
+ // Registry model renames do not reach a saved provider config on their own.
2
+ //
3
+ // `reconcileOAuthProviders` refuses to touch a row whose `authMode` is not
4
+ // `oauth` (src/oauth/index.ts), and `enrichProviderFromRegistry` is fill-only by
5
+ // design: it backfills a MISSING field and never rewrites a present one, so a
6
+ // user's hand-edited model list survives an upgrade. Both postures are correct.
7
+ // Their gap is the case where the registry did not ADD a model but RENAMED one:
8
+ // the saved row keeps a retired id forever, the supported id never appears, and
9
+ // the capability metadata stays keyed to an id the vendor is taking offline
10
+ // (issue #1610 — `qwen3.8-max-preview` persisted through the `qwen3.8-max`
11
+ // rename in six separate fields, including a reasoning ladder that had since
12
+ // diverged from the registry's).
13
+ //
14
+ // This migration is deliberately NOT general reconciliation. It rewrites exactly
15
+ // one thing: an id this file declares retired, on a provider that still carries
16
+ // the registry's transport, and only when the registry currently seeds the
17
+ // replacement. Everything else in the row is left alone.
18
+
19
+ import { PROVIDER_REGISTRY } from "./registry";
20
+ import type { OcxConfig, OcxProviderConfig } from "../types";
21
+
22
+ export interface ModelRename {
23
+ /** Registry provider id whose saved rows may carry the retired model id. */
24
+ provider: string;
25
+ from: string;
26
+ to: string;
27
+ /** Why the vendor retired it, for the startup warning and future readers. */
28
+ reason: string;
29
+ /**
30
+ * Drop the retired key from `modelReasoningEffortMap` instead of renaming it.
31
+ *
32
+ * Renaming preserves the VALUE, which is right for records whose values describe the
33
+ * model — a context window or an effort ladder survives a rename — and wrong for the
34
+ * one record whose values are themselves wire ids. An effort map saved as
35
+ * `high -> gemini-3.6-flash-high` would keep naming a dead wire id under the new key,
36
+ * and the adapter maps effort BEFORE resolving CCA routing, so that value arrives as
37
+ * an unrecognised effort and silently degrades to the default tier.
38
+ *
39
+ * Only that one record is dropped. Emptying the others would be worse than the bug:
40
+ * catalog enrichment treats an existing `{}` as "already populated" and will not
41
+ * restore the registry's records, so the migrated user would keep routing correctly
42
+ * but lose the reasoning picker entirely.
43
+ */
44
+ dropReasoningEffortMap?: boolean;
45
+ }
46
+
47
+ /**
48
+ * Renames already applied to `PROVIDER_REGISTRY`. An entry stays here after the
49
+ * registry moves on: it is what repairs configs saved before that move. Removing
50
+ * one strands every config that has not started since the rename shipped.
51
+ */
52
+ export const MODEL_RENAMES: readonly ModelRename[] = [
53
+ {
54
+ provider: "alibaba-token-plan",
55
+ from: "qwen3.8-max-preview",
56
+ to: "qwen3.8-max",
57
+ reason: "Alibaba shipped Qwen3.8-Max as stable and documents the preview endpoint as liable to be taken offline once preview concludes",
58
+ },
59
+ {
60
+ provider: "alibaba-token-plan-intl",
61
+ from: "qwen3.8-max-preview",
62
+ to: "qwen3.8-max",
63
+ reason: "Alibaba shipped Qwen3.8-Max as stable and documents the preview endpoint as liable to be taken offline once preview concludes",
64
+ },
65
+ // Antigravity Flash generations. Google takes the previous Flash model off Cloud Code
66
+ // Assist almost immediately when the next ships, so a saved 3.6 (or older 3.5) id is a
67
+ // dead selection rather than a merely outdated one. Routing already redirects these ids
68
+ // at request time; this migration repairs the saved config so the picker, the allowlist
69
+ // and the capability maps stop naming a model the backend no longer serves.
70
+ ...(["gemini-3.6-flash", "gemini-3.6-flash-low", "gemini-3.6-flash-medium", "gemini-3.6-flash-high",
71
+ "gemini-3.5-flash-extra-low", "gemini-3.5-flash-low", "gemini-3.5-flash-mid", "gemini-3.5-flash-high",
72
+ "gemini-3-flash-agent"] as const).map(from => ({
73
+ provider: "google-antigravity",
74
+ from,
75
+ to: "gemini-3.7-flash",
76
+ reason: "Google retires the previous Antigravity Flash generation from Cloud Code Assist when its successor ships, so the saved id no longer resolves to a live model",
77
+ // The retired Flash tiers were wire ids, so any saved per-model record keyed by one
78
+ // may also hold one as a value. 3.7 expresses tiers as thinkingLevel names instead.
79
+ dropReasoningEffortMap: true,
80
+ })),
81
+ ];
82
+
83
+ /** Provider fields that key metadata by model id. */
84
+ const MODEL_KEYED_RECORDS = [
85
+ "modelContextWindows",
86
+ "modelMaxOutputTokens",
87
+ "modelInputModalities",
88
+ "modelReasoningEfforts",
89
+ "modelDefaultReasoningEfforts",
90
+ "modelReasoningEffortMap",
91
+ ] as const;
92
+
93
+ /** Provider fields that are flat lists of model ids. */
94
+ const MODEL_ID_LISTS = [
95
+ "models",
96
+ // A retired id left here is worse than a stale label: `filterCatalogModels` treats
97
+ // `selectedModels` as an exact-match allowlist, so a user who allowlisted only the
98
+ // retired model gets NO replacement row at all — the model silently vanishes from
99
+ // their catalog instead of being renamed. OAuth reconciliation does not cover this
100
+ // field, so the rename has to.
101
+ "selectedModels",
102
+ "noVisionModels",
103
+ "noReasoningModels",
104
+ "noTemperatureModels",
105
+ "noTopPModels",
106
+ "noPenaltyModels",
107
+ "autoToolChoiceOnlyModels",
108
+ "preserveReasoningContentModels",
109
+ "thinkingBudgetModels",
110
+ "directReasoningEffortModels",
111
+ ] as const;
112
+
113
+ function renameInList(value: unknown, from: string, to: string): string[] | null {
114
+ if (!Array.isArray(value) || !value.includes(from)) return null;
115
+ const seen = new Set<string>();
116
+ const next: string[] = [];
117
+ // Rename in place to preserve ordering, and collapse a duplicate if the target
118
+ // id was already present alongside the retired one.
119
+ for (const entry of value) {
120
+ if (typeof entry !== "string") continue;
121
+ const mapped = entry === from ? to : entry;
122
+ if (seen.has(mapped)) continue;
123
+ seen.add(mapped);
124
+ next.push(mapped);
125
+ }
126
+ return next;
127
+ }
128
+
129
+ function renameInRecord(value: unknown, from: string, to: string): Record<string, unknown> | null {
130
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
131
+ const record = value as Record<string, unknown>;
132
+ if (!(from in record)) return null;
133
+ const next: Record<string, unknown> = {};
134
+ for (const [key, entry] of Object.entries(record)) {
135
+ const mapped = key === from ? to : key;
136
+ if (mapped in next) continue;
137
+ // An explicit entry already saved under the new id is the newer intent.
138
+ next[mapped] = key === from && to in record ? record[to] : entry;
139
+ }
140
+ return next;
141
+ }
142
+
143
+ /** Drop the retired key entirely, leaving any entry already saved under the new id. */
144
+ function dropFromRecord(value: unknown, from: string): Record<string, unknown> | null {
145
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
146
+ const record = value as Record<string, unknown>;
147
+ if (!(from in record)) return null;
148
+ const next: Record<string, unknown> = {};
149
+ for (const [key, entry] of Object.entries(record)) {
150
+ if (key === from) continue;
151
+ next[key] = entry;
152
+ }
153
+ return next;
154
+ }
155
+
156
+ /**
157
+ * `provider/model` rows in the top-level `disabledModels` list.
158
+ *
159
+ * The retired row is DROPPED rather than renamed: carrying its disabled state to
160
+ * the new id would hide the supported model behind a toggle the user set for a
161
+ * different model. An existing row for the new id is left untouched.
162
+ */
163
+ function renameDisabledModels(config: OcxConfig, rename: ModelRename): boolean {
164
+ const list = config.disabledModels;
165
+ if (!Array.isArray(list)) return false;
166
+ const retired = `${rename.provider}/${rename.from}`;
167
+ if (!list.includes(retired)) return false;
168
+ config.disabledModels = list.filter(entry => entry !== retired);
169
+ return true;
170
+ }
171
+
172
+ /**
173
+ * Only migrate a row that still points at the registry's own endpoint. A user who
174
+ * repointed `baseUrl` at a different vendor owns their model ids.
175
+ */
176
+ function providerStillMatchesRegistry(name: string, prov: OcxProviderConfig): boolean {
177
+ const entry = PROVIDER_REGISTRY.find(row => row.id === name);
178
+ if (!entry) return false;
179
+ if (!prov.baseUrl || !entry.baseUrl) return true;
180
+ const choices = entry.baseUrlChoices?.map(choice => choice.baseUrl) ?? [];
181
+ const known = [entry.baseUrl, ...choices]
182
+ .filter((url): url is string => typeof url === "string")
183
+ .map(url => url.replace(/\/+$/, ""));
184
+ return known.includes(prov.baseUrl.replace(/\/+$/, ""));
185
+ }
186
+
187
+ /** Guard against a stale rename: only apply when the registry actually seeds `to`. */
188
+ function registrySeedsTarget(rename: ModelRename): boolean {
189
+ const entry = PROVIDER_REGISTRY.find(row => row.id === rename.provider);
190
+ return !!entry?.models?.includes(rename.to);
191
+ }
192
+
193
+ export interface ModelRenameProjection {
194
+ config: OcxConfig;
195
+ changed: boolean;
196
+ warnings: string[];
197
+ }
198
+
199
+ /**
200
+ * Pure projection: apply every applicable rename and report what changed. The
201
+ * caller decides whether to persist.
202
+ */
203
+ export function projectModelRenames(
204
+ config: OcxConfig,
205
+ renames: readonly ModelRename[] = MODEL_RENAMES,
206
+ ): ModelRenameProjection {
207
+ const warnings: string[] = [];
208
+ let changed = false;
209
+
210
+ for (const rename of renames) {
211
+ const prov = config.providers?.[rename.provider];
212
+ if (!prov) continue;
213
+ if (!registrySeedsTarget(rename)) {
214
+ warnings.push(
215
+ `registry no longer seeds "${rename.to}" for "${rename.provider}"; skipping the `
216
+ + `"${rename.from}" rename rather than writing an id the registry does not know.`,
217
+ );
218
+ continue;
219
+ }
220
+ if (!providerStillMatchesRegistry(rename.provider, prov)) continue;
221
+
222
+ // Provider config is a closed interface, so index through one unknown-cast
223
+ // view rather than casting at each assignment.
224
+ const row = prov as unknown as Record<string, unknown>;
225
+ let touched = false;
226
+ for (const field of MODEL_ID_LISTS) {
227
+ const next = renameInList(row[field], rename.from, rename.to);
228
+ if (!next) continue;
229
+ row[field] = next;
230
+ touched = true;
231
+ }
232
+ for (const field of MODEL_KEYED_RECORDS) {
233
+ const next = rename.dropReasoningEffortMap && field === "modelReasoningEffortMap"
234
+ ? dropFromRecord(row[field], rename.from)
235
+ : renameInRecord(row[field], rename.from, rename.to);
236
+ if (!next) continue;
237
+ row[field] = next;
238
+ touched = true;
239
+ }
240
+ if (prov.defaultModel === rename.from) {
241
+ prov.defaultModel = rename.to;
242
+ touched = true;
243
+ }
244
+ if (renameDisabledModels(config, rename)) touched = true;
245
+
246
+ if (touched) {
247
+ changed = true;
248
+ warnings.push(
249
+ `renamed "${rename.provider}/${rename.from}" to "${rename.to}" in the saved config: ${rename.reason}.`,
250
+ );
251
+ }
252
+ }
253
+
254
+ return { config, changed, warnings };
255
+ }
@@ -0,0 +1,28 @@
1
+ import { saveConfig } from "../config";
2
+ import { projectModelRenames } from "./model-rename-migration";
3
+ import type { OcxConfig } from "../types";
4
+
5
+ export interface ModelRenameStartupDeps {
6
+ project: typeof projectModelRenames;
7
+ save: (config: OcxConfig) => void;
8
+ }
9
+
10
+ /**
11
+ * Apply registry model renames to the saved config at startup (issue #1610).
12
+ *
13
+ * No backup is taken, unlike the OpenAI tier and Alibaba region migrations: those
14
+ * rewrite credentials and provider identity, where a bad projection is not
15
+ * recoverable from the config alone. This one only rewrites model ids that the
16
+ * registry itself no longer seeds, and the pre-migration value is a string this
17
+ * file still names, so the change is reversible by hand.
18
+ */
19
+ export function runModelRenameStartupMigration(
20
+ config: OcxConfig,
21
+ deps: ModelRenameStartupDeps = { project: projectModelRenames, save: saveConfig },
22
+ ): OcxConfig {
23
+ const projection = deps.project(config);
24
+ for (const warning of projection.warnings) console.warn(`[model-rename-migration] ${warning}`);
25
+ if (!projection.changed) return projection.config;
26
+ deps.save(projection.config);
27
+ return projection.config;
28
+ }
@@ -0,0 +1,146 @@
1
+ import type { OcxConfig } from "../types";
2
+ import { routedSlug } from "./slug-codec";
3
+
4
+ export const MODEL_REMOVAL_GRACE_FETCHES = 3;
5
+ export const MAX_KNOWN_MODELS_PER_PROVIDER = 2_000;
6
+ export const MAX_RECENT_ARRIVALS_PER_PROVIDER = 50;
7
+
8
+ export type KnownModelBaseline = NonNullable<NonNullable<OcxConfig["modelDiscovery"]>["knownModels"]>[string];
9
+ export type NewModelPolicy = "on" | "off";
10
+
11
+ export interface NewModelPolicyResult {
12
+ newIds: string[];
13
+ nextBaseline: KnownModelBaseline;
14
+ slugsToDisable: string[];
15
+ arrivals: Array<{ id: string; at: string }>;
16
+ overflow: boolean;
17
+ }
18
+
19
+ /**
20
+ * Whether a transition actually changes persisted state.
21
+ *
22
+ * `updatedAt` moves on every successful fetch, so comparing whole baselines would report a
23
+ * change every time and rewrite config.json on every catalog convergence — a write amplification
24
+ * that also churns the config generation other writers revalidate against. Only the fields that
25
+ * carry meaning are compared.
26
+ */
27
+ function baselineDiffers(prior: KnownModelBaseline | undefined, next: KnownModelBaseline): boolean {
28
+ if (!prior) return true;
29
+ const sameList = (a: readonly string[], b: readonly string[]) =>
30
+ a.length === b.length && a.every((value, index) => value === b[index]);
31
+ if (!sameList(prior.ids, next.ids) || !sameList(prior.removed, next.removed)) return true;
32
+ const priorMissing = prior.missing ?? {};
33
+ const nextMissing = next.missing ?? {};
34
+ const keys = new Set([...Object.keys(priorMissing), ...Object.keys(nextMissing)]);
35
+ for (const key of keys) if (priorMissing[key] !== nextMissing[key]) return true;
36
+ return false;
37
+ }
38
+
39
+ /** Pure successful-discovery transition. An absent baseline bootstraps without hiding anything. */
40
+ export function applyNewModelPolicy(options: {
41
+ provider: string;
42
+ discoveredIds: Iterable<string>;
43
+ baseline?: KnownModelBaseline;
44
+ policy: NewModelPolicy;
45
+ hasSelectedModels?: boolean;
46
+ now: string;
47
+ }): NewModelPolicyResult {
48
+ const discovered = [...new Set(options.discoveredIds)].sort();
49
+ const prior = options.baseline;
50
+ if (!prior) {
51
+ return {
52
+ newIds: [],
53
+ nextBaseline: { ids: discovered, removed: [], updatedAt: options.now },
54
+ slugsToDisable: [], arrivals: [],
55
+ overflow: discovered.length > MAX_KNOWN_MODELS_PER_PROVIDER,
56
+ };
57
+ }
58
+ const active = new Set(prior.ids);
59
+ const removed = new Set(prior.removed);
60
+ const seen = new Set(discovered);
61
+ const newIds = discovered.filter(id => !active.has(id) && !removed.has(id));
62
+ const missing: Record<string, number> = {};
63
+ for (const id of active) {
64
+ if (seen.has(id)) continue;
65
+ const count = (prior.missing?.[id] ?? 0) + 1;
66
+ if (count >= MODEL_REMOVAL_GRACE_FETCHES) {
67
+ active.delete(id);
68
+ removed.add(id);
69
+ } else missing[id] = count;
70
+ }
71
+ for (const id of discovered) active.add(id);
72
+ const unionSize = active.size + removed.size;
73
+ const overflow = unionSize > MAX_KNOWN_MODELS_PER_PROVIDER;
74
+ const arrivals = newIds.map(id => ({ id, at: options.now }));
75
+ return {
76
+ newIds,
77
+ nextBaseline: {
78
+ ids: [...active].sort(), removed: [...removed].sort(), updatedAt: options.now,
79
+ ...(Object.keys(missing).length ? { missing } : {}),
80
+ },
81
+ // A non-empty preset/custom allowlist already excludes arrivals. This explicit no-op is
82
+ // deliberate: preset mode owns which matching flagships arrive on.
83
+ slugsToDisable: !overflow && options.policy === "off" && !options.hasSelectedModels
84
+ ? newIds.map(id => routedSlug(options.provider, id)) : [],
85
+ arrivals,
86
+ overflow,
87
+ };
88
+ }
89
+
90
+ export function effectiveNewModelPolicy(config: OcxConfig, provider: string): NewModelPolicy {
91
+ const local = config.providers[provider]?.newModelPolicy;
92
+ if (local === "on" || local === "off") return local;
93
+ return config.modelDiscovery?.newModelPolicy ?? "on";
94
+ }
95
+
96
+ /** Apply authoritative provider rows to a mutable convergence copy; degraded providers are omitted. */
97
+ export function reconcileSuccessfulModelDiscoveries(options: {
98
+ config: OcxConfig;
99
+ models: Iterable<{ provider: string; id: string; custom?: boolean }>;
100
+ authoritativeProviders: Iterable<string>;
101
+ now: string;
102
+ }): boolean {
103
+ const byProvider = new Map<string, string[]>();
104
+ for (const model of options.models) {
105
+ if (model.custom) continue;
106
+ const ids = byProvider.get(model.provider) ?? [];
107
+ ids.push(model.id); byProvider.set(model.provider, ids);
108
+ }
109
+ let changed = false;
110
+ for (const provider of options.authoritativeProviders) {
111
+ const configured = options.config.providers[provider];
112
+ if (!configured || configured.liveModels === false) continue;
113
+ const discoveredIds = byProvider.get(provider) ?? [];
114
+ const discovery = options.config.modelDiscovery ??= {};
115
+ const known = discovery.knownModels ??= {};
116
+ const result = applyNewModelPolicy({
117
+ provider, discoveredIds, baseline: known[provider],
118
+ policy: effectiveNewModelPolicy(options.config, provider),
119
+ hasSelectedModels: (configured.selectedModels?.length ?? 0) > 0,
120
+ now: options.now,
121
+ });
122
+ if (result.overflow) continue;
123
+ const priorBaseline = known[provider];
124
+ const baselineChanged = baselineDiffers(priorBaseline, result.nextBaseline);
125
+ known[provider] = result.nextBaseline;
126
+ // Keep the previous timestamp when nothing else moved, so a steady-state roster does not
127
+ // make the baseline look dirty on the next comparison either.
128
+ if (!baselineChanged && priorBaseline) known[provider] = priorBaseline;
129
+ if (result.slugsToDisable.length) {
130
+ const disabled = options.config.disabledModels ??= [];
131
+ for (const slug of result.slugsToDisable) {
132
+ if (disabled.includes(slug)) continue;
133
+ disabled.push(slug);
134
+ changed = true;
135
+ }
136
+ }
137
+ if (result.arrivals.length) {
138
+ const recent = discovery.recentArrivals ??= {};
139
+ recent[provider] = [...(recent[provider] ?? []), ...result.arrivals]
140
+ .slice(-MAX_RECENT_ARRIVALS_PER_PROVIDER);
141
+ changed = true;
142
+ }
143
+ if (baselineChanged) changed = true;
144
+ }
145
+ return changed;
146
+ }
@@ -1,9 +1,11 @@
1
1
  import { resolveEnvValue } from "../config";
2
2
  import {
3
+ CodexPoolAuthenticationError,
3
4
  headersForCodexAuthContext,
4
5
  hasCallerCodexBearer,
5
6
  isCodexAuthContextUsable,
6
7
  resolveCodexAuthContext,
8
+ type CodexAccountSelectionAdmission,
7
9
  type CodexAuthContext,
8
10
  } from "../codex/auth-context";
9
11
  import { recordCodexUpstreamOutcome, type CodexUpstreamOutcome } from "../codex/routing";
@@ -11,6 +13,7 @@ import { extractAccountId } from "../oauth/chatgpt";
11
13
  import { ForwardAdmissionCredentialError, validateForwardAdmissionCredential } from "../server/auth-cors";
12
14
  import type { CodexAccountMode, OcxConfig, OcxProviderConfig } from "../types";
13
15
  import {
16
+ CODEX_FORWARD_BASE_URL,
14
17
  isCanonicalOpenAiForwardProvider,
15
18
  OPENAI_API_PROVIDER_ID,
16
19
  OPENAI_CODEX_PROVIDER_ID,
@@ -29,6 +32,17 @@ export interface ResolvedOpenAiForwardSidecar extends OpenAiForwardSidecarCandid
29
32
  recordOutcome?: (outcome: CodexUpstreamOutcome) => void;
30
33
  }
31
34
 
35
+ /**
36
+ * Server-resolved exact account selection for a ChatGPT sidecar call.
37
+ * `accountId` must come from a validated account-qualified route; request headers
38
+ * are never trusted as account ids. `modelId` is the sidecar's actual upstream
39
+ * model so cooldown admission and outcome recording use the correct quota scope.
40
+ */
41
+ export interface ExactOpenAiSidecarAccount {
42
+ accountId: string;
43
+ modelId: string;
44
+ }
45
+
32
46
  export interface OpenAiImagesProviderSelection {
33
47
  forwardCandidates: OpenAiForwardSidecarCandidate[];
34
48
  keyed?: {
@@ -41,11 +55,24 @@ export interface OpenAiImagesProviderSelection {
41
55
 
42
56
  export function listOpenAiForwardSidecarCandidates(config: OcxConfig): OpenAiForwardSidecarCandidate[] {
43
57
  const provider = config.providers[OPENAI_CODEX_PROVIDER_ID];
44
- if (!provider || provider.disabled === true || !isCanonicalOpenAiForwardProvider(provider)) return [];
58
+ if (!provider || provider.disabled === true) return [];
59
+ // The built-in registry defaults an omitted authMode to forward. Normalize only that
60
+ // missing field before the strict adapter/destination check; explicit key mode and
61
+ // noncanonical destinations remain ineligible for ChatGPT credential injection.
62
+ const canonicalProvider = provider.authMode === undefined
63
+ ? { ...provider, authMode: "forward" as const }
64
+ : provider;
65
+ if (!isCanonicalOpenAiForwardProvider(canonicalProvider)) return [];
66
+ // The predicate accepts harmless trailing-slash variants. Pin the provider returned
67
+ // to credential-bearing sidecars so every consumer builds one exact ChatGPT path
68
+ // instead of independently concatenating the operator's equivalent spelling.
69
+ const pinnedProvider = canonicalProvider.baseUrl === CODEX_FORWARD_BASE_URL
70
+ ? canonicalProvider
71
+ : { ...canonicalProvider, baseUrl: CODEX_FORWARD_BASE_URL };
45
72
  return [{
46
73
  providerName: OPENAI_CODEX_PROVIDER_ID,
47
- provider,
48
- accountMode: providerCodexAccountMode(OPENAI_CODEX_PROVIDER_ID, provider) ?? "pool",
74
+ provider: pinnedProvider,
75
+ accountMode: providerCodexAccountMode(OPENAI_CODEX_PROVIDER_ID, pinnedProvider) ?? "pool",
49
76
  }];
50
77
  }
51
78
 
@@ -70,7 +97,12 @@ export async function resolveFirstUsableOpenAiSidecar(
70
97
  candidates: readonly OpenAiForwardSidecarCandidate[],
71
98
  incomingHeaders: Headers,
72
99
  config: OcxConfig,
100
+ options: {
101
+ exactAccount?: ExactOpenAiSidecarAccount;
102
+ beginCodexAccountSelection?: () => CodexAccountSelectionAdmission | undefined;
103
+ } = {},
73
104
  ): Promise<ResolvedOpenAiForwardSidecar | undefined> {
105
+ const { exactAccount } = options;
74
106
  let callerBearerMayBeForwarded = true;
75
107
  try {
76
108
  validateForwardAdmissionCredential(incomingHeaders, config);
@@ -79,6 +111,39 @@ export async function resolveFirstUsableOpenAiSidecar(
79
111
  callerBearerMayBeForwarded = false;
80
112
  }
81
113
  for (const candidate of candidates) {
114
+ if (exactAccount) {
115
+ // An account-qualified model is an explicit user choice. Resolve the stored
116
+ // credential directly even when the provider is globally Direct, and never
117
+ // consult Pool active state, affinity, probes, or alternates.
118
+ const authContext = await resolveCodexAuthContext(incomingHeaders, config, "pool", {
119
+ accountId: exactAccount.accountId,
120
+ modelId: exactAccount.modelId,
121
+ beginCodexAccountSelection: options.beginCodexAccountSelection,
122
+ });
123
+ if ((authContext.kind !== "pool" && authContext.kind !== "main-pool")
124
+ || !isCodexAuthContextUsable(authContext, config)) {
125
+ // Exact selection is fail-closed. A generation/runtime-state race must not fall through
126
+ // to the caller-bearer error or let a later candidate select another account.
127
+ throw new CodexPoolAuthenticationError("Selected Codex account is unavailable");
128
+ }
129
+ return {
130
+ ...candidate,
131
+ authContext,
132
+ headers: headersForCodexAuthContext(incomingHeaders, authContext),
133
+ recordOutcome: (outcome: CodexUpstreamOutcome) => recordCodexUpstreamOutcome(
134
+ config,
135
+ authContext.accountId,
136
+ outcome,
137
+ {
138
+ modelId: exactAccount.modelId,
139
+ fixedAccount: true,
140
+ probeLeaseId: authContext.probeLeaseId,
141
+ probeQuotaScope: authContext.probeQuotaScope,
142
+ writerGeneration: authContext.writerGeneration,
143
+ },
144
+ ),
145
+ };
146
+ }
82
147
  if (candidate.accountMode === "direct") {
83
148
  if (!callerBearerMayBeForwarded || !hasCallerCodexBearer(incomingHeaders)) continue;
84
149
  const headers = directSidecarHeaders(incomingHeaders);
@@ -89,7 +154,9 @@ export async function resolveFirstUsableOpenAiSidecar(
89
154
  headers,
90
155
  };
91
156
  }
92
- const authContext = await resolveCodexAuthContext(incomingHeaders, config, candidate.accountMode);
157
+ const authContext = await resolveCodexAuthContext(incomingHeaders, config, candidate.accountMode, {
158
+ beginCodexAccountSelection: options.beginCodexAccountSelection,
159
+ });
93
160
  if (!isCodexAuthContextUsable(authContext, config)) continue;
94
161
  return {
95
162
  ...candidate,
@@ -102,6 +169,7 @@ export async function resolveFirstUsableOpenAiSidecar(
102
169
  authContext.accountId,
103
170
  outcome,
104
171
  {
172
+ threadId: authContext.affinityKey,
105
173
  probeLeaseId: authContext.probeLeaseId,
106
174
  writerGeneration: authContext.writerGeneration,
107
175
  },
@@ -1,4 +1,10 @@
1
- import { backupConfigBeforeOpenAiTierMigration, saveConfig } from "../config";
1
+ import {
2
+ backupConfigBeforeOpenAiTierMigration,
3
+ OpenAiTierBackupCollisionError,
4
+ OpenAiTierRollbackPreserveError,
5
+ preserveOpenAiTierRollbackSnapshot,
6
+ saveConfig,
7
+ } from "../config";
2
8
  import type { OcxConfig } from "../types";
3
9
  import { projectOpenAiTierMigration } from "./openai-tiers";
4
10
 
@@ -6,6 +12,23 @@ export interface OpenAiTierStartupDeps {
6
12
  project: typeof projectOpenAiTierMigration;
7
13
  backup: () => void;
8
14
  save: (config: OcxConfig) => void;
15
+ preserveRollback?: (error: OpenAiTierBackupCollisionError) => void;
16
+ }
17
+
18
+ function defaultPreserveRollback(error: OpenAiTierBackupCollisionError): void {
19
+ if (!error.configPath) throw error;
20
+ try {
21
+ const preserved = preserveOpenAiTierRollbackSnapshot(error.configPath);
22
+ console.warn(`[openai-provider-migration] Preserved rollback snapshot at ${preserved}`);
23
+ } catch (cause) {
24
+ if (
25
+ cause instanceof OpenAiTierRollbackPreserveError
26
+ && (cause.code === "missing" || cause.code === "not-rollback")
27
+ ) {
28
+ throw error;
29
+ }
30
+ throw cause;
31
+ }
9
32
  }
10
33
 
11
34
  const DEFAULT_DEPS: OpenAiTierStartupDeps = {
@@ -20,7 +43,13 @@ export function runOpenAiTierStartupMigration(
20
43
  ): OcxConfig {
21
44
  const projection = deps.project(config);
22
45
  if (!projection.changed) return projection.config;
23
- deps.backup();
46
+ try {
47
+ deps.backup();
48
+ } catch (error) {
49
+ if (!(error instanceof OpenAiTierBackupCollisionError)) throw error;
50
+ (deps.preserveRollback ?? defaultPreserveRollback)(error);
51
+ deps.backup();
52
+ }
24
53
  deps.save(projection.config);
25
54
  for (const warning of projection.warnings) console.warn(`[openai-provider-migration] ${warning}`);
26
55
  return projection.config;