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.
Files changed (70) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/README.md +8 -5
  3. package/config/schema/config.schema.json +8 -0
  4. package/dist/lib/accounts.d.ts +5 -0
  5. package/dist/lib/accounts.js +33 -4
  6. package/dist/lib/api-route-store.d.ts +7 -0
  7. package/dist/lib/api-route-store.js +100 -31
  8. package/dist/lib/auth/account-access.js +2 -1
  9. package/dist/lib/auth/auth.js +3 -2
  10. package/dist/lib/auth/device-auth.js +2 -1
  11. package/dist/lib/auth/http.d.ts +1 -0
  12. package/dist/lib/auth/http.js +1 -0
  13. package/dist/lib/codex-manager/account-manager-commands.js +1 -0
  14. package/dist/lib/codex-manager/api-login-menu.d.ts +1 -1
  15. package/dist/lib/codex-manager/api-login-menu.js +4 -6
  16. package/dist/lib/codex-manager/commands/locale.d.ts +12 -0
  17. package/dist/lib/codex-manager/commands/locale.js +25 -0
  18. package/dist/lib/codex-manager/commands/status.d.ts +5 -1
  19. package/dist/lib/codex-manager/commands/status.js +501 -13
  20. package/dist/lib/codex-manager/help.js +3 -1
  21. package/dist/lib/codex-manager.js +98 -23
  22. package/dist/lib/config.js +3 -0
  23. package/dist/lib/constants.d.ts +1 -0
  24. package/dist/lib/constants.js +1 -0
  25. package/dist/lib/locale.d.ts +7 -0
  26. package/dist/lib/locale.js +35 -0
  27. package/dist/lib/model-route-policy.d.ts +1 -0
  28. package/dist/lib/model-route-policy.js +2 -1
  29. package/dist/lib/quota-probe.js +2 -1
  30. package/dist/lib/request/proxy-fetch.d.ts +5 -0
  31. package/dist/lib/request/proxy-fetch.js +37 -0
  32. package/dist/lib/request/url-rewriting.js +17 -0
  33. package/dist/lib/runtime/account-model-catalog.d.ts +3 -0
  34. package/dist/lib/runtime/account-model-catalog.js +7 -2
  35. package/dist/lib/runtime/api-model-capabilities.d.ts +11 -2
  36. package/dist/lib/runtime/api-model-capabilities.js +155 -24
  37. package/dist/lib/runtime/app-bind.d.ts +7 -0
  38. package/dist/lib/runtime/app-bind.js +92 -1
  39. package/dist/lib/runtime/automatic-api-checks.d.ts +15 -0
  40. package/dist/lib/runtime/automatic-api-checks.js +188 -0
  41. package/dist/lib/runtime/model-discovery-status.d.ts +11 -1
  42. package/dist/lib/runtime/model-discovery-status.js +64 -49
  43. package/dist/lib/runtime/persisted-api-capabilities.d.ts +20 -0
  44. package/dist/lib/runtime/persisted-api-capabilities.js +387 -0
  45. package/dist/lib/runtime/responses-websocket.d.ts +48 -5
  46. package/dist/lib/runtime/responses-websocket.js +275 -39
  47. package/dist/lib/runtime/rotation-account-selection.d.ts +25 -5
  48. package/dist/lib/runtime/rotation-account-selection.js +94 -4
  49. package/dist/lib/runtime/rotation-proxy-state.d.ts +26 -0
  50. package/dist/lib/runtime/rotation-proxy-state.js +376 -4
  51. package/dist/lib/runtime/rotation-server-types.d.ts +6 -0
  52. package/dist/lib/runtime/rotation-token-refresh.d.ts +23 -1
  53. package/dist/lib/runtime/rotation-token-refresh.js +218 -27
  54. package/dist/lib/runtime-rotation-proxy.js +601 -105
  55. package/dist/lib/schemas.d.ts +5 -0
  56. package/dist/lib/schemas.js +1 -0
  57. package/dist/lib/storage/credential-sidecars.js +34 -2
  58. package/dist/lib/storage/file-lock.d.ts +2 -0
  59. package/dist/lib/storage/file-lock.js +44 -30
  60. package/dist/lib/usage/ledger.js +1 -0
  61. package/dist/lib/usage/redaction.js +1 -0
  62. package/dist/lib/usage/types.d.ts +1 -1
  63. package/dist/lib/web-dashboard-html.d.ts +1 -0
  64. package/dist/lib/web-dashboard-html.js +66 -0
  65. package/dist/lib/web-dashboard.d.ts +36 -0
  66. package/dist/lib/web-dashboard.js +322 -0
  67. package/package.json +3 -1
  68. package/scripts/codex-app-router.js +86 -14
  69. package/scripts/codex-routing.js +1 -0
  70. 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
- const runListOrStatusCommand = (rest) => runStatusCommand({
271
- loadApiRoutes,
272
- loadResetCreditState,
273
- loadInferenceRequestTimes,
274
- loadModelInventory,
275
- setStoragePath,
276
- getStoragePath,
277
- loadAccounts,
278
- inspectStorageHealth,
279
- resolveActiveIndex,
280
- formatRateLimitEntry,
281
- loadRuntimeObservabilitySnapshot: loadPersistedRuntimeObservabilitySnapshot,
282
- loadAppBindStatus: async () => getAppBindStatus()
283
- .then((status) => (status.running && status.router ? { ...status.router, nativeOpenai: status.state?.nativeOpenai === true } : null))
284
- .catch(() => null),
285
- loadAppHelperStatus: readAppRuntimeHelperAccountSignal,
286
- loadQuotaCache,
287
- json: rest.includes("--json") || rest.includes("-j"),
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 = rawArgs[0] && rawArgs[0] !== "auth" && ACCOUNT_MANAGER_COMMANDS.has(rawArgs[0])
513
- ? ["auth", ...rawArgs]
514
- : [...rawArgs];
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
- return handler(rest);
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;
@@ -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",
@@ -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";
@@ -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",
@@ -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 fetch(`${CODEX_BASE_URL}/codex/responses`, {
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 = entry && !entry.error && entry.checkedAt <= this.now() && this.now() - entry.checkedAt < 15 * 60_000;
91
- return { id: key, kind: "oauth", priority: index, enabled: true, models: usable ? entry.models ?? [] : [], visibleModels: null };
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
  }