codex-multi-auth 2.12.0 → 2.13.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 +7 -1
- package/dist/lib/accounts.d.ts +75 -0
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +213 -42
- package/dist/lib/codex-manager/account-manager-commands.d.ts.map +1 -1
- package/dist/lib/codex-manager/account-manager-commands.js +1 -0
- package/dist/lib/codex-manager/commands/limits.d.ts +21 -0
- package/dist/lib/codex-manager/commands/limits.d.ts.map +1 -0
- package/dist/lib/codex-manager/commands/limits.js +158 -0
- package/dist/lib/codex-manager/help.d.ts.map +1 -1
- package/dist/lib/codex-manager/help.js +1 -0
- package/dist/lib/codex-manager/persist-selected-account.d.ts.map +1 -1
- package/dist/lib/codex-manager/persist-selected-account.js +14 -0
- package/dist/lib/codex-manager.d.ts.map +1 -1
- package/dist/lib/codex-manager.js +12 -0
- package/dist/lib/forecast.d.ts.map +1 -1
- package/dist/lib/forecast.js +1 -14
- package/dist/lib/preemptive-quota-scheduler.d.ts +12 -0
- package/dist/lib/preemptive-quota-scheduler.d.ts.map +1 -1
- package/dist/lib/preemptive-quota-scheduler.js +21 -0
- package/dist/lib/redaction.d.ts +17 -0
- package/dist/lib/redaction.d.ts.map +1 -0
- package/dist/lib/redaction.js +28 -0
- package/dist/lib/runtime/rotation-storage-meta.d.ts.map +1 -1
- package/dist/lib/runtime/rotation-storage-meta.js +4 -1
- package/dist/lib/runtime-rotation-proxy.d.ts +13 -0
- package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
- package/dist/lib/runtime-rotation-proxy.js +47 -7
- package/dist/lib/storage.d.ts +20 -2
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +69 -25
- package/package.json +1 -1
- package/scripts/codex-routing.js +1 -0
- package/scripts/codex.js +157 -2
package/dist/lib/storage.js
CHANGED
|
@@ -898,7 +898,8 @@ export function normalizeAccountStorage(data) {
|
|
|
898
898
|
return undefined;
|
|
899
899
|
if (typeof raw !== "number" ||
|
|
900
900
|
!Number.isFinite(raw) ||
|
|
901
|
-
|
|
901
|
+
// See readAffinityGenerationFromDisk: an unsafe integer wedges the bump.
|
|
902
|
+
!Number.isSafeInteger(raw) ||
|
|
902
903
|
raw < 0) {
|
|
903
904
|
log.warn("Dropping invalid affinityGeneration from storage", {
|
|
904
905
|
value: raw,
|
|
@@ -941,7 +942,11 @@ export function readAffinityGenerationFromDisk(path) {
|
|
|
941
942
|
const generation = parsed.affinityGeneration;
|
|
942
943
|
if (typeof generation === "number" &&
|
|
943
944
|
Number.isFinite(generation) &&
|
|
944
|
-
|
|
945
|
+
// Safe, not merely integral: past 2^53 `Math.max(gen, disk) + 1 === gen`,
|
|
946
|
+
// so `bumpStorageAffinityGeneration` silently stops advancing and no
|
|
947
|
+
// running proxy ever observes another `switch`. Rejecting the value
|
|
948
|
+
// lets the next bump write 1 and repair the file.
|
|
949
|
+
Number.isSafeInteger(generation) &&
|
|
945
950
|
generation >= 0) {
|
|
946
951
|
return generation;
|
|
947
952
|
}
|
|
@@ -978,7 +983,12 @@ export function bumpStorageAffinityGeneration(storage, storagePath) {
|
|
|
978
983
|
// No resolvable storage path (never configured). The in-memory counter
|
|
979
984
|
// is still bumped below; only the lost-update guard is unavailable.
|
|
980
985
|
}
|
|
981
|
-
|
|
986
|
+
// Guard the in-memory side too, not just the disk read. Past 2^53
|
|
987
|
+
// `Math.max(gen, disk) + 1 === gen`, so an unsafe value here would write the
|
|
988
|
+
// same generation forever and no running proxy would observe another
|
|
989
|
+
// `switch`. Treating it as 0 makes the next write repair the counter.
|
|
990
|
+
const rawInMemory = storage.affinityGeneration ?? 0;
|
|
991
|
+
const inMemoryGeneration = Number.isSafeInteger(rawInMemory) && rawInMemory >= 0 ? rawInMemory : 0;
|
|
982
992
|
const next = Math.max(inMemoryGeneration, diskGeneration) + 1;
|
|
983
993
|
storage.affinityGeneration = next;
|
|
984
994
|
return next;
|
|
@@ -990,34 +1000,68 @@ export function bumpStorageAffinityGeneration(storage, storagePath) {
|
|
|
990
1000
|
* persisting routine state (rate-limit hits, cooldowns, etc.) so a CLI
|
|
991
1001
|
* `switch`/`unpin` that landed between proxy startup and the save is not
|
|
992
1002
|
* clobbered. Returns `pinnedAccountIndex: undefined` and
|
|
993
|
-
* `affinityGeneration: 0` on any failure. See issue #474.
|
|
1003
|
+
* `affinityGeneration: 0` on any failure by default. See issue #474.
|
|
1004
|
+
*
|
|
1005
|
+
* `strict` callers additionally need to know when the metadata could not be
|
|
1006
|
+
* OBSERVED AT ALL, because persisting a stale in-memory pin over a newer disk
|
|
1007
|
+
* pin is exactly the #474 regression. Strict mode therefore throws for the two
|
|
1008
|
+
* cases where a newer selection may be hiding behind the failure:
|
|
1009
|
+
* - the file exists but cannot be read (EBUSY/EPERM/EACCES: on Windows an AV
|
|
1010
|
+
* scanner or indexer can hold accounts.json open), and
|
|
1011
|
+
* - the bytes are not valid JSON (a torn read of a concurrent atomic write).
|
|
1012
|
+
*
|
|
1013
|
+
* A MISSING file and a successfully parsed file with INVALID field values are
|
|
1014
|
+
* deliberately not failures, in either mode. Neither can be concealing a newer
|
|
1015
|
+
* pin (there is no file, or we can see the whole file and the field is
|
|
1016
|
+
* unusable), and both are self-repaired by the very write that follows. Making
|
|
1017
|
+
* them throw would wedge persistence permanently, since recreating or repairing
|
|
1018
|
+
* storage goes through the same save path, and it would take every rate-limit
|
|
1019
|
+
* window, cooldown and rotated refresh token in memory down with it.
|
|
994
1020
|
*/
|
|
995
|
-
export function readPinAndGenFromDisk(path) {
|
|
1021
|
+
export function readPinAndGenFromDisk(path, options) {
|
|
1022
|
+
const unselected = {
|
|
1023
|
+
pinnedAccountIndex: undefined,
|
|
1024
|
+
affinityGeneration: 0,
|
|
1025
|
+
};
|
|
996
1026
|
if (!existsSync(path)) {
|
|
997
|
-
return {
|
|
1027
|
+
return { ...unselected };
|
|
998
1028
|
}
|
|
1029
|
+
let parsed;
|
|
999
1030
|
try {
|
|
1000
|
-
|
|
1001
|
-
const parsed = JSON.parse(bytes.toString("utf8"));
|
|
1002
|
-
const rawPin = parsed.pinnedAccountIndex;
|
|
1003
|
-
const pinnedAccountIndex = typeof rawPin === "number" &&
|
|
1004
|
-
Number.isFinite(rawPin) &&
|
|
1005
|
-
Number.isInteger(rawPin) &&
|
|
1006
|
-
rawPin >= 0
|
|
1007
|
-
? rawPin
|
|
1008
|
-
: undefined;
|
|
1009
|
-
const rawGen = parsed.affinityGeneration;
|
|
1010
|
-
const affinityGeneration = typeof rawGen === "number" &&
|
|
1011
|
-
Number.isFinite(rawGen) &&
|
|
1012
|
-
Number.isInteger(rawGen) &&
|
|
1013
|
-
rawGen >= 0
|
|
1014
|
-
? rawGen
|
|
1015
|
-
: 0;
|
|
1016
|
-
return { pinnedAccountIndex, affinityGeneration };
|
|
1031
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
1017
1032
|
}
|
|
1018
|
-
catch {
|
|
1019
|
-
|
|
1033
|
+
catch (error) {
|
|
1034
|
+
// The file was deleted or renamed between existsSync and the read; same
|
|
1035
|
+
// reasoning as the missing-file branch above.
|
|
1036
|
+
if (error.code === "ENOENT") {
|
|
1037
|
+
return { ...unselected };
|
|
1038
|
+
}
|
|
1039
|
+
if (options?.strict) {
|
|
1040
|
+
throw new Error("Unable to read account selection metadata", {
|
|
1041
|
+
cause: error,
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
return { ...unselected };
|
|
1045
|
+
}
|
|
1046
|
+
if (!isRecord(parsed)) {
|
|
1047
|
+
return { ...unselected };
|
|
1020
1048
|
}
|
|
1049
|
+
const rawPin = parsed.pinnedAccountIndex;
|
|
1050
|
+
const pinnedAccountIndex = typeof rawPin === "number" &&
|
|
1051
|
+
Number.isFinite(rawPin) &&
|
|
1052
|
+
Number.isInteger(rawPin) &&
|
|
1053
|
+
rawPin >= 0
|
|
1054
|
+
? rawPin
|
|
1055
|
+
: undefined;
|
|
1056
|
+
const rawGen = parsed.affinityGeneration;
|
|
1057
|
+
const affinityGeneration = typeof rawGen === "number" &&
|
|
1058
|
+
Number.isFinite(rawGen) &&
|
|
1059
|
+
// See readAffinityGenerationFromDisk: an unsafe integer wedges the bump.
|
|
1060
|
+
Number.isSafeInteger(rawGen) &&
|
|
1061
|
+
rawGen >= 0
|
|
1062
|
+
? rawGen
|
|
1063
|
+
: 0;
|
|
1064
|
+
return { pinnedAccountIndex, affinityGeneration };
|
|
1021
1065
|
}
|
|
1022
1066
|
/**
|
|
1023
1067
|
* Loads OAuth accounts from disk storage.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-multi-auth",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.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-routing.js
CHANGED
package/scripts/codex.js
CHANGED
|
@@ -78,6 +78,8 @@ const APP_RUNTIME_HELPER_USE_CANONICAL_HOME_ENV =
|
|
|
78
78
|
"CODEX_MULTI_AUTH_APP_ROTATION_USE_CANONICAL_HOME";
|
|
79
79
|
const APP_RUNTIME_HELPER_INSTALL_APP_SERVER_SHIM_ENV =
|
|
80
80
|
"CODEX_MULTI_AUTH_APP_ROTATION_INSTALL_APP_SERVER_SHIM";
|
|
81
|
+
const RUNTIME_ROTATION_PROXY_UPSTREAM_BASE_URL_ENV =
|
|
82
|
+
"CODEX_MULTI_AUTH_RUNTIME_PROXY_UPSTREAM_BASE_URL";
|
|
81
83
|
const APP_SERVER_CONFIG_ARGS_ENV =
|
|
82
84
|
"CODEX_MULTI_AUTH_APP_SERVER_CONFIG_ARGS_JSON";
|
|
83
85
|
const APP_RUNTIME_HELPER_STATUS_FILE =
|
|
@@ -168,6 +170,95 @@ let warnedShadowHomeSqliteLinkFailure = false;
|
|
|
168
170
|
const warnedShadowHomeLinkOnlyDirectoryFailures = new Set();
|
|
169
171
|
const warnedShadowHomeSqliteSidecarPlaceholderFailures = new Set();
|
|
170
172
|
|
|
173
|
+
/**
|
|
174
|
+
* True only for a NUMERIC loopback literal, as `new URL().hostname` reports it.
|
|
175
|
+
*
|
|
176
|
+
* A name is deliberately not enough. `localhost` is resolved by the OS at
|
|
177
|
+
* connect time, so an /etc/hosts or Windows hosts entry can point it at a
|
|
178
|
+
* routable address, and the upstream request carries the managed OAuth bearer
|
|
179
|
+
* token and the request body. WHATWG URL always reports an IPv6 host in its
|
|
180
|
+
* bracketed form, so `::1` is only ever seen here as `[::1]`.
|
|
181
|
+
*/
|
|
182
|
+
function isLoopbackUrlHostname(hostname) {
|
|
183
|
+
if (hostname === "[::1]") {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
const ipv4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
|
|
187
|
+
if (!ipv4) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
const octets = ipv4.slice(1).map((part) => Number(part));
|
|
191
|
+
if (octets.some((octet) => !Number.isInteger(octet) || octet > 255)) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
// The whole 127.0.0.0/8 block is loopback, not just 127.0.0.1.
|
|
195
|
+
return octets[0] === 127;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Whether the raw URL text carries an explicit `:port`.
|
|
200
|
+
*
|
|
201
|
+
* `new URL()` erases a port that matches the scheme default, so
|
|
202
|
+
* `http://127.0.0.1:80/x` reports `parsed.port === ""` and a `!parsed.port`
|
|
203
|
+
* check would reject a perfectly valid, explicitly-ported loopback URL.
|
|
204
|
+
*/
|
|
205
|
+
function hasExplicitUrlPort(raw) {
|
|
206
|
+
const withoutScheme = raw.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "");
|
|
207
|
+
const authority = withoutScheme.split(/[/?#]/, 1)[0] ?? "";
|
|
208
|
+
// Strip userinfo first, or a password containing ':' reads as a port.
|
|
209
|
+
const hostAndPort = authority.slice(authority.lastIndexOf("@") + 1);
|
|
210
|
+
if (hostAndPort.startsWith("[")) {
|
|
211
|
+
const close = hostAndPort.indexOf("]");
|
|
212
|
+
return close !== -1 && /^:\d+$/.test(hostAndPort.slice(close + 1));
|
|
213
|
+
}
|
|
214
|
+
const colon = hostAndPort.indexOf(":");
|
|
215
|
+
return colon !== -1 && /^:\d+$/.test(hostAndPort.slice(colon));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function resolveRuntimeRotationProxyUpstreamBaseUrl(env = process.env) {
|
|
219
|
+
const raw = (env[RUNTIME_ROTATION_PROXY_UPSTREAM_BASE_URL_ENV] ?? "").trim();
|
|
220
|
+
if (!raw) {
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let parsed;
|
|
225
|
+
try {
|
|
226
|
+
parsed = new URL(raw);
|
|
227
|
+
} catch {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`${RUNTIME_ROTATION_PROXY_UPSTREAM_BASE_URL_ENV} must be an absolute loopback HTTP URL.`,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (
|
|
234
|
+
parsed.protocol !== "http:" ||
|
|
235
|
+
!isLoopbackUrlHostname(parsed.hostname.toLowerCase()) ||
|
|
236
|
+
!(parsed.port || hasExplicitUrlPort(raw)) ||
|
|
237
|
+
parsed.username ||
|
|
238
|
+
parsed.password ||
|
|
239
|
+
parsed.search ||
|
|
240
|
+
parsed.hash
|
|
241
|
+
) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
`${RUNTIME_ROTATION_PROXY_UPSTREAM_BASE_URL_ENV} must use HTTP with a numeric loopback host (127.0.0.0/8 or [::1]; a name such as "localhost" is not accepted because it can resolve off-host), an explicit port, and no credentials, query, or fragment.`,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return parsed.toString().replace(/\/$/, "");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* @param clientApiKey Shared secret the wrapper and proxy authenticate with.
|
|
252
|
+
* @param upstreamBaseUrl Already-resolved upstream, or undefined for the
|
|
253
|
+
* default backend. Callers pass the value they resolved rather than the env, so
|
|
254
|
+
* one launch never parses (and never re-reports) the same variable twice.
|
|
255
|
+
*/
|
|
256
|
+
function createRuntimeRotationProxyOptions(clientApiKey, upstreamBaseUrl) {
|
|
257
|
+
return upstreamBaseUrl
|
|
258
|
+
? { clientApiKey, upstreamBaseUrl }
|
|
259
|
+
: { clientApiKey };
|
|
260
|
+
}
|
|
261
|
+
|
|
171
262
|
async function loadRuntimeConstants() {
|
|
172
263
|
const fallback = {
|
|
173
264
|
RUNTIME_ROTATION_PROXY_PROVIDER_ID: `${APP_SERVER_ACCOUNT_DISPLAY_NAME}-runtime-proxy`,
|
|
@@ -4711,7 +4802,12 @@ async function runRuntimeRotationAppHelper(identityToken = "") {
|
|
|
4711
4802
|
throw new Error("runtime rotation config helpers are unavailable");
|
|
4712
4803
|
}
|
|
4713
4804
|
const clientApiKey = createRuntimeRotationProxyClientApiKey();
|
|
4714
|
-
proxyServer = await proxyModule.startRuntimeRotationProxy(
|
|
4805
|
+
proxyServer = await proxyModule.startRuntimeRotationProxy(
|
|
4806
|
+
createRuntimeRotationProxyOptions(
|
|
4807
|
+
clientApiKey,
|
|
4808
|
+
resolveRuntimeRotationProxyUpstreamBaseUrl(),
|
|
4809
|
+
),
|
|
4810
|
+
);
|
|
4715
4811
|
const useCanonicalHome =
|
|
4716
4812
|
(process.env[APP_RUNTIME_HELPER_USE_CANONICAL_HOME_ENV] ?? "").trim() ===
|
|
4717
4813
|
"1";
|
|
@@ -5112,13 +5208,52 @@ async function createRuntimeRotationProxyContextIfEnabled(
|
|
|
5112
5208
|
baseContext,
|
|
5113
5209
|
rawArgs,
|
|
5114
5210
|
) {
|
|
5211
|
+
// Resolve the configured upstream BEFORE the enabled gate. Every path that
|
|
5212
|
+
// leaves this function with `baseContext` forwards Responses traffic, and its
|
|
5213
|
+
// managed OAuth bearer token, straight to the real backend. An explicit
|
|
5214
|
+
// upstream is a routing requirement rather than a rotation preference, so
|
|
5215
|
+
// `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0`, `CODEX_MULTI_AUTH_BYPASS=1`, a
|
|
5216
|
+
// missing `dist/lib/config.js` or a disabled setting must all fail closed
|
|
5217
|
+
// here instead of silently reaching chatgpt.com.
|
|
5218
|
+
let configuredUpstreamBaseUrl;
|
|
5219
|
+
try {
|
|
5220
|
+
configuredUpstreamBaseUrl =
|
|
5221
|
+
resolveRuntimeRotationProxyUpstreamBaseUrl(baseContext.env);
|
|
5222
|
+
} catch (error) {
|
|
5223
|
+
baseContext.cleanup?.();
|
|
5224
|
+
return {
|
|
5225
|
+
startupError: `codex-multi-auth runtime rotation upstream is invalid: ${
|
|
5226
|
+
error instanceof Error ? error.message : String(error)
|
|
5227
|
+
}`,
|
|
5228
|
+
};
|
|
5229
|
+
}
|
|
5230
|
+
// The one exception is a subcommand that never reaches the backend at all
|
|
5231
|
+
// (`--version`, `login`, ...): there is no traffic to misroute, so it passes
|
|
5232
|
+
// through with the upstream simply unused.
|
|
5233
|
+
const requireConfiguredUpstream =
|
|
5234
|
+
configuredUpstreamBaseUrl !== undefined &&
|
|
5235
|
+
shouldUseRuntimeRoutingForForwardedArgs(rawArgs);
|
|
5236
|
+
|
|
5115
5237
|
const enabled = await isRuntimeRotationProxyEnabled(rawArgs, baseContext.env);
|
|
5116
5238
|
if (!enabled) {
|
|
5239
|
+
if (requireConfiguredUpstream) {
|
|
5240
|
+
baseContext.cleanup?.();
|
|
5241
|
+
return {
|
|
5242
|
+
startupError: `codex-multi-auth runtime rotation is disabled, so ${RUNTIME_ROTATION_PROXY_UPSTREAM_BASE_URL_ENV} cannot be honored and requests would go to the direct backend instead. Enable runtime rotation or unset that variable.`,
|
|
5243
|
+
};
|
|
5244
|
+
}
|
|
5117
5245
|
return baseContext;
|
|
5118
5246
|
}
|
|
5119
5247
|
|
|
5120
5248
|
const configTomlModule = await loadRuntimeConfigTomlModule();
|
|
5121
5249
|
if (!configTomlModule) {
|
|
5250
|
+
if (requireConfiguredUpstream) {
|
|
5251
|
+
baseContext.cleanup?.();
|
|
5252
|
+
return {
|
|
5253
|
+
startupError:
|
|
5254
|
+
"codex-multi-auth runtime rotation config helpers are unavailable; configured upstream routing cannot continue.",
|
|
5255
|
+
};
|
|
5256
|
+
}
|
|
5122
5257
|
console.error(
|
|
5123
5258
|
"codex-multi-auth runtime rotation config helpers are unavailable; continuing without runtime rotation.",
|
|
5124
5259
|
);
|
|
@@ -5188,6 +5323,13 @@ async function createRuntimeRotationProxyContextIfEnabled(
|
|
|
5188
5323
|
|
|
5189
5324
|
const proxyModule = await loadRuntimeRotationProxyModule();
|
|
5190
5325
|
if (!proxyModule) {
|
|
5326
|
+
if (requireConfiguredUpstream) {
|
|
5327
|
+
baseContext.cleanup?.();
|
|
5328
|
+
return {
|
|
5329
|
+
startupError:
|
|
5330
|
+
"codex-multi-auth runtime rotation proxy is unavailable; configured upstream routing cannot continue.",
|
|
5331
|
+
};
|
|
5332
|
+
}
|
|
5191
5333
|
console.error(
|
|
5192
5334
|
"codex-multi-auth runtime rotation proxy is unavailable; continuing without runtime rotation.",
|
|
5193
5335
|
);
|
|
@@ -5198,7 +5340,12 @@ async function createRuntimeRotationProxyContextIfEnabled(
|
|
|
5198
5340
|
let shadowContext;
|
|
5199
5341
|
try {
|
|
5200
5342
|
const clientApiKey = createRuntimeRotationProxyClientApiKey();
|
|
5201
|
-
proxyServer = await proxyModule.startRuntimeRotationProxy(
|
|
5343
|
+
proxyServer = await proxyModule.startRuntimeRotationProxy(
|
|
5344
|
+
createRuntimeRotationProxyOptions(
|
|
5345
|
+
clientApiKey,
|
|
5346
|
+
configuredUpstreamBaseUrl,
|
|
5347
|
+
),
|
|
5348
|
+
);
|
|
5202
5349
|
shadowContext = createRuntimeRotationProxyCodexHome(
|
|
5203
5350
|
baseContext.env,
|
|
5204
5351
|
proxyServer.baseUrl,
|
|
@@ -5211,6 +5358,14 @@ async function createRuntimeRotationProxyContextIfEnabled(
|
|
|
5211
5358
|
} catch {
|
|
5212
5359
|
// Best-effort cleanup only.
|
|
5213
5360
|
}
|
|
5361
|
+
if (requireConfiguredUpstream) {
|
|
5362
|
+
baseContext.cleanup?.();
|
|
5363
|
+
return {
|
|
5364
|
+
startupError: `codex-multi-auth runtime rotation proxy failed to start with configured upstream: ${
|
|
5365
|
+
error instanceof Error ? error.message : String(error)
|
|
5366
|
+
}`,
|
|
5367
|
+
};
|
|
5368
|
+
}
|
|
5214
5369
|
console.error(
|
|
5215
5370
|
`codex-multi-auth runtime rotation proxy failed to start; continuing without runtime rotation: ${error instanceof Error ? error.message : String(error)}`,
|
|
5216
5371
|
);
|