codex-multi-auth 2.13.0 → 2.15.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-multi-auth",
3
- "version": "2.13.0",
3
+ "version": "2.15.0",
4
4
  "description": "Install and operate codex-multi-auth for the official @openai/codex CLI with multi-account OAuth rotation, switching, health checks, and recovery tools.",
5
5
  "interface": {
6
6
  "composerIcon": "./assets/codex-multi-auth-icon.svg"
package/README.md CHANGED
@@ -29,6 +29,8 @@ Use it when you need a local Codex CLI multi-account workflow with visible accou
29
29
  - Local usage ledger, budget guards, account policy controls, routing profiles, and model/account capability views
30
30
  - Runtime counters, budget/cooldown state, and multi-auth probe visibility in `codex-multi-auth status` / `codex-multi-auth report`
31
31
  - Default-on loopback Responses proxy for live account rotation inside forwarded Codex CLI/app sessions
32
+ - [Image generation/edit transport](docs/reference/image-routes.md) through the same authenticated runtime proxy
33
+ - [Built-in image_gen provider compatibility](docs/reference/imagegen-provider-compatibility.md) through the same authenticated runtime proxy
32
34
  - Optional loopback-only local bridge for `/health`, `/v1/models`, and `/v1/responses`, protected by hashed local client tokens
33
35
  - Reversible packaged Codex app bind and user-level launcher routing helpers that do not patch official app binaries
34
36
  - Session affinity, live account sync, proactive refresh, and preemptive quota deferral controls
@@ -298,6 +300,7 @@ Selected runtime/environment overrides:
298
300
  | `CODEX_MULTI_AUTH_BYPASS=1` | Skip multi-auth intercept and forward straight to official Codex |
299
301
  | `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0/1` | Opt out/in of live Responses proxy rotation for forwarded Codex CLI/app sessions |
300
302
  | `CODEX_MULTI_AUTH_RUNTIME_PROXY_UPSTREAM_BASE_URL=http://127.0.0.1:<port>/<path>` | Route the runtime rotation proxy through an explicit loopback HTTP upstream; requires an explicit port and a numeric loopback host (`127.0.0.0/8` or `[::1]`), and rejects credentials, query strings, fragments, HTTPS, and every other host |
303
+ | `CODEX_MULTI_AUTH_MODEL_CAPACITY_RETRY_MS=<ms>` | Wall-clock ceiling on how long one request waits out a "selected model is at capacity" response before giving up (default 600000, max 3600000, `0` disables) |
301
304
  | `CODEX_MULTI_AUTH_APP_ROTATION_IDLE_MS=<ms>` | Override automatic Codex app helper idle shutdown |
302
305
  | `CODEX_MULTI_AUTH_APP_BIND_INSTALL=0/1` | Opt out/in of packaged Codex app bind self-heal on first CLI run or rotation enable |
303
306
  | `CODEX_MULTI_AUTH_APP_LAUNCHER_INSTALL=0/1` | Opt out/in of routing supported app shortcuts on first CLI run or rotation enable |
@@ -322,6 +325,8 @@ Runtime rotation is enabled by default for request-bearing wrapper-launched Code
322
325
 
323
326
  Advanced local proxy chains can set `CODEX_MULTI_AUTH_RUNTIME_PROXY_UPSTREAM_BASE_URL` for one wrapper process. The wrapper passes that URL to both the shadow-runtime and interactive-helper rotation paths and fails closed if the configured upstream is invalid or unavailable; it never falls back silently to the direct backend while an explicit upstream is required. Because the request that reaches the upstream carries the managed OAuth bearer token, the host must be a numeric loopback literal: a name such as `localhost` is rejected, since a hosts-file entry can point it at a routable address. "Fails closed" also covers the case where runtime rotation itself is off (`CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0`, `CODEX_MULTI_AUTH_BYPASS=1`, or the setting disabled): a request-bearing invocation exits non-zero rather than bypassing the configured upstream. Subcommands that make no backend requests still run normally.
324
327
 
328
+ When the backend answers that the selected model is at capacity, the runtime proxy waits and re-sends the request instead of burning the pool. Capacity is a property of the model, not of an account, so rotating spends the transient budget against accounts that all fail identically and the request ends as a 503 within seconds. The wait backs off (2s, 5s, 15s, 30s, then 60s), honors an upstream `retry-after` when one is sent, and stops at a wall-clock deadline set by `CODEX_MULTI_AUTH_MODEL_CAPACITY_RETRY_MS` (10 minutes by default, 1 hour maximum). Set that variable to `0` to restore the previous rotate-and-fail behavior. The responding account is left completely unpenalized: its pool token is refunded and it is never marked rate limited or cooled down, so selection runs normally on the next pass and may pick any healthy account. The wait is abandoned if the client disconnects, so no further upstream request is sent for a response nobody is reading. See [#689](https://github.com/ndycode/codex-multi-auth/issues/689).
329
+
325
330
  Trust boundary: the loopback check bounds exposure to the local machine, and nothing more. It does not authenticate the process listening on that port, and plaintext HTTP is what a local inspection proxy needs, so any process that can bind the port you name receives the managed bearer token in cleartext. Set this variable only on a machine where you trust every local process that can bind a loopback port, and only for as long as you need the chain.
326
331
 
327
332
  Installed wrappers may perform a best-effort daily npm version check during normal forwarded Codex startup. When a newer package is detected, the wrapper only prints a manual notice on an interactive TTY or when `CODEX_MULTI_AUTH_DEBUG=1`: `npm install -g codex-multi-auth@latest`. It never runs npm install or update commands for you.
@@ -412,7 +417,7 @@ codex-multi-auth doctor --json
412
417
 
413
418
  ## Release Notes
414
419
 
415
- - Current stable: [docs/releases/v2.13.0.md](docs/releases/v2.13.0.md) — install via `npm i -g codex-multi-auth`
420
+ - Current stable: [docs/releases/v2.15.0.md](docs/releases/v2.15.0.md) — install via `npm i -g codex-multi-auth`
416
421
  - Previous stable: [docs/releases/v2.8.3.md](docs/releases/v2.8.3.md)
417
422
  - Previous stable: [docs/releases/v2.8.2.md](docs/releases/v2.8.2.md)
418
423
  - Previous stable: [docs/releases/v2.7.1.md](docs/releases/v2.7.1.md)
@@ -35,15 +35,22 @@ export declare function resolveUnsupportedCodexFallbackModel(options: ResolveUns
35
35
  */
36
36
  export declare function shouldFallbackToGpt52OnUnsupportedGpt53(requestedModel: string | undefined, errorBody: unknown): boolean;
37
37
  /**
38
- * Detects whether an error code or response body indicates an entitlement/subscription issue for Codex models.
38
+ * Detects an upstream response saying the selected model is temporarily out of
39
+ * capacity, as opposed to the account being rate limited or the server being
40
+ * broken.
39
41
  *
40
- * Entitlement errors signal that the requested feature is not included in the user's plan and should not be treated as rate limits.
41
- * This function is pure and safe to call concurrently; it performs no filesystem access (including on Windows) and does not read or redact tokens — callers must avoid passing sensitive credentials in `code` or `bodyText`.
42
+ * The distinction matters because the two want opposite handling. A rate limit
43
+ * is per account, so rotating to another account clears it. Capacity is a
44
+ * property of the MODEL, so every account in the pool fails identically and
45
+ * rotating just burns the pool's transient budget in a few seconds, ending the
46
+ * request as a 503. The caller waits and re-sends on the same account instead.
47
+ * See issue #689.
42
48
  *
43
- * @param code - The error code string returned by the service
44
- * @param bodyText - The response body text to inspect for entitlement-related phrases
45
- * @returns `true` if the combined `code` or `bodyText` indicates an entitlement/subscription issue, `false` otherwise
49
+ * @param status - Upstream HTTP status.
50
+ * @param bodyText - Upstream error body, already read.
51
+ * @returns `true` when the request should be waited out rather than rotated.
46
52
  */
53
+ export declare function isModelAtCapacityError(status: number, bodyText: string): boolean;
47
54
  export declare function isEntitlementError(code: string, bodyText: string): boolean;
48
55
  /**
49
56
  * Detects whether an error indicates the workspace/account has been disabled or expired.
@@ -1 +1 @@
1
- {"version":3,"file":"error-classification.d.ts","sourceRoot":"","sources":["../../../lib/request/error-classification.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,MAAM,WAAW,gBAAgB;IACzB,aAAa,EAAE,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACvB;AAED,sGAAsG;AACtG,eAAO,MAAM,oCAAoC,6CAA6C,CAAC;AAQ/F,eAAO,MAAM,wCAAwC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAoD7E,CAAC;AAEF,MAAM,WAAW,yBAAyB;IACzC,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,sCAAsC;IACtD,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACnC,+BAA+B,EAAE,OAAO,CAAC;IACzC,iCAAiC,EAAE,OAAO,CAAC;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACvC;AAiDD,wBAAgB,oCAAoC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgBzF;AAED,sGAAsG;AACtG,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAQ3F;AAED,wBAAgB,4BAA4B,CAC3C,SAAS,EAAE,OAAO,GAChB,yBAAyB,CAqD3B;AAED,wBAAgB,oCAAoC,CACnD,OAAO,EAAE,sCAAsC,GAC7C,MAAM,GAAG,SAAS,CAkCpB;AAED;;GAEG;AACH,wBAAgB,uCAAuC,CACtD,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,SAAS,EAAE,OAAO,GAChB,OAAO,CAgBT;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAK1E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAChC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,MAAM,GACrB,OAAO,CAoDT;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,CAmB1E"}
1
+ {"version":3,"file":"error-classification.d.ts","sourceRoot":"","sources":["../../../lib/request/error-classification.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,MAAM,WAAW,gBAAgB;IACzB,aAAa,EAAE,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACvB;AAED,sGAAsG;AACtG,eAAO,MAAM,oCAAoC,6CAA6C,CAAC;AAQ/F,eAAO,MAAM,wCAAwC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAoD7E,CAAC;AAEF,MAAM,WAAW,yBAAyB;IACzC,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,sCAAsC;IACtD,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACnC,+BAA+B,EAAE,OAAO,CAAC;IACzC,iCAAiC,EAAE,OAAO,CAAC;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACvC;AAiDD,wBAAgB,oCAAoC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgBzF;AAED,sGAAsG;AACtG,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAQ3F;AAED,wBAAgB,4BAA4B,CAC3C,SAAS,EAAE,OAAO,GAChB,yBAAyB,CAqD3B;AAED,wBAAgB,oCAAoC,CACnD,OAAO,EAAE,sCAAsC,GAC7C,MAAM,GAAG,SAAS,CAkCpB;AAED;;GAEG;AACH,wBAAgB,uCAAuC,CACtD,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,SAAS,EAAE,OAAO,GAChB,OAAO,CAgBT;AAqCD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CACrC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACd,OAAO,CAIT;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAK1E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAChC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,MAAM,GACrB,OAAO,CAoDT;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,CAmB1E"}
@@ -242,6 +242,51 @@ export function shouldFallbackToGpt52OnUnsupportedGpt53(requestedModel, errorBod
242
242
  * @param bodyText - The response body text to inspect for entitlement-related phrases
243
243
  * @returns `true` if the combined `code` or `bodyText` indicates an entitlement/subscription issue, `false` otherwise
244
244
  */
245
+ /**
246
+ * Statuses that are never a capacity signal, whatever the body says.
247
+ *
248
+ * Auth, payment, entitlement and not-found responses are terminal for this
249
+ * request and are each handled by their own branch upstream of the capacity
250
+ * check. Retrying them would spin until the deadline for no reason.
251
+ */
252
+ const NON_CAPACITY_STATUSES = new Set([401, 402, 403, 404]);
253
+ /**
254
+ * Phrases and codes that mean the model itself is out of capacity right now.
255
+ *
256
+ * This is matched on the body TEXT rather than on a status code, because the
257
+ * status this arrives with is not documented and has been reported differently
258
+ * by different callers. Matching text the way `isEntitlementError` and
259
+ * `isWorkspaceDisabledError` already do means a wrong guess about the status
260
+ * leaves behavior exactly as it is today, rather than breaking a path.
261
+ *
262
+ * `at capacity` is deliberately broad. In an OpenAI error body that phrase does
263
+ * not occur outside capacity pressure, and a false positive costs a bounded
264
+ * wait and one re-send, not a wrong answer.
265
+ */
266
+ const MODEL_AT_CAPACITY_PATTERN = /\bat capacity\b|\bmodel_capacity_exceeded\b|\bcapacity_exceeded\b|\bcurrently overloaded\b|\boverloaded_error\b|\bslow_down\b/i;
267
+ /**
268
+ * Detects an upstream response saying the selected model is temporarily out of
269
+ * capacity, as opposed to the account being rate limited or the server being
270
+ * broken.
271
+ *
272
+ * The distinction matters because the two want opposite handling. A rate limit
273
+ * is per account, so rotating to another account clears it. Capacity is a
274
+ * property of the MODEL, so every account in the pool fails identically and
275
+ * rotating just burns the pool's transient budget in a few seconds, ending the
276
+ * request as a 503. The caller waits and re-sends on the same account instead.
277
+ * See issue #689.
278
+ *
279
+ * @param status - Upstream HTTP status.
280
+ * @param bodyText - Upstream error body, already read.
281
+ * @returns `true` when the request should be waited out rather than rotated.
282
+ */
283
+ export function isModelAtCapacityError(status, bodyText) {
284
+ if (NON_CAPACITY_STATUSES.has(status))
285
+ return false;
286
+ if (!bodyText)
287
+ return false;
288
+ return MODEL_AT_CAPACITY_PATTERN.test(bodyText);
289
+ }
245
290
  export function isEntitlementError(code, bodyText) {
246
291
  const haystack = `${code} ${bodyText}`.toLowerCase();
247
292
  // "usage_not_included" means the subscription doesn't include this feature
@@ -6,7 +6,7 @@ import { type FastSessionInputTrimPlan } from "./request-transformer.js";
6
6
  import type { UserConfig, RequestBody } from "../types.js";
7
7
  import type { UsageTokenCounts } from "../usage/types.js";
8
8
  export { shouldRefreshToken, refreshAndUpdateToken } from "./token-refresh.js";
9
- export { DEFAULT_UNSUPPORTED_CODEX_FALLBACK_CHAIN, extractUnsupportedCodexModelFromText, getUnsupportedCodexModelInfo, resolveUnsupportedCodexFallbackModel, shouldFallbackToGpt52OnUnsupportedGpt53, isEntitlementError, isWorkspaceDisabledError, createEntitlementErrorResponse, } from "./error-classification.js";
9
+ export { DEFAULT_UNSUPPORTED_CODEX_FALLBACK_CHAIN, extractUnsupportedCodexModelFromText, getUnsupportedCodexModelInfo, resolveUnsupportedCodexFallbackModel, shouldFallbackToGpt52OnUnsupportedGpt53, isEntitlementError, isModelAtCapacityError, isWorkspaceDisabledError, createEntitlementErrorResponse, } from "./error-classification.js";
10
10
  export type { EntitlementError, UnsupportedCodexModelInfo, ResolveUnsupportedCodexFallbackOptions, } from "./error-classification.js";
11
11
  export { extractRequestUrl, rewriteUrlForCodex, resolveProxyUrlForRequest, closeSharedProxyDispatchers, applyProxyCompatibleInit, } from "./url-rewriting.js";
12
12
  export type { ProxyCompatibleRequestInit } from "./url-rewriting.js";
@@ -1 +1 @@
1
- {"version":3,"file":"fetch-helpers.d.ts","sourceRoot":"","sources":["../../../lib/request/fetch-helpers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fetch-helpers.d.ts","sourceRoot":"","sources":["../../../lib/request/fetch-helpers.ts"],"names":[],"mappings":"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"}
@@ -11,7 +11,7 @@ import { HTTP_STATUS, ERROR_MESSAGES, LOG_STAGES, MAX_RATE_LIMIT_DELAY_MS, } fro
11
11
  import { CHATGPT_CODEX_UNSUPPORTED_MODEL_CODE, createEntitlementErrorResponse, extractUnsupportedCodexModelFromText, isEntitlementError, isUnsupportedCodexModelForChatGpt, } from "./error-classification.js";
12
12
  import { logDeprecationHeaders } from "./headers.js";
13
13
  export { shouldRefreshToken, refreshAndUpdateToken } from "./token-refresh.js";
14
- export { DEFAULT_UNSUPPORTED_CODEX_FALLBACK_CHAIN, extractUnsupportedCodexModelFromText, getUnsupportedCodexModelInfo, resolveUnsupportedCodexFallbackModel, shouldFallbackToGpt52OnUnsupportedGpt53, isEntitlementError, isWorkspaceDisabledError, createEntitlementErrorResponse, } from "./error-classification.js";
14
+ export { DEFAULT_UNSUPPORTED_CODEX_FALLBACK_CHAIN, extractUnsupportedCodexModelFromText, getUnsupportedCodexModelInfo, resolveUnsupportedCodexFallbackModel, shouldFallbackToGpt52OnUnsupportedGpt53, isEntitlementError, isModelAtCapacityError, isWorkspaceDisabledError, createEntitlementErrorResponse, } from "./error-classification.js";
15
15
  export { extractRequestUrl, rewriteUrlForCodex, resolveProxyUrlForRequest, closeSharedProxyDispatchers, applyProxyCompatibleInit, } from "./url-rewriting.js";
16
16
  export { createCodexHeaders } from "./headers.js";
17
17
  // MAX_RATE_LIMIT_DELAY_MS is centralized in constants.ts (shared with the
@@ -1 +1 @@
1
- {"version":3,"file":"config-toml.d.ts","sourceRoot":"","sources":["../../../lib/runtime/config-toml.ts"],"names":[],"mappings":"AAEA,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAqBvD;AA4FD,wBAAgB,4BAA4B,CAC3C,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,GACpB,MAAM,CAiDR;AAED,wBAAgB,8BAA8B,CAC7C,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,GACpB,MAAM,CAkDR;AA2BD,wBAAgB,2CAA2C,CAC1D,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,YAAY,SAAK,GACf,MAAM,CAkBR;AAED,wBAAgB,4CAA4C,CAC3D,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,GACpB,MAAM,CAYR;AAED;;;;;;;GAOG;AACH,wBAAgB,gCAAgC,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAoB3E;AAED;;;;;;;GAOG;AACH,wBAAgB,yDAAyD,CACxE,aAAa,EAAE,MAAM,EACrB,eAAe,SAAW,GACxB,MAAM,CAkBR"}
1
+ {"version":3,"file":"config-toml.d.ts","sourceRoot":"","sources":["../../../lib/runtime/config-toml.ts"],"names":[],"mappings":"AAEA,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAqBvD;AA4FD,wBAAgB,4BAA4B,CAC3C,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,GACpB,MAAM,CAiDR;AAED,wBAAgB,8BAA8B,CAC7C,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,GACpB,MAAM,CAkDR;AA4BD,wBAAgB,2CAA2C,CAC1D,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,YAAY,SAAK,GACf,MAAM,CAkBR;AAED,wBAAgB,4CAA4C,CAC3D,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,GACpB,MAAM,CAYR;AAED;;;;;;;GAOG;AACH,wBAAgB,gCAAgC,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAoB3E;AAED;;;;;;;GAOG;AACH,wBAAgB,yDAAyD,CACxE,aAAa,EAAE,MAAM,EACrB,eAAe,SAAW,GACxB,MAAM,CAkBR"}
@@ -202,6 +202,7 @@ function createRuntimeRotationProviderBlock(baseUrl, clientApiKey = "") {
202
202
  'name = "codex-multi-auth"',
203
203
  `base_url = ${tomlStringLiteral(baseUrl)}`,
204
204
  "requires_openai_auth = false",
205
+ 'http_headers = { "x-openai-actor-authorization" = "codex-multi-auth-local" }',
205
206
  'wire_api = "responses"',
206
207
  ];
207
208
  if (clientApiKey.trim().length > 0) {
@@ -21,6 +21,12 @@ export interface RotationProxyStateInit {
21
21
  tokenRefreshSkewMs: number;
22
22
  networkErrorCooldownMs: number;
23
23
  serverErrorCooldownMs: number;
24
+ /**
25
+ * Wall-clock ceiling on how long ONE request may wait out a model-capacity
26
+ * response before giving up. `0` restores the pre-#689 behaviour of rotating
27
+ * the pool and returning the exhaustion 503.
28
+ */
29
+ modelCapacityRetryMs: number;
24
30
  tokenInvalidationCooldownMs: number;
25
31
  minRotationIntervalMs: number;
26
32
  pidOffsetEnabled: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"rotation-proxy-state.d.ts","sourceRoot":"","sources":["../../../lib/runtime/rotation-proxy-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAKjF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAC7E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB;IACtC,oBAAoB,EAAE,cAAc,CAAC;IACrC,gBAAgB,EAAE,SAAS,GAAG,QAAQ,CAAC;IACvC,kBAAkB,EAAE,QAAQ,GAAG,YAAY,CAAC;IAC5C,SAAS,EAAE,OAAO,KAAK,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,2BAA2B,EAAE,MAAM,CAAC;IACpC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,yBAAyB,EAAE,MAAM,CAAC;IAClC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,8BAA8B,EAAE,MAAM,CAAC;IACvC,wBAAwB,EAAE,wBAAwB,CAAC;IACnD,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,oBAAoB,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAClD,8BAA8B,EAAE,MAAM,CAAC;IACvC;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAmB,SAAQ,sBAAsB;IACjE,QAAQ,CAAC,oBAAoB,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAC;IAC5C,QAAQ,CAAC,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IACzD,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,yBAAyB,EAAE,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACjE,wBAAwB,EAAE,MAAM,CAAC;CACjC;AAID,gBAAgB;AAChB,wBAAgB,wBAAwB,CACvC,IAAI,EAAE,sBAAsB,GAC1B,kBAAkB,CAuBpB;AAED,gBAAgB;AAChB,wBAAsB,wBAAwB,CAC7C,KAAK,EAAE,kBAAkB,GACvB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAkDhC"}
1
+ {"version":3,"file":"rotation-proxy-state.d.ts","sourceRoot":"","sources":["../../../lib/runtime/rotation-proxy-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAKjF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAC7E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB;IACtC,oBAAoB,EAAE,cAAc,CAAC;IACrC,gBAAgB,EAAE,SAAS,GAAG,QAAQ,CAAC;IACvC,kBAAkB,EAAE,QAAQ,GAAG,YAAY,CAAC;IAC5C,SAAS,EAAE,OAAO,KAAK,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qBAAqB,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAC7B,2BAA2B,EAAE,MAAM,CAAC;IACpC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,yBAAyB,EAAE,MAAM,CAAC;IAClC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,8BAA8B,EAAE,MAAM,CAAC;IACvC,wBAAwB,EAAE,wBAAwB,CAAC;IACnD,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,oBAAoB,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAClD,8BAA8B,EAAE,MAAM,CAAC;IACvC;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAmB,SAAQ,sBAAsB;IACjE,QAAQ,CAAC,oBAAoB,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAC;IAC5C,QAAQ,CAAC,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IACzD,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,yBAAyB,EAAE,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACjE,wBAAwB,EAAE,MAAM,CAAC;CACjC;AAID,gBAAgB;AAChB,wBAAgB,wBAAwB,CACvC,IAAI,EAAE,sBAAsB,GAC1B,kBAAkB,CAuBpB;AAED,gBAAgB;AAChB,wBAAsB,wBAAwB,CAC7C,KAAK,EAAE,kBAAkB,GACvB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAkDhC"}
@@ -48,6 +48,13 @@ export interface RuntimeRotationProxyOptions {
48
48
  * the value survives the launcher -> detached app-helper process boundary.
49
49
  */
50
50
  forcedAccountIndex?: number | null;
51
+ /**
52
+ * Wall-clock ceiling on how long ONE request may wait out a
53
+ * "selected model is at capacity" response before giving up (issue #689).
54
+ * `0` disables the wait entirely. Falls back to
55
+ * `CODEX_MULTI_AUTH_MODEL_CAPACITY_RETRY_MS`, then to a 10 minute default.
56
+ */
57
+ modelCapacityRetryMs?: number;
51
58
  }
52
59
  export interface RequestContext {
53
60
  body: Buffer;
@@ -1 +1 @@
1
- {"version":3,"file":"rotation-server-types.d.ts","sourceRoot":"","sources":["../../../lib/runtime/rotation-server-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,MAAM,WAAW,0BAA0B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,SAAS,EAAE,MAAM,0BAA0B,CAAC;IAC5C;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,MAAM,MAAM,CAAC;CACtC;AAED,MAAM,WAAW,0BAA0B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,2BAA2B;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,8BAA8B,CAAC,EAAE,MAAM,CAAC;IACxC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;;;;;OASG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,MAAM,gBAAgB,GACzB,YAAY,GACZ,cAAc,GACd,eAAe,GACf,cAAc,GACd,QAAQ,GACR,aAAa,GACb,YAAY,CAAC;AAChB,MAAM,MAAM,qBAAqB,GAAG,KAAK,GAAG;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,WAAW,8BAA8B;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CAClB"}
1
+ {"version":3,"file":"rotation-server-types.d.ts","sourceRoot":"","sources":["../../../lib/runtime/rotation-server-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,MAAM,WAAW,0BAA0B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,SAAS,EAAE,MAAM,0BAA0B,CAAC;IAC5C;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,MAAM,MAAM,CAAC;CACtC;AAED,MAAM,WAAW,0BAA0B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,2BAA2B;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,8BAA8B,CAAC,EAAE,MAAM,CAAC;IACxC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;;;;;OASG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,MAAM,gBAAgB,GACzB,YAAY,GACZ,cAAc,GACd,eAAe,GACf,cAAc,GACd,QAAQ,GACR,aAAa,GACb,YAAY,CAAC;AAChB,MAAM,MAAM,qBAAqB,GAAG,KAAK,GAAG;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,WAAW,8BAA8B;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CAClB"}
@@ -26,6 +26,16 @@ export declare function buildQuotaScheduleKey(account: Pick<ManagedAccount, "acc
26
26
  addedAt?: number;
27
27
  recordId?: string;
28
28
  }, family: ModelFamily, model?: string | null): string;
29
+ /**
30
+ * Total time one request may spend waiting out model capacity.
31
+ *
32
+ * `0` disables the behaviour entirely and restores the pre-#689 handling, where
33
+ * a capacity response rotates the pool and 503s. Anything unparseable falls
34
+ * back to the default rather than disabling, so a typo does not silently turn
35
+ * the feature off.
36
+ */
37
+ export declare function normalizeModelCapacityRetryMs(value: unknown): number;
38
+ export declare function resolveModelCapacityRetryMs(env?: NodeJS.ProcessEnv): number;
29
39
  /**
30
40
  * Coerce a forced-account pin from an option (number) or the launcher's env
31
41
  * string into a normalized 0-based index, or null when absent/invalid. Invalid
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-rotation-proxy.d.ts","sourceRoot":"","sources":["../../lib/runtime-rotation-proxy.ts"],"names":[],"mappings":"AAGA,OAAO,EAIN,KAAK,cAAc,EACnB,MAAM,eAAe,CAAC;AAkCvB,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAkDtE,OAAO,KAAK,EAKX,2BAA2B,EAC3B,0BAA0B,EAE1B,MAAM,oCAAoC,CAAC;AAe5C,YAAY,EACX,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,GAC1B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACN,+BAA+B,EAC/B,0BAA0B,GAC1B,MAAM,kCAAkC,CAAC;AAC1C,YAAY,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AACxE,OAAO,EACN,+BAA+B,EAC/B,8BAA8B,EAC9B,uBAAuB,EACvB,uBAAuB,GACvB,MAAM,oCAAoC,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AA8DtE,kFAAkF;AAClF;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAC9C,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,WAAW,GAAG,OAAO,GAAG,cAAc,CAAC,GAAG;IACvE,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB,GACC,MAAM,CAmBR;AAED,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,WAAW,GAAG,OAAO,GAAG,cAAc,CAAC,GAAG;IACvE,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB,EACD,MAAM,EAAE,WAAW,EACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GACnB,MAAM,CAER;AA8gBD;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAC1C,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GACvC,MAAM,GAAG,IAAI,CAUf;AAED,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,2BAA2B,GAClC,OAAO,CAAC,0BAA0B,CAAC,CAmLrC"}
1
+ {"version":3,"file":"runtime-rotation-proxy.d.ts","sourceRoot":"","sources":["../../lib/runtime-rotation-proxy.ts"],"names":[],"mappings":"AAGA,OAAO,EAIN,KAAK,cAAc,EACnB,MAAM,eAAe,CAAC;AAkCvB,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAqDtE,OAAO,KAAK,EAKX,2BAA2B,EAC3B,0BAA0B,EAE1B,MAAM,oCAAoC,CAAC;AAe5C,YAAY,EACX,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,GAC1B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACN,+BAA+B,EAC/B,0BAA0B,GAC1B,MAAM,kCAAkC,CAAC;AAC1C,YAAY,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AACxE,OAAO,EACN,+BAA+B,EAC/B,8BAA8B,EAC9B,uBAAuB,EACvB,uBAAuB,GACvB,MAAM,oCAAoC,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AA8DtE,kFAAkF;AAClF;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAC9C,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,WAAW,GAAG,OAAO,GAAG,cAAc,CAAC,GAAG;IACvE,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB,GACC,MAAM,CAmBR;AAED,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,WAAW,GAAG,OAAO,GAAG,cAAc,CAAC,GAAG;IACvE,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB,EACD,MAAM,EAAE,WAAW,EACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GACnB,MAAM,CAER;AA0ED;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAKpE;AAED,wBAAgB,2BAA2B,CAC1C,GAAG,GAAE,MAAM,CAAC,UAAwB,GAClC,MAAM,CAQR;AA2fD;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAC1C,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GACvC,MAAM,GAAG,IAAI,CAUf;AAED,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,2BAA2B,GAClC,OAAO,CAAC,0BAA0B,CAAC,CA6LrC"}
@@ -9,7 +9,7 @@ import { CURRENT_CODEX_MODEL } from "./request/helpers/model-map.js";
9
9
  import { mutateRuntimeObservabilitySnapshot, recordRuntimeAccountRecovery, recordRuntimePoolExhaustion, } from "./runtime/runtime-observability.js";
10
10
  import { createRuntimeUsageRecorder, evaluateRuntimePolicy, loadRuntimePolicyState, } from "./policy/runtime-policy.js";
11
11
  import { createUsageStreamScanner } from "./usage/usage-extraction.js";
12
- import { isWorkspaceDisabledError } from "./request/fetch-helpers.js";
12
+ import { isModelAtCapacityError, isWorkspaceDisabledError, } from "./request/fetch-helpers.js";
13
13
  import { PreemptiveQuotaScheduler, readQuotaSchedulerSnapshot, } from "./preemptive-quota-scheduler.js";
14
14
  import { ContextBudgetGuard } from "./context-budget-guard.js";
15
15
  import { buildContextBudgetHeaders, createContextBudgetPauseResponse, } from "./context-budget-response.js";
@@ -144,6 +144,58 @@ function pinnedRetryBackoffMs(attempt) {
144
144
  const index = Math.min(Math.max(0, attempt - 1), PINNED_RETRY_BACKOFF_STEPS_MS.length - 1);
145
145
  return PINNED_RETRY_BACKOFF_STEPS_MS[index] ?? 0;
146
146
  }
147
+ /**
148
+ * Backoff schedule for a model-capacity wait (issue #689).
149
+ *
150
+ * Deliberately much longer than `PINNED_RETRY_BACKOFF_STEPS_MS`. That schedule
151
+ * spaces out re-sends of a request whose account might already be fine;
152
+ * capacity pressure is upstream and measured in minutes, so re-sending every
153
+ * 250ms would just be a tight poll against a busy backend. The last step
154
+ * repeats for every attempt past the table.
155
+ */
156
+ const CAPACITY_RETRY_BACKOFF_STEPS_MS = [
157
+ 2_000, 5_000, 15_000, 30_000, 60_000,
158
+ ];
159
+ /**
160
+ * Default wall-clock ceiling on capacity waiting for ONE request.
161
+ *
162
+ * The reporter's case is a long-running task started before stepping away, so
163
+ * the default has to be long enough to outlast a real capacity blip. It is
164
+ * still a hard ceiling: the request ends with the normal pool-exhausted 503
165
+ * rather than hanging forever.
166
+ */
167
+ const DEFAULT_MODEL_CAPACITY_RETRY_MS = 10 * 60_000;
168
+ const MAX_MODEL_CAPACITY_RETRY_MS = 60 * 60_000;
169
+ const MODEL_CAPACITY_RETRY_ENV = "CODEX_MULTI_AUTH_MODEL_CAPACITY_RETRY_MS";
170
+ function capacityRetryBackoffMs(attempt) {
171
+ const index = Math.min(Math.max(0, attempt - 1), CAPACITY_RETRY_BACKOFF_STEPS_MS.length - 1);
172
+ return CAPACITY_RETRY_BACKOFF_STEPS_MS[index] ?? 0;
173
+ }
174
+ /**
175
+ * Total time one request may spend waiting out model capacity.
176
+ *
177
+ * `0` disables the behaviour entirely and restores the pre-#689 handling, where
178
+ * a capacity response rotates the pool and 503s. Anything unparseable falls
179
+ * back to the default rather than disabling, so a typo does not silently turn
180
+ * the feature off.
181
+ */
182
+ export function normalizeModelCapacityRetryMs(value) {
183
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
184
+ return DEFAULT_MODEL_CAPACITY_RETRY_MS;
185
+ }
186
+ return Math.min(Math.floor(value), MAX_MODEL_CAPACITY_RETRY_MS);
187
+ }
188
+ export function resolveModelCapacityRetryMs(env = process.env) {
189
+ const raw = (env[MODEL_CAPACITY_RETRY_ENV] ?? "").trim();
190
+ if (!raw)
191
+ return DEFAULT_MODEL_CAPACITY_RETRY_MS;
192
+ // An explicit 0 is the documented kill switch and must survive normalization,
193
+ // which otherwise treats "not a usable number" as "use the default".
194
+ const parsed = Number(raw);
195
+ if (parsed === 0)
196
+ return 0;
197
+ return normalizeModelCapacityRetryMs(parsed);
198
+ }
147
199
  const MAX_REQUEST_BODY_BYTES = 64 * 1024 * 1024;
148
200
  const MAX_THREAD_GOAL_FALLBACKS = 512;
149
201
  const ALLOWED_RESPONSES_PATHS = new Set([
@@ -156,6 +208,12 @@ const ALLOWED_MODELS_PATHS = new Set([
156
208
  URL_PATHS.MODELS,
157
209
  `/v1${URL_PATHS.MODELS}`,
158
210
  ]);
211
+ const ALLOWED_IMAGE_PATHS = new Set([
212
+ "/images/generations",
213
+ "/images/edits",
214
+ "/v1/images/generations",
215
+ "/v1/images/edits",
216
+ ]);
159
217
  const ALLOWED_THREAD_GOAL_PATHS = new Set([
160
218
  "/thread/goal/get",
161
219
  "/thread/goal/set",
@@ -200,6 +258,8 @@ function createOutboundHeaders(incoming, account, accessToken, accountId) {
200
258
  // header would ride along with the managed OAuth Bearer to OpenAI.
201
259
  headers.delete("cookie");
202
260
  headers.delete("proxy-authorization");
261
+ // Local capability marker for Codex image_gen, never an upstream credential.
262
+ headers.delete("x-openai-actor-authorization");
203
263
  headers.set("authorization", `Bearer ${accessToken}`);
204
264
  headers.set(OPENAI_HEADERS.ACCOUNT_ID, accountId);
205
265
  headers.set(OPENAI_HEADERS.BETA, OPENAI_HEADER_VALUES.BETA_RESPONSES);
@@ -446,6 +506,13 @@ function buildResponsesRequestContext(req, body) {
446
506
  stableSessionKey: resolveStableSessionKey(headers, parsedBody),
447
507
  };
448
508
  }
509
+ function buildImageRequestContext(req, body, pathname) {
510
+ return {
511
+ ...buildResponsesRequestContext(req, body),
512
+ upstreamPath: `/codex${pathname.replace(/^\/v1/, "")}`,
513
+ family: "codex",
514
+ };
515
+ }
449
516
  function buildModelsRequestContext(req) {
450
517
  return {
451
518
  body: Buffer.alloc(0),
@@ -503,7 +570,7 @@ function writeJson(res, status, payload) {
503
570
  function writeMethodOrPathError(res) {
504
571
  writeJson(res, 404, {
505
572
  error: {
506
- message: "Runtime rotation proxy only accepts Responses API, model discovery, and Codex thread goal requests.",
573
+ message: "Runtime rotation proxy only accepts Responses API, images, model discovery, and Codex thread goal requests.",
507
574
  code: "runtime_rotation_proxy_not_found",
508
575
  },
509
576
  });
@@ -608,6 +675,14 @@ export async function startRuntimeRotationProxy(options) {
608
675
  const tokenRefreshSkewMs = getTokenRefreshSkewMs(pluginConfig);
609
676
  const networkErrorCooldownMs = getNetworkErrorCooldownMs(pluginConfig);
610
677
  const serverErrorCooldownMs = getServerErrorCooldownMs(pluginConfig);
678
+ // Normalize the explicit option as well as the env var: a caller passing a
679
+ // negative or multi-hour value would otherwise bypass the documented cap.
680
+ // `0` stays 0, since it is the documented kill switch.
681
+ const modelCapacityRetryMs = options.modelCapacityRetryMs === 0
682
+ ? 0
683
+ : options.modelCapacityRetryMs === undefined
684
+ ? resolveModelCapacityRetryMs()
685
+ : normalizeModelCapacityRetryMs(options.modelCapacityRetryMs);
611
686
  const tokenInvalidationCooldownMs = getTokenInvalidationCooldownMs(pluginConfig);
612
687
  const minRotationIntervalMs = getMinRotationIntervalMs(pluginConfig);
613
688
  const pidOffsetEnabled = getPidOffsetEnabled(pluginConfig);
@@ -654,6 +729,7 @@ export async function startRuntimeRotationProxy(options) {
654
729
  tokenRefreshSkewMs,
655
730
  networkErrorCooldownMs,
656
731
  serverErrorCooldownMs,
732
+ modelCapacityRetryMs,
657
733
  tokenInvalidationCooldownMs,
658
734
  minRotationIntervalMs,
659
735
  pidOffsetEnabled,
@@ -742,21 +818,26 @@ async function handleRequestInner(state, req, res, traceId) {
742
818
  }
743
819
  const isResponsesRequest = req.method === "POST" && isResponsesPath(incomingUrl.pathname);
744
820
  const isModelsRequest = req.method === "GET" && isModelsPath(incomingUrl.pathname);
821
+ const isImageRequest = req.method === "POST" && ALLOWED_IMAGE_PATHS.has(incomingUrl.pathname);
745
822
  const isThreadGoalRequest = (req.method === "GET" || req.method === "POST") &&
746
823
  isThreadGoalPath(incomingUrl.pathname);
747
- if (!isResponsesRequest && !isModelsRequest && !isThreadGoalRequest) {
824
+ if (!isResponsesRequest && !isModelsRequest &&
825
+ !isThreadGoalRequest && !isImageRequest) {
748
826
  writeMethodOrPathError(res);
749
827
  return;
750
828
  }
751
829
  state.status.totalRequests += 1;
752
- const requestBody = isResponsesRequest || (isThreadGoalRequest && req.method === "POST")
830
+ const requestBody = isResponsesRequest || isImageRequest ||
831
+ (isThreadGoalRequest && req.method === "POST")
753
832
  ? await readRequestBody(req, state.maxRequestBodyBytes)
754
833
  : Buffer.alloc(0);
755
834
  const context = isModelsRequest
756
835
  ? buildModelsRequestContext(req)
757
836
  : isThreadGoalRequest
758
837
  ? buildThreadGoalRequestContext(req, requestBody, incomingUrl.pathname)
759
- : buildResponsesRequestContext(req, requestBody);
838
+ : isImageRequest
839
+ ? buildImageRequestContext(req, requestBody, incomingUrl.pathname)
840
+ : buildResponsesRequestContext(req, requestBody);
760
841
  const requestStartedAt = state.now();
761
842
  let policyDecision = null;
762
843
  let projectKey = null;
@@ -787,7 +868,9 @@ async function handleRequestInner(state, req, res, traceId) {
787
868
  ? "models"
788
869
  : isThreadGoalRequest
789
870
  ? "thread-goal"
790
- : "responses",
871
+ : isImageRequest
872
+ ? "images"
873
+ : "responses",
791
874
  model: context.model,
792
875
  projectKey,
793
876
  requestId: traceId,
@@ -863,6 +946,24 @@ async function handleRequestInner(state, req, res, traceId) {
863
946
  let transientAttemptLimit = Math.max(1, Math.min(accountCount, state.maxRuntimeAccountAttempts));
864
947
  let transientAttempts = 0;
865
948
  let transientExhaustionReason = null;
949
+ // Capacity waits are counted separately from `transientAttempts` and from
950
+ // the pinned selection cap. Both of those bound how many DIFFERENT
951
+ // accounts or re-sends a failing request may burn; a capacity wait is not
952
+ // a failure of any account, it is the same request pausing for an
953
+ // upstream that is busy, and it has its own wall-clock ceiling. See #689.
954
+ let capacityRetries = 0;
955
+ // A wall-clock deadline, not a sum of sleeps. The upstream request and
956
+ // the error-body read also consume the budget, so summing only the
957
+ // planned sleeps let a slow capacity response push one request past the
958
+ // documented ceiling. Request-local, so concurrent requests never share
959
+ // a deadline.
960
+ let capacityDeadlineAt = null;
961
+ // Only the RESPONSE tells us the caller is still there. `req` is an
962
+ // IncomingMessage whose stream Node destroys once the request body has
963
+ // been fully read, which this proxy does up front, so `req.destroyed` is
964
+ // routinely true on a perfectly healthy request and must not be read as a
965
+ // disconnect.
966
+ const clientGone = () => res.destroyed || res.writableEnded;
866
967
  const accountSkipReasons = new Map();
867
968
  let reloadedAfterNoAccount = false;
868
969
  // Read the manual pin and affinity generation from disk (mtime-cached)
@@ -940,11 +1041,79 @@ async function handleRequestInner(state, req, res, traceId) {
940
1041
  if (isPinned) {
941
1042
  transientAttemptLimit = Math.max(1, Math.min(state.maxRuntimeAccountAttempts, MAX_PINNED_TRANSIENT_ATTEMPTS));
942
1043
  }
1044
+ /**
1045
+ * Wait out a "selected model is at capacity" response, then let the loop
1046
+ * re-send the request (issue #689).
1047
+ *
1048
+ * Capacity is a property of the MODEL, not of an account, so the normal
1049
+ * handling is actively wrong for it: rotating spends the pool's transient
1050
+ * budget against accounts that will all fail identically, and the request
1051
+ * ends as a pool-exhausted 503 within seconds. That is what kills a
1052
+ * long-running task started before the user stepped away.
1053
+ *
1054
+ * The responding account is left completely unpenalized: its pool token is
1055
+ * refunded, it is not marked rate limited, not cooled down, and it is
1056
+ * removed from `attemptedIndexes` so it stays selectable. Selection then
1057
+ * runs normally on the next pass, which may pick a different account. That
1058
+ * is deliberate: with capacity being model-wide, no account is a better bet,
1059
+ * and forcing a request-local pin here would duplicate the pinning path for
1060
+ * no gain.
1061
+ *
1062
+ * @param retryAfterMs Upstream hint when one was actually sent, else `null`
1063
+ * so the backoff table is used. Do NOT pass a synthesized default.
1064
+ * @param account Account that just saw the capacity response.
1065
+ * @returns `"retry"` to re-send, `"give-up"` to fall through to normal
1066
+ * handling, `"client-gone"` when the caller must abandon the request.
1067
+ */
1068
+ const waitOutModelCapacity = async (retryAfterMs, account) => {
1069
+ const budgetMs = state.modelCapacityRetryMs;
1070
+ if (!Number.isFinite(budgetMs) || budgetMs <= 0)
1071
+ return "give-up";
1072
+ if (capacityDeadlineAt === null) {
1073
+ capacityDeadlineAt = state.now() + budgetMs;
1074
+ }
1075
+ const remainingMs = capacityDeadlineAt - state.now();
1076
+ if (remainingMs <= 0)
1077
+ return "give-up";
1078
+ if (clientGone())
1079
+ return "client-gone";
1080
+ const hinted = retryAfterMs !== null &&
1081
+ Number.isFinite(retryAfterMs) &&
1082
+ retryAfterMs > 0
1083
+ ? retryAfterMs
1084
+ : capacityRetryBackoffMs(capacityRetries + 1);
1085
+ const waitMs = Math.min(Math.max(0, Math.floor(hinted)), remainingMs);
1086
+ if (waitMs <= 0)
1087
+ return "give-up";
1088
+ capacityRetries += 1;
1089
+ state.status.retries += 1;
1090
+ proxyLog.warn("model at capacity; waiting before re-sending", {
1091
+ traceId,
1092
+ waitMs,
1093
+ attempt: capacityRetries,
1094
+ remainingMs,
1095
+ budgetMs,
1096
+ });
1097
+ // The attempt debited a pool token before the upstream call. The account
1098
+ // did not fail, so refund it: without this a sustained capacity event
1099
+ // drains healthy accounts and later requests are refused admission with
1100
+ // `token-exhausted` well before the retry budget expires.
1101
+ refundConsumedPoolToken(account);
1102
+ attemptedIndexes.delete(account.index);
1103
+ await sleep(waitMs);
1104
+ // A capacity wait runs for tens of seconds. Re-sending an authenticated
1105
+ // upstream request for a response nobody is reading wastes upstream
1106
+ // capacity and extends how long the token is in flight.
1107
+ if (clientGone())
1108
+ return "client-gone";
1109
+ return "retry";
1110
+ };
943
1111
  let runtimeSelectionIterations = 0;
944
1112
  while ((isPinned || attemptedIndexes.size < accountCount) &&
945
1113
  transientAttempts < transientAttemptLimit &&
946
1114
  (!isPinned ||
947
- runtimeSelectionIterations < MAX_PINNED_SELECTION_ITERATIONS)) {
1115
+ runtimeSelectionIterations - capacityRetries <
1116
+ MAX_PINNED_SELECTION_ITERATIONS)) {
948
1117
  // Space out same-account re-sends. The loop has no other delay in it,
949
1118
  // and the pinned retry deliberately waives the account's own cooldown
950
1119
  // (below), so without this two copies of the same non-idempotent
@@ -1140,9 +1309,27 @@ async function handleRequestInner(state, req, res, traceId) {
1140
1309
  recordLastRuntimeAccount(state.status, accountIdentity);
1141
1310
  const outboundHeaders = createOutboundHeaders(context.headers, refreshed.account, refreshed.accessToken, accountId);
1142
1311
  let upstream;
1312
+ // Abort the in-flight upstream fetch when the client disconnects
1313
+ // before headers arrive. Image generation holds upstream capacity
1314
+ // for the full fetch timeout, so a caller that goes away right
1315
+ // after sending must not leave that work running. `forwardStreamingResponse`
1316
+ // already cancels the stream once headers are written; this covers
1317
+ // the pre-header window instead. `writableEnded` distinguishes a
1318
+ // premature close from the clean `res.end()` that ends every request.
1319
+ //
1320
+ // The listener is removed in `finally` once the fetch settles. Without
1321
+ // that, every retry above the 10-listener default threshold that reaches
1322
+ // this fetch leaks another one-shot `close` handler onto the same `res`,
1323
+ // emitting `MaxListenersExceededWarning` when `retryAllAccountsMaxRetries`
1324
+ // is high and the account pool is large.
1325
+ const fetchAbortController = new AbortController();
1326
+ const onClientClose = () => {
1327
+ if (!res.writableEnded)
1328
+ fetchAbortController.abort();
1329
+ };
1143
1330
  try {
1144
1331
  state.status.upstreamRequests += 1;
1145
- const fetchAbortController = new AbortController();
1332
+ res.once("close", onClientClose);
1146
1333
  const upstreamRequestInit = {
1147
1334
  method: context.method,
1148
1335
  headers: outboundHeaders,
@@ -1151,7 +1338,10 @@ async function handleRequestInner(state, req, res, traceId) {
1151
1338
  if (context.method === "POST") {
1152
1339
  upstreamRequestInit.body = context.body;
1153
1340
  }
1154
- upstream = await withTimeout(state.fetchImpl(upstreamUrl, upstreamRequestInit), state.fetchTimeoutMs, () => fetchAbortController.abort(), `upstream fetch timed out after ${state.fetchTimeoutMs}ms`);
1341
+ const fetchTimeoutMs = isImageRequest
1342
+ ? Math.max(state.fetchTimeoutMs, 300_000)
1343
+ : state.fetchTimeoutMs;
1344
+ upstream = await withTimeout(state.fetchImpl(upstreamUrl, upstreamRequestInit), fetchTimeoutMs, () => fetchAbortController.abort(), `upstream fetch timed out after ${fetchTimeoutMs}ms`);
1155
1345
  }
1156
1346
  catch (error) {
1157
1347
  // errors-logging-08: a custom fetchImpl, a proxy agent, or an undici
@@ -1168,6 +1358,20 @@ async function handleRequestInner(state, req, res, traceId) {
1168
1358
  error: transportError,
1169
1359
  });
1170
1360
  refundConsumedPoolToken(refreshed.account);
1361
+ // A timeout may occur after generation; do not retry within this request.
1362
+ if (isImageRequest) {
1363
+ writeJson(res, 502, {
1364
+ error: {
1365
+ code: "image_upstream_transport_error",
1366
+ message: "Image upstream transport failed; not retried by the proxy.",
1367
+ },
1368
+ });
1369
+ await usageRecorder.record({
1370
+ outcome: "failure", statusCode: 502,
1371
+ errorCode: "image_upstream_transport_error", account: refreshed.account,
1372
+ });
1373
+ return;
1374
+ }
1171
1375
  // A pre-header transport exception is a property of the network path,
1172
1376
  // not of this account's credentials or quota, so it must NOT feed the
1173
1377
  // account's circuit breaker / health tracker: that is what would
@@ -1190,6 +1394,9 @@ async function handleRequestInner(state, req, res, traceId) {
1190
1394
  noteRotation();
1191
1395
  continue;
1192
1396
  }
1397
+ finally {
1398
+ res.off("close", onClientClose);
1399
+ }
1193
1400
  reconcileManualSelection();
1194
1401
  const quotaSnapshot = readQuotaSchedulerSnapshot(upstream.headers, upstream.status, state.now());
1195
1402
  if (quotaSnapshot) {
@@ -1197,11 +1404,32 @@ async function handleRequestInner(state, req, res, traceId) {
1197
1404
  }
1198
1405
  if (upstream.status === HTTP_STATUS.TOO_MANY_REQUESTS) {
1199
1406
  const bodyText = await readErrorBody(upstream, state.streamStallTimeoutMs);
1200
- const retryAfterMs = parseRetryAfterHeaderMs(upstream.headers, state.now()) ??
1201
- parseRetryAfterBodyMs(bodyText, state.now()) ??
1202
- 60_000;
1407
+ // Keep the upstream HINT separate from the 60s fallback. Passing the
1408
+ // synthesized default into the capacity wait would make every
1409
+ // hint-less capacity 429 sleep a full minute and leave the 2s/5s/15s
1410
+ // backoff table dead on this path.
1411
+ const retryAfterHintMs = parseRetryAfterHeaderMs(upstream.headers, state.now()) ??
1412
+ parseRetryAfterBodyMs(bodyText, state.now());
1413
+ const retryAfterMs = retryAfterHintMs ?? 60_000;
1203
1414
  // Reading the body awaited I/O; a switch may have landed meanwhile.
1204
1415
  reconcileManualSelection();
1416
+ // A capacity 429 is not this account's quota. Marking it rate
1417
+ // limited would take a healthy account out of the pool for the
1418
+ // retry-after window on an outage that affects every account.
1419
+ if (isModelAtCapacityError(upstream.status, bodyText)) {
1420
+ const outcome = await waitOutModelCapacity(retryAfterHintMs, refreshed.account);
1421
+ if (outcome === "client-gone") {
1422
+ await usageRecorder.record({
1423
+ outcome: "failure",
1424
+ statusCode: upstream.status,
1425
+ errorCode: "client_disconnected_during_capacity_wait",
1426
+ account: refreshed.account,
1427
+ });
1428
+ return;
1429
+ }
1430
+ if (outcome === "retry")
1431
+ continue;
1432
+ }
1205
1433
  state.preemptiveQuotaScheduler.markRateLimited(quotaScheduleKey, retryAfterMs, state.now());
1206
1434
  // A 429 is the upstream quota signal for the attempted account, so
1207
1435
  // keep the consumed runtime token drained.
@@ -1339,8 +1567,35 @@ async function handleRequestInner(state, req, res, traceId) {
1339
1567
  noteRotation();
1340
1568
  continue;
1341
1569
  }
1570
+ // Forward image validation/moderation/5xx errors without cross-account replay.
1571
+ // Explicit 429 quota and 401 auth handling above still use the existing pool.
1572
+ if (isImageRequest && upstream.status >= 400) {
1573
+ const forwarded = await forwardStreamingResponse(upstream, res, state.status, () => undefined, state.streamStallTimeoutMs);
1574
+ await usageRecorder.record({
1575
+ outcome: "failure", statusCode: upstream.status,
1576
+ errorCode: forwarded ? "image_upstream_error" : "stream_forward_failed",
1577
+ account: refreshed.account,
1578
+ });
1579
+ return;
1580
+ }
1342
1581
  if (upstream.status >= 500) {
1343
- await readErrorBody(upstream, state.streamStallTimeoutMs);
1582
+ const bodyText = await readErrorBody(upstream, state.streamStallTimeoutMs);
1583
+ // A capacity 5xx is upstream load, not a broken account, so it
1584
+ // must not cool the account down or count against the pool.
1585
+ if (isModelAtCapacityError(upstream.status, bodyText)) {
1586
+ const outcome = await waitOutModelCapacity(parseRetryAfterHeaderMs(upstream.headers, state.now()), refreshed.account);
1587
+ if (outcome === "client-gone") {
1588
+ await usageRecorder.record({
1589
+ outcome: "failure",
1590
+ statusCode: upstream.status,
1591
+ errorCode: "client_disconnected_during_capacity_wait",
1592
+ account: refreshed.account,
1593
+ });
1594
+ return;
1595
+ }
1596
+ if (outcome === "retry")
1597
+ continue;
1598
+ }
1344
1599
  refundConsumedPoolToken(refreshed.account);
1345
1600
  accountManager.recordFailure(refreshed.account, context.family, context.model);
1346
1601
  accountManager.markAccountCoolingDown(refreshed.account, state.serverErrorCooldownMs, "server-error");
@@ -1 +1 @@
1
- {"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../../../lib/usage/ledger.ts"],"names":[],"mappings":"AASA,OAAO,EAEN,KAAK,sBAAsB,EAE3B,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EAErB,KAAK,cAAc,EAEnB,KAAK,YAAY,EAGjB,KAAK,iBAAiB,EAEtB,MAAM,YAAY,CAAC;AAqOpB,wBAAgB,mBAAmB,IAAI,gBAAgB,CAMtD;AAED,wBAAsB,oBAAoB,CACzC,KAAK,EAAE,sBAAsB,GAC3B,OAAO,CAAC,cAAc,CAAC,CASzB;AAuKD,wBAAsB,mBAAmB,CACxC,KAAK,GAAE,gBAAqB,GAC1B,OAAO,CAAC,cAAc,EAAE,CAAC,CAc3B;AAuDD,wBAAgB,kBAAkB,CACjC,IAAI,EAAE,cAAc,EAAE,EACtB,KAAK,GAAE,iBAAsB,GAC3B,YAAY,CAwBd;AAED,wBAAsB,oBAAoB,CACzC,KAAK,GAAE,iBAAsB,GAC3B,OAAO,CAAC,YAAY,CAAC,CAEvB;AAED,wBAAsB,iBAAiB,CAAC,OAAO,GAAE;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA2B9B;AAED,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD"}
1
+ {"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../../../lib/usage/ledger.ts"],"names":[],"mappings":"AASA,OAAO,EAEN,KAAK,sBAAsB,EAE3B,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EAErB,KAAK,cAAc,EAEnB,KAAK,YAAY,EAGjB,KAAK,iBAAiB,EAEtB,MAAM,YAAY,CAAC;AAsOpB,wBAAgB,mBAAmB,IAAI,gBAAgB,CAMtD;AAED,wBAAsB,oBAAoB,CACzC,KAAK,EAAE,sBAAsB,GAC3B,OAAO,CAAC,cAAc,CAAC,CASzB;AAuKD,wBAAsB,mBAAmB,CACxC,KAAK,GAAE,gBAAqB,GAC1B,OAAO,CAAC,cAAc,EAAE,CAAC,CAc3B;AAuDD,wBAAgB,kBAAkB,CACjC,IAAI,EAAE,cAAc,EAAE,EACtB,KAAK,GAAE,iBAAsB,GAC3B,YAAY,CAwBd;AAED,wBAAsB,oBAAoB,CACzC,KAAK,GAAE,iBAAsB,GAC3B,OAAO,CAAC,YAAY,CAAC,CAEvB;AAED,wBAAsB,iBAAiB,CAAC,OAAO,GAAE;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA2B9B;AAED,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD"}
@@ -21,6 +21,7 @@ const VALID_SOURCES = new Set([
21
21
  ]);
22
22
  const VALID_OPERATIONS = new Set([
23
23
  "responses",
24
+ "images",
24
25
  "models",
25
26
  "thread-goal",
26
27
  "auth-refresh",
@@ -1 +1 @@
1
- {"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../../../lib/usage/redaction.ts"],"names":[],"mappings":"AACA,OAAO,EAEN,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAG3B,KAAK,cAAc,EAGnB,MAAM,YAAY,CAAC;AA2EpB,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC5C,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,GAAG,qBAAqB,GAAG,IAAI,CAkB/B;AAkCD,wBAAgB,uBAAuB,CACtC,KAAK,EAAE,sBAAsB,GAC3B,cAAc,CA0BhB;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,CAE9D"}
1
+ {"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../../../lib/usage/redaction.ts"],"names":[],"mappings":"AACA,OAAO,EAEN,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAG3B,KAAK,cAAc,EAGnB,MAAM,YAAY,CAAC;AA4EpB,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC5C,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,GAAG,qBAAqB,GAAG,IAAI,CAkB/B;AAkCD,wBAAgB,uBAAuB,CACtC,KAAK,EAAE,sBAAsB,GAC3B,cAAc,CA0BhB;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,CAE9D"}
@@ -10,6 +10,7 @@ const VALID_SOURCES = new Set([
10
10
  ]);
11
11
  const VALID_OPERATIONS = new Set([
12
12
  "responses",
13
+ "images",
13
14
  "models",
14
15
  "thread-goal",
15
16
  "auth-refresh",
@@ -1,5 +1,5 @@
1
1
  export type UsageLedgerSource = "runtime-proxy" | "plugin-host" | "local-bridge" | "cli" | "unknown";
2
- export type UsageLedgerOperation = "responses" | "models" | "thread-goal" | "auth-refresh" | "diagnostic" | "unknown";
2
+ export type UsageLedgerOperation = "responses" | "images" | "models" | "thread-goal" | "auth-refresh" | "diagnostic" | "unknown";
3
3
  export type UsageLedgerOutcome = "success" | "failure" | "blocked" | "cancelled";
4
4
  /**
5
5
  * The service tier a response was actually billed at.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../lib/usage/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../lib/usage/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-multi-auth",
3
- "version": "2.13.0",
3
+ "version": "2.15.0",
4
4
  "description": "Codex CLI multi-account OAuth manager with account switching, health checks, runtime rotation, diagnostics, and recovery tools for @openai/codex",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
package/scripts/codex.js CHANGED
@@ -3890,6 +3890,7 @@ function createRuntimeRotationProxyCanonicalCodexHome(
3890
3890
  `${providerTable}.base_url=${configTomlModule.tomlStringLiteral(proxyBaseUrl)}`,
3891
3891
  `${providerTable}.env_key=${configTomlModule.tomlStringLiteral("OPENAI_API_KEY")}`,
3892
3892
  `${providerTable}.requires_openai_auth=false`,
3893
+ `${providerTable}.http_headers.x-openai-actor-authorization=${configTomlModule.tomlStringLiteral("codex-multi-auth-local")}`,
3893
3894
  `${providerTable}.wire_api=${configTomlModule.tomlStringLiteral("responses")}`,
3894
3895
  "disable_response_storage=false",
3895
3896
  ].flatMap((assignment) => ["-c", assignment]);