codex-multi-auth 2.16.0 → 2.17.2

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 (303) hide show
  1. package/.codex-plugin/plugin.json +9 -9
  2. package/LICENSE +21 -21
  3. package/README.md +487 -463
  4. package/assets/codex-multi-auth-icon.svg +39 -39
  5. package/assets/readme-hero.svg +31 -31
  6. package/config/README.md +31 -31
  7. package/config/codex-legacy.json +1221 -1221
  8. package/config/codex-modern.json +423 -423
  9. package/config/minimal-codex.json +15 -15
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +8 -3
  12. package/dist/lib/account-policy.d.ts +4 -0
  13. package/dist/lib/account-policy.d.ts.map +1 -1
  14. package/dist/lib/account-policy.js +8 -0
  15. package/dist/lib/accounts/rate-limits.d.ts.map +1 -1
  16. package/dist/lib/accounts/rate-limits.js +2 -0
  17. package/dist/lib/accounts.d.ts +15 -3
  18. package/dist/lib/accounts.d.ts.map +1 -1
  19. package/dist/lib/accounts.js +231 -30
  20. package/dist/lib/api-route-store.d.ts +24 -0
  21. package/dist/lib/api-route-store.d.ts.map +1 -0
  22. package/dist/lib/api-route-store.js +145 -0
  23. package/dist/lib/auth/account-access.d.ts +115 -0
  24. package/dist/lib/auth/account-access.d.ts.map +1 -0
  25. package/dist/lib/auth/account-access.js +214 -0
  26. package/dist/lib/auth/token-utils.d.ts +54 -1
  27. package/dist/lib/auth/token-utils.d.ts.map +1 -1
  28. package/dist/lib/auth/token-utils.js +92 -7
  29. package/dist/lib/cli.d.ts +1 -1
  30. package/dist/lib/cli.d.ts.map +1 -1
  31. package/dist/lib/cli.js +4 -0
  32. package/dist/lib/codex-cli/state.d.ts +7 -0
  33. package/dist/lib/codex-cli/state.d.ts.map +1 -1
  34. package/dist/lib/codex-cli/state.js +18 -0
  35. package/dist/lib/codex-cli/writer.d.ts.map +1 -1
  36. package/dist/lib/codex-cli/writer.js +60 -8
  37. package/dist/lib/codex-manager/account-manager-commands.d.ts.map +1 -1
  38. package/dist/lib/codex-manager/account-manager-commands.js +1 -0
  39. package/dist/lib/codex-manager/account-pool-write.d.ts +3 -1
  40. package/dist/lib/codex-manager/account-pool-write.d.ts.map +1 -1
  41. package/dist/lib/codex-manager/account-pool-write.js +4 -0
  42. package/dist/lib/codex-manager/api-login-menu.d.ts +14 -0
  43. package/dist/lib/codex-manager/api-login-menu.d.ts.map +1 -0
  44. package/dist/lib/codex-manager/api-login-menu.js +224 -0
  45. package/dist/lib/codex-manager/commands/account.d.ts.map +1 -1
  46. package/dist/lib/codex-manager/commands/account.js +27 -0
  47. package/dist/lib/codex-manager/commands/best.d.ts.map +1 -1
  48. package/dist/lib/codex-manager/commands/best.js +6 -2
  49. package/dist/lib/codex-manager/commands/check.d.ts +7 -1
  50. package/dist/lib/codex-manager/commands/check.d.ts.map +1 -1
  51. package/dist/lib/codex-manager/commands/check.js +24 -3
  52. package/dist/lib/codex-manager/commands/forecast.d.ts +1 -1
  53. package/dist/lib/codex-manager/commands/forecast.d.ts.map +1 -1
  54. package/dist/lib/codex-manager/commands/forecast.js +7 -3
  55. package/dist/lib/codex-manager/commands/report.d.ts.map +1 -1
  56. package/dist/lib/codex-manager/commands/report.js +5 -1
  57. package/dist/lib/codex-manager/commands/resets.d.ts +2 -0
  58. package/dist/lib/codex-manager/commands/resets.d.ts.map +1 -0
  59. package/dist/lib/codex-manager/commands/resets.js +67 -0
  60. package/dist/lib/codex-manager/commands/rotation.d.ts +2 -2
  61. package/dist/lib/codex-manager/commands/rotation.d.ts.map +1 -1
  62. package/dist/lib/codex-manager/commands/rotation.js +63 -12
  63. package/dist/lib/codex-manager/commands/status.d.ts +12 -1
  64. package/dist/lib/codex-manager/commands/status.d.ts.map +1 -1
  65. package/dist/lib/codex-manager/commands/status.js +166 -22
  66. package/dist/lib/codex-manager/commands/uninstall.js +1 -1
  67. package/dist/lib/codex-manager/formatters/quota-formatters.d.ts.map +1 -1
  68. package/dist/lib/codex-manager/formatters/quota-formatters.js +3 -2
  69. package/dist/lib/codex-manager/health-check.d.ts +3 -0
  70. package/dist/lib/codex-manager/health-check.d.ts.map +1 -1
  71. package/dist/lib/codex-manager/health-check.js +57 -7
  72. package/dist/lib/codex-manager/help.d.ts.map +1 -1
  73. package/dist/lib/codex-manager/help.js +4 -2
  74. package/dist/lib/codex-manager/login-flow.d.ts.map +1 -1
  75. package/dist/lib/codex-manager/login-flow.js +69 -7
  76. package/dist/lib/codex-manager/login-menu-data.d.ts.map +1 -1
  77. package/dist/lib/codex-manager/login-menu-data.js +21 -8
  78. package/dist/lib/codex-manager/login-oauth.d.ts +3 -0
  79. package/dist/lib/codex-manager/login-oauth.d.ts.map +1 -1
  80. package/dist/lib/codex-manager/login-oauth.js +6 -1
  81. package/dist/lib/codex-manager/login-workspace-choice.d.ts +11 -0
  82. package/dist/lib/codex-manager/login-workspace-choice.d.ts.map +1 -0
  83. package/dist/lib/codex-manager/login-workspace-choice.js +37 -0
  84. package/dist/lib/codex-manager/persist-selected-account.d.ts.map +1 -1
  85. package/dist/lib/codex-manager/persist-selected-account.js +2 -1
  86. package/dist/lib/codex-manager/quota-cache-helpers.d.ts +7 -1
  87. package/dist/lib/codex-manager/quota-cache-helpers.d.ts.map +1 -1
  88. package/dist/lib/codex-manager/quota-cache-helpers.js +34 -19
  89. package/dist/lib/codex-manager/repair-commands.d.ts +13 -0
  90. package/dist/lib/codex-manager/repair-commands.d.ts.map +1 -1
  91. package/dist/lib/codex-manager/repair-commands.js +144 -9
  92. package/dist/lib/codex-manager.d.ts.map +1 -1
  93. package/dist/lib/codex-manager.js +25 -6
  94. package/dist/lib/concurrency.d.ts +3 -0
  95. package/dist/lib/concurrency.d.ts.map +1 -0
  96. package/dist/lib/concurrency.js +31 -0
  97. package/dist/lib/context-budget-response.js +8 -8
  98. package/dist/lib/context-overflow.js +10 -10
  99. package/dist/lib/model-route-policy.d.ts +52 -0
  100. package/dist/lib/model-route-policy.d.ts.map +1 -0
  101. package/dist/lib/model-route-policy.js +159 -0
  102. package/dist/lib/oauth-success.html +125 -125
  103. package/dist/lib/policy/runtime-policy.d.ts +1 -0
  104. package/dist/lib/policy/runtime-policy.d.ts.map +1 -1
  105. package/dist/lib/policy/runtime-policy.js +3 -0
  106. package/dist/lib/preemptive-quota-scheduler.d.ts +2 -0
  107. package/dist/lib/preemptive-quota-scheduler.d.ts.map +1 -1
  108. package/dist/lib/preemptive-quota-scheduler.js +4 -0
  109. package/dist/lib/prompts/codex-host-bridge.js +138 -138
  110. package/dist/lib/prompts/codex.js +79 -79
  111. package/dist/lib/quota-cache.d.ts +7 -3
  112. package/dist/lib/quota-cache.d.ts.map +1 -1
  113. package/dist/lib/quota-cache.js +91 -59
  114. package/dist/lib/quota-probe.d.ts +5 -0
  115. package/dist/lib/quota-probe.d.ts.map +1 -1
  116. package/dist/lib/quota-probe.js +26 -2
  117. package/dist/lib/quota-readiness.d.ts +7 -2
  118. package/dist/lib/quota-readiness.d.ts.map +1 -1
  119. package/dist/lib/quota-readiness.js +25 -4
  120. package/dist/lib/request/client-cancellation.d.ts +5 -0
  121. package/dist/lib/request/client-cancellation.d.ts.map +1 -0
  122. package/dist/lib/request/client-cancellation.js +7 -0
  123. package/dist/lib/request/response-outcome.d.ts +25 -0
  124. package/dist/lib/request/response-outcome.d.ts.map +1 -0
  125. package/dist/lib/request/response-outcome.js +49 -0
  126. package/dist/lib/request/stream-failover-runtime.d.ts +2 -1
  127. package/dist/lib/request/stream-failover-runtime.d.ts.map +1 -1
  128. package/dist/lib/request/stream-failover-runtime.js +29 -10
  129. package/dist/lib/runtime/account-check.d.ts.map +1 -1
  130. package/dist/lib/runtime/account-check.js +2 -8
  131. package/dist/lib/runtime/account-model-catalog.d.ts +64 -0
  132. package/dist/lib/runtime/account-model-catalog.d.ts.map +1 -0
  133. package/dist/lib/runtime/account-model-catalog.js +154 -0
  134. package/dist/lib/runtime/account-pool.d.ts +3 -1
  135. package/dist/lib/runtime/account-pool.d.ts.map +1 -1
  136. package/dist/lib/runtime/account-pool.js +4 -0
  137. package/dist/lib/runtime/account-reset-credits.d.ts +42 -0
  138. package/dist/lib/runtime/account-reset-credits.d.ts.map +1 -0
  139. package/dist/lib/runtime/account-reset-credits.js +82 -0
  140. package/dist/lib/runtime/api-model-capabilities.d.ts +30 -0
  141. package/dist/lib/runtime/api-model-capabilities.d.ts.map +1 -0
  142. package/dist/lib/runtime/api-model-capabilities.js +434 -0
  143. package/dist/lib/runtime/api-model-runtime.d.ts +42 -0
  144. package/dist/lib/runtime/api-model-runtime.d.ts.map +1 -0
  145. package/dist/lib/runtime/api-model-runtime.js +358 -0
  146. package/dist/lib/runtime/app-bind.d.ts +19 -1
  147. package/dist/lib/runtime/app-bind.d.ts.map +1 -1
  148. package/dist/lib/runtime/app-bind.js +89 -11
  149. package/dist/lib/runtime/automatic-account-checks.d.ts +35 -0
  150. package/dist/lib/runtime/automatic-account-checks.d.ts.map +1 -0
  151. package/dist/lib/runtime/automatic-account-checks.js +140 -0
  152. package/dist/lib/runtime/automatic-subscription-checks.d.ts +7 -0
  153. package/dist/lib/runtime/automatic-subscription-checks.d.ts.map +1 -0
  154. package/dist/lib/runtime/automatic-subscription-checks.js +54 -0
  155. package/dist/lib/runtime/catalog-capabilities.d.ts +8 -0
  156. package/dist/lib/runtime/catalog-capabilities.d.ts.map +1 -0
  157. package/dist/lib/runtime/catalog-capabilities.js +61 -0
  158. package/dist/lib/runtime/inference-activity.d.ts +15 -0
  159. package/dist/lib/runtime/inference-activity.d.ts.map +1 -0
  160. package/dist/lib/runtime/inference-activity.js +95 -0
  161. package/dist/lib/runtime/model-discovery-status.d.ts +100 -0
  162. package/dist/lib/runtime/model-discovery-status.d.ts.map +1 -0
  163. package/dist/lib/runtime/model-discovery-status.js +387 -0
  164. package/dist/lib/runtime/native-account-storage.d.ts +11 -0
  165. package/dist/lib/runtime/native-account-storage.d.ts.map +1 -0
  166. package/dist/lib/runtime/native-account-storage.js +50 -0
  167. package/dist/lib/runtime/native-account-sync.d.ts +19 -0
  168. package/dist/lib/runtime/native-account-sync.d.ts.map +1 -0
  169. package/dist/lib/runtime/native-account-sync.js +62 -0
  170. package/dist/lib/runtime/native-binding-lock.d.ts +3 -0
  171. package/dist/lib/runtime/native-binding-lock.d.ts.map +1 -0
  172. package/dist/lib/runtime/native-binding-lock.js +5 -0
  173. package/dist/lib/runtime/native-client-auth.d.ts +3 -0
  174. package/dist/lib/runtime/native-client-auth.d.ts.map +1 -0
  175. package/dist/lib/runtime/native-client-auth.js +42 -0
  176. package/dist/lib/runtime/native-provider-config.d.ts +4 -0
  177. package/dist/lib/runtime/native-provider-config.d.ts.map +1 -0
  178. package/dist/lib/runtime/native-provider-config.js +52 -0
  179. package/dist/lib/runtime/native-rate-limits.d.ts +18 -0
  180. package/dist/lib/runtime/native-rate-limits.d.ts.map +1 -0
  181. package/dist/lib/runtime/native-rate-limits.js +135 -0
  182. package/dist/lib/runtime/reset-credit-routing.d.ts +41 -0
  183. package/dist/lib/runtime/reset-credit-routing.d.ts.map +1 -0
  184. package/dist/lib/runtime/reset-credit-routing.js +66 -0
  185. package/dist/lib/runtime/reset-credits.d.ts +101 -0
  186. package/dist/lib/runtime/reset-credits.d.ts.map +1 -0
  187. package/dist/lib/runtime/reset-credits.js +162 -0
  188. package/dist/lib/runtime/response-output-history.d.ts +17 -0
  189. package/dist/lib/runtime/response-output-history.d.ts.map +1 -0
  190. package/dist/lib/runtime/response-output-history.js +85 -0
  191. package/dist/lib/runtime/responses-websocket.d.ts +31 -0
  192. package/dist/lib/runtime/responses-websocket.d.ts.map +1 -0
  193. package/dist/lib/runtime/responses-websocket.js +636 -0
  194. package/dist/lib/runtime/resume-picker.d.ts +41 -0
  195. package/dist/lib/runtime/resume-picker.d.ts.map +1 -0
  196. package/dist/lib/runtime/resume-picker.js +169 -0
  197. package/dist/lib/runtime/rotation-account-selection.d.ts +6 -0
  198. package/dist/lib/runtime/rotation-account-selection.d.ts.map +1 -1
  199. package/dist/lib/runtime/rotation-account-selection.js +63 -0
  200. package/dist/lib/runtime/rotation-proxy-state.d.ts +33 -0
  201. package/dist/lib/runtime/rotation-proxy-state.d.ts.map +1 -1
  202. package/dist/lib/runtime/rotation-server-types.d.ts +18 -0
  203. package/dist/lib/runtime/rotation-server-types.d.ts.map +1 -1
  204. package/dist/lib/runtime/runtime-capability-failures.d.ts +14 -0
  205. package/dist/lib/runtime/runtime-capability-failures.d.ts.map +1 -0
  206. package/dist/lib/runtime/runtime-capability-failures.js +76 -0
  207. package/dist/lib/runtime/runtime-observability.d.ts +7 -0
  208. package/dist/lib/runtime/runtime-observability.d.ts.map +1 -1
  209. package/dist/lib/runtime/runtime-observability.js +42 -2
  210. package/dist/lib/runtime/subscription-first-use.d.ts +10 -0
  211. package/dist/lib/runtime/subscription-first-use.d.ts.map +1 -0
  212. package/dist/lib/runtime/subscription-first-use.js +64 -0
  213. package/dist/lib/runtime/subscription-quota-event.d.ts +6 -0
  214. package/dist/lib/runtime/subscription-quota-event.d.ts.map +1 -0
  215. package/dist/lib/runtime/subscription-quota-event.js +31 -0
  216. package/dist/lib/runtime/subscription-quota-order.d.ts +17 -0
  217. package/dist/lib/runtime/subscription-quota-order.d.ts.map +1 -0
  218. package/dist/lib/runtime/subscription-quota-order.js +31 -0
  219. package/dist/lib/runtime/workspace-model-scopes.d.ts +15 -0
  220. package/dist/lib/runtime/workspace-model-scopes.d.ts.map +1 -0
  221. package/dist/lib/runtime/workspace-model-scopes.js +34 -0
  222. package/dist/lib/runtime-constants.d.ts +3 -0
  223. package/dist/lib/runtime-constants.d.ts.map +1 -1
  224. package/dist/lib/runtime-constants.js +5 -0
  225. package/dist/lib/runtime-paths.d.ts.map +1 -1
  226. package/dist/lib/runtime-paths.js +4 -1
  227. package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
  228. package/dist/lib/runtime-rotation-proxy.js +852 -42
  229. package/dist/lib/schemas.d.ts +20 -0
  230. package/dist/lib/schemas.d.ts.map +1 -1
  231. package/dist/lib/schemas.js +3 -0
  232. package/dist/lib/storage/credential-sidecars.d.ts +13 -0
  233. package/dist/lib/storage/credential-sidecars.d.ts.map +1 -0
  234. package/dist/lib/storage/credential-sidecars.js +52 -0
  235. package/dist/lib/storage/file-lock.d.ts +9 -0
  236. package/dist/lib/storage/file-lock.d.ts.map +1 -0
  237. package/dist/lib/storage/file-lock.js +186 -0
  238. package/dist/lib/storage/flagged-storage.d.ts.map +1 -1
  239. package/dist/lib/storage/flagged-storage.js +12 -0
  240. package/dist/lib/storage/path-state.d.ts +2 -0
  241. package/dist/lib/storage/path-state.d.ts.map +1 -1
  242. package/dist/lib/storage/path-state.js +9 -0
  243. package/dist/lib/storage/paths.js +4 -4
  244. package/dist/lib/storage/pending-auth.d.ts +18 -0
  245. package/dist/lib/storage/pending-auth.d.ts.map +1 -0
  246. package/dist/lib/storage/pending-auth.js +155 -0
  247. package/dist/lib/storage/public-types.d.ts +13 -0
  248. package/dist/lib/storage/public-types.d.ts.map +1 -1
  249. package/dist/lib/storage/record-identity.d.ts +16 -0
  250. package/dist/lib/storage/record-identity.d.ts.map +1 -0
  251. package/dist/lib/storage/record-identity.js +16 -0
  252. package/dist/lib/storage/snapshot-merge.d.ts +16 -0
  253. package/dist/lib/storage/snapshot-merge.d.ts.map +1 -0
  254. package/dist/lib/storage/snapshot-merge.js +206 -0
  255. package/dist/lib/storage/transactions.d.ts.map +1 -1
  256. package/dist/lib/storage/transactions.js +5 -4
  257. package/dist/lib/storage.d.ts +6 -0
  258. package/dist/lib/storage.d.ts.map +1 -1
  259. package/dist/lib/storage.js +77 -7
  260. package/dist/lib/ui/auth-menu-builder.d.ts +2 -0
  261. package/dist/lib/ui/auth-menu-builder.d.ts.map +1 -1
  262. package/dist/lib/ui/auth-menu-builder.js +1 -0
  263. package/dist/lib/ui/auth-menu.d.ts.map +1 -1
  264. package/dist/lib/ui/auth-menu.js +1 -0
  265. package/dist/lib/ui/check-progress.d.ts +8 -0
  266. package/dist/lib/ui/check-progress.d.ts.map +1 -0
  267. package/dist/lib/ui/check-progress.js +30 -0
  268. package/dist/lib/ui/format.d.ts +3 -1
  269. package/dist/lib/ui/format.d.ts.map +1 -1
  270. package/dist/lib/ui/format.js +8 -0
  271. package/dist/lib/ui/ui-copy.d.ts +2 -2
  272. package/dist/lib/ui/ui-copy.js +2 -2
  273. package/dist/lib/usage/pricing.js +2 -2
  274. package/dist/lib/usage/usage-extraction.d.ts +3 -1
  275. package/dist/lib/usage/usage-extraction.d.ts.map +1 -1
  276. package/dist/lib/usage/usage-extraction.js +46 -9
  277. package/node_modules/@codex-ai/plugin/dist/index.d.ts +2 -2
  278. package/node_modules/@codex-ai/plugin/dist/index.js +2 -2
  279. package/node_modules/@codex-ai/plugin/dist/tool.d.ts +42 -42
  280. package/node_modules/@codex-ai/plugin/dist/tool.js +29 -29
  281. package/node_modules/@codex-ai/plugin/package.json +9 -9
  282. package/node_modules/@codex-ai/sdk/dist/index.d.ts +4 -4
  283. package/node_modules/@codex-ai/sdk/dist/index.js +2 -2
  284. package/node_modules/@codex-ai/sdk/package.json +8 -8
  285. package/package.json +200 -197
  286. package/scripts/codex-app-launcher.js +653 -653
  287. package/scripts/codex-app-router.js +373 -366
  288. package/scripts/codex-bin-resolver.js +313 -281
  289. package/scripts/codex-multi-auth.js +59 -59
  290. package/scripts/codex-routing.js +62 -61
  291. package/scripts/codex.js +6642 -6564
  292. package/scripts/install-codex-auth-utils.js +213 -213
  293. package/scripts/mcodex.js +367 -367
  294. package/scripts/postinstall.js +114 -114
  295. package/scripts/preuninstall.js +265 -265
  296. package/vendor/codex-ai-plugin/dist/index.d.ts +2 -2
  297. package/vendor/codex-ai-plugin/dist/index.js +2 -2
  298. package/vendor/codex-ai-plugin/dist/tool.d.ts +42 -42
  299. package/vendor/codex-ai-plugin/dist/tool.js +29 -29
  300. package/vendor/codex-ai-plugin/package.json +9 -9
  301. package/vendor/codex-ai-sdk/dist/index.d.ts +4 -4
  302. package/vendor/codex-ai-sdk/dist/index.js +2 -2
  303. package/vendor/codex-ai-sdk/package.json +8 -8
