codex-multi-auth 2.14.0 → 2.16.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 +3 -1
- package/config/README.md +2 -2
- package/config/codex-legacy.json +109 -184
- package/config/codex-modern.json +44 -114
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/lib/context-budget/model-context-windows.d.ts +1 -1
- package/dist/lib/context-budget/model-context-windows.d.ts.map +1 -1
- package/dist/lib/context-budget/model-context-windows.js +9 -10
- package/dist/lib/prompts/codex.d.ts.map +1 -1
- package/dist/lib/prompts/codex.js +3 -1
- package/dist/lib/request/error-classification.d.ts.map +1 -1
- package/dist/lib/request/error-classification.js +39 -25
- package/dist/lib/request/fetch-helpers.js +1 -1
- package/dist/lib/request/helpers/model-map.d.ts +12 -2
- package/dist/lib/request/helpers/model-map.d.ts.map +1 -1
- package/dist/lib/request/helpers/model-map.js +174 -147
- package/dist/lib/runtime/config-toml.d.ts.map +1 -1
- package/dist/lib/runtime/config-toml.js +1 -0
- package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
- package/dist/lib/runtime-rotation-proxy.js +81 -11
- package/dist/lib/usage/ledger.d.ts.map +1 -1
- package/dist/lib/usage/ledger.js +1 -0
- package/dist/lib/usage/pricing.d.ts +19 -2
- package/dist/lib/usage/pricing.d.ts.map +1 -1
- package/dist/lib/usage/pricing.js +100 -39
- package/dist/lib/usage/redaction.d.ts.map +1 -1
- package/dist/lib/usage/redaction.js +1 -0
- package/dist/lib/usage/types.d.ts +1 -1
- package/dist/lib/usage/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/scripts/codex.js +110 -104
|
@@ -52,7 +52,21 @@ const TOOL_CAPABILITIES = {
|
|
|
52
52
|
compaction: false,
|
|
53
53
|
},
|
|
54
54
|
};
|
|
55
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Where every codex-named id routes. No codex model is left: upstream Codex
|
|
57
|
+
* dropped `gpt-5.3-codex` from its catalog on 2026-07-08, and OpenAI's
|
|
58
|
+
* deprecations page names `gpt-5.6-sol` as the replacement for every retired
|
|
59
|
+
* codex id (`gpt-5-codex`, `gpt-5.1-codex*`, `gpt-5.2-codex`). The export
|
|
60
|
+
* keeps its name so callers that mean "the model a codex request runs on"
|
|
61
|
+
* still read naturally.
|
|
62
|
+
*/
|
|
63
|
+
export const CURRENT_CODEX_MODEL = "gpt-5.6-sol";
|
|
64
|
+
/**
|
|
65
|
+
* The retired codex mini ids (`gpt-5.1-codex-mini`, `gpt-5-codex-mini`,
|
|
66
|
+
* `codex-mini-latest`) go to Terra, OpenAI's named replacement for
|
|
67
|
+
* `gpt-5.1-codex-mini`, rather than up to Sol.
|
|
68
|
+
*/
|
|
69
|
+
const CODEX_MINI_REPLACEMENT_MODEL = "gpt-5.6-terra";
|
|
56
70
|
export const DEFAULT_MODEL = "gpt-5.5";
|
|
57
71
|
// Model used for diagnostic live/quota probes (`check`, `report`, `best`).
|
|
58
72
|
// Deliberately distinct from DEFAULT_MODEL: GPT-5.6 is the latest general family
|
|
@@ -70,18 +84,18 @@ export const DEFAULT_MODEL = "gpt-5.5";
|
|
|
70
84
|
export const DEFAULT_PROBE_MODEL = "gpt-5.6-sol";
|
|
71
85
|
// Single source of truth for the live/quota probe fallback chain. Both the
|
|
72
86
|
// manager probe (lib/quota-probe.ts) and the runtime probe (lib/runtime/quota-probe.ts)
|
|
73
|
-
// import this so the ordered candidate list cannot drift between them.
|
|
74
|
-
//
|
|
75
|
-
// a
|
|
87
|
+
// import this so the ordered candidate list cannot drift between them.
|
|
88
|
+
//
|
|
89
|
+
// Every entry must be a model the Codex backend still serves. The chain used to
|
|
90
|
+
// end on `gpt-5.4` and three codex models; all four are gone from the upstream
|
|
91
|
+
// catalog (`gpt-5.4` carries `retirement_at: 2026-08-31`), so a probe that
|
|
92
|
+
// reached them spent requests on guaranteed failures. `gpt-6-luna` closes it
|
|
93
|
+
// because it is offered on the most plans of any catalog model (24).
|
|
76
94
|
export const QUOTA_PROBE_MODEL_CHAIN = [
|
|
77
95
|
DEFAULT_PROBE_MODEL,
|
|
78
96
|
DEFAULT_MODEL,
|
|
79
|
-
"gpt-
|
|
80
|
-
"gpt-5.3-codex",
|
|
81
|
-
"gpt-5.2-codex",
|
|
82
|
-
"gpt-5-codex",
|
|
97
|
+
"gpt-6-luna",
|
|
83
98
|
];
|
|
84
|
-
const LEGACY_CODEX_MODEL = "gpt-5-codex";
|
|
85
99
|
/**
|
|
86
100
|
* GPT-5.6 tiers, per the upstream Codex catalog
|
|
87
101
|
* (openai/codex `codex-rs/models-manager/models.json`).
|
|
@@ -112,8 +126,8 @@ const GPT_5_6_LUNA_EFFORTS = [
|
|
|
112
126
|
/**
|
|
113
127
|
* GPT-6 Astra, OpenAI's 2026-09-03 frontier release.
|
|
114
128
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
129
|
+
* The launch lineup was the flagship plus `aeon`, a long-horizon variant built
|
|
130
|
+
* for runs measured in days; Sol and Luna followed on 2026-09-22 (below).
|
|
117
131
|
* `gpt-6-astra` is the API model name OpenAI published at launch;
|
|
118
132
|
* `gpt-6-astra-aeon` is the second slug that shipped beside it in the Codex
|
|
119
133
|
* model list. "Astra Pro" is a plan tier, not a separate slug we have seen, so
|
|
@@ -139,6 +153,24 @@ const GPT_6_ASTRA_EFFORTS = [
|
|
|
139
153
|
"max",
|
|
140
154
|
"ultra",
|
|
141
155
|
];
|
|
156
|
+
/**
|
|
157
|
+
* GPT-6 Sol and Luna, added to the upstream Codex catalog on 2026-09-22
|
|
158
|
+
* (openai/codex #47332) beside Astra. Sol is the everyday workhorse and Luna
|
|
159
|
+
* the small, cheap tier; there is no GPT-6 Terra. Upstream migrates `gpt-5.5`,
|
|
160
|
+
* `gpt-5.6-sol` and `gpt-5.6-terra` users to Sol and `gpt-5.6-luna` users to
|
|
161
|
+
* Luna. Ladders and defaults below are the catalog's own values: Sol reaches
|
|
162
|
+
* `ultra`, Luna stops at `max` (same split as 5.6), both default to `medium`.
|
|
163
|
+
*/
|
|
164
|
+
const GPT_6_SOL_MODEL = "gpt-6-sol";
|
|
165
|
+
const GPT_6_LUNA_MODEL = "gpt-6-luna";
|
|
166
|
+
const GPT_6_SOL_EFFORTS = GPT_6_ASTRA_EFFORTS;
|
|
167
|
+
const GPT_6_LUNA_EFFORTS = [
|
|
168
|
+
"low",
|
|
169
|
+
"medium",
|
|
170
|
+
"high",
|
|
171
|
+
"xhigh",
|
|
172
|
+
"max",
|
|
173
|
+
];
|
|
142
174
|
/**
|
|
143
175
|
* Cyber-specialty models from the upstream Codex catalog
|
|
144
176
|
* (openai/codex `codex-rs/models-manager/models.json`).
|
|
@@ -166,33 +198,43 @@ const GPT_5_5_RELEASE_MODEL = "gpt-5.5-2026-04-23";
|
|
|
166
198
|
const GPT_5_5_PRO_RELEASE_MODEL = "gpt-5.5-pro-2026-04-23";
|
|
167
199
|
const GPT_5_5_RELEASE_COMPAT_MODEL = "gpt-5.5-20260423";
|
|
168
200
|
const GPT_5_5_PRO_RELEASE_COMPAT_MODEL = "gpt-5.5-pro-20260423";
|
|
201
|
+
/**
|
|
202
|
+
* Where an unrecognised `gpt-5.<minor>` id lands. Every minor below 5.5 is
|
|
203
|
+
* retired (OpenAI deprecations page, upstream catalog removals), so each maps
|
|
204
|
+
* to the replacement OpenAI names for it rather than to a model that no longer
|
|
205
|
+
* answers: 5.1/5.2 to Sol, 5.4 to GPT-6 Sol/Luna as upstream Codex migrates it,
|
|
206
|
+
* and every `pro` to the one live pro model.
|
|
207
|
+
*/
|
|
169
208
|
const GENERAL_GPT5_VERSION_CATALOG = {
|
|
170
209
|
1: {
|
|
171
|
-
base:
|
|
210
|
+
base: GPT_5_6_SOL_MODEL,
|
|
211
|
+
pro: GPT_5_5_PRO_CANONICAL_MODEL,
|
|
172
212
|
},
|
|
173
213
|
2: {
|
|
174
|
-
base:
|
|
175
|
-
pro:
|
|
214
|
+
base: GPT_5_6_SOL_MODEL,
|
|
215
|
+
pro: GPT_5_5_PRO_CANONICAL_MODEL,
|
|
176
216
|
},
|
|
177
217
|
4: {
|
|
178
|
-
base:
|
|
179
|
-
pro:
|
|
180
|
-
mini:
|
|
181
|
-
nano:
|
|
218
|
+
base: GPT_6_SOL_MODEL,
|
|
219
|
+
pro: GPT_5_5_PRO_CANONICAL_MODEL,
|
|
220
|
+
mini: GPT_6_LUNA_MODEL,
|
|
221
|
+
nano: GPT_6_LUNA_MODEL,
|
|
182
222
|
},
|
|
183
223
|
5: {
|
|
184
224
|
base: GPT_5_5_CANONICAL_MODEL,
|
|
185
225
|
pro: GPT_5_5_PRO_CANONICAL_MODEL,
|
|
186
|
-
mini:
|
|
187
|
-
nano:
|
|
226
|
+
mini: GPT_5_6_TERRA_MODEL,
|
|
227
|
+
nano: GPT_5_6_LUNA_MODEL,
|
|
188
228
|
},
|
|
189
229
|
};
|
|
190
230
|
const GENERAL_GPT5_STABLE_VARIANTS = GENERAL_GPT5_VERSION_CATALOG[5];
|
|
231
|
+
// `gpt-5-mini`/`gpt-5-nano` point at snapshots OpenAI retires on 2026-12-11;
|
|
232
|
+
// the deprecations page names Terra and Luna as their replacements.
|
|
191
233
|
const GENERAL_GPT5_GENERIC_VARIANTS = {
|
|
192
234
|
base: DEFAULT_MODEL,
|
|
193
235
|
pro: GPT_5_5_PRO_CANONICAL_MODEL,
|
|
194
|
-
mini:
|
|
195
|
-
nano:
|
|
236
|
+
mini: GPT_5_6_TERRA_MODEL,
|
|
237
|
+
nano: GPT_5_6_LUNA_MODEL,
|
|
196
238
|
};
|
|
197
239
|
/**
|
|
198
240
|
* Effective model profiles keyed by canonical model name.
|
|
@@ -203,41 +245,6 @@ const GENERAL_GPT5_GENERIC_VARIANTS = {
|
|
|
203
245
|
* present in the latest upstream release.
|
|
204
246
|
*/
|
|
205
247
|
export const MODEL_PROFILES = {
|
|
206
|
-
[CURRENT_CODEX_MODEL]: {
|
|
207
|
-
normalizedModel: CURRENT_CODEX_MODEL,
|
|
208
|
-
promptFamily: "gpt-5-codex",
|
|
209
|
-
defaultReasoningEffort: "high",
|
|
210
|
-
supportedReasoningEfforts: ["low", "medium", "high", "xhigh"],
|
|
211
|
-
capabilities: TOOL_CAPABILITIES.basic,
|
|
212
|
-
},
|
|
213
|
-
"gpt-5.4": {
|
|
214
|
-
normalizedModel: "gpt-5.4",
|
|
215
|
-
promptFamily: "gpt-5.2",
|
|
216
|
-
defaultReasoningEffort: "none",
|
|
217
|
-
supportedReasoningEfforts: ["none", "low", "medium", "high", "xhigh"],
|
|
218
|
-
capabilities: TOOL_CAPABILITIES.full,
|
|
219
|
-
},
|
|
220
|
-
"gpt-5.4-pro": {
|
|
221
|
-
normalizedModel: "gpt-5.4-pro",
|
|
222
|
-
promptFamily: "gpt-5.2",
|
|
223
|
-
defaultReasoningEffort: "high",
|
|
224
|
-
supportedReasoningEfforts: ["medium", "high", "xhigh"],
|
|
225
|
-
capabilities: TOOL_CAPABILITIES.computerAndCompact,
|
|
226
|
-
},
|
|
227
|
-
"gpt-5.4-mini": {
|
|
228
|
-
normalizedModel: "gpt-5.4-mini",
|
|
229
|
-
promptFamily: "gpt-5.2",
|
|
230
|
-
defaultReasoningEffort: "medium",
|
|
231
|
-
supportedReasoningEfforts: ["medium"],
|
|
232
|
-
capabilities: TOOL_CAPABILITIES.compactOnly,
|
|
233
|
-
},
|
|
234
|
-
"gpt-5.4-nano": {
|
|
235
|
-
normalizedModel: "gpt-5.4-nano",
|
|
236
|
-
promptFamily: "gpt-5.2",
|
|
237
|
-
defaultReasoningEffort: "medium",
|
|
238
|
-
supportedReasoningEfforts: ["medium"],
|
|
239
|
-
capabilities: TOOL_CAPABILITIES.compactOnly,
|
|
240
|
-
},
|
|
241
248
|
// Like GPT-5.6, GPT-6 Astra ships its base instructions inline in the
|
|
242
249
|
// upstream model catalog rather than as a `gpt_6_prompt.md`, so it stays on
|
|
243
250
|
// the GPT-5.2 prompt family with every other post-5.2 general model. Adding
|
|
@@ -258,6 +265,20 @@ export const MODEL_PROFILES = {
|
|
|
258
265
|
supportedReasoningEfforts: GPT_6_ASTRA_EFFORTS,
|
|
259
266
|
capabilities: TOOL_CAPABILITIES.full,
|
|
260
267
|
},
|
|
268
|
+
[GPT_6_SOL_MODEL]: {
|
|
269
|
+
normalizedModel: GPT_6_SOL_MODEL,
|
|
270
|
+
promptFamily: "gpt-5.2",
|
|
271
|
+
defaultReasoningEffort: "medium",
|
|
272
|
+
supportedReasoningEfforts: GPT_6_SOL_EFFORTS,
|
|
273
|
+
capabilities: TOOL_CAPABILITIES.full,
|
|
274
|
+
},
|
|
275
|
+
[GPT_6_LUNA_MODEL]: {
|
|
276
|
+
normalizedModel: GPT_6_LUNA_MODEL,
|
|
277
|
+
promptFamily: "gpt-5.2",
|
|
278
|
+
defaultReasoningEffort: "medium",
|
|
279
|
+
supportedReasoningEfforts: GPT_6_LUNA_EFFORTS,
|
|
280
|
+
capabilities: TOOL_CAPABILITIES.full,
|
|
281
|
+
},
|
|
261
282
|
[DAYBREAK_BLUE_MODEL]: {
|
|
262
283
|
normalizedModel: DAYBREAK_BLUE_MODEL,
|
|
263
284
|
promptFamily: "gpt-5.2",
|
|
@@ -310,41 +331,12 @@ export const MODEL_PROFILES = {
|
|
|
310
331
|
supportedReasoningEfforts: ["medium", "high", "xhigh"],
|
|
311
332
|
capabilities: TOOL_CAPABILITIES.computerAndCompact,
|
|
312
333
|
},
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
},
|
|
320
|
-
"gpt-5.2": {
|
|
321
|
-
normalizedModel: "gpt-5.2",
|
|
322
|
-
promptFamily: "gpt-5.2",
|
|
323
|
-
defaultReasoningEffort: "none",
|
|
324
|
-
supportedReasoningEfforts: ["none", "low", "medium", "high", "xhigh"],
|
|
325
|
-
capabilities: TOOL_CAPABILITIES.basic,
|
|
326
|
-
},
|
|
327
|
-
"gpt-5.1": {
|
|
328
|
-
normalizedModel: "gpt-5.1",
|
|
329
|
-
promptFamily: "gpt-5.1",
|
|
330
|
-
defaultReasoningEffort: "none",
|
|
331
|
-
supportedReasoningEfforts: ["none", "low", "medium", "high"],
|
|
332
|
-
capabilities: TOOL_CAPABILITIES.basic,
|
|
333
|
-
},
|
|
334
|
-
"gpt-5-mini": {
|
|
335
|
-
normalizedModel: "gpt-5-mini",
|
|
336
|
-
promptFamily: "gpt-5.2",
|
|
337
|
-
defaultReasoningEffort: "medium",
|
|
338
|
-
supportedReasoningEfforts: ["medium"],
|
|
339
|
-
capabilities: TOOL_CAPABILITIES.compactOnly,
|
|
340
|
-
},
|
|
341
|
-
"gpt-5-nano": {
|
|
342
|
-
normalizedModel: "gpt-5-nano",
|
|
343
|
-
promptFamily: "gpt-5.2",
|
|
344
|
-
defaultReasoningEffort: "medium",
|
|
345
|
-
supportedReasoningEfforts: ["medium"],
|
|
346
|
-
capabilities: TOOL_CAPABILITIES.compactOnly,
|
|
347
|
-
},
|
|
334
|
+
// Every model older than 5.5 is retired: `gpt-5.1`, `gpt-5.2` and the whole
|
|
335
|
+
// 5.4 family left the upstream Codex catalog, the codex models and the
|
|
336
|
+
// chat-latest snapshots are past their shutdown date on OpenAI's
|
|
337
|
+
// deprecations page, and `gpt-5-mini`/`gpt-5-nano` shut down 2026-12-11.
|
|
338
|
+
// Their ids stay accepted as aliases of the named replacement (below) so an
|
|
339
|
+
// old config keeps working, but no request is sent under a dead name.
|
|
348
340
|
};
|
|
349
341
|
const MODEL_MAP = {};
|
|
350
342
|
function addAlias(alias, normalizedModel) {
|
|
@@ -382,6 +374,11 @@ function addGpt6Aliases() {
|
|
|
382
374
|
// rather than letting it fall through to GPT-5.5.
|
|
383
375
|
addEffortAliases("astra", GPT_6_ASTRA_MODEL, GPT_6_ASTRA_EFFORTS);
|
|
384
376
|
addEffortAliases("astra-aeon", GPT_6_ASTRA_AEON_MODEL, GPT_6_ASTRA_EFFORTS);
|
|
377
|
+
// No bare `sol`/`luna` aliases: those names already mean the 5.6 tiers to
|
|
378
|
+
// anyone who used them before 2026-09-22, and re-pointing them would swap a
|
|
379
|
+
// user's model generation without asking.
|
|
380
|
+
addEffortAliases(GPT_6_SOL_MODEL, GPT_6_SOL_MODEL, GPT_6_SOL_EFFORTS);
|
|
381
|
+
addEffortAliases(GPT_6_LUNA_MODEL, GPT_6_LUNA_MODEL, GPT_6_LUNA_EFFORTS);
|
|
385
382
|
}
|
|
386
383
|
function addDaybreakAliases() {
|
|
387
384
|
addEffortAliases(DAYBREAK_BLUE_MODEL, DAYBREAK_BLUE_MODEL, DAYBREAK_EFFORTS);
|
|
@@ -396,35 +393,61 @@ function addGeneralAliases() {
|
|
|
396
393
|
addReasoningAliases(GPT_5_5_PRO_CANONICAL_MODEL, GPT_5_5_PRO_CANONICAL_MODEL);
|
|
397
394
|
addReasoningAliases(GPT_5_5_PRO_RELEASE_MODEL, GPT_5_5_PRO_CANONICAL_MODEL);
|
|
398
395
|
addReasoningAliases(GPT_5_5_PRO_RELEASE_COMPAT_MODEL, GPT_5_5_PRO_CANONICAL_MODEL);
|
|
399
|
-
addReasoningAliases("gpt-5.4", "gpt-5.4");
|
|
400
|
-
addReasoningAliases("gpt-5.4-pro", "gpt-5.4-pro");
|
|
401
|
-
addReasoningAliases("gpt-5.4-mini", "gpt-5.4-mini");
|
|
402
|
-
addReasoningAliases("gpt-5.4-nano", "gpt-5.4-nano");
|
|
403
|
-
addReasoningAliases("gpt-5.2-pro", "gpt-5.2-pro");
|
|
404
396
|
addReasoningAliases("gpt-5-pro", GPT_5_5_PRO_CANONICAL_MODEL);
|
|
405
|
-
addReasoningAliases("gpt-5.2", "gpt-5.2");
|
|
406
|
-
addReasoningAliases("gpt-5.1", "gpt-5.1");
|
|
407
397
|
addReasoningAliases("gpt-5", DEFAULT_MODEL);
|
|
408
|
-
addReasoningAliases("gpt-5-mini", "gpt-5-mini");
|
|
409
|
-
addReasoningAliases("gpt-5-nano", "gpt-5-nano");
|
|
410
|
-
addReasoningAliases("gpt-5.1-chat-latest", "gpt-5.1");
|
|
411
|
-
addReasoningAliases("gpt-5-chat-latest", DEFAULT_MODEL);
|
|
412
398
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
399
|
+
/**
|
|
400
|
+
* Retired general ids, each kept as an alias of the replacement OpenAI names
|
|
401
|
+
* for it: the upstream Codex catalog's migration target where it has one
|
|
402
|
+
* (`gpt-5.4` -> GPT-6 Sol, `gpt-5.4-mini` -> GPT-6 Luna), otherwise the
|
|
403
|
+
* deprecations page's recommended replacement. The effort-suffixed forms
|
|
404
|
+
* (`gpt-5.4-high`, `gpt-5.1-none`, ...) come along so old configs resolve;
|
|
405
|
+
* efforts the replacement does not accept are coerced as for any request.
|
|
406
|
+
*/
|
|
407
|
+
const RETIRED_GENERAL_MODEL_REPLACEMENTS = {
|
|
408
|
+
"gpt-5.4": GPT_6_SOL_MODEL,
|
|
409
|
+
"gpt-5.4-mini": GPT_6_LUNA_MODEL,
|
|
410
|
+
"gpt-5.4-nano": GPT_6_LUNA_MODEL,
|
|
411
|
+
"gpt-5.4-pro": GPT_5_5_PRO_CANONICAL_MODEL,
|
|
412
|
+
"gpt-5.2": GPT_5_6_SOL_MODEL,
|
|
413
|
+
"gpt-5.2-pro": GPT_5_5_PRO_CANONICAL_MODEL,
|
|
414
|
+
"gpt-5.1": GPT_5_6_SOL_MODEL,
|
|
415
|
+
"gpt-5-mini": GPT_5_6_TERRA_MODEL,
|
|
416
|
+
"gpt-5-nano": GPT_5_6_LUNA_MODEL,
|
|
417
|
+
"gpt-5-chat-latest": GPT_5_6_SOL_MODEL,
|
|
418
|
+
"gpt-5.1-chat-latest": GPT_5_6_SOL_MODEL,
|
|
419
|
+
"gpt-5.2-chat-latest": GPT_5_6_SOL_MODEL,
|
|
420
|
+
"gpt-5.3-chat-latest": GPT_5_6_SOL_MODEL,
|
|
421
|
+
};
|
|
422
|
+
/**
|
|
423
|
+
* Retired codex ids. All shut down on OpenAI's deprecations page or left the
|
|
424
|
+
* upstream Codex catalog; OpenAI names `gpt-5.6-sol` as the replacement for
|
|
425
|
+
* the full-size ones and `gpt-5.6-terra` for `gpt-5.1-codex-mini`.
|
|
426
|
+
*/
|
|
427
|
+
const RETIRED_CODEX_MODEL_REPLACEMENTS = {
|
|
428
|
+
"gpt-5.3-codex": CURRENT_CODEX_MODEL,
|
|
429
|
+
"gpt-5.3-codex-spark": CURRENT_CODEX_MODEL,
|
|
430
|
+
"gpt-5.2-codex": CURRENT_CODEX_MODEL,
|
|
431
|
+
"gpt-5.1-codex": CURRENT_CODEX_MODEL,
|
|
432
|
+
"gpt-5.1-codex-max": CURRENT_CODEX_MODEL,
|
|
433
|
+
"gpt-5-codex": CURRENT_CODEX_MODEL,
|
|
434
|
+
"codex-max": CURRENT_CODEX_MODEL,
|
|
435
|
+
"gpt-5.1-codex-mini": CODEX_MINI_REPLACEMENT_MODEL,
|
|
436
|
+
"gpt-5-codex-mini": CODEX_MINI_REPLACEMENT_MODEL,
|
|
437
|
+
"codex-mini-latest": CODEX_MINI_REPLACEMENT_MODEL,
|
|
438
|
+
};
|
|
439
|
+
/** Every retired id and its replacement, for diagnostics and tests. */
|
|
440
|
+
export const RETIRED_MODEL_REPLACEMENTS = {
|
|
441
|
+
...RETIRED_GENERAL_MODEL_REPLACEMENTS,
|
|
442
|
+
...RETIRED_CODEX_MODEL_REPLACEMENTS,
|
|
443
|
+
};
|
|
444
|
+
function addRetiredAliases() {
|
|
445
|
+
for (const [retired, replacement] of Object.entries(RETIRED_MODEL_REPLACEMENTS)) {
|
|
446
|
+
addReasoningAliases(retired, replacement);
|
|
447
|
+
}
|
|
419
448
|
addAlias("gpt_5_codex", CURRENT_CODEX_MODEL);
|
|
420
|
-
addReasoningAliases("codex-max", CURRENT_CODEX_MODEL);
|
|
421
|
-
addReasoningAliases("gpt-5.1-codex-max", CURRENT_CODEX_MODEL);
|
|
422
|
-
addAlias("codex-max", CURRENT_CODEX_MODEL);
|
|
423
|
-
addAlias("codex-mini-latest", CURRENT_CODEX_MODEL);
|
|
424
|
-
addReasoningAliases("gpt-5-codex-mini", CURRENT_CODEX_MODEL);
|
|
425
|
-
addReasoningAliases("gpt-5.1-codex-mini", CURRENT_CODEX_MODEL);
|
|
426
449
|
}
|
|
427
|
-
|
|
450
|
+
addRetiredAliases();
|
|
428
451
|
addGeneralAliases();
|
|
429
452
|
addGpt56Aliases();
|
|
430
453
|
addGpt6Aliases();
|
|
@@ -461,39 +484,21 @@ function resolveStableGeneralGpt5Variant(variant) {
|
|
|
461
484
|
}
|
|
462
485
|
throw new Error(`Stable GPT-5 fallback is missing for variant ${variant}`);
|
|
463
486
|
}
|
|
487
|
+
/**
|
|
488
|
+
* Any id carrying a `codex` token. Every codex model is retired, so this only
|
|
489
|
+
* picks the replacement: a `mini` codex id goes to Terra, everything else to
|
|
490
|
+
* Sol (see RETIRED_CODEX_MODEL_REPLACEMENTS).
|
|
491
|
+
*/
|
|
464
492
|
function resolveCodexCatalogModel(modelId) {
|
|
465
493
|
const normalized = modelId.toLowerCase();
|
|
466
|
-
if (normalized.includes("
|
|
467
|
-
|
|
468
|
-
return CURRENT_CODEX_MODEL;
|
|
469
|
-
}
|
|
470
|
-
if (normalized.includes("gpt-5.3-codex") ||
|
|
471
|
-
normalized.includes("gpt 5.3 codex")) {
|
|
472
|
-
return CURRENT_CODEX_MODEL;
|
|
473
|
-
}
|
|
474
|
-
if (normalized.includes("gpt-5.2-codex") ||
|
|
475
|
-
normalized.includes("gpt 5.2 codex")) {
|
|
476
|
-
return CURRENT_CODEX_MODEL;
|
|
477
|
-
}
|
|
478
|
-
if (normalized.includes("gpt-5.1-codex-max") ||
|
|
479
|
-
normalized.includes("gpt 5.1 codex max")) {
|
|
480
|
-
return CURRENT_CODEX_MODEL;
|
|
481
|
-
}
|
|
482
|
-
if (normalized.includes("gpt-5.1-codex-mini") ||
|
|
483
|
-
normalized.includes("gpt 5.1 codex mini") ||
|
|
484
|
-
normalized.includes("codex-mini-latest") ||
|
|
485
|
-
normalized.includes("gpt-5-codex-mini") ||
|
|
486
|
-
normalized.includes("gpt 5 codex mini")) {
|
|
487
|
-
return CURRENT_CODEX_MODEL;
|
|
494
|
+
if (!normalized.includes("codex")) {
|
|
495
|
+
return undefined;
|
|
488
496
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
normalized.includes("gpt 5.1 codex") ||
|
|
493
|
-
normalized.includes("codex")) {
|
|
494
|
-
return CURRENT_CODEX_MODEL;
|
|
497
|
+
// Anchored so `codex-minimal` (an effort suffix) is not read as a mini.
|
|
498
|
+
if (/codex[- ]mini(?!mal)/.test(normalized)) {
|
|
499
|
+
return CODEX_MINI_REPLACEMENT_MODEL;
|
|
495
500
|
}
|
|
496
|
-
return
|
|
501
|
+
return CURRENT_CODEX_MODEL;
|
|
497
502
|
}
|
|
498
503
|
/**
|
|
499
504
|
* Resolve GPT-5.6 identifiers that are not exact aliases (for example a future
|
|
@@ -514,8 +519,9 @@ function resolveCodexCatalogModel(modelId) {
|
|
|
514
519
|
* `gpt 5` token pair) and every unrecognised GPT-6 id lands on `DEFAULT_MODEL`,
|
|
515
520
|
* running GPT-5.5 for a caller who asked for the frontier model. `aeon` keeps
|
|
516
521
|
* its own canonical id because it is a behaviourally different model (long
|
|
517
|
-
* horizon), not a rename of the flagship
|
|
518
|
-
* flagship, matching OpenAI's bare
|
|
522
|
+
* horizon), not a rename of the flagship. A `sol`/`luna` tier token picks that
|
|
523
|
+
* tier; everything else resolves to the flagship, matching OpenAI's bare
|
|
524
|
+
* `gpt-6` alias.
|
|
519
525
|
*
|
|
520
526
|
* Ids carrying a `codex` token are left to `resolveCodexCatalogModel`, exactly
|
|
521
527
|
* as the 5.6 resolver defers them.
|
|
@@ -547,6 +553,16 @@ function resolveGpt6CatalogModel(modelId) {
|
|
|
547
553
|
}
|
|
548
554
|
if (tokens.includes("aeon"))
|
|
549
555
|
return GPT_6_ASTRA_AEON_MODEL;
|
|
556
|
+
if (isAstra)
|
|
557
|
+
return GPT_6_ASTRA_MODEL;
|
|
558
|
+
// Before Sol and Luna existed every non-Astra GPT-6 id fell to the line
|
|
559
|
+
// below, so `gpt-6-luna` silently ran Astra at 100x Luna's price. `terra`
|
|
560
|
+
// goes to Sol because there is no GPT-6 Terra and upstream migrates
|
|
561
|
+
// `gpt-5.6-terra` users to Sol.
|
|
562
|
+
if (tokens.includes("luna"))
|
|
563
|
+
return GPT_6_LUNA_MODEL;
|
|
564
|
+
if (tokens.includes("sol") || tokens.includes("terra"))
|
|
565
|
+
return GPT_6_SOL_MODEL;
|
|
550
566
|
return GPT_6_ASTRA_MODEL;
|
|
551
567
|
}
|
|
552
568
|
/**
|
|
@@ -647,6 +663,17 @@ export function resolveNormalizedModel(model) {
|
|
|
647
663
|
if (mappedModel) {
|
|
648
664
|
return mappedModel;
|
|
649
665
|
}
|
|
666
|
+
// `max`/`ultra` are not generated as aliases for the pre-5.6 ids, so
|
|
667
|
+
// `gpt-5.3-chat-latest-max` missed the retired table and the general GPT-5
|
|
668
|
+
// resolver sent it to 5.5 instead of its replacement. Retry the lookup
|
|
669
|
+
// without the suffix before any fuzzy resolver runs.
|
|
670
|
+
const withoutTopEffort = modelId.replace(/-(max|ultra)$/i, "");
|
|
671
|
+
if (withoutTopEffort !== modelId) {
|
|
672
|
+
const mappedWithoutEffort = lookupMappedModel(withoutTopEffort);
|
|
673
|
+
if (mappedWithoutEffort) {
|
|
674
|
+
return mappedWithoutEffort;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
650
677
|
// Daybreak first: its slugs carry neither a `codex` nor a `gpt 5` token, so
|
|
651
678
|
// every other resolver declines them and they would reach DEFAULT_MODEL.
|
|
652
679
|
const daybreakCatalogModel = resolveDaybreakCatalogModel(modelId);
|
|
@@ -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;
|
|
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) {
|
|
@@ -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;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;
|
|
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;AA0fD;;;;;;;;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"}
|
|
@@ -208,6 +208,12 @@ const ALLOWED_MODELS_PATHS = new Set([
|
|
|
208
208
|
URL_PATHS.MODELS,
|
|
209
209
|
`/v1${URL_PATHS.MODELS}`,
|
|
210
210
|
]);
|
|
211
|
+
const ALLOWED_IMAGE_PATHS = new Set([
|
|
212
|
+
"/images/generations",
|
|
213
|
+
"/images/edits",
|
|
214
|
+
"/v1/images/generations",
|
|
215
|
+
"/v1/images/edits",
|
|
216
|
+
]);
|
|
211
217
|
const ALLOWED_THREAD_GOAL_PATHS = new Set([
|
|
212
218
|
"/thread/goal/get",
|
|
213
219
|
"/thread/goal/set",
|
|
@@ -252,6 +258,8 @@ function createOutboundHeaders(incoming, account, accessToken, accountId) {
|
|
|
252
258
|
// header would ride along with the managed OAuth Bearer to OpenAI.
|
|
253
259
|
headers.delete("cookie");
|
|
254
260
|
headers.delete("proxy-authorization");
|
|
261
|
+
// Local capability marker for Codex image_gen, never an upstream credential.
|
|
262
|
+
headers.delete("x-openai-actor-authorization");
|
|
255
263
|
headers.set("authorization", `Bearer ${accessToken}`);
|
|
256
264
|
headers.set(OPENAI_HEADERS.ACCOUNT_ID, accountId);
|
|
257
265
|
headers.set(OPENAI_HEADERS.BETA, OPENAI_HEADER_VALUES.BETA_RESPONSES);
|
|
@@ -488,16 +496,22 @@ function buildResponsesRequestContext(req, body) {
|
|
|
488
496
|
method: "POST",
|
|
489
497
|
upstreamPath: URL_PATHS.CODEX_RESPONSES,
|
|
490
498
|
model,
|
|
491
|
-
//
|
|
492
|
-
//
|
|
493
|
-
// the
|
|
494
|
-
// family is gpt-5.2 and would mis-account a pass-through codex request.
|
|
499
|
+
// A model-less request buckets under the model a codex request runs on.
|
|
500
|
+
// With every codex model retired that is `gpt-5.6-sol`, whose family is
|
|
501
|
+
// gpt-5.2, the same family as DEFAULT_MODEL.
|
|
495
502
|
family: getModelFamily(model ?? CURRENT_CODEX_MODEL),
|
|
496
503
|
stream: parsedBody?.stream === true,
|
|
497
504
|
sessionKey: resolveSessionKey(headers, parsedBody),
|
|
498
505
|
stableSessionKey: resolveStableSessionKey(headers, parsedBody),
|
|
499
506
|
};
|
|
500
507
|
}
|
|
508
|
+
function buildImageRequestContext(req, body, pathname) {
|
|
509
|
+
return {
|
|
510
|
+
...buildResponsesRequestContext(req, body),
|
|
511
|
+
upstreamPath: `/codex${pathname.replace(/^\/v1/, "")}`,
|
|
512
|
+
family: "codex",
|
|
513
|
+
};
|
|
514
|
+
}
|
|
501
515
|
function buildModelsRequestContext(req) {
|
|
502
516
|
return {
|
|
503
517
|
body: Buffer.alloc(0),
|
|
@@ -555,7 +569,7 @@ function writeJson(res, status, payload) {
|
|
|
555
569
|
function writeMethodOrPathError(res) {
|
|
556
570
|
writeJson(res, 404, {
|
|
557
571
|
error: {
|
|
558
|
-
message: "Runtime rotation proxy only accepts Responses API, model discovery, and Codex thread goal requests.",
|
|
572
|
+
message: "Runtime rotation proxy only accepts Responses API, images, model discovery, and Codex thread goal requests.",
|
|
559
573
|
code: "runtime_rotation_proxy_not_found",
|
|
560
574
|
},
|
|
561
575
|
});
|
|
@@ -803,21 +817,26 @@ async function handleRequestInner(state, req, res, traceId) {
|
|
|
803
817
|
}
|
|
804
818
|
const isResponsesRequest = req.method === "POST" && isResponsesPath(incomingUrl.pathname);
|
|
805
819
|
const isModelsRequest = req.method === "GET" && isModelsPath(incomingUrl.pathname);
|
|
820
|
+
const isImageRequest = req.method === "POST" && ALLOWED_IMAGE_PATHS.has(incomingUrl.pathname);
|
|
806
821
|
const isThreadGoalRequest = (req.method === "GET" || req.method === "POST") &&
|
|
807
822
|
isThreadGoalPath(incomingUrl.pathname);
|
|
808
|
-
if (!isResponsesRequest && !isModelsRequest &&
|
|
823
|
+
if (!isResponsesRequest && !isModelsRequest &&
|
|
824
|
+
!isThreadGoalRequest && !isImageRequest) {
|
|
809
825
|
writeMethodOrPathError(res);
|
|
810
826
|
return;
|
|
811
827
|
}
|
|
812
828
|
state.status.totalRequests += 1;
|
|
813
|
-
const requestBody = isResponsesRequest ||
|
|
829
|
+
const requestBody = isResponsesRequest || isImageRequest ||
|
|
830
|
+
(isThreadGoalRequest && req.method === "POST")
|
|
814
831
|
? await readRequestBody(req, state.maxRequestBodyBytes)
|
|
815
832
|
: Buffer.alloc(0);
|
|
816
833
|
const context = isModelsRequest
|
|
817
834
|
? buildModelsRequestContext(req)
|
|
818
835
|
: isThreadGoalRequest
|
|
819
836
|
? buildThreadGoalRequestContext(req, requestBody, incomingUrl.pathname)
|
|
820
|
-
:
|
|
837
|
+
: isImageRequest
|
|
838
|
+
? buildImageRequestContext(req, requestBody, incomingUrl.pathname)
|
|
839
|
+
: buildResponsesRequestContext(req, requestBody);
|
|
821
840
|
const requestStartedAt = state.now();
|
|
822
841
|
let policyDecision = null;
|
|
823
842
|
let projectKey = null;
|
|
@@ -848,7 +867,9 @@ async function handleRequestInner(state, req, res, traceId) {
|
|
|
848
867
|
? "models"
|
|
849
868
|
: isThreadGoalRequest
|
|
850
869
|
? "thread-goal"
|
|
851
|
-
:
|
|
870
|
+
: isImageRequest
|
|
871
|
+
? "images"
|
|
872
|
+
: "responses",
|
|
852
873
|
model: context.model,
|
|
853
874
|
projectKey,
|
|
854
875
|
requestId: traceId,
|
|
@@ -1287,9 +1308,27 @@ async function handleRequestInner(state, req, res, traceId) {
|
|
|
1287
1308
|
recordLastRuntimeAccount(state.status, accountIdentity);
|
|
1288
1309
|
const outboundHeaders = createOutboundHeaders(context.headers, refreshed.account, refreshed.accessToken, accountId);
|
|
1289
1310
|
let upstream;
|
|
1311
|
+
// Abort the in-flight upstream fetch when the client disconnects
|
|
1312
|
+
// before headers arrive. Image generation holds upstream capacity
|
|
1313
|
+
// for the full fetch timeout, so a caller that goes away right
|
|
1314
|
+
// after sending must not leave that work running. `forwardStreamingResponse`
|
|
1315
|
+
// already cancels the stream once headers are written; this covers
|
|
1316
|
+
// the pre-header window instead. `writableEnded` distinguishes a
|
|
1317
|
+
// premature close from the clean `res.end()` that ends every request.
|
|
1318
|
+
//
|
|
1319
|
+
// The listener is removed in `finally` once the fetch settles. Without
|
|
1320
|
+
// that, every retry above the 10-listener default threshold that reaches
|
|
1321
|
+
// this fetch leaks another one-shot `close` handler onto the same `res`,
|
|
1322
|
+
// emitting `MaxListenersExceededWarning` when `retryAllAccountsMaxRetries`
|
|
1323
|
+
// is high and the account pool is large.
|
|
1324
|
+
const fetchAbortController = new AbortController();
|
|
1325
|
+
const onClientClose = () => {
|
|
1326
|
+
if (!res.writableEnded)
|
|
1327
|
+
fetchAbortController.abort();
|
|
1328
|
+
};
|
|
1290
1329
|
try {
|
|
1291
1330
|
state.status.upstreamRequests += 1;
|
|
1292
|
-
|
|
1331
|
+
res.once("close", onClientClose);
|
|
1293
1332
|
const upstreamRequestInit = {
|
|
1294
1333
|
method: context.method,
|
|
1295
1334
|
headers: outboundHeaders,
|
|
@@ -1298,7 +1337,10 @@ async function handleRequestInner(state, req, res, traceId) {
|
|
|
1298
1337
|
if (context.method === "POST") {
|
|
1299
1338
|
upstreamRequestInit.body = context.body;
|
|
1300
1339
|
}
|
|
1301
|
-
|
|
1340
|
+
const fetchTimeoutMs = isImageRequest
|
|
1341
|
+
? Math.max(state.fetchTimeoutMs, 300_000)
|
|
1342
|
+
: state.fetchTimeoutMs;
|
|
1343
|
+
upstream = await withTimeout(state.fetchImpl(upstreamUrl, upstreamRequestInit), fetchTimeoutMs, () => fetchAbortController.abort(), `upstream fetch timed out after ${fetchTimeoutMs}ms`);
|
|
1302
1344
|
}
|
|
1303
1345
|
catch (error) {
|
|
1304
1346
|
// errors-logging-08: a custom fetchImpl, a proxy agent, or an undici
|
|
@@ -1315,6 +1357,20 @@ async function handleRequestInner(state, req, res, traceId) {
|
|
|
1315
1357
|
error: transportError,
|
|
1316
1358
|
});
|
|
1317
1359
|
refundConsumedPoolToken(refreshed.account);
|
|
1360
|
+
// A timeout may occur after generation; do not retry within this request.
|
|
1361
|
+
if (isImageRequest) {
|
|
1362
|
+
writeJson(res, 502, {
|
|
1363
|
+
error: {
|
|
1364
|
+
code: "image_upstream_transport_error",
|
|
1365
|
+
message: "Image upstream transport failed; not retried by the proxy.",
|
|
1366
|
+
},
|
|
1367
|
+
});
|
|
1368
|
+
await usageRecorder.record({
|
|
1369
|
+
outcome: "failure", statusCode: 502,
|
|
1370
|
+
errorCode: "image_upstream_transport_error", account: refreshed.account,
|
|
1371
|
+
});
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1318
1374
|
// A pre-header transport exception is a property of the network path,
|
|
1319
1375
|
// not of this account's credentials or quota, so it must NOT feed the
|
|
1320
1376
|
// account's circuit breaker / health tracker: that is what would
|
|
@@ -1337,6 +1393,9 @@ async function handleRequestInner(state, req, res, traceId) {
|
|
|
1337
1393
|
noteRotation();
|
|
1338
1394
|
continue;
|
|
1339
1395
|
}
|
|
1396
|
+
finally {
|
|
1397
|
+
res.off("close", onClientClose);
|
|
1398
|
+
}
|
|
1340
1399
|
reconcileManualSelection();
|
|
1341
1400
|
const quotaSnapshot = readQuotaSchedulerSnapshot(upstream.headers, upstream.status, state.now());
|
|
1342
1401
|
if (quotaSnapshot) {
|
|
@@ -1507,6 +1566,17 @@ async function handleRequestInner(state, req, res, traceId) {
|
|
|
1507
1566
|
noteRotation();
|
|
1508
1567
|
continue;
|
|
1509
1568
|
}
|
|
1569
|
+
// Forward image validation/moderation/5xx errors without cross-account replay.
|
|
1570
|
+
// Explicit 429 quota and 401 auth handling above still use the existing pool.
|
|
1571
|
+
if (isImageRequest && upstream.status >= 400) {
|
|
1572
|
+
const forwarded = await forwardStreamingResponse(upstream, res, state.status, () => undefined, state.streamStallTimeoutMs);
|
|
1573
|
+
await usageRecorder.record({
|
|
1574
|
+
outcome: "failure", statusCode: upstream.status,
|
|
1575
|
+
errorCode: forwarded ? "image_upstream_error" : "stream_forward_failed",
|
|
1576
|
+
account: refreshed.account,
|
|
1577
|
+
});
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1510
1580
|
if (upstream.status >= 500) {
|
|
1511
1581
|
const bodyText = await readErrorBody(upstream, state.streamStallTimeoutMs);
|
|
1512
1582
|
// A capacity 5xx is upstream load, not a broken account, so it
|
|
@@ -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;
|
|
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"}
|