codex-multi-auth 2.21.0 → 2.23.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 +8 -5
- package/config/schema/config.schema.json +8 -0
- package/dist/lib/accounts.d.ts +5 -0
- package/dist/lib/accounts.js +33 -4
- package/dist/lib/api-route-store.d.ts +7 -0
- package/dist/lib/api-route-store.js +100 -31
- package/dist/lib/auth/account-access.js +2 -1
- package/dist/lib/auth/auth.js +3 -2
- package/dist/lib/auth/device-auth.js +2 -1
- package/dist/lib/auth/http.d.ts +1 -0
- package/dist/lib/auth/http.js +1 -0
- package/dist/lib/codex-manager/account-manager-commands.js +1 -0
- package/dist/lib/codex-manager/api-login-menu.d.ts +1 -1
- package/dist/lib/codex-manager/api-login-menu.js +4 -6
- package/dist/lib/codex-manager/commands/locale.d.ts +12 -0
- package/dist/lib/codex-manager/commands/locale.js +25 -0
- package/dist/lib/codex-manager/commands/status.d.ts +5 -1
- package/dist/lib/codex-manager/commands/status.js +501 -13
- package/dist/lib/codex-manager/help.js +3 -1
- package/dist/lib/codex-manager.js +98 -23
- package/dist/lib/config.js +3 -0
- package/dist/lib/constants.d.ts +1 -0
- package/dist/lib/constants.js +1 -0
- package/dist/lib/locale.d.ts +7 -0
- package/dist/lib/locale.js +35 -0
- package/dist/lib/model-route-policy.d.ts +1 -0
- package/dist/lib/model-route-policy.js +2 -1
- package/dist/lib/quota-probe.js +2 -1
- package/dist/lib/request/proxy-fetch.d.ts +5 -0
- package/dist/lib/request/proxy-fetch.js +37 -0
- package/dist/lib/request/url-rewriting.js +17 -0
- package/dist/lib/runtime/account-model-catalog.d.ts +3 -0
- package/dist/lib/runtime/account-model-catalog.js +7 -2
- package/dist/lib/runtime/api-model-capabilities.d.ts +11 -2
- package/dist/lib/runtime/api-model-capabilities.js +155 -24
- package/dist/lib/runtime/app-bind.d.ts +7 -0
- package/dist/lib/runtime/app-bind.js +92 -1
- package/dist/lib/runtime/automatic-api-checks.d.ts +15 -0
- package/dist/lib/runtime/automatic-api-checks.js +188 -0
- package/dist/lib/runtime/model-discovery-status.d.ts +11 -1
- package/dist/lib/runtime/model-discovery-status.js +64 -49
- package/dist/lib/runtime/persisted-api-capabilities.d.ts +20 -0
- package/dist/lib/runtime/persisted-api-capabilities.js +387 -0
- package/dist/lib/runtime/responses-websocket.d.ts +48 -5
- package/dist/lib/runtime/responses-websocket.js +275 -39
- package/dist/lib/runtime/rotation-account-selection.d.ts +25 -5
- package/dist/lib/runtime/rotation-account-selection.js +94 -4
- package/dist/lib/runtime/rotation-proxy-state.d.ts +26 -0
- package/dist/lib/runtime/rotation-proxy-state.js +376 -4
- package/dist/lib/runtime/rotation-server-types.d.ts +6 -0
- package/dist/lib/runtime/rotation-token-refresh.d.ts +23 -1
- package/dist/lib/runtime/rotation-token-refresh.js +218 -27
- package/dist/lib/runtime-rotation-proxy.js +601 -105
- package/dist/lib/schemas.d.ts +5 -0
- package/dist/lib/schemas.js +1 -0
- package/dist/lib/storage/credential-sidecars.js +34 -2
- package/dist/lib/storage/file-lock.d.ts +2 -0
- package/dist/lib/storage/file-lock.js +44 -30
- package/dist/lib/usage/ledger.js +1 -0
- package/dist/lib/usage/redaction.js +1 -0
- package/dist/lib/usage/types.d.ts +1 -1
- package/dist/lib/web-dashboard-html.d.ts +1 -0
- package/dist/lib/web-dashboard-html.js +66 -0
- package/dist/lib/web-dashboard.d.ts +36 -0
- package/dist/lib/web-dashboard.js +322 -0
- package/package.json +3 -1
- package/scripts/codex-app-router.js +86 -14
- package/scripts/codex-routing.js +1 -0
- package/scripts/codex.js +150 -13
|
@@ -39,10 +39,13 @@ import { runInitConfigCommand } from "./codex-manager/commands/init-config.js";
|
|
|
39
39
|
import { runReportCommand } from "./codex-manager/commands/report.js";
|
|
40
40
|
import { runRotationCommand } from "./codex-manager/commands/rotation.js";
|
|
41
41
|
import { runFeaturesCommand, runStatusCommand, } from "./codex-manager/commands/status.js";
|
|
42
|
+
import { runWebCommand } from "./web-dashboard.js";
|
|
42
43
|
import { loadPersistedRuntimeObservabilitySnapshot } from "./runtime/runtime-observability.js";
|
|
43
44
|
import { runSwitchCommand } from "./codex-manager/commands/switch.js";
|
|
44
45
|
import { runHistoryCommand } from "./codex-manager/commands/history.js";
|
|
46
|
+
import { runLocaleCommand } from "./codex-manager/commands/locale.js";
|
|
45
47
|
import { runUnpinCommand } from "./codex-manager/commands/unpin.js";
|
|
48
|
+
import { resolveOutputLocale } from "./locale.js";
|
|
46
49
|
import { runWorkspaceCommand } from "./codex-manager/commands/workspace.js";
|
|
47
50
|
import { runUsageCommand } from "./codex-manager/commands/usage.js";
|
|
48
51
|
import { refreshQuotaCacheForMenu } from "./codex-manager/login-menu-data.js";
|
|
@@ -140,6 +143,7 @@ const IMPLEMENTED_FEATURES = [
|
|
|
140
143
|
{ id: 52, name: "Why-selected and monitor diagnostics" },
|
|
141
144
|
{ id: 53, name: "Config explain, init-config templates, and debug bundle" },
|
|
142
145
|
{ id: 54, name: "mcodex convenience launcher (monitor/tmux/forward)" },
|
|
146
|
+
{ id: 55, name: "Local real-time web pool dashboard" },
|
|
143
147
|
];
|
|
144
148
|
async function runForecast(args) {
|
|
145
149
|
return runForecastCommand(args, {
|
|
@@ -267,25 +271,50 @@ export function buildSelectAccountTraced() {
|
|
|
267
271
|
/**
|
|
268
272
|
* Shared handler for `list` and `status` (aliases of the same view).
|
|
269
273
|
*/
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
274
|
+
function buildStatusCommandDeps(rest, logInfo) {
|
|
275
|
+
return {
|
|
276
|
+
loadApiRoutes,
|
|
277
|
+
loadResetCreditState,
|
|
278
|
+
loadInferenceRequestTimes,
|
|
279
|
+
loadModelInventory,
|
|
280
|
+
setStoragePath,
|
|
281
|
+
getStoragePath,
|
|
282
|
+
loadAccounts,
|
|
283
|
+
inspectStorageHealth,
|
|
284
|
+
resolveActiveIndex,
|
|
285
|
+
formatRateLimitEntry,
|
|
286
|
+
loadRuntimeObservabilitySnapshot: loadPersistedRuntimeObservabilitySnapshot,
|
|
287
|
+
loadAppBindStatus: async () => getAppBindStatus()
|
|
288
|
+
.then((status) => status.router ? {
|
|
289
|
+
...status.router,
|
|
290
|
+
// Keep historical diagnostics, but never revive a crashed router's
|
|
291
|
+
// persisted "running" marker or its account-selection signal.
|
|
292
|
+
state: status.running ? status.router.state : "stopped",
|
|
293
|
+
nativeOpenai: status.running && status.state?.nativeOpenai === true,
|
|
294
|
+
} : null)
|
|
295
|
+
.catch(() => null),
|
|
296
|
+
loadAppHelperStatus: readAppRuntimeHelperAccountSignal,
|
|
297
|
+
loadQuotaCache,
|
|
298
|
+
json: rest.includes("--json") || rest.includes("-j"),
|
|
299
|
+
compact: !rest.includes("--details") && !rest.includes("--verbose") && !rest.includes("--json") && !rest.includes("-j"),
|
|
300
|
+
locale: resolveOutputLocale(loadPluginConfig().locale),
|
|
301
|
+
...(logInfo ? { logInfo } : {}),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
const runListOrStatusCommand = (rest) => runStatusCommand(buildStatusCommandDeps(rest));
|
|
305
|
+
async function loadStatusJsonSnapshot() {
|
|
306
|
+
let serialized = "";
|
|
307
|
+
const exitCode = await runStatusCommand(buildStatusCommandDeps(["--json"], (message) => {
|
|
308
|
+
serialized = message;
|
|
309
|
+
}));
|
|
310
|
+
if (exitCode !== 0)
|
|
311
|
+
throw new Error("Unable to load status snapshot.");
|
|
312
|
+
const parsed = JSON.parse(serialized);
|
|
313
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
314
|
+
throw new Error("Status snapshot is not an object.");
|
|
315
|
+
}
|
|
316
|
+
return parsed;
|
|
317
|
+
}
|
|
289
318
|
/**
|
|
290
319
|
* Command registry for the `auth` dispatcher (audit roadmap §4.1.1 phase 2).
|
|
291
320
|
*
|
|
@@ -301,6 +330,10 @@ const CLI_COMMAND_HANDLERS = new Map([
|
|
|
301
330
|
["login", (rest) => runAuthLogin(rest, { runForecast, createRepairCommandDeps })],
|
|
302
331
|
["list", runListOrStatusCommand],
|
|
303
332
|
["status", runListOrStatusCommand],
|
|
333
|
+
["web", (rest) => runWebCommand(rest, {
|
|
334
|
+
getStatus: loadStatusJsonSnapshot,
|
|
335
|
+
locale: resolveOutputLocale(loadPluginConfig().locale),
|
|
336
|
+
})],
|
|
304
337
|
[
|
|
305
338
|
"limits",
|
|
306
339
|
(rest) => runLimitsCommand(rest, {
|
|
@@ -475,6 +508,12 @@ const CLI_COMMAND_HANDLERS = new Map([
|
|
|
475
508
|
if (subcommand === "template") {
|
|
476
509
|
return runInitConfigCommand(configArgs);
|
|
477
510
|
}
|
|
511
|
+
if (subcommand === "locale") {
|
|
512
|
+
return runLocaleCommand(configArgs, {
|
|
513
|
+
loadConfig: () => loadPluginConfig(),
|
|
514
|
+
saveConfig: savePluginConfig,
|
|
515
|
+
});
|
|
516
|
+
}
|
|
478
517
|
console.error(`Unknown config command: ${subcommand ?? "(missing)"}`);
|
|
479
518
|
return 1;
|
|
480
519
|
},
|
|
@@ -500,6 +539,32 @@ const CLI_COMMAND_HANDLERS = new Map([
|
|
|
500
539
|
],
|
|
501
540
|
]);
|
|
502
541
|
export async function runCodexMultiAuthCli(rawArgs) {
|
|
542
|
+
const localeArgs = [...rawArgs];
|
|
543
|
+
const previousLocaleOverride = process.env.CODEX_MULTI_AUTH_LOCALE;
|
|
544
|
+
let localeOverrideChanged = false;
|
|
545
|
+
const restoreLocaleOverride = () => {
|
|
546
|
+
if (!localeOverrideChanged)
|
|
547
|
+
return;
|
|
548
|
+
if (previousLocaleOverride === undefined)
|
|
549
|
+
delete process.env.CODEX_MULTI_AUTH_LOCALE;
|
|
550
|
+
else
|
|
551
|
+
process.env.CODEX_MULTI_AUTH_LOCALE = previousLocaleOverride;
|
|
552
|
+
};
|
|
553
|
+
for (let i = 0; i < localeArgs.length; i += 1) {
|
|
554
|
+
const arg = localeArgs[i];
|
|
555
|
+
if (arg === "--locale" && localeArgs[i + 1]) {
|
|
556
|
+
process.env.CODEX_MULTI_AUTH_LOCALE = localeArgs[i + 1];
|
|
557
|
+
localeOverrideChanged = true;
|
|
558
|
+
localeArgs.splice(i, 2);
|
|
559
|
+
i -= 1;
|
|
560
|
+
}
|
|
561
|
+
else if (typeof arg === "string" && arg.startsWith("--locale=")) {
|
|
562
|
+
process.env.CODEX_MULTI_AUTH_LOCALE = arg.slice("--locale=".length);
|
|
563
|
+
localeOverrideChanged = true;
|
|
564
|
+
localeArgs.splice(i, 1);
|
|
565
|
+
i -= 1;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
503
568
|
// Lazy install setup (audit roadmap §4.5.4): app detection, Codex app bind,
|
|
504
569
|
// and launcher routing moved out of npm postinstall to the first CLI run.
|
|
505
570
|
// ensureFirstRunSetup never throws; the catch is belt-and-braces so no
|
|
@@ -509,31 +574,41 @@ export async function runCodexMultiAuthCli(rawArgs) {
|
|
|
509
574
|
}).catch(() => undefined);
|
|
510
575
|
const startupDisplaySettings = await loadDashboardDisplaySettings();
|
|
511
576
|
applyUiThemeFromDashboardSettings(startupDisplaySettings);
|
|
512
|
-
const args =
|
|
513
|
-
? ["auth", ...
|
|
514
|
-
: [...
|
|
577
|
+
const args = localeArgs[0] && localeArgs[0] !== "auth" && ACCOUNT_MANAGER_COMMANDS.has(localeArgs[0])
|
|
578
|
+
? ["auth", ...localeArgs]
|
|
579
|
+
: [...localeArgs];
|
|
515
580
|
if (args.length === 0) {
|
|
581
|
+
restoreLocaleOverride();
|
|
516
582
|
printUsage();
|
|
517
583
|
return 0;
|
|
518
584
|
}
|
|
519
585
|
if (args[0] === "--help" || args[0] === "-h") {
|
|
586
|
+
restoreLocaleOverride();
|
|
520
587
|
printUsage();
|
|
521
588
|
return 0;
|
|
522
589
|
}
|
|
523
590
|
const [root, sub, ...rest] = args;
|
|
524
591
|
if (root !== "auth") {
|
|
592
|
+
restoreLocaleOverride();
|
|
525
593
|
printUsage();
|
|
526
594
|
return 1;
|
|
527
595
|
}
|
|
528
596
|
const command = sub ?? "login";
|
|
529
597
|
if (command === "--help" || command === "-h") {
|
|
598
|
+
restoreLocaleOverride();
|
|
530
599
|
printUsage();
|
|
531
600
|
return 0;
|
|
532
601
|
}
|
|
533
602
|
const handler = CLI_COMMAND_HANDLERS.get(command);
|
|
534
603
|
if (handler) {
|
|
535
|
-
|
|
604
|
+
try {
|
|
605
|
+
return await handler(rest);
|
|
606
|
+
}
|
|
607
|
+
finally {
|
|
608
|
+
restoreLocaleOverride();
|
|
609
|
+
}
|
|
536
610
|
}
|
|
611
|
+
restoreLocaleOverride();
|
|
537
612
|
console.error(`Unknown command: ${command}`);
|
|
538
613
|
printUsage();
|
|
539
614
|
return 1;
|
package/dist/lib/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, promises as fs, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { parseBooleanEnv } from "./env-parsing.js";
|
|
4
|
+
import { normalizeOutputLocaleSetting } from "./locale.js";
|
|
4
5
|
import { stripModelEffortSuffix } from "./constants.js";
|
|
5
6
|
import { withRetry, withRetrySync } from "./fs-retry.js";
|
|
6
7
|
import { logWarn } from "./logger.js";
|
|
@@ -113,6 +114,7 @@ function resolvePluginConfigPath() {
|
|
|
113
114
|
* CODEX_MODE is enabled by default for better Codex CLI parity
|
|
114
115
|
*/
|
|
115
116
|
export const DEFAULT_PLUGIN_CONFIG = {
|
|
117
|
+
locale: "auto",
|
|
116
118
|
codexMode: true,
|
|
117
119
|
codexRuntimeRotationProxy: true,
|
|
118
120
|
codexTuiV2: true,
|
|
@@ -1275,6 +1277,7 @@ function normalizeConfigExplainValue(value) {
|
|
|
1275
1277
|
return value;
|
|
1276
1278
|
}
|
|
1277
1279
|
const CONFIG_EXPLAIN_ENTRIES = [
|
|
1280
|
+
{ key: "locale", envNames: ["CODEX_MULTI_AUTH_LOCALE"], getValue: config => normalizeOutputLocaleSetting(process.env.CODEX_MULTI_AUTH_LOCALE?.trim() || config.locale || "auto") ?? "auto" },
|
|
1278
1281
|
{ key: "codexMode", envNames: ["CODEX_MODE"], getValue: getCodexMode },
|
|
1279
1282
|
{
|
|
1280
1283
|
key: "codexRuntimeRotationProxy",
|
package/dist/lib/constants.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ export declare const URL_PATHS: {
|
|
|
48
48
|
readonly MODELS: "/models";
|
|
49
49
|
readonly RESPONSES: "/responses";
|
|
50
50
|
readonly CODEX_RESPONSES: "/codex/responses";
|
|
51
|
+
readonly TRANSCRIBE: "/transcribe";
|
|
51
52
|
};
|
|
52
53
|
/** JWT claim path for ChatGPT account ID */
|
|
53
54
|
export declare const JWT_CLAIM_PATH: "https://api.openai.com/auth";
|
package/dist/lib/constants.js
CHANGED
|
@@ -48,6 +48,7 @@ export const URL_PATHS = {
|
|
|
48
48
|
MODELS: "/models",
|
|
49
49
|
RESPONSES: "/responses",
|
|
50
50
|
CODEX_RESPONSES: "/codex/responses",
|
|
51
|
+
TRANSCRIBE: "/transcribe",
|
|
51
52
|
};
|
|
52
53
|
/** JWT claim path for ChatGPT account ID */
|
|
53
54
|
export const JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type OutputLocale = "en" | "zh";
|
|
2
|
+
export type OutputLocaleSetting = OutputLocale | "auto";
|
|
3
|
+
/** Resolve an explicit locale, environment override, or system language. */
|
|
4
|
+
export declare function resolveOutputLocale(requested?: string | null, env?: NodeJS.ProcessEnv): OutputLocale;
|
|
5
|
+
export declare function normalizeOutputLocaleSetting(value: string): OutputLocaleSetting | null;
|
|
6
|
+
export declare function localized(locale: OutputLocale, en: string, zh: string): string;
|
|
7
|
+
export declare function localeName(locale: OutputLocale): string;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const SUPPORTED_LOCALES = new Set(["auto", "en", "zh"]);
|
|
2
|
+
/** Resolve an explicit locale, environment override, or system language. */
|
|
3
|
+
export function resolveOutputLocale(requested, env = process.env) {
|
|
4
|
+
const configured = requested?.trim().toLowerCase();
|
|
5
|
+
const value = (env.CODEX_MULTI_AUTH_LOCALE?.trim() || configured || "auto")
|
|
6
|
+
.trim()
|
|
7
|
+
.toLowerCase();
|
|
8
|
+
if (value === "en" || value.startsWith("en-"))
|
|
9
|
+
return "en";
|
|
10
|
+
if (value === "zh" || value.startsWith("zh-"))
|
|
11
|
+
return "zh";
|
|
12
|
+
if (value !== "" && value !== "auto" && SUPPORTED_LOCALES.has(value)) {
|
|
13
|
+
return value === "zh" ? "zh" : "en";
|
|
14
|
+
}
|
|
15
|
+
const system = [env.LC_ALL, env.LC_MESSAGES, env.LANG]
|
|
16
|
+
.find((candidate) => typeof candidate === "string" && candidate.trim())
|
|
17
|
+
?.toLowerCase() ?? "";
|
|
18
|
+
return system.startsWith("zh") ? "zh" : "en";
|
|
19
|
+
}
|
|
20
|
+
export function normalizeOutputLocaleSetting(value) {
|
|
21
|
+
const normalized = value.trim().toLowerCase();
|
|
22
|
+
if (normalized === "auto")
|
|
23
|
+
return "auto";
|
|
24
|
+
if (normalized === "en" || normalized.startsWith("en-"))
|
|
25
|
+
return "en";
|
|
26
|
+
if (normalized === "zh" || normalized.startsWith("zh-"))
|
|
27
|
+
return "zh";
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
export function localized(locale, en, zh) {
|
|
31
|
+
return locale === "zh" ? zh : en;
|
|
32
|
+
}
|
|
33
|
+
export function localeName(locale) {
|
|
34
|
+
return locale === "zh" ? "中文" : "English";
|
|
35
|
+
}
|
|
@@ -38,6 +38,7 @@ export declare function buildVisibleModelUnion(catalogs: RouteCatalog[]): RouteM
|
|
|
38
38
|
/** Whitelist capability metadata; never persist model instructions or arbitrary fields. */
|
|
39
39
|
export declare function modelEntitlements(model: RouteModel): {
|
|
40
40
|
model: string;
|
|
41
|
+
programCheckedAt?: number | undefined;
|
|
41
42
|
checkedAt?: number | undefined;
|
|
42
43
|
probes?: {
|
|
43
44
|
[k: string]: "downgraded" | "unsupported" | "unverified" | "verified";
|
|
@@ -129,12 +129,13 @@ export function modelEntitlements(model) {
|
|
|
129
129
|
const safe = (v) => typeof v === "string" && /^[A-Za-z0-9._-]{1,80}$/.test(v);
|
|
130
130
|
return {
|
|
131
131
|
model: model.slug,
|
|
132
|
+
...(typeof model.program_checked_at === "number" && Number.isFinite(model.program_checked_at) && model.program_checked_at >= 0 && model.program_checked_at <= 8.64e15 ? { programCheckedAt: model.program_checked_at } : {}),
|
|
132
133
|
...(typeof model.capability_checked_at === "number"
|
|
133
134
|
? { checkedAt: model.capability_checked_at }
|
|
134
135
|
: {}),
|
|
135
136
|
...(isRecord(model.capability_probe_status)
|
|
136
137
|
? {
|
|
137
|
-
probes: Object.fromEntries(Object.entries(model.capability_probe_status).filter((entry) => /^(responses|fast|ultrafast|effort:(none|minimal|low|medium|high|xhigh|max|ultra))$/.test(entry[0]) &&
|
|
138
|
+
probes: Object.fromEntries(Object.entries(model.capability_probe_status).filter((entry) => /^(responses|fast|ultrafast|effort:(none|minimal|low|medium|high|xhigh|max|ultra)|program:cyber:(standard|daybreak_blue|daybreak_red))$/.test(entry[0]) &&
|
|
138
139
|
[
|
|
139
140
|
"verified",
|
|
140
141
|
"unsupported",
|
package/dist/lib/quota-probe.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CODEX_BASE_URL, MAX_RATE_LIMIT_DELAY_MS } from "./constants.js";
|
|
2
|
+
import { fetchWithProxy } from "./request/proxy-fetch.js";
|
|
2
3
|
import { createCodexHeaders, getUnsupportedCodexModelInfo } from "./request/fetch-helpers.js";
|
|
3
4
|
import { QUOTA_PROBE_MODEL_CHAIN, resolveProbeReasoningEffort, } from "./request/helpers/model-map.js";
|
|
4
5
|
import { CodexUnavailableError, isCodexUnavailableError } from "./errors.js";
|
|
@@ -367,7 +368,7 @@ export async function fetchCodexQuotaSnapshot(options) {
|
|
|
367
368
|
snapshot.diagnosticProbeRequests += 1;
|
|
368
369
|
snapshot.runtimeMetrics.diagnosticProbeRequests += 1;
|
|
369
370
|
});
|
|
370
|
-
response = await
|
|
371
|
+
response = await fetchWithProxy(`${CODEX_BASE_URL}/codex/responses`, {
|
|
371
372
|
method: "POST",
|
|
372
373
|
headers,
|
|
373
374
|
body: JSON.stringify(probeBody),
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
type FetchInput = Parameters<typeof globalThis.fetch>[0];
|
|
2
|
+
type FetchInit = Parameters<typeof globalThis.fetch>[1];
|
|
3
|
+
export declare function setProxyFetchImpl(impl: typeof globalThis.fetch | null): void;
|
|
4
|
+
export declare function fetchWithProxy(input: FetchInput, init?: FetchInit): ReturnType<typeof globalThis.fetch>;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { fetch as undiciFetch } from "undici";
|
|
2
|
+
import { applyProxyCompatibleInit, extractRequestUrl, } from "./url-rewriting.js";
|
|
3
|
+
// Transport selection contract, in precedence order:
|
|
4
|
+
// 1. An implementation registered via setProxyFetchImpl (embedding contract;
|
|
5
|
+
// works regardless of when the host installs it).
|
|
6
|
+
// 2. A global fetch replaced after this module loaded, or a test double.
|
|
7
|
+
// 3. A global fetch installed before module load — honored when it is
|
|
8
|
+
// distinguishable from the platform fetch (Node's built-in is named
|
|
9
|
+
// "fetch"); hosts needing guaranteed interception use setProxyFetchImpl.
|
|
10
|
+
// 4. The package's Undici fetch, but only when the request carries a proxy
|
|
11
|
+
// dispatcher: Node 22's built-in fetch rejects the Undici 8 handler
|
|
12
|
+
// contract, so a proxied call needs the matching implementation.
|
|
13
|
+
// 5. The ambient global fetch for ordinary direct requests.
|
|
14
|
+
const initialGlobalFetch = globalThis.fetch;
|
|
15
|
+
let overrideFetch = null;
|
|
16
|
+
export function setProxyFetchImpl(impl) {
|
|
17
|
+
overrideFetch = impl;
|
|
18
|
+
}
|
|
19
|
+
function isTestDouble(fetchImpl) {
|
|
20
|
+
const candidate = fetchImpl;
|
|
21
|
+
return candidate._isMockFunction === true || candidate.mock !== undefined;
|
|
22
|
+
}
|
|
23
|
+
function isPlatformFetch(fetchImpl) {
|
|
24
|
+
return fetchImpl.name === "fetch" && !isTestDouble(fetchImpl);
|
|
25
|
+
}
|
|
26
|
+
export function fetchWithProxy(input, init) {
|
|
27
|
+
const url = extractRequestUrl(input);
|
|
28
|
+
const proxiedInit = applyProxyCompatibleInit(url, init);
|
|
29
|
+
const currentGlobalFetch = globalThis.fetch;
|
|
30
|
+
const baseFetch = overrideFetch ??
|
|
31
|
+
(currentGlobalFetch !== initialGlobalFetch || !isPlatformFetch(currentGlobalFetch)
|
|
32
|
+
? currentGlobalFetch
|
|
33
|
+
: proxiedInit.dispatcher
|
|
34
|
+
? undiciFetch
|
|
35
|
+
: currentGlobalFetch);
|
|
36
|
+
return baseFetch(input, proxiedInit);
|
|
37
|
+
}
|
|
@@ -98,11 +98,28 @@ function shouldBypassProxyForUrl(url, noProxyValue) {
|
|
|
98
98
|
}
|
|
99
99
|
return false;
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Loopback destinations never traverse a configured proxy: routing them through
|
|
103
|
+
* HTTP_PROXY would leak managed bearer tokens to the proxy and break the
|
|
104
|
+
* loopback-only runtime proxy contract. This bypass applies even when NO_PROXY
|
|
105
|
+
* is unset, matching curl/git proxy conventions.
|
|
106
|
+
*/
|
|
107
|
+
function isLoopbackRequestHost(url) {
|
|
108
|
+
const hostname = url.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
109
|
+
return (hostname === "localhost" ||
|
|
110
|
+
hostname.endsWith(".localhost") ||
|
|
111
|
+
hostname === "::1" ||
|
|
112
|
+
hostname.startsWith("127.") ||
|
|
113
|
+
hostname.startsWith("::ffff:127."));
|
|
114
|
+
}
|
|
101
115
|
export function resolveProxyUrlForRequest(url, env = process.env) {
|
|
102
116
|
const parsed = new URL(url);
|
|
103
117
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
104
118
|
return undefined;
|
|
105
119
|
}
|
|
120
|
+
if (isLoopbackRequestHost(parsed)) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
106
123
|
const httpProxy = resolveProxyEnvValue(env, "http_proxy", "HTTP_PROXY");
|
|
107
124
|
const httpsProxy = resolveProxyEnvValue(env, "https_proxy", "HTTPS_PROXY");
|
|
108
125
|
if (!httpProxy && !httpsProxy) {
|
|
@@ -23,6 +23,8 @@ export declare class AccountModelCatalog {
|
|
|
23
23
|
private readonly pending;
|
|
24
24
|
constructor(fetchCatalog: (accountKey: string) => Promise<unknown>, now?: () => number, ttlMs?: number);
|
|
25
25
|
private read;
|
|
26
|
+
/** Includes successful empty catalogs; stale or failed discovery remains unknown. */
|
|
27
|
+
hasUsablePickerSnapshot(key: string): boolean;
|
|
26
28
|
/** Picker-only stale-while-refresh view; routing still awaits fresh discovery. */
|
|
27
29
|
cachedList(accountKeys: string[]): CatalogModel[];
|
|
28
30
|
invalidate(): void;
|
|
@@ -47,6 +49,7 @@ export declare class AccountModelCatalog {
|
|
|
47
49
|
models: string[];
|
|
48
50
|
entitlements: {
|
|
49
51
|
model: string;
|
|
52
|
+
programCheckedAt?: number | undefined;
|
|
50
53
|
checkedAt?: number | undefined;
|
|
51
54
|
probes?: {
|
|
52
55
|
[k: string]: "downgraded" | "unsupported" | "unverified" | "verified";
|
|
@@ -83,12 +83,17 @@ export class AccountModelCatalog {
|
|
|
83
83
|
this.pending.delete(key);
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
+
/** Includes successful empty catalogs; stale or failed discovery remains unknown. */
|
|
87
|
+
hasUsablePickerSnapshot(key) {
|
|
88
|
+
const entry = this.cache.get(key);
|
|
89
|
+
return Boolean(entry && !entry.error && entry.checkedAt <= this.now() && this.now() - entry.checkedAt < 15 * 60_000);
|
|
90
|
+
}
|
|
86
91
|
/** Picker-only stale-while-refresh view; routing still awaits fresh discovery. */
|
|
87
92
|
cachedList(accountKeys) {
|
|
88
93
|
return buildVisibleModelUnion(accountKeys.map((key, index) => {
|
|
89
94
|
const entry = this.cache.get(key);
|
|
90
|
-
const usable =
|
|
91
|
-
return { id: key, kind: "oauth", priority: index, enabled: true, models: usable ? entry
|
|
95
|
+
const usable = this.hasUsablePickerSnapshot(key);
|
|
96
|
+
return { id: key, kind: "oauth", priority: index, enabled: true, models: usable ? entry?.models ?? [] : [], visibleModels: null };
|
|
92
97
|
}));
|
|
93
98
|
}
|
|
94
99
|
invalidate() { this.cache.clear(); }
|
|
@@ -3,6 +3,9 @@ import { type RouteModel } from "../model-route-policy.js";
|
|
|
3
3
|
/** Read the explicit model-specific statement, never surrounding examples or another model's settings. */
|
|
4
4
|
export declare function parseApiReasoningDocumentation(id: string, text: string): string[];
|
|
5
5
|
export type ProbeResult = {
|
|
6
|
+
ownerEpoch?: string;
|
|
7
|
+
programCheckedAt?: number;
|
|
8
|
+
programRetry?: boolean;
|
|
6
9
|
at: number;
|
|
7
10
|
levels: string[];
|
|
8
11
|
tiers: string[];
|
|
@@ -11,19 +14,25 @@ export type ProbeResult = {
|
|
|
11
14
|
export declare class ApiModelCapabilities {
|
|
12
15
|
private readonly fetchImpl;
|
|
13
16
|
private readonly now;
|
|
17
|
+
private readonly probeSlot?;
|
|
18
|
+
private readonly documentationSource?;
|
|
19
|
+
private readonly programRefreshBefore?;
|
|
14
20
|
private activeProbes;
|
|
15
21
|
private readonly probeWaiters;
|
|
16
22
|
private withProbeSlot;
|
|
17
23
|
private readonly inFlight;
|
|
18
24
|
private readonly probes;
|
|
19
25
|
private readonly cache;
|
|
20
|
-
constructor(fetchImpl?: typeof fetch, now?: () => number);
|
|
26
|
+
constructor(fetchImpl?: typeof fetch, now?: () => number, probeSlot?: (<T>(operation: () => Promise<T>) => Promise<T>) | undefined, documentationSource?: ApiModelCapabilities | undefined, programRefreshBefore?: number | undefined);
|
|
21
27
|
/** Probe results keyed by an opaque hash, for a fresh CLI process to honour the 15-minute cache. */
|
|
22
28
|
exportProbes(): Array<[string, ProbeResult]>;
|
|
23
29
|
importProbes(entries: Array<[string, ProbeResult]>): void;
|
|
24
30
|
reasoning(id: string, refresh?: boolean): Promise<string[]>;
|
|
25
31
|
private documentation;
|
|
32
|
+
private programsCoverCheck;
|
|
33
|
+
private freshProbe;
|
|
34
|
+
private reconcileReasoning;
|
|
26
35
|
private probe;
|
|
27
36
|
private runProbe;
|
|
28
|
-
enrich(models: RouteModel[], refresh?: boolean, route?: ApiRouteCredential, forceProbes?: boolean): Promise<RouteModel[]>;
|
|
37
|
+
enrich(models: RouteModel[], refresh?: boolean, route?: ApiRouteCredential, forceProbes?: boolean, cachedProbesOnly?: Array<[string, ProbeResult]>): Promise<RouteModel[]>;
|
|
29
38
|
}
|