codex-multi-auth 2.13.0 → 2.14.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.
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +4 -1
- package/dist/lib/request/error-classification.d.ts +13 -6
- package/dist/lib/request/error-classification.d.ts.map +1 -1
- package/dist/lib/request/error-classification.js +45 -0
- package/dist/lib/request/fetch-helpers.d.ts +1 -1
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +1 -1
- package/dist/lib/runtime/rotation-proxy-state.d.ts +6 -0
- package/dist/lib/runtime/rotation-proxy-state.d.ts.map +1 -1
- package/dist/lib/runtime/rotation-server-types.d.ts +7 -0
- package/dist/lib/runtime/rotation-server-types.d.ts.map +1 -1
- package/dist/lib/runtime-rotation-proxy.d.ts +10 -0
- package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
- package/dist/lib/runtime-rotation-proxy.js +190 -6
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-multi-auth",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.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
|
@@ -298,6 +298,7 @@ Selected runtime/environment overrides:
|
|
|
298
298
|
| `CODEX_MULTI_AUTH_BYPASS=1` | Skip multi-auth intercept and forward straight to official Codex |
|
|
299
299
|
| `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0/1` | Opt out/in of live Responses proxy rotation for forwarded Codex CLI/app sessions |
|
|
300
300
|
| `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 |
|
|
301
|
+
| `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
302
|
| `CODEX_MULTI_AUTH_APP_ROTATION_IDLE_MS=<ms>` | Override automatic Codex app helper idle shutdown |
|
|
302
303
|
| `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
304
|
| `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 +323,8 @@ Runtime rotation is enabled by default for request-bearing wrapper-launched Code
|
|
|
322
323
|
|
|
323
324
|
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
325
|
|
|
326
|
+
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).
|
|
327
|
+
|
|
325
328
|
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
329
|
|
|
327
330
|
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 +415,7 @@ codex-multi-auth doctor --json
|
|
|
412
415
|
|
|
413
416
|
## Release Notes
|
|
414
417
|
|
|
415
|
-
- Current stable: [docs/releases/v2.
|
|
418
|
+
- Current stable: [docs/releases/v2.14.0.md](docs/releases/v2.14.0.md) — install via `npm i -g codex-multi-auth`
|
|
416
419
|
- Previous stable: [docs/releases/v2.8.3.md](docs/releases/v2.8.3.md)
|
|
417
420
|
- Previous stable: [docs/releases/v2.8.2.md](docs/releases/v2.8.2.md)
|
|
418
421
|
- 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
|
|
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
|
-
*
|
|
41
|
-
*
|
|
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
|
|
44
|
-
* @param bodyText -
|
|
45
|
-
* @returns `true`
|
|
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;
|
|
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":"AAAA;;;GAGG;AAIH,OAAO,EAIN,KAAK,wBAAwB,EAC7B,MAAM,0BAA0B,CAAC;AAMlC,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAiB1D,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EACN,wCAAwC,EACxC,oCAAoC,EACpC,4BAA4B,EAC5B,oCAAoC,EACpC,uCAAuC,EACvC,kBAAkB,EAClB,sBAAsB,EACtB,wBAAwB,EACxB,8BAA8B,GAC9B,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACX,gBAAgB,EAChB,yBAAyB,EACzB,sCAAsC,GACtC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACN,iBAAiB,EACjB,kBAAkB,EAClB,yBAAyB,EACzB,2BAA2B,EAC3B,wBAAwB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,YAAY,EACX,yBAAyB,EACzB,wBAAwB,GACxB,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,aAAa;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACrB;AASD,MAAM,WAAW,8BAA8B;IAC9C,IAAI,EAAE,WAAW,CAAC;IAClB,WAAW,EAAE,WAAW,CAAC;IACzB,4BAA4B,CAAC,EAAE,wBAAwB,CAAC,MAAM,CAAC,CAAC;CAChE;AAED,MAAM,WAAW,mBAAmB;IAC5B,QAAQ,EAAE,QAAQ,CAAC;IACnB,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACpC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,wBAAwB,CAC7C,IAAI,EAAE,WAAW,GAAG,SAAS,EAC7B,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,UAAU,EACtB,SAAS,UAAO,EAChB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,OAAO,CAAC,EAAE;IACT,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,mBAAmB,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1C,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACnC,GACC,OAAO,CAAC,8BAA8B,GAAG,SAAS,CAAC,CA4FrD;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,oBAAoB,GACnC,OAAO,CAAC,mBAAmB,CAAC,CA4C9B;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACvC,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,OAAO,EACpB,OAAO,CAAC,EAAE;IACZ,YAAY,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC5C,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B,GACC,OAAO,CAAC,QAAQ,CAAC,CAkBnB"}
|
|
@@ -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
|
|
@@ -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;
|
|
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;
|
|
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;AAueD;;;;;;;;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([
|
|
@@ -608,6 +660,14 @@ export async function startRuntimeRotationProxy(options) {
|
|
|
608
660
|
const tokenRefreshSkewMs = getTokenRefreshSkewMs(pluginConfig);
|
|
609
661
|
const networkErrorCooldownMs = getNetworkErrorCooldownMs(pluginConfig);
|
|
610
662
|
const serverErrorCooldownMs = getServerErrorCooldownMs(pluginConfig);
|
|
663
|
+
// Normalize the explicit option as well as the env var: a caller passing a
|
|
664
|
+
// negative or multi-hour value would otherwise bypass the documented cap.
|
|
665
|
+
// `0` stays 0, since it is the documented kill switch.
|
|
666
|
+
const modelCapacityRetryMs = options.modelCapacityRetryMs === 0
|
|
667
|
+
? 0
|
|
668
|
+
: options.modelCapacityRetryMs === undefined
|
|
669
|
+
? resolveModelCapacityRetryMs()
|
|
670
|
+
: normalizeModelCapacityRetryMs(options.modelCapacityRetryMs);
|
|
611
671
|
const tokenInvalidationCooldownMs = getTokenInvalidationCooldownMs(pluginConfig);
|
|
612
672
|
const minRotationIntervalMs = getMinRotationIntervalMs(pluginConfig);
|
|
613
673
|
const pidOffsetEnabled = getPidOffsetEnabled(pluginConfig);
|
|
@@ -654,6 +714,7 @@ export async function startRuntimeRotationProxy(options) {
|
|
|
654
714
|
tokenRefreshSkewMs,
|
|
655
715
|
networkErrorCooldownMs,
|
|
656
716
|
serverErrorCooldownMs,
|
|
717
|
+
modelCapacityRetryMs,
|
|
657
718
|
tokenInvalidationCooldownMs,
|
|
658
719
|
minRotationIntervalMs,
|
|
659
720
|
pidOffsetEnabled,
|
|
@@ -863,6 +924,24 @@ async function handleRequestInner(state, req, res, traceId) {
|
|
|
863
924
|
let transientAttemptLimit = Math.max(1, Math.min(accountCount, state.maxRuntimeAccountAttempts));
|
|
864
925
|
let transientAttempts = 0;
|
|
865
926
|
let transientExhaustionReason = null;
|
|
927
|
+
// Capacity waits are counted separately from `transientAttempts` and from
|
|
928
|
+
// the pinned selection cap. Both of those bound how many DIFFERENT
|
|
929
|
+
// accounts or re-sends a failing request may burn; a capacity wait is not
|
|
930
|
+
// a failure of any account, it is the same request pausing for an
|
|
931
|
+
// upstream that is busy, and it has its own wall-clock ceiling. See #689.
|
|
932
|
+
let capacityRetries = 0;
|
|
933
|
+
// A wall-clock deadline, not a sum of sleeps. The upstream request and
|
|
934
|
+
// the error-body read also consume the budget, so summing only the
|
|
935
|
+
// planned sleeps let a slow capacity response push one request past the
|
|
936
|
+
// documented ceiling. Request-local, so concurrent requests never share
|
|
937
|
+
// a deadline.
|
|
938
|
+
let capacityDeadlineAt = null;
|
|
939
|
+
// Only the RESPONSE tells us the caller is still there. `req` is an
|
|
940
|
+
// IncomingMessage whose stream Node destroys once the request body has
|
|
941
|
+
// been fully read, which this proxy does up front, so `req.destroyed` is
|
|
942
|
+
// routinely true on a perfectly healthy request and must not be read as a
|
|
943
|
+
// disconnect.
|
|
944
|
+
const clientGone = () => res.destroyed || res.writableEnded;
|
|
866
945
|
const accountSkipReasons = new Map();
|
|
867
946
|
let reloadedAfterNoAccount = false;
|
|
868
947
|
// Read the manual pin and affinity generation from disk (mtime-cached)
|
|
@@ -940,11 +1019,79 @@ async function handleRequestInner(state, req, res, traceId) {
|
|
|
940
1019
|
if (isPinned) {
|
|
941
1020
|
transientAttemptLimit = Math.max(1, Math.min(state.maxRuntimeAccountAttempts, MAX_PINNED_TRANSIENT_ATTEMPTS));
|
|
942
1021
|
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Wait out a "selected model is at capacity" response, then let the loop
|
|
1024
|
+
* re-send the request (issue #689).
|
|
1025
|
+
*
|
|
1026
|
+
* Capacity is a property of the MODEL, not of an account, so the normal
|
|
1027
|
+
* handling is actively wrong for it: rotating spends the pool's transient
|
|
1028
|
+
* budget against accounts that will all fail identically, and the request
|
|
1029
|
+
* ends as a pool-exhausted 503 within seconds. That is what kills a
|
|
1030
|
+
* long-running task started before the user stepped away.
|
|
1031
|
+
*
|
|
1032
|
+
* The responding account is left completely unpenalized: its pool token is
|
|
1033
|
+
* refunded, it is not marked rate limited, not cooled down, and it is
|
|
1034
|
+
* removed from `attemptedIndexes` so it stays selectable. Selection then
|
|
1035
|
+
* runs normally on the next pass, which may pick a different account. That
|
|
1036
|
+
* is deliberate: with capacity being model-wide, no account is a better bet,
|
|
1037
|
+
* and forcing a request-local pin here would duplicate the pinning path for
|
|
1038
|
+
* no gain.
|
|
1039
|
+
*
|
|
1040
|
+
* @param retryAfterMs Upstream hint when one was actually sent, else `null`
|
|
1041
|
+
* so the backoff table is used. Do NOT pass a synthesized default.
|
|
1042
|
+
* @param account Account that just saw the capacity response.
|
|
1043
|
+
* @returns `"retry"` to re-send, `"give-up"` to fall through to normal
|
|
1044
|
+
* handling, `"client-gone"` when the caller must abandon the request.
|
|
1045
|
+
*/
|
|
1046
|
+
const waitOutModelCapacity = async (retryAfterMs, account) => {
|
|
1047
|
+
const budgetMs = state.modelCapacityRetryMs;
|
|
1048
|
+
if (!Number.isFinite(budgetMs) || budgetMs <= 0)
|
|
1049
|
+
return "give-up";
|
|
1050
|
+
if (capacityDeadlineAt === null) {
|
|
1051
|
+
capacityDeadlineAt = state.now() + budgetMs;
|
|
1052
|
+
}
|
|
1053
|
+
const remainingMs = capacityDeadlineAt - state.now();
|
|
1054
|
+
if (remainingMs <= 0)
|
|
1055
|
+
return "give-up";
|
|
1056
|
+
if (clientGone())
|
|
1057
|
+
return "client-gone";
|
|
1058
|
+
const hinted = retryAfterMs !== null &&
|
|
1059
|
+
Number.isFinite(retryAfterMs) &&
|
|
1060
|
+
retryAfterMs > 0
|
|
1061
|
+
? retryAfterMs
|
|
1062
|
+
: capacityRetryBackoffMs(capacityRetries + 1);
|
|
1063
|
+
const waitMs = Math.min(Math.max(0, Math.floor(hinted)), remainingMs);
|
|
1064
|
+
if (waitMs <= 0)
|
|
1065
|
+
return "give-up";
|
|
1066
|
+
capacityRetries += 1;
|
|
1067
|
+
state.status.retries += 1;
|
|
1068
|
+
proxyLog.warn("model at capacity; waiting before re-sending", {
|
|
1069
|
+
traceId,
|
|
1070
|
+
waitMs,
|
|
1071
|
+
attempt: capacityRetries,
|
|
1072
|
+
remainingMs,
|
|
1073
|
+
budgetMs,
|
|
1074
|
+
});
|
|
1075
|
+
// The attempt debited a pool token before the upstream call. The account
|
|
1076
|
+
// did not fail, so refund it: without this a sustained capacity event
|
|
1077
|
+
// drains healthy accounts and later requests are refused admission with
|
|
1078
|
+
// `token-exhausted` well before the retry budget expires.
|
|
1079
|
+
refundConsumedPoolToken(account);
|
|
1080
|
+
attemptedIndexes.delete(account.index);
|
|
1081
|
+
await sleep(waitMs);
|
|
1082
|
+
// A capacity wait runs for tens of seconds. Re-sending an authenticated
|
|
1083
|
+
// upstream request for a response nobody is reading wastes upstream
|
|
1084
|
+
// capacity and extends how long the token is in flight.
|
|
1085
|
+
if (clientGone())
|
|
1086
|
+
return "client-gone";
|
|
1087
|
+
return "retry";
|
|
1088
|
+
};
|
|
943
1089
|
let runtimeSelectionIterations = 0;
|
|
944
1090
|
while ((isPinned || attemptedIndexes.size < accountCount) &&
|
|
945
1091
|
transientAttempts < transientAttemptLimit &&
|
|
946
1092
|
(!isPinned ||
|
|
947
|
-
runtimeSelectionIterations
|
|
1093
|
+
runtimeSelectionIterations - capacityRetries <
|
|
1094
|
+
MAX_PINNED_SELECTION_ITERATIONS)) {
|
|
948
1095
|
// Space out same-account re-sends. The loop has no other delay in it,
|
|
949
1096
|
// and the pinned retry deliberately waives the account's own cooldown
|
|
950
1097
|
// (below), so without this two copies of the same non-idempotent
|
|
@@ -1197,11 +1344,32 @@ async function handleRequestInner(state, req, res, traceId) {
|
|
|
1197
1344
|
}
|
|
1198
1345
|
if (upstream.status === HTTP_STATUS.TOO_MANY_REQUESTS) {
|
|
1199
1346
|
const bodyText = await readErrorBody(upstream, state.streamStallTimeoutMs);
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1347
|
+
// Keep the upstream HINT separate from the 60s fallback. Passing the
|
|
1348
|
+
// synthesized default into the capacity wait would make every
|
|
1349
|
+
// hint-less capacity 429 sleep a full minute and leave the 2s/5s/15s
|
|
1350
|
+
// backoff table dead on this path.
|
|
1351
|
+
const retryAfterHintMs = parseRetryAfterHeaderMs(upstream.headers, state.now()) ??
|
|
1352
|
+
parseRetryAfterBodyMs(bodyText, state.now());
|
|
1353
|
+
const retryAfterMs = retryAfterHintMs ?? 60_000;
|
|
1203
1354
|
// Reading the body awaited I/O; a switch may have landed meanwhile.
|
|
1204
1355
|
reconcileManualSelection();
|
|
1356
|
+
// A capacity 429 is not this account's quota. Marking it rate
|
|
1357
|
+
// limited would take a healthy account out of the pool for the
|
|
1358
|
+
// retry-after window on an outage that affects every account.
|
|
1359
|
+
if (isModelAtCapacityError(upstream.status, bodyText)) {
|
|
1360
|
+
const outcome = await waitOutModelCapacity(retryAfterHintMs, refreshed.account);
|
|
1361
|
+
if (outcome === "client-gone") {
|
|
1362
|
+
await usageRecorder.record({
|
|
1363
|
+
outcome: "failure",
|
|
1364
|
+
statusCode: upstream.status,
|
|
1365
|
+
errorCode: "client_disconnected_during_capacity_wait",
|
|
1366
|
+
account: refreshed.account,
|
|
1367
|
+
});
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
if (outcome === "retry")
|
|
1371
|
+
continue;
|
|
1372
|
+
}
|
|
1205
1373
|
state.preemptiveQuotaScheduler.markRateLimited(quotaScheduleKey, retryAfterMs, state.now());
|
|
1206
1374
|
// A 429 is the upstream quota signal for the attempted account, so
|
|
1207
1375
|
// keep the consumed runtime token drained.
|
|
@@ -1340,7 +1508,23 @@ async function handleRequestInner(state, req, res, traceId) {
|
|
|
1340
1508
|
continue;
|
|
1341
1509
|
}
|
|
1342
1510
|
if (upstream.status >= 500) {
|
|
1343
|
-
await readErrorBody(upstream, state.streamStallTimeoutMs);
|
|
1511
|
+
const bodyText = await readErrorBody(upstream, state.streamStallTimeoutMs);
|
|
1512
|
+
// A capacity 5xx is upstream load, not a broken account, so it
|
|
1513
|
+
// must not cool the account down or count against the pool.
|
|
1514
|
+
if (isModelAtCapacityError(upstream.status, bodyText)) {
|
|
1515
|
+
const outcome = await waitOutModelCapacity(parseRetryAfterHeaderMs(upstream.headers, state.now()), refreshed.account);
|
|
1516
|
+
if (outcome === "client-gone") {
|
|
1517
|
+
await usageRecorder.record({
|
|
1518
|
+
outcome: "failure",
|
|
1519
|
+
statusCode: upstream.status,
|
|
1520
|
+
errorCode: "client_disconnected_during_capacity_wait",
|
|
1521
|
+
account: refreshed.account,
|
|
1522
|
+
});
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
if (outcome === "retry")
|
|
1526
|
+
continue;
|
|
1527
|
+
}
|
|
1344
1528
|
refundConsumedPoolToken(refreshed.account);
|
|
1345
1529
|
accountManager.recordFailure(refreshed.account, context.family, context.model);
|
|
1346
1530
|
accountManager.markAccountCoolingDown(refreshed.account, state.serverErrorCooldownMs, "server-error");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-multi-auth",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.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",
|