@@ -0,0 +1,358 @@
1
+ import { ClientCancellationError } from "../request/client-cancellation.js";
2
+ import { mapWithConcurrency } from "../concurrency.js";
3
+ import { RuntimeCapabilityFailures, classifyCapabilityFailure, } from "./runtime-capability-failures.js";
4
+ import { readErrorBody } from "../request/stream-failover-runtime.js";
5
+ import { createHash } from "node:crypto";
6
+ import { resolveModelRoute, parseModelRoute, modelEntitlements, } from "../model-route-policy.js";
7
+ import { isRecord } from "../utils.js";
8
+ /** Endpoint-family exclusions, not a fixed allowlist of text model releases.
9
+ * Model ID discovery alone does not prove Responses/tool compatibility. */
10
+ export function isNonResponsesModel(id) {
11
+ return (/(?:^|[-_])(?:tts|whisper|embedding|moderation|transcribe|transcription|realtime|audio|image|sora)(?:[-_]|$)/i.test(id) ||
12
+ /^(?:dall-e|babbage|davinci)(?:-|$)/i.test(id) ||
13
+ /(?:^|-)live(?:-|$)/i.test(id));
14
+ }
15
+ /** Conservative native-picker metadata: discovery proves an ID, not optional tool capabilities. */
16
+ export function apiPickerModel(id) {
17
+ return {
18
+ slug: id,
19
+ display_name: id,
20
+ description: "API model; optional capabilities are not advertised by model discovery.",
21
+ supported_reasoning_levels: [],
22
+ default_reasoning_level: null,
23
+ shell_type: "shell_command",
24
+ visibility: "list",
25
+ supported_in_api: true,
26
+ priority: 0,
27
+ availability_nux: null,
28
+ upgrade: null,
29
+ model_messages: {
30
+ instructions_template: "You are a coding assistant. Follow the user instructions and supplied tool definitions.",
31
+ },
32
+ support_verbosity: false,
33
+ default_verbosity: null,
34
+ apply_patch_tool_type: null,
35
+ truncation_policy: { mode: "bytes", limit: 10000 },
36
+ experimental_supported_tools: [],
37
+ input_modalities: ["text"],
38
+ supports_reasoning_summary_parameter: false,
39
+ use_responses_lite: false,
40
+ };
41
+ }
42
+ /** Apply current credential policy, never metadata inherited from another credential or old config. */
43
+ function withAccessPrograms(model, route) {
44
+ const result = { ...model };
45
+ delete result.available_access_programs;
46
+ const override = route.modelAccessPrograms && Object.hasOwn(route.modelAccessPrograms, model.slug)
47
+ ? route.modelAccessPrograms[model.slug] : undefined;
48
+ const programs = override ?? route.accessPrograms;
49
+ if (programs)
50
+ result.available_access_programs = structuredClone(programs);
51
+ return result;
52
+ }
53
+ export async function boundedJson(response, max = 8 * 1024 * 1024) {
54
+ const reader = response.body?.getReader();
55
+ if (!reader)
56
+ throw Error("Empty model catalog");
57
+ const chunks = [];
58
+ let size = 0;
59
+ try {
60
+ for (;;) {
61
+ const { done, value } = await reader.read();
62
+ if (done)
63
+ break;
64
+ size += value.byteLength;
65
+ if (size > max)
66
+ throw Error("Model catalog too large");
67
+ chunks.push(value);
68
+ }
69
+ }
70
+ finally {
71
+ await reader.cancel();
72
+ }
73
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
74
+ }
75
+ export class ApiModelRuntime {
76
+ fetchImpl;
77
+ now;
78
+ updateDiscovery;
79
+ enrichModels;
80
+ onCatalogUpdated;
81
+ discoveryUpdates = new Map();
82
+ lastPersistenceError = null;
83
+ failures = new RuntimeCapabilityFailures(() => this.now());
84
+ visibility = new Map();
85
+ snapshots = new Map();
86
+ activeKeys = new Set();
87
+ cachedCatalogs() {
88
+ return [...this.snapshots]
89
+ .filter(([key]) => this.activeKeys.has(key))
90
+ .map(([, catalog]) => catalog);
91
+ }
92
+ cache = new Map();
93
+ constructor(fetchImpl = fetch, now = Date.now, updateDiscovery, enrichModels, onCatalogUpdated) {
94
+ this.fetchImpl = fetchImpl;
95
+ this.now = now;
96
+ this.updateDiscovery = updateDiscovery;
97
+ this.enrichModels = enrichModels;
98
+ this.onCatalogUpdated = onCatalogUpdated;
99
+ }
100
+ key(route) {
101
+ return createHash("sha256")
102
+ .update(route.id)
103
+ .update("\0")
104
+ .update(route.apiKey)
105
+ .digest("hex");
106
+ }
107
+ async catalogs(routes, refresh = false, forceProbes = false, waitForCapabilities = true, waitForPersistence = true, requestKind) {
108
+ this.activeKeys = new Set(routes.map((route) => this.key(route)));
109
+ return mapWithConcurrency(requestKind ? routes.filter(route => route.kind === requestKind) : routes, 3, async (route) => {
110
+ const key = this.key(route), cached = this.cache.get(key);
111
+ if (route.enabled &&
112
+ (refresh ||
113
+ !cached ||
114
+ cached.at + (cached.error ? 5000 : 5 * 60_000) <= this.now())) {
115
+ let models = [];
116
+ let error = false;
117
+ try {
118
+ const response = await this.fetchImpl("https://api.openai.com/v1/models", {
119
+ method: "GET",
120
+ headers: { authorization: `Bearer ${route.apiKey}` },
121
+ redirect: "error",
122
+ signal: AbortSignal.timeout(15000),
123
+ });
124
+ if (!response.ok) {
125
+ await response.body?.cancel();
126
+ throw Error("API catalog unavailable");
127
+ }
128
+ const value = await boundedJson(response);
129
+ if (!isRecord(value) ||
130
+ !Array.isArray(value.data) ||
131
+ value.data.length > 10000)
132
+ throw Error("Invalid API catalog");
133
+ models = value.data.flatMap((m) => isRecord(m) &&
134
+ typeof m.id === "string" &&
135
+ /^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/.test(m.id) &&
136
+ !isNonResponsesModel(m.id)
137
+ ? [
138
+ {
139
+ ...apiPickerModel(m.id),
140
+ ...cached?.models.find((old) => old.slug === m.id),
141
+ },
142
+ ]
143
+ : []);
144
+ }
145
+ catch {
146
+ error = true;
147
+ }
148
+ if (this.cache.size >= 100)
149
+ this.cache.delete(this.cache.keys().next().value ?? "");
150
+ this.cache.set(key, { at: this.now(), models, error });
151
+ }
152
+ const entry = this.cache.get(key);
153
+ if (route.enabled && entry && !entry.error && this.updateDiscovery) {
154
+ let pending = this.discoveryUpdates.get(key);
155
+ if (!pending) {
156
+ pending = this.updateDiscovery(route, entry.models.map(m => m.slug));
157
+ this.discoveryUpdates.set(key, pending);
158
+ void pending.then(() => { this.lastPersistenceError = null; }, () => { this.lastPersistenceError = "api_catalog_persistence_failed"; }).finally(() => { this.discoveryUpdates.delete(key); });
159
+ }
160
+ if (waitForPersistence) {
161
+ try {
162
+ route = await pending;
163
+ }
164
+ catch { /* Keep the explicit policy read for this request; discovery cannot expand it. */ }
165
+ }
166
+ }
167
+ this.visibility.set(key, route.visibleModels);
168
+ const snapshot = {
169
+ id: route.id,
170
+ kind: route.kind,
171
+ enabled: route.enabled,
172
+ priority: route.priority,
173
+ models: (entry?.models ?? []).map(model => withAccessPrograms(model, route)),
174
+ visibleModels: route.visibleModels,
175
+ };
176
+ this.snapshots.set(key, snapshot);
177
+ if (route.enabled && entry && !entry.error && this.enrichModels && (!requestKind || !cached || cached.error)) {
178
+ const pending = this.enrichModels(entry.models.filter((m) => route.visibleModels.includes(m.slug)), refresh, requestKind ? { ...route, probeCapabilities: false } : route, forceProbes).then((enriched) => {
179
+ const byId = new Map(enriched.map((m) => [m.slug, m]));
180
+ snapshot.models = entry.models.map((m) => withAccessPrograms(byId.get(m.slug) ?? m, route));
181
+ // The caller receives its own completed result; only the newest refresh may replace the shared cache.
182
+ if (this.cache.get(key) !== entry ||
183
+ this.snapshots.get(key) !== snapshot ||
184
+ !this.activeKeys.has(key))
185
+ return;
186
+ entry.models = snapshot.models;
187
+ this.onCatalogUpdated?.();
188
+ });
189
+ if (waitForCapabilities)
190
+ await pending;
191
+ else
192
+ void pending.catch(() => {
193
+ entry.error = true;
194
+ });
195
+ }
196
+ return snapshot;
197
+ });
198
+ }
199
+ statuses(routes) {
200
+ return routes.map((r) => {
201
+ const c = this.cache.get(this.key(r));
202
+ return {
203
+ id: r.id,
204
+ kind: r.kind,
205
+ label: r.label,
206
+ priority: r.priority,
207
+ checkedAt: c?.at ?? 0,
208
+ error: c?.error ?? true,
209
+ availableModels: c?.models.map((m) => m.slug) ?? [],
210
+ entitlements: (c?.models ?? [])
211
+ .filter((m) => (this.visibility.get(this.key(r)) ?? r.visibleModels).includes(m.slug))
212
+ .map(model => modelEntitlements(withAccessPrograms(model, r))),
213
+ visibleModels: (this.visibility.get(this.key(r)) ?? r.visibleModels).filter((id) => c?.models.some((m) => m.slug === id)),
214
+ };
215
+ });
216
+ }
217
+ async request(alias, body, routes, signal, onAttempt) {
218
+ let route;
219
+ try {
220
+ if (isNonResponsesModel(parseModelRoute(alias).upstreamModel))
221
+ return this.failure(400, "model_requires_other_endpoint", "This model uses a media, audio, embedding, or legacy endpoint and cannot serve a Codex Responses turn. Select a text/coding model.");
222
+ route = resolveModelRoute(alias, await this.catalogs(routes, false, false, true, false, parseModelRoute(alias).kind), {
223
+ serviceTier: typeof body.service_tier === "string" ? body.service_tier : undefined,
224
+ reasoningEffort: isRecord(body.reasoning) && typeof body.reasoning.effort === "string"
225
+ ? body.reasoning.effort
226
+ : undefined,
227
+ });
228
+ }
229
+ catch {
230
+ return this.failure(400, "invalid_model_route");
231
+ }
232
+ if (route.kind === "oauth")
233
+ return this.failure(400, "explicit_api_model_required");
234
+ // Until connection-affinity support exists, never guess the owner of server-side response state.
235
+ if (body.background === true || body.conversation)
236
+ return this.failure(400, "stateful_api_route_unsupported");
237
+ if (body.previous_response_id)
238
+ return this.failure(400, "previous_response_not_found");
239
+ const payload = {
240
+ ...body,
241
+ model: route.upstreamModel,
242
+ store: false,
243
+ };
244
+ if (route.serviceTier)
245
+ payload.service_tier = route.serviceTier;
246
+ delete payload.client_metadata;
247
+ let lastStatus = 503;
248
+ let lastRejection;
249
+ for (const candidate of route.candidates) {
250
+ const credential = routes.find((r) => r.id === candidate.id);
251
+ if (!credential)
252
+ continue;
253
+ const effort = isRecord(body.reasoning) && typeof body.reasoning.effort === "string"
254
+ ? body.reasoning.effort
255
+ : undefined;
256
+ if (!this.failures.supports(this.key(credential), route.upstreamModel, effort, route.serviceTier))
257
+ continue;
258
+ if (signal?.aborted)
259
+ throw new ClientCancellationError();
260
+ const controller = new AbortController();
261
+ const abort = () => controller.abort();
262
+ signal?.addEventListener("abort", abort, { once: true });
263
+ const timer = setTimeout(abort, 60000);
264
+ try {
265
+ onAttempt?.(routes.indexOf(credential));
266
+ const response = await this.fetchImpl("https://api.openai.com/v1/responses", {
267
+ method: "POST",
268
+ headers: {
269
+ authorization: `Bearer ${credential.apiKey}`,
270
+ "content-type": "application/json",
271
+ },
272
+ body: JSON.stringify(payload),
273
+ redirect: "error",
274
+ signal: controller.signal,
275
+ });
276
+ lastStatus = response.status;
277
+ lastRejection = undefined;
278
+ if (response.ok) {
279
+ const headers = new Headers();
280
+ for (const name of [
281
+ "content-type",
282
+ "cache-control",
283
+ "x-request-id",
284
+ "retry-after",
285
+ ]) {
286
+ const value = response.headers.get(name);
287
+ if (value)
288
+ headers.set(name, value);
289
+ }
290
+ return new Response(response.body, {
291
+ status: response.status,
292
+ headers,
293
+ });
294
+ }
295
+ const errorBody = await readErrorBody(response, 5000, 65536);
296
+ let data;
297
+ try {
298
+ data = JSON.parse(errorBody);
299
+ }
300
+ catch {
301
+ data = null;
302
+ }
303
+ const rejection = classifyCapabilityFailure(response.status, data);
304
+ const source = isRecord(data) && isRecord(data.error) ? data.error : {};
305
+ const sanitized = { message: "Upstream rejected the API request.", code: "api_request_rejected" };
306
+ for (const field of ["code", "type", "param"]) {
307
+ const value = source[field];
308
+ if (typeof value === "string" && /^[A-Za-z0-9_.]{1,80}$/.test(value))
309
+ sanitized[field] = value;
310
+ }
311
+ if (rejection) {
312
+ lastRejection = Response.json({ error: sanitized }, { status: response.status });
313
+ this.failures.record(this.key(credential), route.upstreamModel, rejection, effort, route.serviceTier);
314
+ continue;
315
+ }
316
+ if (![401, 403, 429, 500, 502, 503, 504].includes(response.status)) {
317
+ return Response.json({ error: sanitized }, { status: response.status });
318
+ }
319
+ }
320
+ catch (error) {
321
+ if (error instanceof ClientCancellationError)
322
+ throw error;
323
+ if (signal?.aborted)
324
+ throw new ClientCancellationError();
325
+ lastStatus = 502;
326
+ lastRejection = undefined;
327
+ }
328
+ finally {
329
+ clearTimeout(timer);
330
+ signal?.removeEventListener("abort", abort);
331
+ }
332
+ }
333
+ if (lastRejection)
334
+ return lastRejection;
335
+ // A 401/403 here belongs to the API key, not the desktop login; the native
336
+ // client must not read an exhausted credential pool as a bad login.
337
+ return this.failure(lastStatus === 401 || lastStatus === 403 ? 503 : lastStatus, "model_route_pool_unavailable");
338
+ }
339
+ recordStreamFailure(credential, alias, body, error) {
340
+ const route = parseModelRoute(alias);
341
+ if (route.kind !== credential.kind)
342
+ return;
343
+ const rejection = classifyCapabilityFailure(400, error);
344
+ if (!rejection)
345
+ return;
346
+ const effort = isRecord(body.reasoning) && typeof body.reasoning.effort === "string" ? body.reasoning.effort : undefined;
347
+ const tier = route.serviceTier ?? (typeof body.service_tier === "string" ? body.service_tier : undefined);
348
+ this.failures.record(this.key(credential), route.upstreamModel, rejection, effort, tier);
349
+ }
350
+ failure(status, code, message = "No eligible credential could serve the selected API model route. No other pool was used.") {
351
+ return Response.json({
352
+ error: {
353
+ code,
354
+ message,
355
+ },
356
+ }, { status });
357
+ }
358
+ }
@@ -11,6 +11,11 @@ export interface AppBindPaths {
11
11
  launchAgentPath: string | null;
12
12
  }
