codex-multi-auth 2.16.0 → 2.17.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 (256) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/README.md +22 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +8 -3
  5. package/dist/lib/account-policy.d.ts +4 -0
  6. package/dist/lib/account-policy.d.ts.map +1 -1
  7. package/dist/lib/account-policy.js +8 -0
  8. package/dist/lib/accounts/rate-limits.d.ts.map +1 -1
  9. package/dist/lib/accounts/rate-limits.js +2 -0
  10. package/dist/lib/accounts.d.ts +21 -3
  11. package/dist/lib/accounts.d.ts.map +1 -1
  12. package/dist/lib/accounts.js +229 -17
  13. package/dist/lib/api-route-store.d.ts +24 -0
  14. package/dist/lib/api-route-store.d.ts.map +1 -0
  15. package/dist/lib/api-route-store.js +145 -0
  16. package/dist/lib/auth/account-access.d.ts +115 -0
  17. package/dist/lib/auth/account-access.d.ts.map +1 -0
  18. package/dist/lib/auth/account-access.js +214 -0
  19. package/dist/lib/auth/token-utils.d.ts +54 -1
  20. package/dist/lib/auth/token-utils.d.ts.map +1 -1
  21. package/dist/lib/auth/token-utils.js +92 -7
  22. package/dist/lib/cli.d.ts +1 -1
  23. package/dist/lib/cli.d.ts.map +1 -1
  24. package/dist/lib/cli.js +4 -0
  25. package/dist/lib/codex-cli/state.d.ts +7 -0
  26. package/dist/lib/codex-cli/state.d.ts.map +1 -1
  27. package/dist/lib/codex-cli/state.js +18 -0
  28. package/dist/lib/codex-cli/writer.d.ts.map +1 -1
  29. package/dist/lib/codex-cli/writer.js +60 -8
  30. package/dist/lib/codex-manager/account-manager-commands.d.ts.map +1 -1
  31. package/dist/lib/codex-manager/account-manager-commands.js +1 -0
  32. package/dist/lib/codex-manager/account-pool-write.d.ts +3 -1
  33. package/dist/lib/codex-manager/account-pool-write.d.ts.map +1 -1
  34. package/dist/lib/codex-manager/account-pool-write.js +4 -0
  35. package/dist/lib/codex-manager/api-login-menu.d.ts +14 -0
  36. package/dist/lib/codex-manager/api-login-menu.d.ts.map +1 -0
  37. package/dist/lib/codex-manager/api-login-menu.js +224 -0
  38. package/dist/lib/codex-manager/commands/account.d.ts.map +1 -1
  39. package/dist/lib/codex-manager/commands/account.js +27 -0
  40. package/dist/lib/codex-manager/commands/best.d.ts.map +1 -1
  41. package/dist/lib/codex-manager/commands/best.js +6 -2
  42. package/dist/lib/codex-manager/commands/check.d.ts +7 -1
  43. package/dist/lib/codex-manager/commands/check.d.ts.map +1 -1
  44. package/dist/lib/codex-manager/commands/check.js +24 -3
  45. package/dist/lib/codex-manager/commands/forecast.d.ts.map +1 -1
  46. package/dist/lib/codex-manager/commands/forecast.js +5 -1
  47. package/dist/lib/codex-manager/commands/report.d.ts.map +1 -1
  48. package/dist/lib/codex-manager/commands/report.js +5 -1
  49. package/dist/lib/codex-manager/commands/resets.d.ts +2 -0
  50. package/dist/lib/codex-manager/commands/resets.d.ts.map +1 -0
  51. package/dist/lib/codex-manager/commands/resets.js +67 -0
  52. package/dist/lib/codex-manager/commands/rotation.d.ts +2 -2
  53. package/dist/lib/codex-manager/commands/rotation.d.ts.map +1 -1
  54. package/dist/lib/codex-manager/commands/rotation.js +63 -12
  55. package/dist/lib/codex-manager/commands/status.d.ts +12 -1
  56. package/dist/lib/codex-manager/commands/status.d.ts.map +1 -1
  57. package/dist/lib/codex-manager/commands/status.js +166 -22
  58. package/dist/lib/codex-manager/commands/uninstall.js +1 -1
  59. package/dist/lib/codex-manager/formatters/quota-formatters.d.ts.map +1 -1
  60. package/dist/lib/codex-manager/formatters/quota-formatters.js +3 -2
  61. package/dist/lib/codex-manager/health-check.d.ts +3 -0
  62. package/dist/lib/codex-manager/health-check.d.ts.map +1 -1
  63. package/dist/lib/codex-manager/health-check.js +56 -6
  64. package/dist/lib/codex-manager/help.d.ts.map +1 -1
  65. package/dist/lib/codex-manager/help.js +4 -2
  66. package/dist/lib/codex-manager/login-flow.d.ts.map +1 -1
  67. package/dist/lib/codex-manager/login-flow.js +69 -7
  68. package/dist/lib/codex-manager/login-menu-data.d.ts.map +1 -1
  69. package/dist/lib/codex-manager/login-menu-data.js +14 -5
  70. package/dist/lib/codex-manager/login-oauth.d.ts +3 -0
  71. package/dist/lib/codex-manager/login-oauth.d.ts.map +1 -1
  72. package/dist/lib/codex-manager/login-oauth.js +6 -1
  73. package/dist/lib/codex-manager/login-workspace-choice.d.ts +11 -0
  74. package/dist/lib/codex-manager/login-workspace-choice.d.ts.map +1 -0
  75. package/dist/lib/codex-manager/login-workspace-choice.js +37 -0
  76. package/dist/lib/codex-manager/persist-selected-account.d.ts.map +1 -1
  77. package/dist/lib/codex-manager/persist-selected-account.js +2 -1
  78. package/dist/lib/codex-manager/repair-commands.d.ts +13 -0
  79. package/dist/lib/codex-manager/repair-commands.d.ts.map +1 -1
  80. package/dist/lib/codex-manager/repair-commands.js +143 -8
  81. package/dist/lib/codex-manager.d.ts.map +1 -1
  82. package/dist/lib/codex-manager.js +25 -6
  83. package/dist/lib/concurrency.d.ts +3 -0
  84. package/dist/lib/concurrency.d.ts.map +1 -0
  85. package/dist/lib/concurrency.js +31 -0
  86. package/dist/lib/model-route-policy.d.ts +52 -0
  87. package/dist/lib/model-route-policy.d.ts.map +1 -0
  88. package/dist/lib/model-route-policy.js +159 -0
  89. package/dist/lib/policy/runtime-policy.d.ts +1 -0
  90. package/dist/lib/policy/runtime-policy.d.ts.map +1 -1
  91. package/dist/lib/policy/runtime-policy.js +3 -0
  92. package/dist/lib/preemptive-quota-scheduler.d.ts +2 -0
  93. package/dist/lib/preemptive-quota-scheduler.d.ts.map +1 -1
  94. package/dist/lib/preemptive-quota-scheduler.js +4 -0
  95. package/dist/lib/quota-probe.d.ts +5 -0
  96. package/dist/lib/quota-probe.d.ts.map +1 -1
  97. package/dist/lib/quota-probe.js +26 -2
  98. package/dist/lib/request/client-cancellation.d.ts +5 -0
  99. package/dist/lib/request/client-cancellation.d.ts.map +1 -0
  100. package/dist/lib/request/client-cancellation.js +7 -0
  101. package/dist/lib/request/response-outcome.d.ts +25 -0
  102. package/dist/lib/request/response-outcome.d.ts.map +1 -0
  103. package/dist/lib/request/response-outcome.js +49 -0
  104. package/dist/lib/request/stream-failover-runtime.d.ts +2 -1
  105. package/dist/lib/request/stream-failover-runtime.d.ts.map +1 -1
  106. package/dist/lib/request/stream-failover-runtime.js +29 -10
  107. package/dist/lib/runtime/account-check.d.ts.map +1 -1
  108. package/dist/lib/runtime/account-check.js +2 -8
  109. package/dist/lib/runtime/account-model-catalog.d.ts +64 -0
  110. package/dist/lib/runtime/account-model-catalog.d.ts.map +1 -0
  111. package/dist/lib/runtime/account-model-catalog.js +154 -0
  112. package/dist/lib/runtime/account-pool.d.ts +3 -1
  113. package/dist/lib/runtime/account-pool.d.ts.map +1 -1
  114. package/dist/lib/runtime/account-pool.js +4 -0
  115. package/dist/lib/runtime/account-reset-credits.d.ts +42 -0
  116. package/dist/lib/runtime/account-reset-credits.d.ts.map +1 -0
  117. package/dist/lib/runtime/account-reset-credits.js +82 -0
  118. package/dist/lib/runtime/api-model-capabilities.d.ts +30 -0
  119. package/dist/lib/runtime/api-model-capabilities.d.ts.map +1 -0
  120. package/dist/lib/runtime/api-model-capabilities.js +434 -0
  121. package/dist/lib/runtime/api-model-runtime.d.ts +42 -0
  122. package/dist/lib/runtime/api-model-runtime.d.ts.map +1 -0
  123. package/dist/lib/runtime/api-model-runtime.js +358 -0
  124. package/dist/lib/runtime/app-bind.d.ts +19 -1
  125. package/dist/lib/runtime/app-bind.d.ts.map +1 -1
  126. package/dist/lib/runtime/app-bind.js +79 -9
  127. package/dist/lib/runtime/automatic-account-checks.d.ts +31 -0
  128. package/dist/lib/runtime/automatic-account-checks.d.ts.map +1 -0
  129. package/dist/lib/runtime/automatic-account-checks.js +113 -0
  130. package/dist/lib/runtime/automatic-subscription-checks.d.ts +7 -0
  131. package/dist/lib/runtime/automatic-subscription-checks.d.ts.map +1 -0
  132. package/dist/lib/runtime/automatic-subscription-checks.js +57 -0
  133. package/dist/lib/runtime/catalog-capabilities.d.ts +8 -0
  134. package/dist/lib/runtime/catalog-capabilities.d.ts.map +1 -0
  135. package/dist/lib/runtime/catalog-capabilities.js +61 -0
  136. package/dist/lib/runtime/inference-activity.d.ts +15 -0
  137. package/dist/lib/runtime/inference-activity.d.ts.map +1 -0
  138. package/dist/lib/runtime/inference-activity.js +95 -0
  139. package/dist/lib/runtime/model-discovery-status.d.ts +100 -0
  140. package/dist/lib/runtime/model-discovery-status.d.ts.map +1 -0
  141. package/dist/lib/runtime/model-discovery-status.js +387 -0
  142. package/dist/lib/runtime/native-account-storage.d.ts +11 -0
  143. package/dist/lib/runtime/native-account-storage.d.ts.map +1 -0
  144. package/dist/lib/runtime/native-account-storage.js +50 -0
  145. package/dist/lib/runtime/native-account-sync.d.ts +19 -0
  146. package/dist/lib/runtime/native-account-sync.d.ts.map +1 -0
  147. package/dist/lib/runtime/native-account-sync.js +62 -0
  148. package/dist/lib/runtime/native-binding-lock.d.ts +3 -0
  149. package/dist/lib/runtime/native-binding-lock.d.ts.map +1 -0
  150. package/dist/lib/runtime/native-binding-lock.js +5 -0
  151. package/dist/lib/runtime/native-client-auth.d.ts +3 -0
  152. package/dist/lib/runtime/native-client-auth.d.ts.map +1 -0
  153. package/dist/lib/runtime/native-client-auth.js +42 -0
  154. package/dist/lib/runtime/native-provider-config.d.ts +4 -0
  155. package/dist/lib/runtime/native-provider-config.d.ts.map +1 -0
  156. package/dist/lib/runtime/native-provider-config.js +52 -0
  157. package/dist/lib/runtime/native-rate-limits.d.ts +18 -0
  158. package/dist/lib/runtime/native-rate-limits.d.ts.map +1 -0
  159. package/dist/lib/runtime/native-rate-limits.js +135 -0
  160. package/dist/lib/runtime/reset-credit-routing.d.ts +41 -0
  161. package/dist/lib/runtime/reset-credit-routing.d.ts.map +1 -0
  162. package/dist/lib/runtime/reset-credit-routing.js +66 -0
  163. package/dist/lib/runtime/reset-credits.d.ts +101 -0
  164. package/dist/lib/runtime/reset-credits.d.ts.map +1 -0
  165. package/dist/lib/runtime/reset-credits.js +162 -0
  166. package/dist/lib/runtime/response-output-history.d.ts +17 -0
  167. package/dist/lib/runtime/response-output-history.d.ts.map +1 -0
  168. package/dist/lib/runtime/response-output-history.js +85 -0
  169. package/dist/lib/runtime/responses-websocket.d.ts +31 -0
  170. package/dist/lib/runtime/responses-websocket.d.ts.map +1 -0
  171. package/dist/lib/runtime/responses-websocket.js +636 -0
  172. package/dist/lib/runtime/resume-picker.d.ts +41 -0
  173. package/dist/lib/runtime/resume-picker.d.ts.map +1 -0
  174. package/dist/lib/runtime/resume-picker.js +169 -0
  175. package/dist/lib/runtime/rotation-account-selection.d.ts +6 -0
  176. package/dist/lib/runtime/rotation-account-selection.d.ts.map +1 -1
  177. package/dist/lib/runtime/rotation-account-selection.js +63 -0
  178. package/dist/lib/runtime/rotation-proxy-state.d.ts +33 -0
  179. package/dist/lib/runtime/rotation-proxy-state.d.ts.map +1 -1
  180. package/dist/lib/runtime/rotation-server-types.d.ts +18 -0
  181. package/dist/lib/runtime/rotation-server-types.d.ts.map +1 -1
  182. package/dist/lib/runtime/runtime-capability-failures.d.ts +14 -0
  183. package/dist/lib/runtime/runtime-capability-failures.d.ts.map +1 -0
  184. package/dist/lib/runtime/runtime-capability-failures.js +76 -0
  185. package/dist/lib/runtime/runtime-observability.d.ts +7 -0
  186. package/dist/lib/runtime/runtime-observability.d.ts.map +1 -1
  187. package/dist/lib/runtime/runtime-observability.js +42 -2
  188. package/dist/lib/runtime/subscription-first-use.d.ts +10 -0
  189. package/dist/lib/runtime/subscription-first-use.d.ts.map +1 -0
  190. package/dist/lib/runtime/subscription-first-use.js +64 -0
  191. package/dist/lib/runtime/subscription-quota-event.d.ts +6 -0
  192. package/dist/lib/runtime/subscription-quota-event.d.ts.map +1 -0
  193. package/dist/lib/runtime/subscription-quota-event.js +31 -0
  194. package/dist/lib/runtime/subscription-quota-order.d.ts +17 -0
  195. package/dist/lib/runtime/subscription-quota-order.d.ts.map +1 -0
  196. package/dist/lib/runtime/subscription-quota-order.js +31 -0
  197. package/dist/lib/runtime/workspace-model-scopes.d.ts +15 -0
  198. package/dist/lib/runtime/workspace-model-scopes.d.ts.map +1 -0
  199. package/dist/lib/runtime/workspace-model-scopes.js +34 -0
  200. package/dist/lib/runtime-constants.d.ts +3 -0
  201. package/dist/lib/runtime-constants.d.ts.map +1 -1
  202. package/dist/lib/runtime-constants.js +5 -0
  203. package/dist/lib/runtime-paths.d.ts.map +1 -1
  204. package/dist/lib/runtime-paths.js +4 -1
  205. package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
  206. package/dist/lib/runtime-rotation-proxy.js +852 -42
  207. package/dist/lib/schemas.d.ts +20 -0
  208. package/dist/lib/schemas.d.ts.map +1 -1
  209. package/dist/lib/schemas.js +3 -0
  210. package/dist/lib/storage/credential-sidecars.d.ts +13 -0
  211. package/dist/lib/storage/credential-sidecars.d.ts.map +1 -0
  212. package/dist/lib/storage/credential-sidecars.js +52 -0
  213. package/dist/lib/storage/file-lock.d.ts +9 -0
  214. package/dist/lib/storage/file-lock.d.ts.map +1 -0
  215. package/dist/lib/storage/file-lock.js +186 -0
  216. package/dist/lib/storage/flagged-storage.d.ts.map +1 -1
  217. package/dist/lib/storage/flagged-storage.js +12 -0
  218. package/dist/lib/storage/path-state.d.ts +2 -0
  219. package/dist/lib/storage/path-state.d.ts.map +1 -1
  220. package/dist/lib/storage/path-state.js +9 -0
  221. package/dist/lib/storage/paths.js +4 -4
  222. package/dist/lib/storage/pending-auth.d.ts +17 -0
  223. package/dist/lib/storage/pending-auth.d.ts.map +1 -0
  224. package/dist/lib/storage/pending-auth.js +153 -0
  225. package/dist/lib/storage/public-types.d.ts +13 -0
  226. package/dist/lib/storage/public-types.d.ts.map +1 -1
  227. package/dist/lib/storage/snapshot-merge.d.ts +16 -0
  228. package/dist/lib/storage/snapshot-merge.d.ts.map +1 -0
  229. package/dist/lib/storage/snapshot-merge.js +206 -0
  230. package/dist/lib/storage/transactions.d.ts.map +1 -1
  231. package/dist/lib/storage/transactions.js +5 -4
  232. package/dist/lib/storage.d.ts +6 -0
  233. package/dist/lib/storage.d.ts.map +1 -1
  234. package/dist/lib/storage.js +77 -7
  235. package/dist/lib/ui/auth-menu-builder.d.ts +2 -0
  236. package/dist/lib/ui/auth-menu-builder.d.ts.map +1 -1
  237. package/dist/lib/ui/auth-menu-builder.js +1 -0
  238. package/dist/lib/ui/auth-menu.d.ts.map +1 -1
  239. package/dist/lib/ui/auth-menu.js +1 -0
  240. package/dist/lib/ui/check-progress.d.ts +8 -0
  241. package/dist/lib/ui/check-progress.d.ts.map +1 -0
  242. package/dist/lib/ui/check-progress.js +30 -0
  243. package/dist/lib/ui/format.d.ts +3 -1
  244. package/dist/lib/ui/format.d.ts.map +1 -1
  245. package/dist/lib/ui/format.js +8 -0
  246. package/dist/lib/ui/ui-copy.d.ts +2 -2
  247. package/dist/lib/ui/ui-copy.js +2 -2
  248. package/dist/lib/usage/pricing.js +2 -2
  249. package/dist/lib/usage/usage-extraction.d.ts +3 -1
  250. package/dist/lib/usage/usage-extraction.d.ts.map +1 -1
  251. package/dist/lib/usage/usage-extraction.js +46 -9
  252. package/package.json +7 -4
  253. package/scripts/codex-app-router.js +7 -0
  254. package/scripts/codex-bin-resolver.js +40 -8
  255. package/scripts/codex-routing.js +1 -0
  256. package/scripts/codex.js +100 -22
@@ -0,0 +1,42 @@
1
+ import type { ApiRouteCredential } from "../api-route-store.js";
2
+ import { type RouteCatalog, type RouteModel, modelEntitlements } from "../model-route-policy.js";
3
+ /** Endpoint-family exclusions, not a fixed allowlist of text model releases.
4
+ * Model ID discovery alone does not prove Responses/tool compatibility. */
5
+ export declare function isNonResponsesModel(id: string): boolean;
6
+ /** Conservative native-picker metadata: discovery proves an ID, not optional tool capabilities. */
7
+ export declare function apiPickerModel(id: string): RouteModel;
8
+ export declare function boundedJson(response: Response, max?: number): Promise<unknown>;
9
+ export interface ApiCatalogStatus {
10
+ id: string;
11
+ kind: "api" | "zdr";
12
+ label: string;
13
+ priority: number;
14
+ checkedAt: number;
15
+ error: boolean;
16
+ availableModels: string[];
17
+ visibleModels: string[];
18
+ entitlements: ReturnType<typeof modelEntitlements>[];
19
+ }
20
+ export declare class ApiModelRuntime {
21
+ private readonly fetchImpl;
22
+ private readonly now;
23
+ private readonly updateDiscovery?;
24
+ private readonly enrichModels?;
25
+ private readonly onCatalogUpdated?;
26
+ private readonly discoveryUpdates;
27
+ lastPersistenceError: string | null;
28
+ private readonly failures;
29
+ private readonly visibility;
30
+ private readonly snapshots;
31
+ private activeKeys;
32
+ cachedCatalogs(): RouteCatalog[];
33
+ private readonly cache;
34
+ constructor(fetchImpl?: typeof fetch, now?: () => number, updateDiscovery?: ((route: ApiRouteCredential, models: string[]) => Promise<ApiRouteCredential>) | undefined, enrichModels?: ((models: RouteModel[], refresh: boolean, route: ApiRouteCredential, forceProbes: boolean) => Promise<RouteModel[]>) | undefined, onCatalogUpdated?: (() => void) | undefined);
35
+ private key;
36
+ catalogs(routes: ApiRouteCredential[], refresh?: boolean, forceProbes?: boolean, waitForCapabilities?: boolean, waitForPersistence?: boolean, requestKind?: "oauth" | "api" | "zdr"): Promise<RouteCatalog[]>;
37
+ statuses(routes: ApiRouteCredential[]): ApiCatalogStatus[];
38
+ request(alias: string, body: Record<string, unknown>, routes: ApiRouteCredential[], signal?: AbortSignal, onAttempt?: (credentialIndex: number) => void): Promise<Response>;
39
+ recordStreamFailure(credential: ApiRouteCredential, alias: string, body: Record<string, unknown>, error: unknown): void;
40
+ private failure;
41
+ }
42
+ //# sourceMappingURL=api-model-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-model-runtime.d.ts","sourceRoot":"","sources":["../../../lib/runtime/api-model-runtime.ts"],"names":[],"mappings":"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"}
@@ -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 = {}) {
@@ -960,7 +993,7 @@ export async function getAppBindStatus(options = {}) {
960
993
  if (state === null) {
961
994
  const current = await readConfigIfExists(paths.configPath);
962
995
  unmanagedBind =
963
- current.existed && configHasRuntimeRotationProvider(current.content);
996
+ current.existed && (configHasRuntimeRotationProvider(current.content) || hasNativeProviderConfig(current.content));
964
997
  }
965
998
  return {
966
999
  bound: state !== null || unmanagedBind,
@@ -973,12 +1006,42 @@ export async function getAppBindStatus(options = {}) {
973
1006
  }
974
1007
  export async function bindCodexAppRuntimeRotation(options = {}) {
975
1008
  const paths = resolveAppBindPaths(options);
976
- return withAppBindLock(paths.bindDir, () => bindCodexAppRuntimeRotationLocked(options, paths));
1009
+ return withAppBindLock(paths.bindDir, () => withNativeBindingLock(paths.configPath, () => bindCodexAppRuntimeRotationLocked(options, paths)));
977
1010
  }
978
1011
  async function bindCodexAppRuntimeRotationLocked(options, paths) {
979
1012
  const platform = options.platform ?? process.platform;
980
1013
  const now = options.now?.() ?? Date.now();
981
1014
  const existingState = await readAppBindState(paths.statePath);
1015
+ const nativeOpenai = options.nativeOpenai ?? existingState?.nativeOpenai ??
1016
+ ((options.env ?? process.env).CODEX_MULTI_AUTH_NATIVE_OPENAI === "1");
1017
+ const catalogAccount = options.nativeOpenai === false ? undefined : options.catalogAccount ?? existingState?.catalogAccount;
1018
+ if (existingState && (!!existingState.nativeOpenai !== nativeOpenai || JSON.stringify(catalogAccount) !== JSON.stringify(existingState.catalogAccount))) {
1019
+ const router = await readRouterStatus(paths.statusPath);
1020
+ let ownRouter = false;
1021
+ await stopRouter(router, platform, existingState.routerScriptPath, {
1022
+ log: options.log, identityToken: existingState.identityToken, verifyProcessIdentity: options.verifyProcessIdentity,
1023
+ onOwnershipVerified: () => { ownRouter = true; },
1024
+ });
1025
+ // Refuse only when the recorded PID is verifiably our router and it survived
1026
+ // the stop. A recycled PID now owned by another process must not block
1027
+ // mode changes forever (unbind treats the same case as a warning).
1028
+ if (router?.pid && isProcessAlive(router.pid)) {
1029
+ if (ownRouter)
1030
+ throw new Error("Stop the existing app router before changing provider mode");
1031
+ // A failed identity check is not proof: a slow or failing process probe
1032
+ // (common on Windows) also returns false for a live router. Treat the pid
1033
+ // as recycled only when nothing answers at the recorded router address.
1034
+ const address = await probeRouterAddress(router.baseUrl ?? existingState.baseUrl, options.platform ?? process.platform);
1035
+ if (address !== "refused") {
1036
+ 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.`);
1037
+ }
1038
+ options.log?.(`Warning: recorded router pid ${router.pid} is not the app router; continuing`);
1039
+ // The record describes a router that no longer exists. Drop it so
1040
+ // maybeStartRouter starts a replacement instead of trusting the live
1041
+ // (recycled) pid and leaving the new mode pointed at a dead address.
1042
+ await unlinkIfExists(paths.statusPath);
1043
+ }
1044
+ }
982
1045
  const host = existingState?.host ?? "127.0.0.1";
983
1046
  let port = existingState && existingState.port > 0 ? existingState.port : 0;
984
1047
  let baseUrl = existingState?.baseUrl ?? formatBaseUrl(host, port);
@@ -993,8 +1056,13 @@ async function bindCodexAppRuntimeRotationLocked(options, paths) {
993
1056
  content,
994
1057
  createdAt: now,
995
1058
  };
996
- let boundConfig = rewriteConfigTomlForAppBind(content, baseUrl, clientApiKey);
1059
+ const bindingContent = nativeOpenai
1060
+ ? restoreConfigTomlFromAppBind(content, backup.content)
1061
+ : restoreNativeProviderConfig(content, backup.content);
1062
+ let boundConfig = rewriteConfigTomlForAppBind(bindingContent, baseUrl, clientApiKey, nativeOpenai);
997
1063
  let state = {
1064
+ nativeOpenai,
1065
+ catalogAccount,
998
1066
  version: 1,
999
1067
  platform,
1000
1068
  host,
@@ -1056,7 +1124,7 @@ async function bindCodexAppRuntimeRotationLocked(options, paths) {
1056
1124
  }
1057
1125
  throw new Error("Codex app bind could not resolve a runtime router port; refusing to write config.toml with port=0.");
1058
1126
  }
1059
- boundConfig = rewriteConfigTomlForAppBind(content, baseUrl, clientApiKey);
1127
+ boundConfig = rewriteConfigTomlForAppBind(bindingContent, baseUrl, clientApiKey, nativeOpenai);
1060
1128
  state = {
1061
1129
  ...state,
1062
1130
  port,
@@ -1078,7 +1146,7 @@ async function bindCodexAppRuntimeRotationLocked(options, paths) {
1078
1146
  }
1079
1147
  export async function unbindCodexAppRuntimeRotation(options = {}) {
1080
1148
  const paths = resolveAppBindPaths(options);
1081
- return withAppBindLock(paths.bindDir, () => unbindCodexAppRuntimeRotationLocked(options, paths));
1149
+ return withAppBindLock(paths.bindDir, () => withNativeBindingLock(paths.configPath, () => unbindCodexAppRuntimeRotationLocked(options, paths)));
1082
1150
  }
1083
1151
  async function unbindCodexAppRuntimeRotationLocked(options, paths) {
1084
1152
  const state = await readAppBindState(paths.statePath);
@@ -1262,8 +1330,8 @@ async function unbindCodexAppRuntimeRotationLocked(options, paths) {
1262
1330
  // above can't see this, so consult the config directly and self-heal it
1263
1331
  // back to a working provider when it is bound.
1264
1332
  const current = await readConfigIfExists(paths.configPath);
1265
- if (current.existed && configHasRuntimeRotationProvider(current.content)) {
1266
- await atomicWriteFile(paths.configPath, restoreConfigTomlFromRuntimeRotationProviderWithoutBackup(current.content));
1333
+ if (current.existed && (configHasRuntimeRotationProvider(current.content) || hasNativeProviderConfig(current.content))) {
1334
+ await atomicWriteFile(paths.configPath, restoreConfigTomlFromRuntimeRotationProviderWithoutBackup(restoreNativeProviderConfig(current.content, "")));
1267
1335
  selfHealed = true;
1268
1336
  }
1269
1337
  }
@@ -1328,6 +1396,8 @@ export function formatAppBindStatus(status) {
1328
1396
  else if (status.router?.lastAccountIndex !== null && status.router?.lastAccountIndex !== undefined) {
1329
1397
  parts.push(`lastAccount=Account ${status.router.lastAccountIndex + 1}`);
1330
1398
  }
1399
+ if (status.state.nativeOpenai)
1400
+ return `Codex app bind: ${parts.join(", ")}, native OpenAI, live account catalogs`;
1331
1401
  return [
1332
1402
  `Codex app bind: ${parts.join(", ")}`,
1333
1403
  [