13
13
  export interface AppBindState {
14
+ nativeOpenai?: boolean;
15
+ catalogAccount?: {
16
+ email: string;
17
+ accountId: string;
18
+ };
14
19
  version: 1;
15
20
  platform: NodeJS.Platform;
16
21
  host: string;
@@ -70,6 +75,11 @@ export interface AppBindResult {
70
75
  }
71
76
  export type ProcessIdentityVerifier = (pid: number, startedAt: number, platform: NodeJS.Platform, identityToken?: string) => boolean | Promise<boolean>;
72
77
  export interface AppBindOptions {
78
+ nativeOpenai?: boolean;
79
+ catalogAccount?: {
80
+ email: string;
81
+ accountId: string;
82
+ };
73
83
  env?: NodeJS.ProcessEnv;
74
84
  platform?: NodeJS.Platform;
75
85
  home?: string;
@@ -93,6 +103,8 @@ export interface DetachedProcessStopOptions {
93
103
  /** Expected per-process nonce when verifying a persisted PID. */
94
104
  identityToken?: string;
95
105
  verifyProcessIdentity?: ProcessIdentityVerifier;
106
+ /** Called once the recorded PID is verified as this router, before it is stopped. */
107
+ onOwnershipVerified?: () => void;
96
108
  }
97
109
  export interface RuntimeRotationAppHelperStatus {
98
110
  state: string | null;
@@ -104,9 +116,15 @@ export interface RuntimeRotationAppHelperStatus {
104
116
  /** Per-process nonce echoed by the helper command line. */
105
117
  identityToken?: string | null;
106
118
  }
107
- export declare function rewriteConfigTomlForAppBind(rawConfig: string, baseUrl: string, clientApiKey?: string): string;
119
+ export declare function rewriteConfigTomlForAppBind(rawConfig: string, baseUrl: string, clientApiKey?: string, nativeOpenai?: boolean): string;
108
120
  export declare function restoreConfigTomlFromAppBind(currentConfig: string, originalConfig: string): string;
109
121
  export declare function resolveAppBindPaths(options?: AppBindOptions): AppBindPaths;
122
+ /**
123
+ * Whether anything still accepts connections at a recorded router address.
124
+ * Only "refused" is proof that no router serves it; a timeout or an unusable
125
+ * address is "unknown".
126
+ */
127
+ export declare function probeRouterAddress(baseUrl: string | null | undefined, platform?: NodeJS.Platform, timeoutMs?: number): Promise<"refused" | "listening" | "unknown">;
110
128
  export declare function stopRuntimeRotationRouterProcess(router: Pick<AppBindRouterStatus, "state" | "pid" | "startedAt" | "updatedAt" | "statusPath" | "routerScriptPath" | "identityToken"> | null, platform: NodeJS.Platform, routerScriptPath: string, options?: DetachedProcessStopOptions): Promise<boolean>;
111
129
  /** Parse the documented local-time format emitted by `ps -o lstart=`. */
112
130
  export declare function parsePosixProcessStartTime(value: string): number | null;
@@ -1 +1 @@
1
- {"version":3,"file":"app-bind.d.ts","sourceRoot":"","sources":["../../../lib/runtime/app-bind.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app-bind.d.ts","sourceRoot":"","sources":["../../../lib/runtime/app-bind.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,11 @@
1
+ import { withNativeBindingLock } from "./native-binding-lock.js";
2
+ import { isRecord } from "../utils.js";
3
+ import { hasNativeProviderConfig, rewriteNativeProviderConfig, restoreNativeProviderConfig } from "./native-provider-config.js";
1
4
  import { spawn } from "node:child_process";
2
5
  import { createHash, randomBytes } from "node:crypto";
3
6
  import { closeSync, existsSync, mkdirSync, openSync } from "node:fs";
4
7
  import { mkdir, open, readFile, readdir, rename, rm, unlink } from "node:fs/promises";
8
+ import { connect } from "node:net";
5
9
  import { homedir } from "node:os";
6
10
  import { basename, dirname, join } from "node:path";
7
11
  import process from "node:process";
@@ -51,11 +55,13 @@ async function withAppBindLock(key, operation) {
51
55
  }
52
56
  }
53
57
  }
54
- export function rewriteConfigTomlForAppBind(rawConfig, baseUrl, clientApiKey = "") {
58
+ export function rewriteConfigTomlForAppBind(rawConfig, baseUrl, clientApiKey = "", nativeOpenai = false) {
59
+ if (nativeOpenai)
60
+ return rewriteNativeProviderConfig(rawConfig, baseUrl);
55
61
  return rewriteConfigTomlForRuntimeRotationProvider(rawConfig, baseUrl, clientApiKey);
56
62
  }
57
63
  export function restoreConfigTomlFromAppBind(currentConfig, originalConfig) {
58
- return restoreConfigTomlFromRuntimeRotationProvider(currentConfig, originalConfig);
64
+ return restoreConfigTomlFromRuntimeRotationProvider(restoreNativeProviderConfig(currentConfig, originalConfig), originalConfig);
59
65
  }
60
66
  function sha256(value) {
61
67
  return createHash("sha256").update(value).digest("hex");
@@ -183,6 +189,8 @@ function readAppBindStateRecord(record) {
183
189
  nodePath,
184
190
  routerScriptPath,
185
191
  clientApiKey,
192
+ nativeOpenai: record.nativeOpenai === true,
193
+ catalogAccount: isRecord(record.catalogAccount) && typeof record.catalogAccount.email === "string" && typeof record.catalogAccount.accountId === "string" ? { email: record.catalogAccount.email, accountId: record.catalogAccount.accountId } : undefined,
186
194
  identityToken: identityToken ?? undefined,
187
195
  startupPath: readString(record, "startupPath"),
188
196
  launchAgentPath: readString(record, "launchAgentPath"),
@@ -421,6 +429,30 @@ function spawnRouter(state) {
421
429
  closeSync(logFd);
422
430
  }
423
431
  }
432
+ /**
433
+ * Whether anything still accepts connections at a recorded router address.
434
+ * Only "refused" is proof that no router serves it; a timeout or an unusable
435
+ * address is "unknown".
436
+ */
437
+ export async function probeRouterAddress(baseUrl, platform = process.platform, timeoutMs = platform === "win32" ? WINDOWS_PROCESS_IDENTITY_PROBE_TIMEOUT_MS : 1000) {
438
+ let url;
439
+ try {
440
+ url = new URL(baseUrl ?? "");
441
+ }
442
+ catch {
443
+ return "unknown";
444
+ }
445
+ const port = Number(url.port);
446
+ if (!Number.isInteger(port) || port <= 0)
447
+ return "unknown";
448
+ return new Promise((resolve) => {
449
+ const socket = connect({ host: url.hostname.replace(/^\[|\]$/g, ""), port });
450
+ const done = (result) => { clearTimeout(timer); socket.destroy(); resolve(result); };
451
+ const timer = setTimeout(() => done("unknown"), timeoutMs);
452
+ socket.once("connect", () => done("listening"));
453
+ socket.once("error", (error) => done(error.code === "ECONNREFUSED" ? "refused" : "unknown"));
454
+ });
455
+ }
424
456
  async function maybeStartRouter(state, options) {
425
457
  if (options.spawnDetached === false)
426
458
  return false;
@@ -497,6 +529,7 @@ export async function stopRuntimeRotationRouterProcess(router, platform, routerS
497
529
  if (!verified) {
498
530
  return false;
499
531
  }
532
+ options.onOwnershipVerified?.();
500
533
  return stopDetachedProcess(router.pid, platform, options);
501
534
  }
502
535
  async function stopRouter(router, platform, routerScriptPath, options = {}) {
@@ -537,6 +570,13 @@ async function runWindowsTaskkill(pid) {
537
570
  }
538
571
  });
539
572
  }
573
+ // `ps -o lstart=` is locale-sensitive: under a non-English LC_TIME it prints
574
+ // localized day/month names that parsePosixProcessStartTime cannot read, so
575
+ // every POSIX identity probe would fail and unbind would leak live routers.
576
+ const POSIX_IDENTITY_PROBE_ENV = {
577
+ ...process.env,
578
+ LC_ALL: "C",
579
+ };
540
580
  async function runProcessIdentityProbe(command, args, options = {}) {
541
581
  return new Promise((resolve) => {
542
582
  let output = "";
@@ -568,6 +608,7 @@ async function runProcessIdentityProbe(command, args, options = {}) {
568
608
  child = spawn(command, args, {
569
609
  stdio: ["ignore", "pipe", "ignore"],
570
610
  windowsHide: true,
611
+ ...(options.env ? { env: options.env } : {}),
571
612
  });
572
613
  child.stdout?.setEncoding("utf8");
573
614
  child.stdout?.on("data", (chunk) => {
@@ -616,8 +657,8 @@ async function readProcessIdentity(pid, platform, log) {
616
657
  };
617
658
  }
618
659
  const [startedAtRaw, commandLineRaw] = await Promise.all([
619
- runProcessIdentityProbe("ps", ["-p", String(pid), "-o", "lstart="], { log }),
620
- runProcessIdentityProbe("ps", ["-p", String(pid), "-o", "command="], { log }),
660
+ runProcessIdentityProbe("ps", ["-p", String(pid), "-o", "lstart="], { env: POSIX_IDENTITY_PROBE_ENV, log }),
661
+ runProcessIdentityProbe("ps", ["-p", String(pid), "-o", "command="], { env: POSIX_IDENTITY_PROBE_ENV, log }),
621
662
  ]);
622
663
  if (!startedAtRaw || !commandLineRaw) {
623
664
  log?.(`Process identity probe returned no POSIX record for PID ${pid}`);
@@ -960,7 +1001,7 @@ export async function getAppBindStatus(options = {}) {
960
1001
  if (state === null) {
961
1002
  const current = await readConfigIfExists(paths.configPath);
962
1003
  unmanagedBind =
963
- current.existed && configHasRuntimeRotationProvider(current.content);
1004
+ current.existed && (configHasRuntimeRotationProvider(current.content) || hasNativeProviderConfig(current.content));
964
1005
  }
965
1006
  return {
966
1007
  bound: state !== null || unmanagedBind,
@@ -973,12 +1014,42 @@ export async function getAppBindStatus(options = {}) {
973
1014
  }
974
1015
  export async function bindCodexAppRuntimeRotation(options = {}) {
975
1016
  const paths = resolveAppBindPaths(options);
976
- return withAppBindLock(paths.bindDir, () => bindCodexAppRuntimeRotationLocked(options, paths));
1017
+ return withAppBindLock(paths.bindDir, () => withNativeBindingLock(paths.configPath, () => bindCodexAppRuntimeRotationLocked(options, paths)));
977
1018
  }
978
1019
  async function bindCodexAppRuntimeRotationLocked(options, paths) {
979
1020
  const platform = options.platform ?? process.platform;
980
1021
  const now = options.now?.() ?? Date.now();
981
1022
  const existingState = await readAppBindState(paths.statePath);
1023
+ const nativeOpenai = options.nativeOpenai ?? existingState?.nativeOpenai ??
1024
+ ((options.env ?? process.env).CODEX_MULTI_AUTH_NATIVE_OPENAI === "1");
1025
+ const catalogAccount = options.nativeOpenai === false ? undefined : options.catalogAccount ?? existingState?.catalogAccount;
1026
+ if (existingState && (!!existingState.nativeOpenai !== nativeOpenai || JSON.stringify(catalogAccount) !== JSON.stringify(existingState.catalogAccount))) {
1027
+ const router = await readRouterStatus(paths.statusPath);
1028
+ let ownRouter = false;
1029
+ await stopRouter(router, platform, existingState.routerScriptPath, {
1030
+ log: options.log, identityToken: existingState.identityToken, verifyProcessIdentity: options.verifyProcessIdentity,
1031
+ onOwnershipVerified: () => { ownRouter = true; },
1032
+ });
1033
+ // Refuse only when the recorded PID is verifiably our router and it survived
1034
+ // the stop. A recycled PID now owned by another process must not block
1035
+ // mode changes forever (unbind treats the same case as a warning).
1036
+ if (router?.pid && isProcessAlive(router.pid)) {
1037
+ if (ownRouter)
1038
+ throw new Error("Stop the existing app router before changing provider mode");
1039
+ // A failed identity check is not proof: a slow or failing process probe
1040
+ // (common on Windows) also returns false for a live router. Treat the pid
1041
+ // as recycled only when nothing answers at the recorded router address.
1042
+ const address = await probeRouterAddress(router.baseUrl ?? existingState.baseUrl, options.platform ?? process.platform);
1043
+ if (address !== "refused") {
1044
+ throw new Error(`Could not confirm that the app router recorded as pid ${router.pid} has stopped (its address ${address === "listening" ? "still accepts connections" : "could not be checked"}). Stop it before changing provider mode.`);
1045
+ }
1046
+ options.log?.(`Warning: recorded router pid ${router.pid} is not the app router; continuing`);
1047
+ // The record describes a router that no longer exists. Drop it so
1048
+ // maybeStartRouter starts a replacement instead of trusting the live
1049
+ // (recycled) pid and leaving the new mode pointed at a dead address.
1050
+ await unlinkIfExists(paths.statusPath);
1051
+ }
1052
+ }
982
1053
  const host = existingState?.host ?? "127.0.0.1";
983
1054
  let port = existingState && existingState.port > 0 ? existingState.port : 0;
984
1055
  let baseUrl = existingState?.baseUrl ?? formatBaseUrl(host, port);
@@ -993,8 +1064,13 @@ async function bindCodexAppRuntimeRotationLocked(options, paths) {
993
1064
  content,
994
1065
  createdAt: now,
995
1066
  };
996
- let boundConfig = rewriteConfigTomlForAppBind(content, baseUrl, clientApiKey);
1067
+ const bindingContent = nativeOpenai
1068
+ ? restoreConfigTomlFromAppBind(content, backup.content)
1069
+ : restoreNativeProviderConfig(content, backup.content);
1070
+ let boundConfig = rewriteConfigTomlForAppBind(bindingContent, baseUrl, clientApiKey, nativeOpenai);
997
1071
  let state = {
1072
+ nativeOpenai,
1073
+ catalogAccount,
998
1074
  version: 1,
999
1075
  platform,
1000
1076
  host,
@@ -1056,7 +1132,7 @@ async function bindCodexAppRuntimeRotationLocked(options, paths) {
1056
1132
  }
1057
1133
  throw new Error("Codex app bind could not resolve a runtime router port; refusing to write config.toml with port=0.");
1058
1134
  }
1059
- boundConfig = rewriteConfigTomlForAppBind(content, baseUrl, clientApiKey);
1135
+ boundConfig = rewriteConfigTomlForAppBind(bindingContent, baseUrl, clientApiKey, nativeOpenai);
1060
1136
  state = {
1061
1137
  ...state,
1062
1138
  port,
@@ -1078,7 +1154,7 @@ async function bindCodexAppRuntimeRotationLocked(options, paths) {
1078
1154
  }
1079
1155
  export async function unbindCodexAppRuntimeRotation(options = {}) {
1080
1156
  const paths = resolveAppBindPaths(options);
1081
- return withAppBindLock(paths.bindDir, () => unbindCodexAppRuntimeRotationLocked(options, paths));
1157
+ return withAppBindLock(paths.bindDir, () => withNativeBindingLock(paths.configPath, () => unbindCodexAppRuntimeRotationLocked(options, paths)));
1082
1158
  }
1083
1159
  async function unbindCodexAppRuntimeRotationLocked(options, paths) {
1084
1160
  const state = await readAppBindState(paths.statePath);
@@ -1262,8 +1338,8 @@ async function unbindCodexAppRuntimeRotationLocked(options, paths) {
1262
1338
  // above can't see this, so consult the config directly and self-heal it
1263
1339
  // back to a working provider when it is bound.
1264
1340
  const current = await readConfigIfExists(paths.configPath);
1265
- if (current.existed && configHasRuntimeRotationProvider(current.content)) {
1266
- await atomicWriteFile(paths.configPath, restoreConfigTomlFromRuntimeRotationProviderWithoutBackup(current.content));
1341
+ if (current.existed && (configHasRuntimeRotationProvider(current.content) || hasNativeProviderConfig(current.content))) {
1342
+ await atomicWriteFile(paths.configPath, restoreConfigTomlFromRuntimeRotationProviderWithoutBackup(restoreNativeProviderConfig(current.content, "")));
1267
1343
  selfHealed = true;
1268
1344
  }
1269
1345
  }
@@ -1328,6 +1404,8 @@ export function formatAppBindStatus(status) {
1328
1404
  else if (status.router?.lastAccountIndex !== null && status.router?.lastAccountIndex !== undefined) {
1329
1405
  parts.push(`lastAccount=Account ${status.router.lastAccountIndex + 1}`);
1330
1406
  }
1407
+ if (status.state.nativeOpenai)
1408
+ return `Codex app bind: ${parts.join(", ")}, native OpenAI, live account catalogs`;
1331
1409
  return [
1332
1410
  `Codex app bind: ${parts.join(", ")}`,
1333
1411
  [