@bman654/clodex 2.14.1 → 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/dist/cli.js CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  clearActiveOAuthAccount,
11
11
  collectPristineFacts,
12
12
  contentAddressedBackupPath,
13
+ customProviderId,
13
14
  emitParentNotice,
14
15
  ensureSecureAppHome,
15
16
  findClaudeBinary,
@@ -61,6 +62,7 @@ import {
61
62
  setServerListenMode,
62
63
  setServerMaskGatewayIds,
63
64
  sha256File,
65
+ slugifyProviderId,
64
66
  storeActiveOAuthAccount,
65
67
  tcpListenerUrlHost,
66
68
  tweakccMirrorBackupPath,
@@ -72,7 +74,7 @@ import {
72
74
  withRegistryWriteLockSync,
73
75
  writeParentNoticeLines,
74
76
  writeParentNoticeLinesSync
75
- } from "./chunk-3NCDMS7N.js";
77
+ } from "./chunk-5TG736TX.js";
76
78
 
77
79
  // src/cli.ts
78
80
  import pc13 from "picocolors";
@@ -141,8 +143,8 @@ function fmtProvider(name) {
141
143
  }
142
144
  function fmtProviderBracket(providerId, providerName, isFree) {
143
145
  const color = providerTagColor(providerId);
144
- const text5 = isFree ? `${providerName} \xB7 free` : providerName;
145
- return color(pc.bold(`(${text5})`));
146
+ const text6 = isFree ? `${providerName} \xB7 free` : providerName;
147
+ return color(pc.bold(`(${text6})`));
146
148
  }
147
149
  function providerTagColor(providerId) {
148
150
  switch (providerId) {
@@ -246,7 +248,7 @@ function printFavoritesOnlyPanel() {
246
248
  }
247
249
 
248
250
  // src/cli.ts
249
- import * as p12 from "@clack/prompts";
251
+ import * as p13 from "@clack/prompts";
250
252
  import { realpathSync as realpathSync3 } from "fs";
251
253
  import { fileURLToPath as fileURLToPath2 } from "url";
252
254
 
@@ -344,7 +346,7 @@ import { join } from "path";
344
346
  // package.json
345
347
  var package_default = {
346
348
  name: "@bman654/clodex",
347
- version: "2.14.1",
349
+ version: "2.16.0",
348
350
  publishConfig: {
349
351
  access: "public"
350
352
  },
@@ -692,20 +694,22 @@ function getCacheIndex() {
692
694
  }
693
695
  return cacheIndex;
694
696
  }
695
- function contextWindowFromHeuristics(modelId) {
696
- const cached = heuristicCache.get(modelId);
697
- if (cached !== void 0) return cached;
697
+ function matchHeuristicWindow(modelId) {
698
+ if (heuristicCache.has(modelId)) return heuristicCache.get(modelId);
698
699
  for (const [pattern, size] of HEURISTIC_RULES) {
699
700
  if (pattern.test(modelId)) {
700
701
  heuristicCache.set(modelId, size);
701
702
  return size;
702
703
  }
703
704
  }
704
- heuristicCache.set(modelId, DEFAULT_CONTEXT_WINDOW);
705
- return DEFAULT_CONTEXT_WINDOW;
705
+ heuristicCache.set(modelId, void 0);
706
+ return void 0;
707
+ }
708
+ function lookupKnownContextWindow(modelId) {
709
+ return getCacheIndex().get(modelId) ?? matchHeuristicWindow(modelId);
706
710
  }
707
711
  function lookupContextWindow(modelId) {
708
- return getCacheIndex().get(modelId) ?? contextWindowFromHeuristics(modelId);
712
+ return lookupKnownContextWindow(modelId) ?? DEFAULT_CONTEXT_WINDOW;
709
713
  }
710
714
  function resolveContextWindow(modelId, explicit) {
711
715
  if (typeof explicit === "number" && explicit > 0) return explicit;
@@ -3158,17 +3162,19 @@ function parseContextStop(value) {
3158
3162
  return parsed;
3159
3163
  }
3160
3164
  function resolveContextStop(limits, stop = "standard") {
3161
- const standardRaw = positiveInteger(limits.contextWindow) ?? 0;
3162
- const ceiling = positiveInteger(limits.maxContextWindow) ?? standardRaw;
3163
- const requested = stop === "standard" ? standardRaw : stop === "max" ? ceiling : positiveInteger(stop) ?? standardRaw;
3164
- const raw = Math.min(requested, ceiling);
3165
+ const declaredWindow = positiveInteger(limits.contextWindow);
3166
+ const declaredCeiling = positiveInteger(limits.maxContextWindow);
3167
+ const standardRaw = declaredWindow ?? DEFAULT_CONTEXT_WINDOW;
3168
+ const ceiling = declaredCeiling ?? declaredWindow;
3169
+ const requested = stop === "standard" ? standardRaw : stop === "max" ? declaredCeiling ?? standardRaw : positiveInteger(stop) ?? standardRaw;
3170
+ const raw = ceiling === void 0 ? requested : Math.min(requested, ceiling);
3165
3171
  const effective = effectiveContextWindow(raw, limits.effectiveContextPercent);
3166
3172
  const boundary = positiveInteger(limits.pricingBoundary);
3167
3173
  return {
3168
3174
  stop,
3169
3175
  raw,
3170
3176
  effective,
3171
- ...requested > ceiling ? { clampedFrom: requested } : {},
3177
+ ...ceiling !== void 0 && requested > ceiling ? { clampedFrom: requested } : {},
3172
3178
  crossesPricingBoundary: boundary !== void 0 && effective > boundary
3173
3179
  };
3174
3180
  }
@@ -3537,24 +3543,24 @@ function applyClodexPatches(source, config) {
3537
3543
  }
3538
3544
  const reEsc = (s) => s.replace(/[.*+?^$\{\}()|[\]\\]/g, "\\$&");
3539
3545
  const q = (s) => JSON.stringify(s);
3540
- function log12(status, name, extra) {
3546
+ function log13(status, name, extra) {
3541
3547
  report.push(extra === void 0 ? { status, name } : { status, name, extra });
3542
3548
  }
3543
3549
  function applyOnce(name, regex, fn, { marker, required, noopIsSkip } = {}) {
3544
3550
  if (marker && js.includes(marker)) {
3545
- log12("SKIP", name, "already patched");
3551
+ log13("SKIP", name, "already patched");
3546
3552
  return;
3547
3553
  }
3548
3554
  const g = new RegExp(regex.source, regex.flags.includes("g") ? regex.flags : regex.flags + "g");
3549
3555
  const matches = js.match(g);
3550
3556
  const count = matches ? matches.length : 0;
3551
3557
  if (count === 0) {
3552
- log12("FAIL", name, "anchor not found");
3558
+ log13("FAIL", name, "anchor not found");
3553
3559
  if (required) fail("clodex patch: required patch failed: " + name);
3554
3560
  return;
3555
3561
  }
3556
3562
  if (count > 1) {
3557
- log12("FAIL", name, "anchor matched " + count + " times (expected 1)");
3563
+ log13("FAIL", name, "anchor matched " + count + " times (expected 1)");
3558
3564
  if (required) fail("clodex patch: ambiguous anchor: " + name);
3559
3565
  return;
3560
3566
  }
@@ -3562,14 +3568,14 @@ function applyClodexPatches(source, config) {
3562
3568
  js = js.replace(regex, fn);
3563
3569
  if (js === before) {
3564
3570
  if (noopIsSkip) {
3565
- log12("SKIP", name, "already patched");
3571
+ log13("SKIP", name, "already patched");
3566
3572
  return;
3567
3573
  }
3568
- log12("FAIL", name, "replacement made no change");
3574
+ log13("FAIL", name, "replacement made no change");
3569
3575
  if (required) fail(name);
3570
3576
  return;
3571
3577
  }
3572
- log12("OK", name);
3578
+ log13("OK", name);
3573
3579
  }
3574
3580
  function extendAliasArray(arrLiteral) {
3575
3581
  const toAdd = IDENTITIES.filter((a) => !new RegExp('"' + reEsc(a) + '"').test(arrLiteral));
@@ -3600,7 +3606,7 @@ function applyClodexPatches(source, config) {
3600
3606
  );
3601
3607
  const cases = missing.map((a) => "case" + q(a) + ":return " + q(a) + ";").join("");
3602
3608
  if (ALIASES.length === 0) {
3603
- log12("SKIP", "PATCH 6: alias resolver switch", "no aliases configured");
3609
+ log13("SKIP", "PATCH 6: alias resolver switch", "no aliases configured");
3604
3610
  } else {
3605
3611
  applyOnce(
3606
3612
  "PATCH 6: alias resolver switch",
@@ -3623,9 +3629,9 @@ function applyClodexPatches(source, config) {
3623
3629
  const pickerSite = "PATCH 5: model picker options";
3624
3630
  const modelSelections = [...js.matchAll(/\(([\w$]+)==="opus"\|\|\1==="sonnet"\)/g)];
3625
3631
  if (ALIASES.length === 0) {
3626
- log12("SKIP", pickerSite, "no aliases configured");
3632
+ log13("SKIP", pickerSite, "no aliases configured");
3627
3633
  } else if (modelSelections.length !== 1) {
3628
- log12("FAIL", pickerSite, "model selection appears " + modelSelections.length + " times (expected 1)");
3634
+ log13("FAIL", pickerSite, "model selection appears " + modelSelections.length + " times (expected 1)");
3629
3635
  } else {
3630
3636
  applyOnce(
3631
3637
  pickerSite,
@@ -3753,11 +3759,11 @@ function applyClodexPatches(source, config) {
3753
3759
  "CLAUDE_CODE_OTEL_DIAG_STDERR"
3754
3760
  ];
3755
3761
  const MIN_SCRUBBED_ENV_NAMES = 1;
3756
- const blockEndIndex = (text5, open2) => {
3762
+ const blockEndIndex = (text6, open2) => {
3757
3763
  const depths = [0];
3758
3764
  const templates = [false];
3759
- for (let i = open2; i < text5.length; i++) {
3760
- const ch = text5[i];
3765
+ for (let i = open2; i < text6.length; i++) {
3766
+ const ch = text6[i];
3761
3767
  const inTemplate = templates[templates.length - 1];
3762
3768
  if (inTemplate) {
3763
3769
  if (ch === "\\") {
@@ -3769,7 +3775,7 @@ function applyClodexPatches(source, config) {
3769
3775
  depths.pop();
3770
3776
  continue;
3771
3777
  }
3772
- if (ch === "$" && text5[i + 1] === "{") {
3778
+ if (ch === "$" && text6[i + 1] === "{") {
3773
3779
  templates.push(false);
3774
3780
  depths.push(0);
3775
3781
  i++;
@@ -3782,12 +3788,12 @@ function applyClodexPatches(source, config) {
3782
3788
  }
3783
3789
  if (ch === '"' || ch === "'") {
3784
3790
  const quote = ch;
3785
- for (i++; i < text5.length; i++) {
3786
- if (text5[i] === "\\") {
3791
+ for (i++; i < text6.length; i++) {
3792
+ if (text6[i] === "\\") {
3787
3793
  i++;
3788
3794
  continue;
3789
3795
  }
3790
- if (text5[i] === quote) break;
3796
+ if (text6[i] === quote) break;
3791
3797
  }
3792
3798
  continue;
3793
3799
  }
@@ -3796,12 +3802,12 @@ function applyClodexPatches(source, config) {
3796
3802
  depths.push(0);
3797
3803
  continue;
3798
3804
  }
3799
- if (ch === "/" && text5[i + 1] === "/") {
3800
- while (i < text5.length && text5[i] !== "\n") i++;
3805
+ if (ch === "/" && text6[i + 1] === "/") {
3806
+ while (i < text6.length && text6[i] !== "\n") i++;
3801
3807
  continue;
3802
3808
  }
3803
- if (ch === "/" && text5[i + 1] === "*") {
3804
- i = text5.indexOf("*/", i + 2);
3809
+ if (ch === "/" && text6[i + 1] === "*") {
3810
+ i = text6.indexOf("*/", i + 2);
3805
3811
  if (i < 0) return -1;
3806
3812
  i++;
3807
3813
  continue;
@@ -3824,7 +3830,7 @@ function applyClodexPatches(source, config) {
3824
3830
  };
3825
3831
  const passthroughSites = source.includes(marker) ? 1 : (source.match(/\)return process\.env;let [\w$]+=\{/g) ?? []).length;
3826
3832
  if (passthroughSites !== 1) {
3827
- log12("FAIL", patchName, "child env passthrough appears " + passthroughSites + " times (expected 1)");
3833
+ log13("FAIL", patchName, "child env passthrough appears " + passthroughSites + " times (expected 1)");
3828
3834
  fail("clodex patch: required patch failed: " + patchName);
3829
3835
  }
3830
3836
  applyOnce(
@@ -3839,7 +3845,7 @@ function applyClodexPatches(source, config) {
3839
3845
  const scrubbedSeen = scrubbedEnvNames.filter((name) => body.includes(name));
3840
3846
  const why = missingLiteral !== void 0 ? "body does not contain " + missingLiteral : scrubbedSeen.length < MIN_SCRUBBED_ENV_NAMES ? "body spells only " + scrubbedSeen.length + " of the " + scrubbedEnvNames.length + " known child-env names (expected at least " + MIN_SCRUBBED_ENV_NAMES + ")" : /\bfunction\s*[\w$]*\(/.test(body) ? "body declares a nested function" : braceScanFailed ? "the brace walk never reached the function's closing brace" : bound !== 0 ? "match ends " + Math.abs(bound) + " characters " + (bound > 0 ? "before" : "after") + " the end of the function it started in" : void 0;
3841
3847
  if (why !== void 0) {
3842
- log12("FAIL", patchName, "target validation failed: " + why);
3848
+ log13("FAIL", patchName, "target validation failed: " + why);
3843
3849
  fail("clodex patch: child network environment target validation failed: " + why);
3844
3850
  }
3845
3851
  const restoredBody = body.replace(/process\.env/g, "_clodexChildEnv");
@@ -5206,6 +5212,52 @@ var opencode_go_models_default = [
5206
5212
  upstreamModelId: "minimax-m3",
5207
5213
  family: "minimax"
5208
5214
  },
5215
+ {
5216
+ id: "muse-spark-1.2-contributor",
5217
+ name: "Muse Spark 1.2 Contributor",
5218
+ contextWindow: 1048576,
5219
+ cost: {
5220
+ input: 0.1,
5221
+ output: 0.2,
5222
+ cache_read: 2e-3
5223
+ },
5224
+ modelFormat: "openai",
5225
+ npm: "@ai-sdk/openai",
5226
+ apiUrl: "https://opencode.ai/zen/go/v1",
5227
+ reasoning: true,
5228
+ modalities: [
5229
+ "text",
5230
+ "image"
5231
+ ],
5232
+ compatibility: {
5233
+ supportsReasoningEffort: false
5234
+ },
5235
+ upstreamModelId: "muse-spark-1.2-contributor",
5236
+ family: "muse"
5237
+ },
5238
+ {
5239
+ id: "muse-spark-1.3-contributor",
5240
+ name: "Muse Spark 1.3 Contributor",
5241
+ contextWindow: 1048576,
5242
+ cost: {
5243
+ input: 0.1,
5244
+ output: 0.2,
5245
+ cache_read: 2e-3
5246
+ },
5247
+ modelFormat: "openai",
5248
+ npm: "@ai-sdk/openai",
5249
+ apiUrl: "https://opencode.ai/zen/go/v1",
5250
+ reasoning: true,
5251
+ modalities: [
5252
+ "text",
5253
+ "image"
5254
+ ],
5255
+ compatibility: {
5256
+ supportsReasoningEffort: false
5257
+ },
5258
+ upstreamModelId: "muse-spark-1.3-contributor",
5259
+ family: "muse"
5260
+ },
5209
5261
  {
5210
5262
  id: "qwen3.6-plus",
5211
5263
  name: "Qwen3.6 Plus",
@@ -5469,6 +5521,10 @@ function openCodeGoPinnedApiUrl(npm) {
5469
5521
  if (npm === "@ai-sdk/anthropic") return OPENCODE_GO_ANTHROPIC_BASE_URL;
5470
5522
  return null;
5471
5523
  }
5524
+ function openCodeGoPinnedModelApiUrl(npm) {
5525
+ if (npm === "@ai-sdk/openai") return OPENCODE_GO_COMPLETIONS_BASE_URL;
5526
+ return openCodeGoPinnedApiUrl(npm);
5527
+ }
5472
5528
  function isProviderConfiguredForTemplate(provider, templateId) {
5473
5529
  if (provider.id === templateId) return true;
5474
5530
  return templateId === OPENCODE_GO_PROVIDER_ID && isRetainedOpenCodeGoProvider(provider);
@@ -5955,10 +6011,10 @@ var OPENAI_OAUTH_MODEL_SEEDS = [
5955
6011
  { id: "gpt-5", name: "GPT-5", contextWindow: 272e3, reasoning: true },
5956
6012
  // o-series reasoning (Plus+)
5957
6013
  { id: "o4-mini", name: "o4 Mini", reasoning: true },
5958
- { id: "o3", name: "o3", reasoning: true },
6014
+ { id: "o3", name: "o3", contextWindow: 2e5, reasoning: true },
5959
6015
  { id: "o3-mini", name: "o3 Mini", reasoning: true },
5960
- { id: "o1", name: "o1", reasoning: true },
5961
- { id: "o1-mini", name: "o1 Mini", reasoning: true }
6016
+ { id: "o1", name: "o1", contextWindow: 2e5, reasoning: true },
6017
+ { id: "o1-mini", name: "o1 Mini", contextWindow: 128e3, reasoning: true }
5962
6018
  ];
5963
6019
  function hasPricingBoundary(id) {
5964
6020
  const version = /^gpt-(\d+)(?:\.(\d+))?(?:-|$)/i.exec(id);
@@ -6006,7 +6062,7 @@ function buildOpenAiOAuthModels() {
6006
6062
  upstreamModelId: seed.id,
6007
6063
  family: prefix,
6008
6064
  brand: deriveBrand(prefix),
6009
- contextWindow: resolveContextWindow(seed.id, seed.contextWindow),
6065
+ contextWindow: seed.contextWindow ?? lookupKnownContextWindow(seed.id),
6010
6066
  maxContextWindow: seed.maxContextWindow,
6011
6067
  effectiveContextPercent: seed.effectiveContextPercent,
6012
6068
  pricingBoundary: seed.pricingBoundary ?? pricing.pricingBoundary,
@@ -6046,6 +6102,22 @@ function normalizeGoogleDisplayName(rawName, bareId) {
6046
6102
  return stripGoogleModelPrefix(trimmed);
6047
6103
  }
6048
6104
 
6105
+ // src/registry/server-text.ts
6106
+ function isControlCode(code) {
6107
+ return code <= 31 || code >= 127 && code <= 159;
6108
+ }
6109
+ function hasControlChars(value) {
6110
+ for (let i = 0; i < value.length; i++) {
6111
+ if (isControlCode(value.charCodeAt(i))) return true;
6112
+ }
6113
+ return false;
6114
+ }
6115
+ function printableServerText(value) {
6116
+ let out = "";
6117
+ for (const ch of value) out += isControlCode(ch.charCodeAt(0)) ? " " : ch;
6118
+ return out.trim();
6119
+ }
6120
+
6049
6121
  // src/trace-log.ts
6050
6122
  import {
6051
6123
  chmodSync as chmodSync3,
@@ -6135,8 +6207,8 @@ function compactLogValueWithMarker(value, max) {
6135
6207
  function systemPreview(system) {
6136
6208
  if (typeof system === "string") return compactLogValue(system, REQUEST_PREVIEW_MAX) || void 0;
6137
6209
  if (!Array.isArray(system)) return void 0;
6138
- const text5 = system.map((block) => typeof block === "string" ? block : block && typeof block === "object" && typeof block.text === "string" ? block.text : "").filter(Boolean).join(" ");
6139
- return compactLogValue(text5, REQUEST_PREVIEW_MAX) || void 0;
6210
+ const text6 = system.map((block) => typeof block === "string" ? block : block && typeof block === "object" && typeof block.text === "string" ? block.text : "").filter(Boolean).join(" ");
6211
+ return compactLogValue(text6, REQUEST_PREVIEW_MAX) || void 0;
6140
6212
  }
6141
6213
  function inlineSystemPreview(messages) {
6142
6214
  if (!Array.isArray(messages)) return void 0;
@@ -6162,9 +6234,9 @@ function getLatestMessagePreview(messages, system) {
6162
6234
  if (typeof content === "string") {
6163
6235
  summary = content;
6164
6236
  } else if (Array.isArray(content)) {
6165
- const text5 = content.filter((block) => Boolean(block && typeof block === "object")).filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join(" ");
6166
- if (text5.trim()) {
6167
- summary = text5;
6237
+ const text6 = content.filter((block) => Boolean(block && typeof block === "object")).filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join(" ");
6238
+ if (text6.trim()) {
6239
+ summary = text6;
6168
6240
  } else {
6169
6241
  const blockTypes = [...new Set(content.filter((block) => Boolean(block && typeof block === "object")).map((block) => typeof block.type === "string" ? block.type : "unknown"))];
6170
6242
  if (blockTypes.length > 0) blockSummary = `${role}: [${blockTypes.join(", ")}]`;
@@ -6436,8 +6508,8 @@ function writeSecureLogLine(path, line) {
6436
6508
  function printTraceLog(debugLogPath) {
6437
6509
  if (!existsSync3(debugLogPath)) return;
6438
6510
  const raw = readFileSync6(debugLogPath, "utf8");
6439
- const log12 = redactTraceLog(raw);
6440
- const errorLines = log12.split("\n").filter(
6511
+ const log13 = redactTraceLog(raw);
6512
+ const errorLines = log13.split("\n").filter(
6441
6513
  (l) => l.includes("error") || l.includes("Error") || l.includes('"type":"error"') || l.includes("status") || l.includes("resolveModel failed") || l.includes("resolveModel fallback")
6442
6514
  );
6443
6515
  console.log("\n" + pc2.bold(pc2.cyan("\u2500\u2500 Debug trace \u2500\u2500")));
@@ -6499,13 +6571,16 @@ function parseModelList(body, npm, allowBareArray) {
6499
6571
  for (const row of rows) {
6500
6572
  const rawId = row.id?.trim();
6501
6573
  if (!rawId) continue;
6574
+ if (hasControlChars(rawId) || typeof row.name === "string" && hasControlChars(row.name.trim())) continue;
6502
6575
  const { id, upstreamModelId: upstreamModelId2 } = normalizeGoogleModelId(rawId, npm);
6503
6576
  const family = id.split(/[-/:]/)[0] ?? id;
6504
6577
  const cost = parseNativePricing(row.pricing);
6505
6578
  const freeStatus = classifyFreeStatus({
6506
6579
  model: { cost, isFree: row.isFree }
6507
6580
  });
6508
- const contextWindow = row.context_length ?? row.contextWindow ?? row.context_window ?? resolveContextWindow(id);
6581
+ const contextWindow = row.context_length ?? row.contextWindow ?? row.context_window ?? row.max_model_len ?? // Left undefined when nothing is known: persisting the invented 200,000
6582
+ // would make it this model's permanent ceiling.
6583
+ lookupKnownContextWindow(id);
6509
6584
  models.push({
6510
6585
  id,
6511
6586
  name: normalizeGoogleDisplayName(row.name, id),
@@ -6537,7 +6612,7 @@ function materializeTemplateModel(template, model, baseUrl) {
6537
6612
  upstreamModelId: model.upstreamModelId ?? normalizedUpstream,
6538
6613
  family,
6539
6614
  brand: model.brand ?? deriveBrand(family),
6540
- contextWindow: model.contextWindow ?? resolveContextWindow(id),
6615
+ contextWindow: model.contextWindow ?? lookupKnownContextWindow(id),
6541
6616
  isFree: model.isFree ?? isFreeStatus(freeStatus),
6542
6617
  freeStatus,
6543
6618
  modelFormat: model.modelFormat ?? modelFormatForNpm(npm),
@@ -6739,10 +6814,10 @@ function resolveMaterializedApiUrl(cached, provider, npm) {
6739
6814
  if (!isRetainedOpenCodeGoProvider(provider)) {
6740
6815
  return cached.apiUrl ?? provider.api.url ?? "";
6741
6816
  }
6742
- return openCodeGoPinnedApiUrl(npm);
6817
+ return openCodeGoPinnedModelApiUrl(npm);
6743
6818
  }
6744
6819
  function projectContextStop(cached, providerId, modelId) {
6745
- const limits = contextLimitsFrom(cached, resolveContextWindow(modelId));
6820
+ const limits = contextLimitsFrom(cached, lookupKnownContextWindow(modelId));
6746
6821
  const stop = selectContextStop(providerId, modelId);
6747
6822
  const resolved = resolveContextStop(limits, stop);
6748
6823
  return {
@@ -6930,9 +7005,9 @@ function collectTargets(contents, kind) {
6930
7005
  function readNpmShim(shimPath) {
6931
7006
  const kind = launcherKind(shimPath);
6932
7007
  const unreadable = kind === "sh" ? { kind: "not-a-shim" } : { kind: "unreadable-launcher" };
6933
- const text5 = readLauncherText(shimPath);
6934
- if (text5 === null) return unreadable;
6935
- const rawTargets = collectTargets(text5, kind);
7008
+ const text6 = readLauncherText(shimPath);
7009
+ if (text6 === null) return unreadable;
7010
+ const rawTargets = collectTargets(text6, kind);
6936
7011
  if (rawTargets.length === 0) return unreadable;
6937
7012
  if (!rawTargets.every(isLiteralPath)) return unreadable;
6938
7013
  const absolute = rawTargets.map((target) => resolve2(
@@ -7060,11 +7135,11 @@ function inspectClaudeNativeBinaryPlaceholder(path) {
7060
7135
  offset += bytesRead;
7061
7136
  }
7062
7137
  if (offset !== size) return null;
7063
- const text5 = bytes.toString("utf8");
7138
+ const text6 = bytes.toString("utf8");
7064
7139
  const signals = [
7065
- text5.includes(MISSING_BINARY_MARKER),
7066
- text5.includes(INSTALL_SCRIPT_MARKER),
7067
- text5.includes(OMIT_OPTIONAL_MARKER) && text5.includes(IGNORE_SCRIPTS_MARKER)
7140
+ text6.includes(MISSING_BINARY_MARKER),
7141
+ text6.includes(INSTALL_SCRIPT_MARKER),
7142
+ text6.includes(OMIT_OPTIONAL_MARKER) && text6.includes(IGNORE_SCRIPTS_MARKER)
7068
7143
  ];
7069
7144
  if (signals.filter(Boolean).length < 2) return null;
7070
7145
  return inspectNativePackage(path);
@@ -7874,7 +7949,7 @@ function outboundHttpProxyAgent(targetUrl, env = process.env) {
7874
7949
  }
7875
7950
  return new HttpsProxyAgent(parsedProxy, { keepAlive: true });
7876
7951
  } catch (err) {
7877
- console.error(
7952
+ emitParentNotice(
7878
7953
  `clodex: HTTP(S)_PROXY cannot be used for a CONNECT tunnel; using a direct connection (${err instanceof Error ? err.message : String(err)})`
7879
7954
  );
7880
7955
  return void 0;
@@ -8893,11 +8968,11 @@ function reasoningGapShape(expected, actual, full, storedTail, index) {
8893
8968
  }
8894
8969
  var warnedReasoningGaps = /* @__PURE__ */ new Set();
8895
8970
  var MAX_REASONING_GAP_WARNINGS = 3;
8896
- function warnReasoningNormalizationGap(fields, log12) {
8971
+ function warnReasoningNormalizationGap(fields, log13) {
8897
8972
  const signature = fields.join(",");
8898
8973
  const message = `clodex: warning: a reasoning item with identical encrypted_content failed the continuation match on field(s): ${signature}. Prompt caching is degraded for this turn \u2014 this is a clodex normalization gap, please report it at https://github.com/bman654/clodex/issues`;
8899
8974
  try {
8900
- log12?.(`reasoning normalization gap: ${signature}`);
8975
+ log13?.(`reasoning normalization gap: ${signature}`);
8901
8976
  } catch {
8902
8977
  }
8903
8978
  if (warnedReasoningGaps.has(signature)) return;
@@ -8943,12 +9018,12 @@ function toolArgumentNormalizationGap(expected, actual, defaults, requiredProps)
8943
9018
  }
8944
9019
  var warnedToolArgumentGaps = /* @__PURE__ */ new Set();
8945
9020
  var MAX_TOOL_ARGUMENT_GAP_WARNINGS = 3;
8946
- function warnToolArgumentNormalizationGap(gap, log12) {
9021
+ function warnToolArgumentNormalizationGap(gap, log13) {
8947
9022
  const tool3 = typeof gap.tool === "string" ? gap.tool : "unknown";
8948
9023
  const signature = `${tool3}:filler`;
8949
9024
  const message = `clodex: warning: tool call "${tool3}" failed the continuation match, but both sides are identical once clodex's filler-strip rule is applied, so the head should have matched. Prompt caching is degraded for this turn \u2014 please report it, with the adapter debug log from --trace if you can, at https://github.com/bman654/clodex/issues`;
8950
9025
  try {
8951
- log12?.(`tool argument normalization gap: ${signature}`);
9026
+ log13?.(`tool argument normalization gap: ${signature}`);
8952
9027
  } catch {
8953
9028
  }
8954
9029
  if (warnedToolArgumentGaps.has(signature)) return;
@@ -8959,7 +9034,7 @@ function warnToolArgumentNormalizationGap(gap, log12) {
8959
9034
  emitParentNotice("clodex: warning: further tool-argument normalization warnings suppressed.");
8960
9035
  }
8961
9036
  }
8962
- function continuationMismatchDetails(entry, payload, defaults, log12, warnOnGap = false, deferWarnings) {
9037
+ function continuationMismatchDetails(entry, payload, defaults, log13, warnOnGap = false, deferWarnings) {
8963
9038
  const raise = (warn) => {
8964
9039
  if (deferWarnings) deferWarnings.push(warn);
8965
9040
  else warn();
@@ -8977,7 +9052,7 @@ function continuationMismatchDetails(entry, payload, defaults, log12, warnOnGap
8977
9052
  const expected = mismatch < prefix.length ? prefix[mismatch] : void 0;
8978
9053
  const actual = mismatch < full.length ? full[mismatch] : void 0;
8979
9054
  const reasoningGap = reasoningNormalizationGap(expected, actual, defaults);
8980
- if (reasoningGap && warnOnGap) raise(() => warnReasoningNormalizationGap(reasoningGap, log12));
9055
+ if (reasoningGap && warnOnGap) raise(() => warnReasoningNormalizationGap(reasoningGap, log13));
8981
9056
  let gapExpected = expected;
8982
9057
  if (conversationItemKind(expected) === "reasoning" && conversationItemKind(actual) === "function_call") {
8983
9058
  for (let index = mismatch; index < prefix.length; index += 1) {
@@ -9001,10 +9076,10 @@ function continuationMismatchDetails(entry, payload, defaults, log12, warnOnGap
9001
9076
  }
9002
9077
  if (toolArgumentGap?.equalAfterStrip === true) {
9003
9078
  const gap = toolArgumentGap;
9004
- if (warnOnGap) raise(() => warnToolArgumentNormalizationGap(gap, log12));
9079
+ if (warnOnGap) raise(() => warnToolArgumentNormalizationGap(gap, log13));
9005
9080
  } else if (toolArgumentGap && warnOnGap) {
9006
9081
  try {
9007
- log12?.(`tool argument mismatch beyond the strip rule: ${String(toolArgumentGap.tool)}`);
9082
+ log13?.(`tool argument mismatch beyond the strip rule: ${String(toolArgumentGap.tool)}`);
9008
9083
  } catch {
9009
9084
  }
9010
9085
  }
@@ -9029,17 +9104,17 @@ function continuationMismatchDetails(entry, payload, defaults, log12, warnOnGap
9029
9104
  ...toolArgumentGap ? { toolArgumentNormalizationGap: toolArgumentGap } : {}
9030
9105
  };
9031
9106
  }
9032
- function continuationMismatchSummary(entry, payload, defaults, log12, mismatchDump = false, precomputedDetails) {
9033
- const details = precomputedDetails ?? continuationMismatchDetails(entry, payload, defaults, log12, true);
9107
+ function continuationMismatchSummary(entry, payload, defaults, log13, mismatchDump = false, precomputedDetails) {
9108
+ const details = precomputedDetails ?? continuationMismatchDetails(entry, payload, defaults, log13, true);
9034
9109
  let summary = `full_items=${details.fullItems} expected_prefix_items=${details.expectedPrefixItems} first_mismatch=${details.firstMismatch} expected=${details.expectedKind} actual=${details.actualKind}`;
9035
9110
  if (details.expectedHash || details.actualHash) {
9036
9111
  summary += ` expected_hash=${details.expectedHash ?? "none"} actual_hash=${details.actualHash ?? "none"}`;
9037
- if (mismatchDump && log12) {
9112
+ if (mismatchDump && log13) {
9038
9113
  const full = inputArray(payload);
9039
9114
  const prefix = [...entry.requestInput ?? [], ...entry.expectedAssistant ?? []];
9040
9115
  const index = details.firstMismatch;
9041
- log12(`mismatch dump expected[${index}]: ${mismatchDumpLine(prefix, index, defaults)}`);
9042
- log12(`mismatch dump actual[${index}]: ${mismatchDumpLine(full, index, defaults)}`);
9116
+ log13(`mismatch dump expected[${index}]: ${mismatchDumpLine(prefix, index, defaults)}`);
9117
+ log13(`mismatch dump actual[${index}]: ${mismatchDumpLine(full, index, defaults)}`);
9043
9118
  }
9044
9119
  }
9045
9120
  return summary;
@@ -9382,7 +9457,7 @@ function requiredToolProps(payload) {
9382
9457
  if (record.type !== "function" || typeof record.name !== "string") return;
9383
9458
  const parameters = record.parameters;
9384
9459
  const required = parameters && typeof parameters === "object" && Array.isArray(parameters.required) ? parameters.required : [];
9385
- map.set(record.name, new Set(required.filter((p13) => typeof p13 === "string")));
9460
+ map.set(record.name, new Set(required.filter((p14) => typeof p14 === "string")));
9386
9461
  };
9387
9462
  if (Array.isArray(payload.tools)) for (const tool3 of payload.tools) add(tool3);
9388
9463
  return map;
@@ -9411,12 +9486,12 @@ function expectedAssistantItems(ctx) {
9411
9486
  const type = accumulator.type ?? (typeof done.type === "string" ? done.type : void 0);
9412
9487
  if (type === "message") {
9413
9488
  const doneContent = Array.isArray(done.content) ? done.content : void 0;
9414
- const text5 = accumulator.text || (doneContent ? doneContent.filter((part) => part && typeof part === "object" && part.type === "output_text").map((part) => String(part.text ?? "")).join("") : "");
9415
- output.push({ role: "assistant", content: [{ type: "output_text", text: text5 }] });
9489
+ const text6 = accumulator.text || (doneContent ? doneContent.filter((part) => part && typeof part === "object" && part.type === "output_text").map((part) => String(part.text ?? "")).join("") : "");
9490
+ output.push({ role: "assistant", content: [{ type: "output_text", text: text6 }] });
9416
9491
  continue;
9417
9492
  }
9418
9493
  if (type === "reasoning") {
9419
- const summary = accumulator.summaries.size ? [...accumulator.summaries.entries()].sort(([a], [b]) => a - b).map(([, text5]) => ({ type: "summary_text", text: text5 })) : Array.isArray(done.summary) ? done.summary : [];
9494
+ const summary = accumulator.summaries.size ? [...accumulator.summaries.entries()].sort(([a], [b]) => a - b).map(([, text6]) => ({ type: "summary_text", text: text6 })) : Array.isArray(done.summary) ? done.summary : [];
9420
9495
  output.push({ ...withoutEphemeralFields(done), type: "reasoning", summary });
9421
9496
  continue;
9422
9497
  }
@@ -9706,7 +9781,7 @@ function handleSocketMessage(entry, data) {
9706
9781
  if (entry.connectionDiagnostic) observeIdleFrame(entry, data);
9707
9782
  return;
9708
9783
  }
9709
- const text5 = Array.isArray(data) ? Buffer.concat(data).toString("utf8") : data.toString("utf8");
9784
+ const text6 = Array.isArray(data) ? Buffer.concat(data).toString("utf8") : data.toString("utf8");
9710
9785
  ctx.frameCount += 1;
9711
9786
  if (ctx.transportRetryPending) {
9712
9787
  ctx.transportRetryPending = false;
@@ -9718,9 +9793,9 @@ function handleSocketMessage(entry, data) {
9718
9793
  }
9719
9794
  let event;
9720
9795
  try {
9721
- event = JSON.parse(text5);
9796
+ event = JSON.parse(text6);
9722
9797
  } catch {
9723
- ctx.pendingEvents.push(text5.replace(/\r?\n/g, " "));
9798
+ ctx.pendingEvents.push(text6.replace(/\r?\n/g, " "));
9724
9799
  flushPending(ctx);
9725
9800
  return;
9726
9801
  }
@@ -10068,23 +10143,23 @@ function createConnection(WebSocket, wsUrl, headers, persistent, key, options, d
10068
10143
  function diagnosticCap(cap) {
10069
10144
  return Number.isFinite(cap) ? cap : null;
10070
10145
  }
10071
- function envConnectionCap(name, log12) {
10146
+ function envConnectionCap(name, log13) {
10072
10147
  const raw = process.env[name];
10073
10148
  if (raw === void 0 || raw.trim() === "") return void 0;
10074
10149
  const value = Number(raw.trim());
10075
10150
  if (!Number.isInteger(value) || value < 1) {
10076
10151
  try {
10077
- log12?.(`ws: ignoring ${name}=${raw} (expected a positive integer)`);
10152
+ log13?.(`ws: ignoring ${name}=${raw} (expected a positive integer)`);
10078
10153
  } catch {
10079
10154
  }
10080
10155
  return void 0;
10081
10156
  }
10082
10157
  return value;
10083
10158
  }
10084
- function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
10159
+ function createResponsesWebSocketFetch(wsUrl, log13, options = {}) {
10085
10160
  const debug = (message) => {
10086
10161
  try {
10087
- log12?.(`ws: ${message}`);
10162
+ log13?.(`ws: ${message}`);
10088
10163
  } catch {
10089
10164
  }
10090
10165
  };
@@ -10093,8 +10168,8 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
10093
10168
  hardTtlMs: options.hardTtlMs ?? RESPONSES_WS_HARD_TTL_MS,
10094
10169
  idleTtlMs: options.idleTtlMs ?? RESPONSES_WS_IDLE_TTL_MS,
10095
10170
  nurseryIdleTtlMs: options.nurseryIdleTtlMs ?? Math.min(RESPONSES_WS_NURSERY_IDLE_TTL_MS, options.idleTtlMs ?? RESPONSES_WS_IDLE_TTL_MS),
10096
- maxConnections: options.maxConnections ?? envConnectionCap("CLODEX_WS_MAX_CONNECTIONS", log12) ?? RESPONSES_WS_MAX_CONNECTIONS,
10097
- maxNurseryConnections: options.maxNurseryConnections ?? envConnectionCap("CLODEX_WS_MAX_NURSERY_CONNECTIONS", log12) ?? RESPONSES_WS_MAX_NURSERY_CONNECTIONS,
10171
+ maxConnections: options.maxConnections ?? envConnectionCap("CLODEX_WS_MAX_CONNECTIONS", log13) ?? RESPONSES_WS_MAX_CONNECTIONS,
10172
+ maxNurseryConnections: options.maxNurseryConnections ?? envConnectionCap("CLODEX_WS_MAX_NURSERY_CONNECTIONS", log13) ?? RESPONSES_WS_MAX_NURSERY_CONNECTIONS,
10098
10173
  now: options.now ?? Date.now
10099
10174
  };
10100
10175
  return async (_input, init) => {
@@ -10529,8 +10604,8 @@ function buildClaudeCodeBillingSystemLine() {
10529
10604
  function systemBlockText(block) {
10530
10605
  if (typeof block === "string") return block;
10531
10606
  if (block && typeof block === "object" && "text" in block) {
10532
- const text5 = block.text;
10533
- return typeof text5 === "string" ? text5 : void 0;
10607
+ const text6 = block.text;
10608
+ return typeof text6 === "string" ? text6 : void 0;
10534
10609
  }
10535
10610
  return void 0;
10536
10611
  }
@@ -10758,7 +10833,7 @@ async function createLanguageModel(spec) {
10758
10833
  apiKey: "",
10759
10834
  ...spec.headers ? { headers: spec.headers } : {},
10760
10835
  fetch: fetchWithoutCredentialHeaders
10761
- } : { apiKey, ...spec.headers ? { headers: spec.headers } : {} };
10836
+ } : { apiKey, ...baseURL ? { baseURL } : {}, ...spec.headers ? { headers: spec.headers } : {} };
10762
10837
  const openai = createOpenAI(oauthOptions);
10763
10838
  return useResponsesEndpoint ? openai.responses(modelId) : openai.chat(modelId);
10764
10839
  }
@@ -10903,7 +10978,7 @@ function toCamelCase(str) {
10903
10978
  return str.replace(/[-_]([a-z])/g, (_, g) => g.toUpperCase());
10904
10979
  }
10905
10980
  function hasSupportedParameter(metadata, param) {
10906
- return (metadata?.supportedParameters ?? []).some((p13) => p13 === param);
10981
+ return (metadata?.supportedParameters ?? []).some((p14) => p14 === param);
10907
10982
  }
10908
10983
  function isOpenRouterRoute(npm, metadata) {
10909
10984
  return npm === "@openrouter/ai-sdk-provider" || metadata?.providerId === "openrouter" || metadata?.apiBaseUrl?.includes("openrouter.ai") === true;
@@ -11862,10 +11937,10 @@ async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWant
11862
11937
  res.end(JSON.stringify({ type: "error", error: { type: "api_error", message: "Upstream returned empty response body" } }));
11863
11938
  return;
11864
11939
  }
11865
- let text5 = await upstreamRes.text();
11940
+ let text6 = await upstreamRes.text();
11866
11941
  let parsed;
11867
11942
  try {
11868
- parsed = JSON.parse(text5);
11943
+ parsed = JSON.parse(text6);
11869
11944
  } catch {
11870
11945
  res.writeHead(502, { "Content-Type": "application/json" });
11871
11946
  res.end(JSON.stringify({ type: "error", error: { type: "api_error", message: "Upstream response was not valid JSON" } }));
@@ -11885,13 +11960,13 @@ async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWant
11885
11960
  changed = true;
11886
11961
  }
11887
11962
  }
11888
- if (changed) text5 = JSON.stringify(message);
11963
+ if (changed) text6 = JSON.stringify(message);
11889
11964
  }
11890
11965
  res.writeHead(200, {
11891
11966
  "Content-Type": "application/json",
11892
- "Content-Length": Buffer.byteLength(text5).toString()
11967
+ "Content-Length": Buffer.byteLength(text6).toString()
11893
11968
  });
11894
- res.end(text5);
11969
+ res.end(text6);
11895
11970
  }
11896
11971
 
11897
11972
  // src/proxy.ts
@@ -12243,12 +12318,12 @@ var OpenAiThinkingBlock = class {
12243
12318
  append(part) {
12244
12319
  const entry = this.parts.get(part.id ?? "");
12245
12320
  if (!entry) throw new Error("OpenAI reasoning delta has no matching start");
12246
- const text5 = part.text ?? "";
12247
- if (!text5) return "";
12321
+ const text6 = part.text ?? "";
12322
+ if (!text6) return "";
12248
12323
  const separator = entry.text.length === 0 && this.hasText ? "\n\n" : "";
12249
- entry.text += text5;
12324
+ entry.text += text6;
12250
12325
  this.hasText = true;
12251
- return separator + text5;
12326
+ return separator + text6;
12252
12327
  }
12253
12328
  end(part) {
12254
12329
  const entry = this.parts.get(part.id ?? "");
@@ -12262,7 +12337,7 @@ var OpenAiThinkingBlock = class {
12262
12337
  function isRecord(value) {
12263
12338
  return value !== null && typeof value === "object" && !Array.isArray(value);
12264
12339
  }
12265
- function restoreOpenAiThinking(text5, signature, npm) {
12340
+ function restoreOpenAiThinking(text6, signature, npm) {
12266
12341
  if (!signature?.startsWith(SIGNATURE_PREFIX)) return void 0;
12267
12342
  if (!signature.startsWith(SIGNATURE_V1)) return [];
12268
12343
  try {
@@ -12273,7 +12348,7 @@ function restoreOpenAiThinking(text5, signature, npm) {
12273
12348
  if (!isRecord(part) || typeof part.itemId !== "string" || !part.itemId || typeof part.text !== "string" || part.encryptedContent !== void 0 && typeof part.encryptedContent !== "string") return [];
12274
12349
  parts.push(part);
12275
12350
  }
12276
- if (npm !== "@ai-sdk/openai") return text5 ? [{ type: "reasoning", text: text5 }] : [];
12351
+ if (npm !== "@ai-sdk/openai") return text6 ? [{ type: "reasoning", text: text6 }] : [];
12277
12352
  return parts.map((part) => ({
12278
12353
  type: "reasoning",
12279
12354
  text: part.text,
@@ -12347,10 +12422,10 @@ function supportsOpenAiPromptCacheBreakpoints(modelId) {
12347
12422
  const minor = Number(match[2] ?? 0);
12348
12423
  return major > 5 || major === 5 && minor >= 6;
12349
12424
  }
12350
- function stripClaudeCodeBillingHeader(text5) {
12351
- if (!text5.startsWith(CLAUDE_CODE_BILLING_HEADER_PREFIX)) return text5;
12352
- const newline = text5.indexOf("\n");
12353
- return newline === -1 ? void 0 : text5.slice(newline + 1);
12425
+ function stripClaudeCodeBillingHeader(text6) {
12426
+ if (!text6.startsWith(CLAUDE_CODE_BILLING_HEADER_PREFIX)) return text6;
12427
+ const newline = text6.indexOf("\n");
12428
+ return newline === -1 ? void 0 : text6.slice(newline + 1);
12354
12429
  }
12355
12430
  function systemToString(system, stripAnthropicBillingHeader = false) {
12356
12431
  if (!system) return void 0;
@@ -12359,8 +12434,8 @@ function systemToString(system, stripAnthropicBillingHeader = false) {
12359
12434
  }
12360
12435
  const blocks = system.map((b) => typeof b === "string" ? b : b.text ?? "");
12361
12436
  if (!stripAnthropicBillingHeader) return blocks.join("\n");
12362
- return blocks.flatMap((text5) => {
12363
- const stripped = stripClaudeCodeBillingHeader(text5);
12437
+ return blocks.flatMap((text6) => {
12438
+ const stripped = stripClaudeCodeBillingHeader(text6);
12364
12439
  return stripped === void 0 ? [] : [stripped];
12365
12440
  }).join("\n");
12366
12441
  }
@@ -12376,12 +12451,12 @@ function translateTopLevelSystemForOpenAi(system) {
12376
12451
  }
12377
12452
  return system.flatMap((block) => {
12378
12453
  const raw = typeof block === "string" ? block : block.text ?? "";
12379
- const text5 = stripClaudeCodeBillingHeader(raw) ?? "";
12380
- if (!text5.trim()) return [];
12454
+ const text6 = stripClaudeCodeBillingHeader(raw) ?? "";
12455
+ if (!text6.trim()) return [];
12381
12456
  const cacheControl = typeof block === "string" ? void 0 : block.cache_control;
12382
12457
  return [{
12383
12458
  role: "system",
12384
- content: text5,
12459
+ content: text6,
12385
12460
  ...cacheControl ? { providerOptions: { openai: { promptCacheBreakpoint: { mode: "explicit" } } } } : {}
12386
12461
  }];
12387
12462
  });
@@ -12438,9 +12513,9 @@ function annotateToolNames(messages) {
12438
12513
  }
12439
12514
  }
12440
12515
  function thinkingToSdkPart(block, npm) {
12441
- const text5 = block.thinking ?? "";
12442
- if (npm === "@ai-sdk/openai" && !block.signature && !text5.trim()) return null;
12443
- const part = { type: "reasoning", text: text5 };
12516
+ const text6 = block.thinking ?? "";
12517
+ if (npm === "@ai-sdk/openai" && !block.signature && !text6.trim()) return null;
12518
+ const part = { type: "reasoning", text: text6 };
12444
12519
  if (block.signature) {
12445
12520
  if (npm === "@ai-sdk/google") {
12446
12521
  part.providerOptions = { google: { thoughtSignature: block.signature } };
@@ -12475,10 +12550,10 @@ function translateMessages(messages, npm, openAiPromptCacheBreakpoints = false)
12475
12550
  ...openAiCacheBreakpoint(b, openAiPromptCacheBreakpoints) ? { providerOptions: openAiCacheBreakpoint(b, openAiPromptCacheBreakpoints) } : {}
12476
12551
  });
12477
12552
  } else if (b.type === "image") {
12478
- const p13 = imagePart(b);
12479
- if (p13) {
12553
+ const p14 = imagePart(b);
12554
+ if (p14) {
12480
12555
  parts.push({
12481
- ...p13,
12556
+ ...p14,
12482
12557
  ...openAiCacheBreakpoint(b, openAiPromptCacheBreakpoints) ? { providerOptions: openAiCacheBreakpoint(b, openAiPromptCacheBreakpoints) } : {}
12483
12558
  });
12484
12559
  }
@@ -12531,10 +12606,10 @@ function translateMessages(messages, npm, openAiPromptCacheBreakpoints = false)
12531
12606
  function isPlainObject(value) {
12532
12607
  return value !== null && typeof value === "object" && !Array.isArray(value);
12533
12608
  }
12534
- function parsesAsJson(text5) {
12535
- if (text5.trim() === "") return true;
12609
+ function parsesAsJson(text6) {
12610
+ if (text6.trim() === "") return true;
12536
12611
  try {
12537
- JSON.parse(text5);
12612
+ JSON.parse(text6);
12538
12613
  return true;
12539
12614
  } catch {
12540
12615
  return false;
@@ -12583,7 +12658,7 @@ function isClaudeCodeCompactRequest(body) {
12583
12658
  const finalMessage = body.messages.at(-1);
12584
12659
  if (!finalMessage || finalMessage.role !== "user") return false;
12585
12660
  const texts = typeof finalMessage.content === "string" ? [finalMessage.content] : finalMessage.content.filter((block) => block.type === "text").map((block) => block.text ?? "");
12586
- return texts.some((text5) => text5.startsWith(COMPACT_TEXT_ONLY_START) && text5.includes(COMPACT_TEXT_ONLY_END));
12661
+ return texts.some((text6) => text6.startsWith(COMPACT_TEXT_ONLY_START) && text6.includes(COMPACT_TEXT_ONLY_END));
12587
12662
  }
12588
12663
  var COMPACT_DRIFT_ANCHOR = "Tool calls will be REJECTED and will waste your only turn";
12589
12664
  var COMPACT_DRIFT_ANCHOR_LINE = new RegExp(
@@ -12595,25 +12670,25 @@ function looksLikeDriftedClaudeCodeCompactRequest(body) {
12595
12670
  const finalMessage = body.messages.at(-1);
12596
12671
  if (!finalMessage || finalMessage.role !== "user") return false;
12597
12672
  const texts = typeof finalMessage.content === "string" ? [finalMessage.content] : finalMessage.content.filter((block) => block.type === "text").map((block) => block.text ?? "");
12598
- return texts.some((text5) => !text5.startsWith(COMPACT_TEXT_ONLY_START) && COMPACT_DRIFT_OPENING.test(text5) && COMPACT_DRIFT_ANCHOR_LINE.test(text5));
12673
+ return texts.some((text6) => !text6.startsWith(COMPACT_TEXT_ONLY_START) && COMPACT_DRIFT_OPENING.test(text6) && COMPACT_DRIFT_ANCHOR_LINE.test(text6));
12599
12674
  }
12600
12675
  function claudeCodeVersionFromRequest(body) {
12601
12676
  const texts = typeof body.system === "string" ? [body.system] : (body.system ?? []).map((block) => typeof block === "string" ? block : block.text ?? "");
12602
- for (const text5 of texts) {
12603
- if (!text5.startsWith(CLAUDE_CODE_BILLING_HEADER_PREFIX)) continue;
12604
- const match = text5.match(/\bcc_version=([0-9A-Za-z][0-9A-Za-z._+-]{0,63})(?:;|\s|$)/);
12677
+ for (const text6 of texts) {
12678
+ if (!text6.startsWith(CLAUDE_CODE_BILLING_HEADER_PREFIX)) continue;
12679
+ const match = text6.match(/\bcc_version=([0-9A-Za-z][0-9A-Za-z._+-]{0,63})(?:;|\s|$)/);
12605
12680
  if (match) return match[1];
12606
12681
  }
12607
12682
  return void 0;
12608
12683
  }
12609
12684
  var warnedCompactPromptDrifts = /* @__PURE__ */ new Set();
12610
12685
  var MAX_COMPACT_PROMPT_DRIFT_WARNINGS = 3;
12611
- function reportClaudeCodeCompactPromptDrift(body, log12) {
12686
+ function reportClaudeCodeCompactPromptDrift(body, log13) {
12612
12687
  if (!looksLikeDriftedClaudeCodeCompactRequest(body)) return;
12613
12688
  const version = claudeCodeVersionFromRequest(body);
12614
12689
  const signature = version ?? "unknown-version";
12615
12690
  try {
12616
- log12?.(`possible Claude Code compact prompt drift: ${signature}`);
12691
+ log13?.(`possible Claude Code compact prompt drift: ${signature}`);
12617
12692
  } catch {
12618
12693
  }
12619
12694
  if (warnedCompactPromptDrifts.has(signature)) return;
@@ -12749,7 +12824,7 @@ function forwardAbortSignal(source, target) {
12749
12824
  function translatedMessageId() {
12750
12825
  return "clodex_" + randomUUID5().replace(/-/g, "");
12751
12826
  }
12752
- async function writeAnthropicStream(stream, modelId, write, log12, observer, tools) {
12827
+ async function writeAnthropicStream(stream, modelId, write, log13, observer, tools) {
12753
12828
  const messageId = translatedMessageId();
12754
12829
  const requiredProps = toolRequiredProps(tools);
12755
12830
  let blockIndex = -1;
@@ -12946,7 +13021,7 @@ async function writeAnthropicStream(stream, modelId, write, log12, observer, too
12946
13021
  const errMsg = e?.message || (typeof part.error === "string" ? part.error : JSON.stringify(e?.data ?? part.error));
12947
13022
  const transportCode = sdkUpstreamErrorDetails(part.error)?.transportCode;
12948
13023
  const errorType = anthropicErrorType(upstreamHttpStatus(part.error, errMsg), transportCode);
12949
- log12?.(() => `sdk stream error (${errorType}): ${errMsg}`);
13024
+ log13?.(() => `sdk stream error (${errorType}): ${errMsg}`);
12950
13025
  if (!(transportCode === "websocket_transport_error" && openType === "thinking")) closeOpen();
12951
13026
  throw part.error instanceof Error || part.error && typeof part.error === "object" ? part.error : new Error(errMsg);
12952
13027
  }
@@ -12960,7 +13035,7 @@ async function writeAnthropicStream(stream, modelId, write, log12, observer, too
12960
13035
  emit("message_delta", { type: "message_delta", delta: { stop_reason: finishReason, stop_sequence: null }, usage });
12961
13036
  emit("message_stop", { type: "message_stop" });
12962
13037
  }
12963
- async function streamAnthropicResponse(model, params, modelId, write, log12, observer) {
13038
+ async function streamAnthropicResponse(model, params, modelId, write, log13, observer) {
12964
13039
  const { idleTimeoutMs, totalTimeoutMs, maxRetries } = upstreamRequestBudget({
12965
13040
  idleTimeoutMs: observer?.idleTimeoutMs
12966
13041
  });
@@ -12999,7 +13074,7 @@ async function streamAnthropicResponse(model, params, modelId, write, log12, obs
12999
13074
  clearTimeout(idleTimer);
13000
13075
  }
13001
13076
  })();
13002
- await writeAnthropicStream(watchedStream, modelId, write, log12, { ...observer, abortSignal }, params.tools);
13077
+ await writeAnthropicStream(watchedStream, modelId, write, log13, { ...observer, abortSignal }, params.tools);
13003
13078
  } finally {
13004
13079
  stopForwardingAbort();
13005
13080
  clearTimeout(idleTimer);
@@ -13008,7 +13083,7 @@ async function streamAnthropicResponse(model, params, modelId, write, log12, obs
13008
13083
  }
13009
13084
  }
13010
13085
  async function generateAnthropicResponse(model, params, modelId, options) {
13011
- let text5;
13086
+ let text6;
13012
13087
  let toolCalls;
13013
13088
  let finishReason;
13014
13089
  let usage;
@@ -13074,7 +13149,7 @@ async function generateAnthropicResponse(model, params, modelId, options) {
13074
13149
  clearTimeout(totalTimer);
13075
13150
  if (!forceAbort.signal.aborted) forceAbort.abort();
13076
13151
  }
13077
- text5 = streamedText.join("");
13152
+ text6 = streamedText.join("");
13078
13153
  toolCalls = streamedToolCalls;
13079
13154
  finishReason = streamedFinishReason;
13080
13155
  usage = streamedUsage;
@@ -13094,7 +13169,7 @@ async function generateAnthropicResponse(model, params, modelId, options) {
13094
13169
  maxRetries,
13095
13170
  abortSignal: generateAbort.signal
13096
13171
  });
13097
- ({ text: text5, toolCalls, finishReason, usage, warnings } = r);
13172
+ ({ text: text6, toolCalls, finishReason, usage, warnings } = r);
13098
13173
  } catch (error) {
13099
13174
  if (generateAbort.signal.aborted) throw streamAbortError(generateAbort.signal);
13100
13175
  throw error;
@@ -13113,7 +13188,7 @@ async function generateAnthropicResponse(model, params, modelId, options) {
13113
13188
  role: "assistant",
13114
13189
  model: modelId,
13115
13190
  content: [
13116
- ...text5 ? [{ type: "text", text: text5 }] : [],
13191
+ ...text6 ? [{ type: "text", text: text6 }] : [],
13117
13192
  ...toolCalls.map((tc) => ({
13118
13193
  type: "tool_use",
13119
13194
  id: encodeToolUseId(tc.toolCallId, grabRoundTripSignature(tc)),
@@ -14090,7 +14165,7 @@ function buildDesiredPatchConfig(options = {}) {
14090
14165
  compatibility: model.compatibility,
14091
14166
  upstreamModelId: upstreamModelId2
14092
14167
  });
14093
- const limits = contextLimitsFrom(model, resolveContextWindow(model.id));
14168
+ const limits = contextLimitsFrom(model, lookupKnownContextWindow(model.id));
14094
14169
  const stop = resolveContextStop(
14095
14170
  limits,
14096
14171
  options.sessionStops ? selectContextStop(provider.id, model.id, prefs.modelContextModes) : readContextStop(prefs.modelContextModes, provider.id, model.id) ?? "standard"
@@ -14659,7 +14734,9 @@ async function runPatchCommand(opts = {}) {
14659
14734
  return 1;
14660
14735
  }
14661
14736
  for (const id of desired.unknownWindows) {
14662
- p2.log.warn(`No context window metadata for ${id} \u2014 Claude Code will assume the 200k default.`);
14737
+ p2.log.warn(
14738
+ `No context window metadata for ${id} \u2014 Claude Code will assume the 200k default. Refresh the provider's models, then set a window with \`clodex models --context <model=stop> --save\`, then re-run \`clodex patch\`.`
14739
+ );
14663
14740
  }
14664
14741
  reportRejectedModelAliases(desired.rejectedAliasRejections);
14665
14742
  const localPatches = inspectLocalPatchSource(
@@ -14812,11 +14889,11 @@ function formatModelMetadata(entries) {
14812
14889
 
14813
14890
  // src/first-run.ts
14814
14891
  import pc7 from "picocolors";
14815
- import * as p6 from "@clack/prompts";
14892
+ import * as p7 from "@clack/prompts";
14816
14893
 
14817
14894
  // src/providers-command.ts
14818
14895
  import pc6 from "picocolors";
14819
- import * as p5 from "@clack/prompts";
14896
+ import * as p6 from "@clack/prompts";
14820
14897
 
14821
14898
  // src/registry/import-build.ts
14822
14899
  function oauthAuthRef(providerId) {
@@ -14887,8 +14964,8 @@ async function fetchProviderCatalog(opts) {
14887
14964
  return loadRegistryProviders(warn, { ...opts, warn });
14888
14965
  }
14889
14966
  function providersForPicker(providers) {
14890
- for (const p13 of providers) {
14891
- p13.models.sort((a, b) => {
14967
+ for (const p14 of providers) {
14968
+ p14.models.sort((a, b) => {
14892
14969
  const nameA = a.name || a.id;
14893
14970
  const nameB = b.name || b.id;
14894
14971
  return nameA.localeCompare(nameB, void 0, { sensitivity: "base", numeric: true });
@@ -14904,7 +14981,7 @@ async function resolveLocalProviderApiKey(provider) {
14904
14981
  if (template?.apiKeyOptional || template?.anonymousFreeModels) {
14905
14982
  return "";
14906
14983
  }
14907
- const reg = loadRegistry().providers.find((p13) => p13.id === provider.id);
14984
+ const reg = loadRegistry().providers.find((p14) => p14.id === provider.id);
14908
14985
  const authRef = provider.authRef ?? reg?.authRef ?? oauthAuthRef(provider.id);
14909
14986
  return resolveProviderCredential(provider.id, authRef);
14910
14987
  }
@@ -15545,7 +15622,7 @@ async function addProviderFromTemplate(template, apiKey, opts) {
15545
15622
  }
15546
15623
  };
15547
15624
  if (existing) {
15548
- const idx = registry.providers.findIndex((p13) => p13.id === template.id);
15625
+ const idx = registry.providers.findIndex((p14) => p14.id === template.id);
15549
15626
  registry.providers[idx] = entry;
15550
15627
  } else {
15551
15628
  registry.providers.push(entry);
@@ -15593,127 +15670,8 @@ async function addProviderFromTemplate(template, apiKey, opts) {
15593
15670
  return result;
15594
15671
  }
15595
15672
 
15596
- // src/registry/crud.ts
15597
- async function removeProviderWithinLifecycle(id, opts) {
15598
- const removal = await withRegistryWriteLock(async () => {
15599
- const registry = loadRegistryStrict();
15600
- const index = registry.providers.findIndex((p13) => p13.id === id);
15601
- if (index < 0) {
15602
- return {
15603
- result: {
15604
- removed: false,
15605
- id,
15606
- credentialDeleted: false,
15607
- error: `Provider not found: ${id}`
15608
- },
15609
- queuedRefs: []
15610
- };
15611
- }
15612
- const [removedProvider] = registry.providers.splice(index, 1);
15613
- const queuedRefs = [];
15614
- if (opts?.deleteCredential !== false) {
15615
- const credentialRefs = /* @__PURE__ */ new Set([
15616
- removedProvider.authRef,
15617
- ...removedProvider.defaultAuthRef ? [removedProvider.defaultAuthRef] : [],
15618
- ...Object.values(removedProvider.authAccounts ?? {}).map((slot) => slot.authRef)
15619
- ]);
15620
- for (const authRef of credentialRefs) {
15621
- if (await queueCredentialDelete(authRef)) queuedRefs.push(authRef);
15622
- }
15623
- }
15624
- saveRegistry(registry);
15625
- return {
15626
- result: {
15627
- removed: true,
15628
- id,
15629
- name: removedProvider.name,
15630
- credentialDeleted: false
15631
- },
15632
- queuedRefs
15633
- };
15634
- });
15635
- if (removal.queuedRefs.length > 0) {
15636
- try {
15637
- const cleanup = await reconcilePendingCredentialDeletes();
15638
- removal.result.credentialDeleted = removal.queuedRefs.every((ref) => cleanup.deleted.includes(ref));
15639
- removal.result.credentialCleanupPending = removal.queuedRefs.some((ref) => cleanup.pending.includes(ref)) || cleanup.persistenceError !== void 0;
15640
- } catch {
15641
- removal.result.credentialCleanupPending = true;
15642
- }
15643
- removal.result.credentialCleanupReconciled = true;
15644
- }
15645
- return removal.result;
15646
- }
15647
- async function removeProviderFromRegistry(id, opts) {
15648
- return withProviderMutationLock(id, () => removeProviderWithinLifecycle(id, opts));
15649
- }
15650
- async function setActiveOAuthAccount(id, account) {
15651
- return withProviderMutationLock(id, () => withRegistryWriteLock(() => {
15652
- const registry = loadRegistryStrict();
15653
- const provider = registry.providers.find((p13) => p13.id === id);
15654
- if (!provider) return { updated: false, error: `Provider not found: ${id}` };
15655
- const name = account?.trim().toLowerCase();
15656
- const previous = provider.activeAuthAccount?.trim() || void 0;
15657
- const selectedSlot = name ? getOAuthAccountSlot(provider, name) : void 0;
15658
- let selectionChanged = previous !== name;
15659
- let storageChanged = false;
15660
- if (name && !selectedSlot) {
15661
- const slots = provider.authAccounts ?? {};
15662
- const available = Object.keys(slots).sort().join(", ") || "none";
15663
- return {
15664
- updated: false,
15665
- error: `${provider.name} has no account named "${name}" (available: ${available}).`
15666
- };
15667
- }
15668
- if (name && provider.authType === "oauth") {
15669
- storageChanged = storeActiveOAuthAccount(provider, name, selectedSlot.authRef);
15670
- }
15671
- if (selectionChanged && provider.authType === "oauth") {
15672
- const previousSlot = previous ? getOAuthAccountSlot(provider, previous) : void 0;
15673
- if (previous && previousSlot && provider.modelsCache && provider.authAccounts) {
15674
- provider.authAccounts[previous] = {
15675
- ...previousSlot,
15676
- modelsCache: provider.modelsCache
15677
- };
15678
- }
15679
- const selectedCache = selectedSlot?.modelsCache;
15680
- if (selectedCache) {
15681
- provider.modelsCache = selectedCache;
15682
- provider.refreshedAt = selectedCache.fetchedAt;
15683
- } else {
15684
- delete provider.modelsCache;
15685
- delete provider.refreshedAt;
15686
- }
15687
- }
15688
- if (name && provider.authType !== "oauth") {
15689
- storageChanged = clearActiveOAuthAccount(provider) || storageChanged;
15690
- if (provider.activeAuthAccount !== name) {
15691
- provider.activeAuthAccount = name;
15692
- storageChanged = true;
15693
- }
15694
- } else if (!name && (previous !== void 0 || provider.defaultAuthRef !== void 0)) {
15695
- storageChanged = clearActiveOAuthAccount(provider);
15696
- } else if (!name) {
15697
- selectionChanged = false;
15698
- }
15699
- const migrationNeedsPersistence = registry.schemaVersion < REGISTRY_SCHEMA_VERSION_WITH_MATERIALIZED_ACTIVE_ACCOUNT && provider.defaultAuthRef !== void 0;
15700
- if (selectionChanged || storageChanged || migrationNeedsPersistence) saveRegistry(registry);
15701
- return { updated: true, changed: selectionChanged, ...name ? { account: name } : {}, provider };
15702
- }));
15703
- }
15704
- function toggleProviderEnabled(id) {
15705
- return withRegistryWriteLockSync(() => {
15706
- const registry = loadRegistryStrict();
15707
- const provider = registry.providers.find((p13) => p13.id === id);
15708
- if (!provider) return { toggled: false, error: `Provider not found: ${id}` };
15709
- provider.enabled = !provider.enabled;
15710
- saveRegistry(registry);
15711
- return { toggled: true, enabled: provider.enabled };
15712
- });
15713
- }
15714
-
15715
- // src/registry/refresh-models.ts
15716
- import { isDeepStrictEqual } from "util";
15673
+ // src/providers-custom-add.ts
15674
+ import * as p3 from "@clack/prompts";
15717
15675
 
15718
15676
  // src/registry/url-security.ts
15719
15677
  import { lookup } from "dns/promises";
@@ -15828,6 +15786,13 @@ async function validateCustomEndpointUrl(rawUrl, opts = {}) {
15828
15786
  }
15829
15787
 
15830
15788
  // src/registry/custom-endpoint.ts
15789
+ var CUSTOM_PROVIDER_ALLOCATION_SLOT = "custom-provider-allocation";
15790
+ function npmForKind(kind) {
15791
+ return kind === "anthropic" ? "@ai-sdk/anthropic" : "@ai-sdk/openai-compatible";
15792
+ }
15793
+ function modelFormatForKind(kind) {
15794
+ return kind === "anthropic" ? "anthropic" : "openai";
15795
+ }
15831
15796
  async function fetchAnthropicModels(baseUrl, apiKey, extraHeaders) {
15832
15797
  const root = baseUrl.replace(/\/v1\/?$/, "").replace(/\/$/, "");
15833
15798
  const modelsUrl2 = `${root}/v1/models`;
@@ -15867,13 +15832,14 @@ async function fetchAnthropicModels(baseUrl, apiKey, extraHeaders) {
15867
15832
  for (const row of json.data ?? []) {
15868
15833
  const id = row.id?.trim();
15869
15834
  if (!id) continue;
15835
+ if (hasControlChars(id) || typeof row.name === "string" && hasControlChars(row.name.trim())) continue;
15870
15836
  models.push({
15871
15837
  id,
15872
15838
  name: row.name?.trim() || id,
15873
15839
  upstreamModelId: id,
15874
15840
  family: id.split("-")[0] ?? id,
15875
15841
  brand: deriveBrand(id),
15876
- contextWindow: resolveContextWindow(id),
15842
+ contextWindow: lookupKnownContextWindow(id),
15877
15843
  modelFormat: "anthropic",
15878
15844
  npm: "@ai-sdk/anthropic",
15879
15845
  apiUrl: root
@@ -15901,6 +15867,325 @@ async function fetchAnthropicModels(baseUrl, apiKey, extraHeaders) {
15901
15867
  clearTimeout(timer);
15902
15868
  }
15903
15869
  }
15870
+ function uniqueProviderId(displayName, registry) {
15871
+ let base = customProviderId(displayName);
15872
+ if (!base.startsWith("custom-")) base = `custom-${slugifyProviderId(displayName)}`;
15873
+ if (!isValidProviderId(base)) base = "custom-provider";
15874
+ if (!registry.providers.some((p14) => p14.id === base)) return base;
15875
+ for (let i = 2; ; i++) {
15876
+ const candidate = `${base}-${i}`;
15877
+ if (isValidProviderId(candidate) && !registry.providers.some((p14) => p14.id === candidate)) {
15878
+ return candidate;
15879
+ }
15880
+ }
15881
+ }
15882
+ async function addCustomEndpointProvider(input) {
15883
+ const urlCheck = await validateCustomEndpointUrl(input.baseUrl, {
15884
+ allowInsecureLocal: input.allowInsecureLocal
15885
+ });
15886
+ if (!urlCheck.ok || !urlCheck.normalizedUrl) {
15887
+ return { added: false, error: urlCheck.error, hint: urlCheck.hint };
15888
+ }
15889
+ const normalizedUrl = urlCheck.normalizedUrl;
15890
+ const displayName = input.displayName.trim();
15891
+ const probeProviderId = uniqueProviderId(displayName, { providers: [] });
15892
+ const npm = npmForKind(input.kind);
15893
+ const apiKey = input.apiKey.trim();
15894
+ const anonymous = apiKey.length === 0;
15895
+ const headers = input.headers && Object.keys(input.headers).length > 0 ? input.headers : void 0;
15896
+ let fetched;
15897
+ if (input.kind === "anthropic") {
15898
+ fetched = await fetchAnthropicModels(normalizedUrl, apiKey, headers);
15899
+ } else {
15900
+ fetched = await fetchTemplateModels(
15901
+ {
15902
+ id: probeProviderId,
15903
+ name: displayName,
15904
+ authType: anonymous ? "none" : "api",
15905
+ npm,
15906
+ defaultBaseUrl: normalizedUrl,
15907
+ modelSource: "api-list",
15908
+ supported: true
15909
+ },
15910
+ apiKey,
15911
+ normalizedUrl,
15912
+ headers
15913
+ );
15914
+ }
15915
+ if (fetched.error || fetched.models.length === 0) {
15916
+ return { added: false, error: fetched.error ?? "No models returned.", hint: fetched.hint };
15917
+ }
15918
+ const result = await withProviderMutationLock(CUSTOM_PROVIDER_ALLOCATION_SLOT, async () => {
15919
+ const providerId = await withRegistryWriteLock(
15920
+ () => uniqueProviderId(displayName, loadRegistryStrict())
15921
+ );
15922
+ const account = `provider:${providerId}`;
15923
+ const authRef = anonymous ? "none:anonymous" : credentialInstanceAuthRef(account);
15924
+ const persistAndCommit = async () => {
15925
+ if (!anonymous) {
15926
+ await journalCredentialWrite(authRef);
15927
+ const saved = await provisionProviderCredential(authRef, apiKey);
15928
+ if (!saved) {
15929
+ return {
15930
+ added: false,
15931
+ error: "Could not save API key to the credential store.",
15932
+ hint: "Check credential-store access and try again."
15933
+ };
15934
+ }
15935
+ }
15936
+ return withRegistryWriteLock(async () => {
15937
+ const registry = loadRegistryStrict();
15938
+ if (registry.providers.some((provider) => provider.id === providerId)) {
15939
+ return {
15940
+ added: false,
15941
+ error: `Provider ID ${providerId} changed while its credential was being saved.`,
15942
+ hint: "Retry adding the custom provider."
15943
+ };
15944
+ }
15945
+ const now = (/* @__PURE__ */ new Date()).toISOString();
15946
+ const entry = {
15947
+ id: providerId,
15948
+ templateId: input.kind === "anthropic" ? "custom-anthropic" : "custom-openai",
15949
+ name: displayName,
15950
+ enabled: true,
15951
+ authRef,
15952
+ authType: anonymous ? "none" : "api",
15953
+ api: { npm, url: fetched.baseUrl, ...headers ? { headers } : {} },
15954
+ addedAt: now,
15955
+ refreshedAt: now,
15956
+ modelsCache: {
15957
+ fetchedAt: now,
15958
+ models: fetched.models.map((m) => ({
15959
+ ...m,
15960
+ modelFormat: modelFormatForKind(input.kind),
15961
+ npm,
15962
+ apiUrl: fetched.baseUrl
15963
+ }))
15964
+ }
15965
+ };
15966
+ registry.providers.push(entry);
15967
+ saveRegistry(registry);
15968
+ let credentialCleanupPending = false;
15969
+ if (!anonymous) {
15970
+ try {
15971
+ await cancelCredentialDelete(authRef);
15972
+ } catch {
15973
+ credentialCleanupPending = true;
15974
+ }
15975
+ }
15976
+ return {
15977
+ added: true,
15978
+ provider: entry,
15979
+ modelCount: fetched.models.length,
15980
+ ...credentialCleanupPending ? { credentialCleanupPending: true } : {}
15981
+ };
15982
+ });
15983
+ };
15984
+ if (anonymous) return persistAndCommit();
15985
+ return withCredentialMutationLock(authRef, persistAndCommit);
15986
+ });
15987
+ if (result.added) {
15988
+ try {
15989
+ const cleanup = await reconcilePendingCredentialDeletes();
15990
+ result.credentialCleanupPending = cleanup.pending.length > 0 || cleanup.persistenceError !== void 0;
15991
+ } catch {
15992
+ result.credentialCleanupPending = true;
15993
+ }
15994
+ } else {
15995
+ try {
15996
+ const cleanup = await reconcilePendingCredentialDeletes();
15997
+ result.credentialCleanupPending = cleanup.pending.length > 0 || cleanup.persistenceError !== void 0;
15998
+ } catch {
15999
+ result.credentialCleanupPending = true;
16000
+ }
16001
+ }
16002
+ result.credentialCleanupReconciled = true;
16003
+ return result;
16004
+ }
16005
+
16006
+ // src/providers-custom-add.ts
16007
+ async function runCustomEndpointAddFlow(onResult) {
16008
+ const displayName = await p3.text({
16009
+ message: "Name for this provider:",
16010
+ placeholder: "OpenRouter",
16011
+ validate: (value) => value.trim() ? void 0 : "Name is required"
16012
+ });
16013
+ if (p3.isCancel(displayName)) {
16014
+ p3.cancel("Cancelled.");
16015
+ return 0;
16016
+ }
16017
+ const baseUrl = await p3.text({
16018
+ message: "Base URL of the OpenAI-compatible API (the part before /chat/completions):",
16019
+ placeholder: "https://openrouter.ai/api/v1",
16020
+ validate: (value) => value.trim() ? void 0 : "URL is required"
16021
+ });
16022
+ if (p3.isCancel(baseUrl)) {
16023
+ p3.cancel("Cancelled.");
16024
+ return 0;
16025
+ }
16026
+ let allowInsecureLocal = false;
16027
+ if (/^http:\/\//i.test(String(baseUrl).trim())) {
16028
+ p3.log.warn("HTTP is not encrypted. Only use it for a trusted local or LAN server.");
16029
+ const allow = await p3.confirm({
16030
+ message: "Allow insecure HTTP for this local/LAN server?",
16031
+ initialValue: true
16032
+ });
16033
+ if (p3.isCancel(allow)) {
16034
+ p3.cancel("Cancelled.");
16035
+ return 0;
16036
+ }
16037
+ allowInsecureLocal = allow === true;
16038
+ }
16039
+ const apiKey = await p3.password({
16040
+ message: "API key (leave empty for a local server without auth):"
16041
+ });
16042
+ if (p3.isCancel(apiKey)) {
16043
+ p3.cancel("Cancelled.");
16044
+ return 0;
16045
+ }
16046
+ const spinner6 = p3.spinner();
16047
+ spinner6.start("Testing connection...");
16048
+ const result = await addCustomEndpointProvider({
16049
+ displayName: String(displayName).trim(),
16050
+ baseUrl: String(baseUrl).trim(),
16051
+ apiKey: String(apiKey ?? "").trim(),
16052
+ kind: "openai",
16053
+ allowInsecureLocal
16054
+ });
16055
+ spinner6.stop("");
16056
+ onResult(result);
16057
+ if (!result.added || !result.provider) {
16058
+ p3.log.error(printableServerText(result.error ?? "Could not add the provider."));
16059
+ const hint = printableServerText(result.hint ?? "");
16060
+ if (hint) p3.log.info(hint);
16061
+ return 1;
16062
+ }
16063
+ logConnected(result.provider.name, result.modelCount ?? 0);
16064
+ p3.log.info(`Provider id: ${result.provider.id}. Its models are named clodex:${result.provider.id}:<model>.`);
16065
+ return 0;
16066
+ }
16067
+
16068
+ // src/registry/crud.ts
16069
+ async function removeProviderWithinLifecycle(id, opts) {
16070
+ const removal = await withRegistryWriteLock(async () => {
16071
+ const registry = loadRegistryStrict();
16072
+ const index = registry.providers.findIndex((p14) => p14.id === id);
16073
+ if (index < 0) {
16074
+ return {
16075
+ result: {
16076
+ removed: false,
16077
+ id,
16078
+ credentialDeleted: false,
16079
+ error: `Provider not found: ${id}`
16080
+ },
16081
+ queuedRefs: []
16082
+ };
16083
+ }
16084
+ const [removedProvider] = registry.providers.splice(index, 1);
16085
+ const queuedRefs = [];
16086
+ if (opts?.deleteCredential !== false) {
16087
+ const credentialRefs = /* @__PURE__ */ new Set([
16088
+ removedProvider.authRef,
16089
+ ...removedProvider.defaultAuthRef ? [removedProvider.defaultAuthRef] : [],
16090
+ ...Object.values(removedProvider.authAccounts ?? {}).map((slot) => slot.authRef)
16091
+ ]);
16092
+ for (const authRef of credentialRefs) {
16093
+ if (await queueCredentialDelete(authRef)) queuedRefs.push(authRef);
16094
+ }
16095
+ }
16096
+ saveRegistry(registry);
16097
+ return {
16098
+ result: {
16099
+ removed: true,
16100
+ id,
16101
+ name: removedProvider.name,
16102
+ credentialDeleted: false
16103
+ },
16104
+ queuedRefs
16105
+ };
16106
+ });
16107
+ if (removal.queuedRefs.length > 0) {
16108
+ try {
16109
+ const cleanup = await reconcilePendingCredentialDeletes();
16110
+ removal.result.credentialDeleted = removal.queuedRefs.every((ref) => cleanup.deleted.includes(ref));
16111
+ removal.result.credentialCleanupPending = removal.queuedRefs.some((ref) => cleanup.pending.includes(ref)) || cleanup.persistenceError !== void 0;
16112
+ } catch {
16113
+ removal.result.credentialCleanupPending = true;
16114
+ }
16115
+ removal.result.credentialCleanupReconciled = true;
16116
+ }
16117
+ return removal.result;
16118
+ }
16119
+ async function removeProviderFromRegistry(id, opts) {
16120
+ return withProviderMutationLock(id, () => removeProviderWithinLifecycle(id, opts));
16121
+ }
16122
+ async function setActiveOAuthAccount(id, account) {
16123
+ return withProviderMutationLock(id, () => withRegistryWriteLock(() => {
16124
+ const registry = loadRegistryStrict();
16125
+ const provider = registry.providers.find((p14) => p14.id === id);
16126
+ if (!provider) return { updated: false, error: `Provider not found: ${id}` };
16127
+ const name = account?.trim().toLowerCase();
16128
+ const previous = provider.activeAuthAccount?.trim() || void 0;
16129
+ const selectedSlot = name ? getOAuthAccountSlot(provider, name) : void 0;
16130
+ let selectionChanged = previous !== name;
16131
+ let storageChanged = false;
16132
+ if (name && !selectedSlot) {
16133
+ const slots = provider.authAccounts ?? {};
16134
+ const available = Object.keys(slots).sort().join(", ") || "none";
16135
+ return {
16136
+ updated: false,
16137
+ error: `${provider.name} has no account named "${name}" (available: ${available}).`
16138
+ };
16139
+ }
16140
+ if (name && provider.authType === "oauth") {
16141
+ storageChanged = storeActiveOAuthAccount(provider, name, selectedSlot.authRef);
16142
+ }
16143
+ if (selectionChanged && provider.authType === "oauth") {
16144
+ const previousSlot = previous ? getOAuthAccountSlot(provider, previous) : void 0;
16145
+ if (previous && previousSlot && provider.modelsCache && provider.authAccounts) {
16146
+ provider.authAccounts[previous] = {
16147
+ ...previousSlot,
16148
+ modelsCache: provider.modelsCache
16149
+ };
16150
+ }
16151
+ const selectedCache = selectedSlot?.modelsCache;
16152
+ if (selectedCache) {
16153
+ provider.modelsCache = selectedCache;
16154
+ provider.refreshedAt = selectedCache.fetchedAt;
16155
+ } else {
16156
+ delete provider.modelsCache;
16157
+ delete provider.refreshedAt;
16158
+ }
16159
+ }
16160
+ if (name && provider.authType !== "oauth") {
16161
+ storageChanged = clearActiveOAuthAccount(provider) || storageChanged;
16162
+ if (provider.activeAuthAccount !== name) {
16163
+ provider.activeAuthAccount = name;
16164
+ storageChanged = true;
16165
+ }
16166
+ } else if (!name && (previous !== void 0 || provider.defaultAuthRef !== void 0)) {
16167
+ storageChanged = clearActiveOAuthAccount(provider);
16168
+ } else if (!name) {
16169
+ selectionChanged = false;
16170
+ }
16171
+ const migrationNeedsPersistence = registry.schemaVersion < REGISTRY_SCHEMA_VERSION_WITH_MATERIALIZED_ACTIVE_ACCOUNT && provider.defaultAuthRef !== void 0;
16172
+ if (selectionChanged || storageChanged || migrationNeedsPersistence) saveRegistry(registry);
16173
+ return { updated: true, changed: selectionChanged, ...name ? { account: name } : {}, provider };
16174
+ }));
16175
+ }
16176
+ function toggleProviderEnabled(id) {
16177
+ return withRegistryWriteLockSync(() => {
16178
+ const registry = loadRegistryStrict();
16179
+ const provider = registry.providers.find((p14) => p14.id === id);
16180
+ if (!provider) return { toggled: false, error: `Provider not found: ${id}` };
16181
+ provider.enabled = !provider.enabled;
16182
+ saveRegistry(registry);
16183
+ return { toggled: true, enabled: provider.enabled };
16184
+ });
16185
+ }
16186
+
16187
+ // src/registry/refresh-models.ts
16188
+ import { isDeepStrictEqual } from "util";
15904
16189
 
15905
16190
  // src/registry/model-source.ts
15906
16191
  var MANUAL_ONLY_TEMPLATE_IDS = /* @__PURE__ */ new Set(["vertex", "bedrock", "azure"]);
@@ -16101,7 +16386,7 @@ function buildDynamicOAuthModel(entry, seedById, codexCatalog) {
16101
16386
  upstreamModelId: id,
16102
16387
  family: prefix,
16103
16388
  brand: deriveBrand(prefix),
16104
- contextWindow: entry.context_window ?? resolveContextWindow(id),
16389
+ contextWindow: entry.context_window ?? lookupKnownContextWindow(id),
16105
16390
  maxContextWindow: entry.max_context_window,
16106
16391
  // Absent means no reduction. clodex reports the window the provider actually
16107
16392
  // gives; deciding how much of it to leave free is the client's job, and Claude
@@ -16253,7 +16538,7 @@ async function refreshApiListProvider(provider, apiKey) {
16253
16538
  };
16254
16539
  }
16255
16540
  function updateProviderCache(registry, providerId, models, baseUrl, credentialSnapshot) {
16256
- const idx = registry.providers.findIndex((p13) => p13.id === providerId);
16541
+ const idx = registry.providers.findIndex((p14) => p14.id === providerId);
16257
16542
  if (idx < 0) return;
16258
16543
  const now = (/* @__PURE__ */ new Date()).toISOString();
16259
16544
  const existing = registry.providers[idx];
@@ -16324,7 +16609,7 @@ function assertRefreshCredentialStillCurrent(current, snapshot) {
16324
16609
  }
16325
16610
  async function refreshProviderModels(providerId, apiKey, registry, credentialSnapshot) {
16326
16611
  const workingRegistry = registry ?? loadRegistryStrict();
16327
- const provider = workingRegistry.providers.find((p13) => p13.id === providerId);
16612
+ const provider = workingRegistry.providers.find((p14) => p14.id === providerId);
16328
16613
  if (!provider) {
16329
16614
  return { id: providerId, name: providerId, ok: false, reason: "Provider not found." };
16330
16615
  }
@@ -16539,7 +16824,7 @@ async function refreshProviderModelsWithCredential(providerId, resolveKey, selec
16539
16824
  async function refreshAllProviderModels(resolveKey) {
16540
16825
  const refreshed = [];
16541
16826
  const registry = loadRegistryStrict();
16542
- const enabledProviders = registry.providers.filter((p13) => p13.enabled);
16827
+ const enabledProviders = registry.providers.filter((p14) => p14.enabled);
16543
16828
  for (const provider of enabledProviders) {
16544
16829
  const accountOverride = process.env[OAUTH_ACCOUNT_ENV] ?? null;
16545
16830
  try {
@@ -16563,7 +16848,7 @@ async function refreshAllProviderModels(resolveKey) {
16563
16848
 
16564
16849
  // src/registry/provider-auth.ts
16565
16850
  import pc4 from "picocolors";
16566
- import * as p3 from "@clack/prompts";
16851
+ import * as p4 from "@clack/prompts";
16567
16852
  import open from "open";
16568
16853
  function validateOAuthAccountName(name) {
16569
16854
  const trimmed = name.trim().toLowerCase();
@@ -16586,39 +16871,39 @@ function openBrowser(url) {
16586
16871
  async function runNativeDeviceCode(providerId) {
16587
16872
  const label = PROVIDER_DISPLAY[providerId];
16588
16873
  printOAuthStepsPanel(`${label} \u2014 Sign in`, label);
16589
- const spinner5 = p3.spinner();
16590
- spinner5.start("Waiting for authorization...");
16874
+ const spinner6 = p4.spinner();
16875
+ spinner6.start("Waiting for authorization...");
16591
16876
  try {
16592
16877
  const { tokens, accountId } = await runOpenAiDeviceCodeFlow(({ url, userCode }) => {
16593
- spinner5.stop("");
16594
- p3.log.info(`Visit: ${pc4.cyan(url)}`);
16595
- p3.log.info(`Enter code: ${pc4.bold(userCode)}`);
16878
+ spinner6.stop("");
16879
+ p4.log.info(`Visit: ${pc4.cyan(url)}`);
16880
+ p4.log.info(`Enter code: ${pc4.bold(userCode)}`);
16596
16881
  openBrowser(url);
16597
- spinner5.start("Waiting for authorization...");
16882
+ spinner6.start("Waiting for authorization...");
16598
16883
  });
16599
- spinner5.stop(pc4.green("Signed in to OpenAI ChatGPT"));
16884
+ spinner6.stop(pc4.green("Signed in to OpenAI ChatGPT"));
16600
16885
  return tokensToStoredCredential(tokens, void 0, accountId);
16601
16886
  } catch (err) {
16602
- spinner5.stop("");
16887
+ spinner6.stop("");
16603
16888
  throw err;
16604
16889
  }
16605
16890
  }
16606
16891
  async function runNativeBrowserSignIn(providerId) {
16607
16892
  const label = PROVIDER_DISPLAY[providerId];
16608
16893
  printOAuthBrowserPanel(`${label} \u2014 Sign in`, label);
16609
- const spinner5 = p3.spinner();
16610
- spinner5.start("Opening your browser...");
16894
+ const spinner6 = p4.spinner();
16895
+ spinner6.start("Opening your browser...");
16611
16896
  try {
16612
16897
  const { tokens, accountId } = await runOpenAiBrowserFlow(({ url }) => {
16613
- spinner5.stop("");
16614
- p3.log.info(`If the browser did not open, visit: ${pc4.cyan(url)}`);
16898
+ spinner6.stop("");
16899
+ p4.log.info(`If the browser did not open, visit: ${pc4.cyan(url)}`);
16615
16900
  openBrowser(url);
16616
- spinner5.start("Waiting for sign-in in your browser...");
16901
+ spinner6.start("Waiting for sign-in in your browser...");
16617
16902
  });
16618
- spinner5.stop(pc4.green("Signed in to OpenAI ChatGPT"));
16903
+ spinner6.stop(pc4.green("Signed in to OpenAI ChatGPT"));
16619
16904
  return tokensToStoredCredential(tokens, void 0, accountId);
16620
16905
  } catch (err) {
16621
- spinner5.stop("");
16906
+ spinner6.stop("");
16622
16907
  throw err;
16623
16908
  }
16624
16909
  }
@@ -16827,7 +17112,7 @@ async function authenticateProvider(providerId, options = {}) {
16827
17112
  }
16828
17113
  const cred = options.method === "browser" ? await runNativeBrowserSignIn(providerId) : await runNativeDeviceCode(providerId);
16829
17114
  const persisted = await persistNativeOAuthCredential(providerId, cred, accountName);
16830
- const refreshSpinner = p3.spinner();
17115
+ const refreshSpinner = p4.spinner();
16831
17116
  refreshSpinner.start("Refreshing model list...");
16832
17117
  try {
16833
17118
  const accountOverride = accountName ?? (persisted.registryProvider.activeAuthAccount === void 0 ? null : process.env[OAUTH_ACCOUNT_ENV] ?? null);
@@ -16882,7 +17167,7 @@ ${pc4.bold("Named accounts:")}
16882
17167
  }
16883
17168
 
16884
17169
  // src/prompts.ts
16885
- import * as p4 from "@clack/prompts";
17170
+ import * as p5 from "@clack/prompts";
16886
17171
  import pc5 from "picocolors";
16887
17172
 
16888
17173
  // src/model-search.ts
@@ -16991,12 +17276,12 @@ async function pickModelFromPagedList(list, toOption, messagePrefix, initialMode
16991
17276
  options.push(navOption(SWITCH_SEARCH, "\u2190 New search", ""));
16992
17277
  }
16993
17278
  const initialValue = (initialModelId && pageItems.some((m) => m.id === initialModelId) ? initialModelId : pageItems[0]?.id) ?? options[0]?.value;
16994
- const picked = await p4.select({
17279
+ const picked = await p5.select({
16995
17280
  message: `${messagePrefix} (page ${currentPage + 1} of ${totalPages})`,
16996
17281
  options,
16997
17282
  initialValue
16998
17283
  });
16999
- if (p4.isCancel(picked)) return "menu";
17284
+ if (p5.isCancel(picked)) return "menu";
17000
17285
  const choice = String(picked);
17001
17286
  if (choice === PAGE_PREV) {
17002
17287
  page = currentPage - 1;
@@ -17017,7 +17302,7 @@ async function selectLargeCatalog(models, browseList, toOption, message, initial
17017
17302
  let mode = "choose";
17018
17303
  while (true) {
17019
17304
  if (mode === "choose") {
17020
- const method = await p4.select({
17305
+ const method = await p5.select({
17021
17306
  message: `${message} (${models.length} available)`,
17022
17307
  options: [
17023
17308
  { value: MODE_SEARCH, label: pc5.cyan("Search models"), hint: "Filter by name, id, or brand" },
@@ -17029,7 +17314,7 @@ async function selectLargeCatalog(models, browseList, toOption, message, initial
17029
17314
  navOption("__back__", "\u2190 Go back", "Select a different provider")
17030
17315
  ]
17031
17316
  });
17032
- if (p4.isCancel(method) || String(method) === "__back__") {
17317
+ if (p5.isCancel(method) || String(method) === "__back__") {
17033
17318
  return "back";
17034
17319
  }
17035
17320
  mode = method === MODE_BROWSE ? "browse" : "search";
@@ -17054,17 +17339,17 @@ async function selectLargeCatalog(models, browseList, toOption, message, initial
17054
17339
  if (isSelectedModel(picked)) return picked;
17055
17340
  continue;
17056
17341
  }
17057
- const searchInput = await p4.text({
17342
+ const searchInput = await p5.text({
17058
17343
  message: `Search models (${models.length} available):`,
17059
17344
  placeholder: "e.g. claude, sonnet, llama"
17060
17345
  });
17061
- if (p4.isCancel(searchInput)) {
17346
+ if (p5.isCancel(searchInput)) {
17062
17347
  mode = "choose";
17063
17348
  continue;
17064
17349
  }
17065
17350
  const matched = filterModelsBySearch(browseList, String(searchInput));
17066
17351
  if (matched.length === 0) {
17067
- p4.log.warn("No models match \u2014 try a different search");
17352
+ p5.log.warn("No models match \u2014 try a different search");
17068
17353
  continue;
17069
17354
  }
17070
17355
  const result = await pickModelFromPagedList(
@@ -17095,12 +17380,12 @@ async function selectModelWithSearch(models, toOption, message, initialModelId,
17095
17380
  navOption("__back__", "\u2190 Go back", "")
17096
17381
  ];
17097
17382
  const initialValue = initialModelId && options.some((o) => o.value === initialModelId) ? initialModelId : options[0]?.value;
17098
- const picked = await p4.select({
17383
+ const picked = await p5.select({
17099
17384
  message,
17100
17385
  options,
17101
17386
  initialValue
17102
17387
  });
17103
- if (p4.isCancel(picked) || String(picked) === "__back__") {
17388
+ if (p5.isCancel(picked) || String(picked) === "__back__") {
17104
17389
  return "back";
17105
17390
  }
17106
17391
  const selected = models.find((m) => m.id === String(picked));
@@ -17134,12 +17419,12 @@ async function pickLocalModel(provider, conflicts, prefs) {
17134
17419
  navOption(BROWSE_ALL, "Browse all models \u2192", `${provider.models.length} available`),
17135
17420
  navOption("__back__", "\u2190 Go back", "Select a different provider")
17136
17421
  ];
17137
- const picked = await p4.select({
17422
+ const picked = await p5.select({
17138
17423
  message: "Which model?",
17139
17424
  options,
17140
17425
  initialValue: recentModels[0].id
17141
17426
  });
17142
- if (p4.isCancel(picked) || String(picked) === "__back__") {
17427
+ if (p5.isCancel(picked) || String(picked) === "__back__") {
17143
17428
  return "back";
17144
17429
  }
17145
17430
  if (String(picked) === BROWSE_ALL) {
@@ -17166,12 +17451,12 @@ async function pickLocalModel(provider, conflicts, prefs) {
17166
17451
  }
17167
17452
  noteEnvConflicts(conflicts);
17168
17453
  const modelLabel = formatModelLabel(selectedModel);
17169
- const confirmed = await p4.confirm({
17454
+ const confirmed = await p5.confirm({
17170
17455
  message: confirmLaunchMessage("Claude Code", modelLabel, selectedModel.id, provider.name),
17171
17456
  initialValue: true
17172
17457
  });
17173
- if (p4.isCancel(confirmed) || !confirmed) {
17174
- p4.cancel("Cancelled.");
17458
+ if (p5.isCancel(confirmed) || !confirmed) {
17459
+ p5.cancel("Cancelled.");
17175
17460
  return null;
17176
17461
  }
17177
17462
  relayOutro("Launching", fmtModel(modelLabel, selectedModel.id));
@@ -17187,7 +17472,7 @@ function reportCredentialCleanup(pending, state, reconciled = false) {
17187
17472
  return;
17188
17473
  }
17189
17474
  if (pending) {
17190
- p5.log.warn(CREDENTIAL_CLEANUP_PENDING_MESSAGE);
17475
+ p6.log.warn(CREDENTIAL_CLEANUP_PENDING_MESSAGE);
17191
17476
  }
17192
17477
  }
17193
17478
  async function reconcileCredentialCleanup() {
@@ -17277,7 +17562,7 @@ ${pc6.bold("Usage:")}
17277
17562
 
17278
17563
  ${pc6.bold("Subcommands:")}
17279
17564
  (none) Provider hub wizard
17280
- add Add a built-in provider or sign in with ChatGPT
17565
+ add Add a built-in provider, a custom OpenAI-compatible server, or sign in with ChatGPT
17281
17566
  auth Sign in with ChatGPT/Codex-plan OAuth (device code, or --browser)
17282
17567
  list Show configured providers
17283
17568
  remove Remove a provider by id
@@ -17404,7 +17689,7 @@ function providerLabel(name, modelCount, enabled) {
17404
17689
  return `${fmtEnabledStar(enabled)} ${fmtProvider(name)} ${pc6.dim(`(${modelCount} model${modelCount === 1 ? "" : "s"})`)}`;
17405
17690
  }
17406
17691
  async function promptOAuthMethod() {
17407
- const choice = await p5.select({
17692
+ const choice = await p6.select({
17408
17693
  message: "How do you want to sign in?",
17409
17694
  initialValue: "native",
17410
17695
  options: [
@@ -17412,8 +17697,8 @@ async function promptOAuthMethod() {
17412
17697
  { value: "browser", label: "Browser", hint: "for workspaces that disable device code authorization" }
17413
17698
  ]
17414
17699
  });
17415
- if (p5.isCancel(choice)) {
17416
- p5.cancel("Cancelled.");
17700
+ if (p6.isCancel(choice)) {
17701
+ p6.cancel("Cancelled.");
17417
17702
  return null;
17418
17703
  }
17419
17704
  return choice;
@@ -17422,17 +17707,17 @@ async function runProvidersAuthWithCleanupState(providerId, method, cleanupState
17422
17707
  try {
17423
17708
  const result = await authenticateProvider(providerId, { method, account });
17424
17709
  const slot = account === void 0 ? void 0 : validateOAuthAccountName(account);
17425
- p5.log.success(
17710
+ p6.log.success(
17426
17711
  slot ? `Signed in to ${result.registryProvider.name} (account "${slot}") \u2014 make it the account every launch uses with: clodex providers` : `Signed in to ${result.registryProvider.name} \u2014 credential saved to the credential store.`
17427
17712
  );
17428
17713
  reportCredentialCleanup(result.credentialCleanupPending, cleanupState, true);
17429
17714
  return 0;
17430
17715
  } catch (err) {
17431
17716
  if (err instanceof Error && err.message === "Cancelled") {
17432
- p5.cancel("Cancelled.");
17717
+ p6.cancel("Cancelled.");
17433
17718
  return 0;
17434
17719
  }
17435
- p5.log.error(err instanceof Error ? err.message : String(err));
17720
+ p6.log.error(err instanceof Error ? err.message : String(err));
17436
17721
  return 1;
17437
17722
  }
17438
17723
  }
@@ -17443,13 +17728,13 @@ async function runProvidersRefreshModels(providerId, options = {}) {
17443
17728
  const resolveKey = async (provider) => resolveProviderCredentialWithSource(provider.id, provider.authRef);
17444
17729
  if (providerId) {
17445
17730
  const registry = loadRegistry();
17446
- const provider = registry.providers.find((p13) => p13.id === providerId);
17731
+ const provider = registry.providers.find((p14) => p14.id === providerId);
17447
17732
  if (!provider) {
17448
- p5.log.error(`Provider not found: ${providerId}`);
17733
+ p6.log.error(`Provider not found: ${providerId}`);
17449
17734
  return 1;
17450
17735
  }
17451
- const spinner6 = p5.spinner();
17452
- spinner6.start(`Refreshing ${provider.name}...`);
17736
+ const spinner7 = p6.spinner();
17737
+ spinner7.start(`Refreshing ${provider.name}...`);
17453
17738
  const accountOverride = options.accountOverride === void 0 ? process.env[OAUTH_ACCOUNT_ENV] ?? null : options.accountOverride;
17454
17739
  let result;
17455
17740
  try {
@@ -17465,59 +17750,59 @@ async function runProvidersRefreshModels(providerId, options = {}) {
17465
17750
  { ignoreProviderOverride: options.ignoreProviderCredentialOverride }
17466
17751
  );
17467
17752
  } catch (err) {
17468
- spinner6.stop("");
17469
- p5.log.error(err instanceof Error ? err.message : String(err));
17753
+ spinner7.stop("");
17754
+ p6.log.error(err instanceof Error ? err.message : String(err));
17470
17755
  return 1;
17471
17756
  }
17472
- spinner6.stop("");
17757
+ spinner7.stop("");
17473
17758
  if (result.skipped) {
17474
17759
  const countNote = result.modelCount ? ` (${result.modelCount} cached models kept)` : "";
17475
- p5.log.warn(`${result.name}: ${result.reason}${countNote}`);
17760
+ p6.log.warn(`${result.name}: ${result.reason}${countNote}`);
17476
17761
  return 0;
17477
17762
  }
17478
17763
  if (!result.ok) {
17479
- p5.log.error(`${result.name}: ${result.reason ?? "Refresh failed."}`);
17764
+ p6.log.error(`${result.name}: ${result.reason ?? "Refresh failed."}`);
17480
17765
  return 1;
17481
17766
  }
17482
17767
  const diff = result.previousModelCount === void 0 ? 0 : (result.modelCount ?? 0) - result.previousModelCount;
17483
17768
  const diffStr = result.previousModelCount === void 0 ? "" : diff > 0 ? ` (+${diff})` : diff < 0 ? ` (${diff})` : "";
17484
- p5.log.success(`${result.name}: ${result.modelCount} model${result.modelCount === 1 ? "" : "s"} updated${diffStr}.`);
17769
+ p6.log.success(`${result.name}: ${result.modelCount} model${result.modelCount === 1 ? "" : "s"} updated${diffStr}.`);
17485
17770
  if (result.reason) {
17486
- p5.log.warn(result.reason);
17771
+ p6.log.warn(result.reason);
17487
17772
  }
17488
17773
  return 0;
17489
17774
  }
17490
- const spinner5 = p5.spinner();
17491
- spinner5.start("Refreshing model lists...");
17775
+ const spinner6 = p6.spinner();
17776
+ spinner6.start("Refreshing model lists...");
17492
17777
  const { refreshed } = await refreshAllProviderModels(resolveKey);
17493
- spinner5.stop("");
17778
+ spinner6.stop("");
17494
17779
  const ok = refreshed.filter((r) => r.ok && !r.skipped);
17495
17780
  const skipped = refreshed.filter((r) => r.skipped);
17496
17781
  const failed = refreshed.filter((r) => !r.ok);
17497
17782
  if (ok.length > 0) {
17498
- p5.log.success(`Updated ${ok.length} provider${ok.length === 1 ? "" : "s"}.`);
17783
+ p6.log.success(`Updated ${ok.length} provider${ok.length === 1 ? "" : "s"}.`);
17499
17784
  for (const r of ok) {
17500
17785
  const diff = r.previousModelCount === void 0 ? 0 : (r.modelCount ?? 0) - r.previousModelCount;
17501
17786
  const diffStr = r.previousModelCount === void 0 ? "" : diff > 0 ? ` (+${diff})` : diff < 0 ? ` (${diff})` : "";
17502
- p5.log.info(` ${r.name}: ${r.modelCount} model${r.modelCount === 1 ? "" : "s"}${diffStr}`);
17787
+ p6.log.info(` ${r.name}: ${r.modelCount} model${r.modelCount === 1 ? "" : "s"}${diffStr}`);
17503
17788
  if (r.reason) {
17504
- p5.log.warn(` ${r.reason}`);
17789
+ p6.log.warn(` ${r.reason}`);
17505
17790
  }
17506
17791
  }
17507
17792
  }
17508
17793
  for (const r of skipped) {
17509
17794
  const countNote = r.modelCount ? ` (${r.modelCount} cached models kept)` : "";
17510
- p5.log.warn(`Skipped ${r.name}: ${r.reason}${countNote}`);
17795
+ p6.log.warn(`Skipped ${r.name}: ${r.reason}${countNote}`);
17511
17796
  }
17512
17797
  for (const r of failed) {
17513
- p5.log.error(`${r.name}: ${r.reason ?? "Refresh failed."}`);
17798
+ p6.log.error(`${r.name}: ${r.reason ?? "Refresh failed."}`);
17514
17799
  }
17515
17800
  return failed.length > 0 ? 1 : 0;
17516
17801
  }
17517
17802
  async function runProvidersList() {
17518
17803
  const entries = await resolveProvidersForDisplay();
17519
17804
  if (entries.length === 0) {
17520
- p5.log.info("No providers configured. Run clodex providers add or clodex providers auth openai.");
17805
+ p6.log.info("No providers configured. Run clodex providers add or clodex providers auth openai.");
17521
17806
  return 0;
17522
17807
  }
17523
17808
  console.log("");
@@ -17538,7 +17823,7 @@ async function runTemplateAddFlow(templateId, cleanupState) {
17538
17823
  const template = listRegistryAddableTemplates(registry.providers).find((candidate) => candidate.id === templateId);
17539
17824
  if (!template) {
17540
17825
  const known = getTemplateById(templateId);
17541
- p5.log.info(known ? `${known.name} is already configured.` : `Unknown provider template: ${templateId}`);
17826
+ p6.log.info(known ? `${known.name} is already configured.` : `Unknown provider template: ${templateId}`);
17542
17827
  return known ? 0 : 1;
17543
17828
  }
17544
17829
  if (template.signupUrl) {
@@ -17546,27 +17831,27 @@ async function runTemplateAddFlow(templateId, cleanupState) {
17546
17831
  `${pc6.white("Get an API key at:")} ${fmtUrl(template.signupUrl)}`
17547
17832
  ]);
17548
17833
  }
17549
- const apiKeyInput = await p5.password({
17834
+ const apiKeyInput = await p6.password({
17550
17835
  message: `Paste your ${template.name} API key:`,
17551
17836
  validate: (val) => val.trim() ? void 0 : "Key cannot be empty"
17552
17837
  });
17553
- if (p5.isCancel(apiKeyInput)) {
17554
- p5.cancel("Cancelled.");
17838
+ if (p6.isCancel(apiKeyInput)) {
17839
+ p6.cancel("Cancelled.");
17555
17840
  return 0;
17556
17841
  }
17557
17842
  const apiKey = String(apiKeyInput).trim();
17558
- const spinner5 = p5.spinner();
17559
- spinner5.start(`Testing connection to ${template.name}...`);
17843
+ const spinner6 = p6.spinner();
17844
+ spinner6.start(`Testing connection to ${template.name}...`);
17560
17845
  const result = await addProviderFromTemplate(template, apiKey);
17561
- spinner5.stop("");
17846
+ spinner6.stop("");
17562
17847
  reportCredentialCleanup(
17563
17848
  result.credentialCleanupPending === true,
17564
17849
  cleanupState,
17565
17850
  result.credentialCleanupReconciled === true
17566
17851
  );
17567
17852
  if (!result.added) {
17568
- p5.log.error(result.error ?? "Could not add provider.");
17569
- if (result.hint) p5.log.info(result.hint);
17853
+ p6.log.error(result.error ?? "Could not add provider.");
17854
+ if (result.hint) p6.log.info(result.hint);
17570
17855
  return 1;
17571
17856
  }
17572
17857
  logConnected(template.name, result.modelCount ?? 0);
@@ -17590,16 +17875,17 @@ async function runProvidersAddWithCleanupState(cleanupState) {
17590
17875
  hint: template.id === "openai" ? "platform.openai.com key (usage-billed)" : "provider API key (usage-billed)"
17591
17876
  });
17592
17877
  }
17593
- if (options.length === 0) {
17594
- p5.log.info("All built-in providers are already configured.");
17595
- return 0;
17596
- }
17597
- const choice = await p5.select({
17878
+ options.push({
17879
+ value: "custom",
17880
+ label: "Custom OpenAI-compatible server",
17881
+ hint: "OpenRouter, Together, LM Studio, vLLM \u2014 any base URL"
17882
+ });
17883
+ const choice = await p6.select({
17598
17884
  message: "Add a provider",
17599
17885
  options
17600
17886
  });
17601
- if (p5.isCancel(choice)) {
17602
- p5.cancel("Cancelled.");
17887
+ if (p6.isCancel(choice)) {
17888
+ p6.cancel("Cancelled.");
17603
17889
  return 0;
17604
17890
  }
17605
17891
  if (choice === "oauth") {
@@ -17607,6 +17893,13 @@ async function runProvidersAddWithCleanupState(cleanupState) {
17607
17893
  if (method === null) return 0;
17608
17894
  return runProvidersAuthWithCleanupState("openai", method, cleanupState);
17609
17895
  }
17896
+ if (choice === "custom") {
17897
+ return runCustomEndpointAddFlow((result) => reportCredentialCleanup(
17898
+ result.credentialCleanupPending === true,
17899
+ cleanupState,
17900
+ result.credentialCleanupReconciled === true
17901
+ ));
17902
+ }
17610
17903
  if (typeof choice === "string" && choice.startsWith("api:")) {
17611
17904
  return runTemplateAddFlow(choice.slice("api:".length), cleanupState);
17612
17905
  }
@@ -17619,16 +17912,16 @@ async function runProvidersRemoveWithCleanupState(id, interactive = false, clean
17619
17912
  const registry = loadRegistry();
17620
17913
  const provider = registry.providers.find((pr) => pr.id === id);
17621
17914
  if (!provider) {
17622
- p5.log.error(`Provider not found: ${id}`);
17915
+ p6.log.error(`Provider not found: ${id}`);
17623
17916
  return 1;
17624
17917
  }
17625
17918
  if (interactive) {
17626
- const confirm6 = await p5.confirm({
17919
+ const confirm7 = await p6.confirm({
17627
17920
  message: `Remove ${provider.name} (${id})?`,
17628
17921
  initialValue: false
17629
17922
  });
17630
- if (p5.isCancel(confirm6) || !confirm6) {
17631
- p5.cancel("Cancelled.");
17923
+ if (p6.isCancel(confirm7) || !confirm7) {
17924
+ p6.cancel("Cancelled.");
17632
17925
  return 0;
17633
17926
  }
17634
17927
  }
@@ -17639,16 +17932,16 @@ async function runProvidersRemoveWithCleanupState(id, interactive = false, clean
17639
17932
  result.credentialCleanupReconciled === true
17640
17933
  );
17641
17934
  if (!result.removed) {
17642
- p5.log.error(result.error ?? `Could not remove ${id}`);
17935
+ p6.log.error(result.error ?? `Could not remove ${id}`);
17643
17936
  return 1;
17644
17937
  }
17645
17938
  if (result.error) {
17646
- p5.log.error(result.error);
17939
+ p6.log.error(result.error);
17647
17940
  return 1;
17648
17941
  }
17649
- p5.log.success(`Removed ${result.name ?? id}.`);
17942
+ p6.log.success(`Removed ${result.name ?? id}.`);
17650
17943
  if (result.credentialDeleted) {
17651
- p5.log.info("Provider API key removed from the credential store.");
17944
+ p6.log.info("Provider API key removed from the credential store.");
17652
17945
  }
17653
17946
  return 0;
17654
17947
  }
@@ -17723,11 +18016,11 @@ async function runProviderDetail(id) {
17723
18016
  { value: "remove", label: "Remove provider", hint: "Delete from registry and credential store when safe" },
17724
18017
  { value: "back", label: "Back", hint: "" }
17725
18018
  );
17726
- const action = await p5.select({
18019
+ const action = await p6.select({
17727
18020
  message: "What would you like to do?",
17728
18021
  options: detailOptions
17729
18022
  });
17730
- if (p5.isCancel(action) || action === "back") return "back";
18023
+ if (p6.isCancel(action) || action === "back") return "back";
17731
18024
  if (action === "browse") {
17732
18025
  const cachedModels = projectProviderCachedModels(modelProvider);
17733
18026
  const localModels = cachedModels.map((m) => cachedModelToLocal(m, modelProvider)).filter((m) => m !== null);
@@ -17754,7 +18047,7 @@ async function runProviderDetail(id) {
17754
18047
  const providerDefault = "<default>";
17755
18048
  const stored = provider.activeAuthAccount;
17756
18049
  const current = stored !== void 0 && accountSlots.includes(stored) ? stored : providerDefault;
17757
- const chosen = await p5.select({
18050
+ const chosen = await p6.select({
17758
18051
  message: "Which account should every launch use?",
17759
18052
  initialValue: current,
17760
18053
  options: [
@@ -17770,15 +18063,15 @@ async function runProviderDetail(id) {
17770
18063
  }))
17771
18064
  ]
17772
18065
  });
17773
- if (p5.isCancel(chosen)) return "back";
18066
+ if (p6.isCancel(chosen)) return "back";
17774
18067
  return withProviderMutationLock(id, async () => {
17775
18068
  const result = await setActiveOAuthAccount(id, chosen === providerDefault ? void 0 : chosen);
17776
18069
  if (!result.updated) {
17777
- p5.log.error(result.error ?? "Could not switch account.");
18070
+ p6.log.error(result.error ?? "Could not switch account.");
17778
18071
  return "back";
17779
18072
  }
17780
18073
  if (!result.provider) {
17781
- p5.log.error("Account selection was saved, but the resulting provider state could not be read.");
18074
+ p6.log.error("Account selection was saved, but the resulting provider state could not be read.");
17782
18075
  return "back";
17783
18076
  }
17784
18077
  const refreshExitCode = await runProvidersRefreshModels(id, {
@@ -17787,7 +18080,7 @@ async function runProviderDetail(id) {
17787
18080
  });
17788
18081
  const currentProvider = loadRegistry().providers.find((candidate) => candidate.id === id);
17789
18082
  if (!currentProvider) {
17790
- p5.log.error("Account selection was saved, but the resulting provider state could not be read.");
18083
+ p6.log.error("Account selection was saved, but the resulting provider state could not be read.");
17791
18084
  return "back";
17792
18085
  }
17793
18086
  const outcome = accountSwitchOutcome(
@@ -17799,26 +18092,26 @@ async function runProviderDetail(id) {
17799
18092
  if (outcome.ok && currentProvider.enabled && currentProvider.authType === "oauth" && (refreshExitCode !== 0 || !selectedCatalogReady)) {
17800
18093
  const savedLabel = result.account ?? PROVIDER_DEFAULT_ACCOUNT_LABEL;
17801
18094
  const savedContext = outcome.confirmsLaunch ? `Saved ${savedLabel} for ${currentProvider.name}.` : outcome.message;
17802
- p5.log.warn(
18095
+ p6.log.warn(
17803
18096
  `${savedContext} Automatic model refresh for the saved selection did not produce a usable catalog; choose Switch account again to retry it before relying on that selection for launches.`
17804
18097
  );
17805
18098
  } else if (outcome.ok) {
17806
- p5.log.success(outcome.message);
18099
+ p6.log.success(outcome.message);
17807
18100
  } else {
17808
- p5.log.warn(outcome.message);
18101
+ p6.log.warn(outcome.message);
17809
18102
  }
17810
18103
  const restartWarning = accountSwitchServerRestartWarning(
17811
18104
  readLiveServerRuntimeStates().length,
17812
18105
  result.changed
17813
18106
  );
17814
- if (restartWarning) p5.log.warn(restartWarning);
18107
+ if (restartWarning) p6.log.warn(restartWarning);
17815
18108
  return "back";
17816
18109
  });
17817
18110
  }
17818
18111
  if (action === "toggle") {
17819
18112
  const result = toggleProviderEnabled(id);
17820
18113
  if (result.toggled) {
17821
- p5.log.success(`${provider.name} ${result.enabled ? "enabled" : "disabled"}.`);
18114
+ p6.log.success(`${provider.name} ${result.enabled ? "enabled" : "disabled"}.`);
17822
18115
  }
17823
18116
  return "back";
17824
18117
  }
@@ -17854,11 +18147,11 @@ async function runProvidersHub() {
17854
18147
  options.push({ value: "refresh-all", label: "\u21BA Refresh all models", hint: "Update model lists for all providers" });
17855
18148
  }
17856
18149
  options.push({ value: "done", label: "Done", hint: "" });
17857
- const choice = await p5.select({
18150
+ const choice = await p6.select({
17858
18151
  message: entries.length > 0 ? "Your providers" : "Get started",
17859
18152
  options
17860
18153
  });
17861
- if (p5.isCancel(choice) || choice === "done") {
18154
+ if (p6.isCancel(choice) || choice === "done") {
17862
18155
  return 0;
17863
18156
  }
17864
18157
  if (choice === "add") {
@@ -17876,7 +18169,7 @@ async function runProvidersHub() {
17876
18169
  continue;
17877
18170
  }
17878
18171
  if (choice === "auth-account") {
17879
- const name = await p5.text({
18172
+ const name = await p6.text({
17880
18173
  message: "Name for this account (choose which one launches with: clodex providers)",
17881
18174
  placeholder: "work",
17882
18175
  validate: (value) => {
@@ -17888,7 +18181,7 @@ async function runProvidersHub() {
17888
18181
  }
17889
18182
  }
17890
18183
  });
17891
- if (p5.isCancel(name)) continue;
18184
+ if (p6.isCancel(name)) continue;
17892
18185
  const accountMethod = await promptOAuthMethod();
17893
18186
  if (accountMethod === null) continue;
17894
18187
  await runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState("openai", accountMethod, state, String(name)));
@@ -17904,7 +18197,7 @@ async function runProvidersHub() {
17904
18197
  async function runProvidersCommand(args) {
17905
18198
  const parsed = parseProvidersArgs(args);
17906
18199
  if (parsed.error) {
17907
- p5.log.error(parsed.error);
18200
+ p6.log.error(parsed.error);
17908
18201
  return 1;
17909
18202
  }
17910
18203
  if (parsed.showHelp && parsed.subcommand !== "auth") {
@@ -17941,7 +18234,7 @@ async function needsFirstRunSetup() {
17941
18234
  }
17942
18235
  async function runFirstRunWizard(_trace = false) {
17943
18236
  printWelcomePanel();
17944
- const choice = await p6.select({
18237
+ const choice = await p7.select({
17945
18238
  message: "How do you want to connect to OpenAI?",
17946
18239
  options: [
17947
18240
  {
@@ -17956,8 +18249,8 @@ async function runFirstRunWizard(_trace = false) {
17956
18249
  }
17957
18250
  ]
17958
18251
  });
17959
- if (p6.isCancel(choice)) {
17960
- p6.cancel("Cancelled.");
18252
+ if (p7.isCancel(choice)) {
18253
+ p7.cancel("Cancelled.");
17961
18254
  return "cancel";
17962
18255
  }
17963
18256
  let code;
@@ -17970,14 +18263,14 @@ async function runFirstRunWizard(_trace = false) {
17970
18263
  }
17971
18264
  if (code !== 0) return "cancel";
17972
18265
  if (await needsFirstRunSetup()) return "cancel";
17973
- p6.log.success("OpenAI provider ready \u2014 picking a model next.");
18266
+ p7.log.success("OpenAI provider ready \u2014 picking a model next.");
17974
18267
  return "continue";
17975
18268
  }
17976
18269
 
17977
18270
  // src/server/index.ts
17978
18271
  import pc11 from "picocolors";
17979
18272
  import { networkInterfaces as networkInterfaces2 } from "os";
17980
- import * as p10 from "@clack/prompts";
18273
+ import * as p11 from "@clack/prompts";
17981
18274
 
17982
18275
  // src/target-compatibility.ts
17983
18276
  function blacklistAgentForTarget(target) {
@@ -18015,10 +18308,10 @@ function providersForTarget(providers, target) {
18015
18308
  }
18016
18309
 
18017
18310
  // src/server/prompts.ts
18018
- import * as p7 from "@clack/prompts";
18311
+ import * as p8 from "@clack/prompts";
18019
18312
  import pc8 from "picocolors";
18020
18313
  async function askServerStartMode() {
18021
- const mode = await p7.select({
18314
+ const mode = await p8.select({
18022
18315
  message: "How do you want to start the server?",
18023
18316
  options: [
18024
18317
  { value: "configure", label: pc8.cyan("Configure & start"), hint: "Providers, discovery masking, listen mode" },
@@ -18026,37 +18319,37 @@ async function askServerStartMode() {
18026
18319
  ],
18027
18320
  initialValue: "configure"
18028
18321
  });
18029
- if (p7.isCancel(mode)) {
18030
- p7.cancel("Cancelled.");
18322
+ if (p8.isCancel(mode)) {
18323
+ p8.cancel("Cancelled.");
18031
18324
  return null;
18032
18325
  }
18033
18326
  return mode;
18034
18327
  }
18035
18328
  async function askMaskGatewayIds(initialValue) {
18036
- const mask = await p7.confirm({
18329
+ const mask = await p8.confirm({
18037
18330
  message: "Mask gateway model ids for discovery? (Needed for Claude Desktop / Cowork)",
18038
18331
  initialValue
18039
18332
  });
18040
- if (p7.isCancel(mask)) {
18041
- p7.cancel("Cancelled.");
18333
+ if (p8.isCancel(mask)) {
18334
+ p8.cancel("Cancelled.");
18042
18335
  return null;
18043
18336
  }
18044
18337
  return Boolean(mask);
18045
18338
  }
18046
18339
  async function askFavoritesOnly(initialValue) {
18047
18340
  printFavoritesOnlyPanel();
18048
- const favoritesOnly = await p7.confirm({
18341
+ const favoritesOnly = await p8.confirm({
18049
18342
  message: "Expose only favorite models?",
18050
18343
  initialValue
18051
18344
  });
18052
- if (p7.isCancel(favoritesOnly)) {
18053
- p7.cancel("Cancelled.");
18345
+ if (p8.isCancel(favoritesOnly)) {
18346
+ p8.cancel("Cancelled.");
18054
18347
  return null;
18055
18348
  }
18056
18349
  return Boolean(favoritesOnly);
18057
18350
  }
18058
18351
  async function askListenMode() {
18059
- const mode = await p7.select({
18352
+ const mode = await p8.select({
18060
18353
  message: "Where should the server listen?",
18061
18354
  options: [
18062
18355
  { value: "local", label: pc8.cyan("Local only"), hint: "Only this computer can use it" },
@@ -18064,26 +18357,26 @@ async function askListenMode() {
18064
18357
  ],
18065
18358
  initialValue: "local"
18066
18359
  });
18067
- if (p7.isCancel(mode)) {
18068
- p7.cancel("Cancelled.");
18360
+ if (p8.isCancel(mode)) {
18361
+ p8.cancel("Cancelled.");
18069
18362
  return null;
18070
18363
  }
18071
18364
  return mode;
18072
18365
  }
18073
18366
  async function askServerPassword() {
18074
18367
  printNetworkWarningPanel();
18075
- const password2 = await p7.text({
18368
+ const password3 = await p8.text({
18076
18369
  message: "Choose a server password for this run:",
18077
18370
  validate: (value) => value.trim() ? void 0 : "Password cannot be empty"
18078
18371
  });
18079
- if (p7.isCancel(password2)) {
18080
- p7.cancel("Cancelled.");
18372
+ if (p8.isCancel(password3)) {
18373
+ p8.cancel("Cancelled.");
18081
18374
  return null;
18082
18375
  }
18083
- return String(password2).trim();
18376
+ return String(password3).trim();
18084
18377
  }
18085
18378
  async function askUseSavedServerPassword() {
18086
- const choice = await p7.select({
18379
+ const choice = await p8.select({
18087
18380
  message: "Use saved server password?",
18088
18381
  options: [
18089
18382
  { value: "use-saved", label: pc8.cyan("Use saved password") },
@@ -18091,19 +18384,19 @@ async function askUseSavedServerPassword() {
18091
18384
  ],
18092
18385
  initialValue: "use-saved"
18093
18386
  });
18094
- if (p7.isCancel(choice)) {
18095
- p7.cancel("Cancelled.");
18387
+ if (p8.isCancel(choice)) {
18388
+ p8.cancel("Cancelled.");
18096
18389
  return null;
18097
18390
  }
18098
18391
  return choice;
18099
18392
  }
18100
18393
  async function askSaveServerPassword() {
18101
- const save = await p7.confirm({
18394
+ const save = await p8.confirm({
18102
18395
  message: "Save this server password for future server runs?",
18103
18396
  initialValue: false
18104
18397
  });
18105
- if (p7.isCancel(save)) {
18106
- p7.cancel("Cancelled.");
18398
+ if (p8.isCancel(save)) {
18399
+ p8.cancel("Cancelled.");
18107
18400
  return null;
18108
18401
  }
18109
18402
  return Boolean(save);
@@ -18217,24 +18510,24 @@ function translateOpenAiRequest(body, options) {
18217
18510
  async function collectOpenAiStream(stream) {
18218
18511
  const collected = { text: "", toolCalls: [], finishReason: void 0, usage: void 0 };
18219
18512
  for await (const part of stream) {
18220
- const p13 = part;
18221
- switch (p13.type) {
18513
+ const p14 = part;
18514
+ switch (p14.type) {
18222
18515
  case "text-delta":
18223
- collected.text += p13.textDelta ?? p13.text ?? "";
18516
+ collected.text += p14.textDelta ?? p14.text ?? "";
18224
18517
  break;
18225
18518
  case "tool-call":
18226
18519
  collected.toolCalls.push({
18227
- toolCallId: p13.toolCallId ?? "",
18228
- toolName: p13.toolName ?? "",
18229
- input: p13.input
18520
+ toolCallId: p14.toolCallId ?? "",
18521
+ toolName: p14.toolName ?? "",
18522
+ input: p14.input
18230
18523
  });
18231
18524
  break;
18232
18525
  case "finish":
18233
- collected.finishReason = p13.finishReason ?? collected.finishReason;
18234
- collected.usage = p13.totalUsage ?? p13.usage ?? collected.usage;
18526
+ collected.finishReason = p14.finishReason ?? collected.finishReason;
18527
+ collected.usage = p14.totalUsage ?? p14.usage ?? collected.usage;
18235
18528
  break;
18236
18529
  case "error":
18237
- throw p13.error instanceof Error || p13.error && typeof p13.error === "object" ? p13.error : new Error(typeof p13.error === "string" ? p13.error : "Upstream stream failed");
18530
+ throw p14.error instanceof Error || p14.error && typeof p14.error === "object" ? p14.error : new Error(typeof p14.error === "string" ? p14.error : "Upstream stream failed");
18238
18531
  }
18239
18532
  }
18240
18533
  return collected;
@@ -18359,22 +18652,22 @@ async function streamOpenAiResponse(model, params, responseModelId, onChunk, opt
18359
18652
 
18360
18653
  `);
18361
18654
  for await (const part of watchOpenAiStream(stream, budget)) {
18362
- const p13 = part;
18363
- switch (p13.type) {
18655
+ const p14 = part;
18656
+ switch (p14.type) {
18364
18657
  case "text-delta":
18365
- send({ role: "assistant", content: p13.textDelta ?? p13.text ?? "" });
18658
+ send({ role: "assistant", content: p14.textDelta ?? p14.text ?? "" });
18366
18659
  break;
18367
18660
  case "tool-input-start":
18368
- send({ role: "assistant", tool_calls: [{ index: 0, id: p13.id ?? p13.toolCallId, type: "function", function: { name: p13.toolName, arguments: "" } }] });
18661
+ send({ role: "assistant", tool_calls: [{ index: 0, id: p14.id ?? p14.toolCallId, type: "function", function: { name: p14.toolName, arguments: "" } }] });
18369
18662
  break;
18370
18663
  case "tool-input-delta":
18371
- send({ tool_calls: [{ index: 0, function: { arguments: p13.delta ?? p13.text ?? p13.argsTextDelta ?? "" } }] });
18664
+ send({ tool_calls: [{ index: 0, function: { arguments: p14.delta ?? p14.text ?? p14.argsTextDelta ?? "" } }] });
18372
18665
  break;
18373
18666
  case "finish":
18374
- send({}, p13.finishReason || "stop");
18667
+ send({}, p14.finishReason || "stop");
18375
18668
  break;
18376
18669
  case "error":
18377
- throw p13.error instanceof Error || p13.error && typeof p13.error === "object" ? p13.error : new Error(typeof p13.error === "string" ? p13.error : "Upstream stream failed");
18670
+ throw p14.error instanceof Error || p14.error && typeof p14.error === "object" ? p14.error : new Error(typeof p14.error === "string" ? p14.error : "Upstream stream failed");
18378
18671
  }
18379
18672
  }
18380
18673
  onChunk("data: [DONE]\n\n");
@@ -19108,7 +19401,7 @@ function summarizeServerProviders(models) {
19108
19401
 
19109
19402
  // src/server/provider-select.ts
19110
19403
  import pc9 from "picocolors";
19111
- import * as p8 from "@clack/prompts";
19404
+ import * as p9 from "@clack/prompts";
19112
19405
  function isSelected(list, id) {
19113
19406
  return list.includes(id);
19114
19407
  }
@@ -19118,7 +19411,7 @@ function resolveInitialServerProviders(initial, available) {
19118
19411
  }
19119
19412
  async function selectServerProviders(available, initial) {
19120
19413
  if (available.length === 0) {
19121
- p8.log.warn("No providers available to expose.");
19414
+ p9.log.warn("No providers available to expose.");
19122
19415
  return null;
19123
19416
  }
19124
19417
  let selected = resolveInitialServerProviders(initial, available);
@@ -19144,31 +19437,31 @@ async function selectServerProviders(available, initial) {
19144
19437
  }
19145
19438
  options.push({ value: "__done__", label: "Done", hint: "" });
19146
19439
  const header = selected.length === 0 ? `Exposed providers (0/${available.length}) \u2014 add providers to expose` : `Exposed providers (${selected.length}/${available.length}) \u2014 select to stop exposing`;
19147
- const choice = await p8.select({
19440
+ const choice = await p9.select({
19148
19441
  message: header,
19149
19442
  options,
19150
19443
  initialValue: "__done__"
19151
19444
  });
19152
- if (p8.isCancel(choice) || choice === "__done__") {
19445
+ if (p9.isCancel(choice) || choice === "__done__") {
19153
19446
  if (selected.length === 0) {
19154
- p8.log.warn("Select at least one provider to expose.");
19447
+ p9.log.warn("Select at least one provider to expose.");
19155
19448
  continue;
19156
19449
  }
19157
19450
  break;
19158
19451
  }
19159
19452
  if (choice === "__all__") {
19160
19453
  selected = available.map((provider) => provider.id);
19161
- p8.log.success(`Exposing all ${available.length} providers.`);
19454
+ p9.log.success(`Exposing all ${available.length} providers.`);
19162
19455
  continue;
19163
19456
  }
19164
19457
  if (choice === "__clear__") {
19165
19458
  selected = [];
19166
- p8.log.success("Cleared provider list \u2014 add the ones you want to expose.");
19459
+ p9.log.success("Cleared provider list \u2014 add the ones you want to expose.");
19167
19460
  continue;
19168
19461
  }
19169
19462
  if (choice === "__add__") {
19170
19463
  if (unselected.length === 0) continue;
19171
- const picked = await p8.select({
19464
+ const picked = await p9.select({
19172
19465
  message: "Which provider?",
19173
19466
  options: unselected.map((provider) => ({
19174
19467
  value: provider.id,
@@ -19176,7 +19469,7 @@ async function selectServerProviders(available, initial) {
19176
19469
  hint: `${provider.modelCount} model${provider.modelCount !== 1 ? "s" : ""}`
19177
19470
  }))
19178
19471
  });
19179
- if (p8.isCancel(picked)) continue;
19472
+ if (p9.isCancel(picked)) continue;
19180
19473
  selected = [...selected, picked];
19181
19474
  continue;
19182
19475
  }
@@ -19186,7 +19479,7 @@ async function selectServerProviders(available, initial) {
19186
19479
  if (!id) continue;
19187
19480
  const provider = lookup2.get(id);
19188
19481
  selected = selected.filter((_, i) => i !== idx);
19189
- p8.log.success(`Removed ${provider?.name ?? id}.`);
19482
+ p9.log.success(`Removed ${provider?.name ?? id}.`);
19190
19483
  }
19191
19484
  }
19192
19485
  return selected;
@@ -19194,7 +19487,7 @@ async function selectServerProviders(available, initial) {
19194
19487
 
19195
19488
  // src/http-proxy/index.ts
19196
19489
  import pc10 from "picocolors";
19197
- import * as p9 from "@clack/prompts";
19490
+ import * as p10 from "@clack/prompts";
19198
19491
 
19199
19492
  // src/http-proxy/server.ts
19200
19493
  import * as http2 from "http";
@@ -20198,6 +20491,14 @@ async function startHttpProxy(options) {
20198
20491
  );
20199
20492
  });
20200
20493
  const sockets = /* @__PURE__ */ new Set();
20494
+ let warnedConnectSelfProxy = false;
20495
+ const connectTunnelAgents = /* @__PURE__ */ new Map();
20496
+ const connectTunnelAgent = (proxyUrl, targetUrl) => {
20497
+ if (!connectTunnelAgents.has(proxyUrl)) {
20498
+ connectTunnelAgents.set(proxyUrl, outboundHttpProxyAgent(targetUrl));
20499
+ }
20500
+ return connectTunnelAgents.get(proxyUrl);
20501
+ };
20201
20502
  mitmServer.on("upgrade", (req, socket, head) => {
20202
20503
  forwardAnthropicUpgrade(
20203
20504
  req,
@@ -20227,6 +20528,65 @@ async function startHttpProxy(options) {
20227
20528
  clientSocket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
20228
20529
  return;
20229
20530
  }
20531
+ const targetUrl = `https://${req.url}`;
20532
+ const outboundProxyUrl = outboundProxyUrlForTarget(targetUrl);
20533
+ const bound = proxyServer.address();
20534
+ const selfTargeting = outboundProxyUrl !== void 0 && bound !== null && typeof bound !== "string" && proxyUrlTargetsListener(outboundProxyUrl, bound.address, bound.port);
20535
+ if (selfTargeting && !warnedConnectSelfProxy) {
20536
+ warnedConnectSelfProxy = true;
20537
+ emitParentNotice(
20538
+ "clodex: HTTP(S)_PROXY points at this proxy; tunneling CONNECT direct"
20539
+ );
20540
+ }
20541
+ const outboundAgent = outboundProxyUrl !== void 0 && !selfTargeting ? connectTunnelAgent(outboundProxyUrl, targetUrl) : void 0;
20542
+ if (outboundAgent) {
20543
+ let upstream2;
20544
+ let proxyConnectStatus;
20545
+ req.once("proxyConnect", (response) => {
20546
+ proxyConnectStatus = response.statusCode;
20547
+ });
20548
+ clientSocket.once("close", () => {
20549
+ if (upstream2 && !upstream2.destroyed) upstream2.destroy();
20550
+ });
20551
+ void outboundAgent.connect(req, {
20552
+ host: target.host,
20553
+ port: target.port,
20554
+ secureEndpoint: false
20555
+ }).then((connected) => {
20556
+ upstream2 = connected;
20557
+ if (proxyConnectStatus !== 200 || clientSocket.destroyed) {
20558
+ connected.destroy();
20559
+ if (!clientSocket.destroyed) {
20560
+ clientSocket.end("HTTP/1.1 502 Bad Gateway\r\n\r\n", () => clientSocket.destroy());
20561
+ }
20562
+ return;
20563
+ }
20564
+ let tunnelEstablished2 = false;
20565
+ sockets.add(connected);
20566
+ connected.once("close", () => {
20567
+ sockets.delete(connected);
20568
+ if (tunnelEstablished2 && !clientSocket.destroyed) clientSocket.destroy();
20569
+ });
20570
+ connected.once("error", () => {
20571
+ if (clientSocket.destroyed) return;
20572
+ if (tunnelEstablished2) {
20573
+ clientSocket.destroy();
20574
+ return;
20575
+ }
20576
+ clientSocket.end("HTTP/1.1 502 Bad Gateway\r\n\r\n", () => clientSocket.destroy());
20577
+ });
20578
+ tunnelEstablished2 = true;
20579
+ clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
20580
+ if (head.length > 0) connected.write(head);
20581
+ clientSocket.pipe(connected);
20582
+ connected.pipe(clientSocket);
20583
+ }).catch(() => {
20584
+ if (!clientSocket.destroyed) {
20585
+ clientSocket.end("HTTP/1.1 502 Bad Gateway\r\n\r\n", () => clientSocket.destroy());
20586
+ }
20587
+ });
20588
+ return;
20589
+ }
20230
20590
  const upstream = net.connect(target.port, target.host);
20231
20591
  let tunnelEstablished = false;
20232
20592
  sockets.add(upstream);
@@ -20292,6 +20652,8 @@ async function startHttpProxy(options) {
20292
20652
  for (const socket of sockets) socket.destroy();
20293
20653
  await new Promise((resolve4) => proxyServer.close(() => resolve4()));
20294
20654
  mitmServer.close();
20655
+ for (const agent of connectTunnelAgents.values()) agent?.destroy();
20656
+ connectTunnelAgents.clear();
20295
20657
  anthropicAgent?.destroy();
20296
20658
  adapter?.close();
20297
20659
  }
@@ -20348,10 +20710,10 @@ function printHttpProxyModels(routes, aliases = []) {
20348
20710
  }
20349
20711
  function reportSkippedHttpProxyFavorites(loaded) {
20350
20712
  if (loaded.unavailable.length > 0) {
20351
- p9.log.warn(`${loaded.unavailable.length} favorite${loaded.unavailable.length === 1 ? "" : "s"} unavailable or missing credentials.`);
20713
+ p10.log.warn(`${loaded.unavailable.length} favorite${loaded.unavailable.length === 1 ? "" : "s"} unavailable or missing credentials.`);
20352
20714
  }
20353
20715
  if (loaded.unsupported.length > 0) {
20354
- p9.log.warn(
20716
+ p10.log.warn(
20355
20717
  `${loaded.unsupported.length} favorite${loaded.unsupported.length === 1 ? "" : "s"} skipped \u2014 HTTP proxy mode supports non-Anthropic AI SDK routes only.`
20356
20718
  );
20357
20719
  }
@@ -20363,7 +20725,7 @@ function reportSkippedHttpProxyFavorites(loaded) {
20363
20725
  describeModelAliasRejection(rejection.reason)
20364
20726
  ])
20365
20727
  );
20366
- p9.log.warn(
20728
+ p10.log.warn(
20367
20729
  `${loaded.unavailableAliases.length} model alias${loaded.unavailableAliases.length === 1 ? "" : "es"} skipped. Saved entries were preserved.
20368
20730
  ` + loaded.unavailableAliases.map((alias) => ` ${JSON.stringify(alias.name)} \u2014 ${reasonByAlias.get(alias) ?? "target unavailable"}`).join("\n")
20369
20731
  );
@@ -20444,7 +20806,7 @@ async function runHttpProxyServerCommand(debug = false, webSocketDiagnostics = f
20444
20806
  webSocketDiagnosticsLogPath
20445
20807
  );
20446
20808
  } catch (err) {
20447
- p9.log.error(`Failed to start HTTP proxy: ${err instanceof Error ? err.message : String(err)}`);
20809
+ p10.log.error(`Failed to start HTTP proxy: ${err instanceof Error ? err.message : String(err)}`);
20448
20810
  return 1;
20449
20811
  }
20450
20812
  const { handle, loaded, caWarning } = started;
@@ -20647,8 +21009,8 @@ async function getServerPasswordForQuickMode(mode, passwordOverride) {
20647
21009
  if (trimmedOverride) return { password: trimmedOverride, wasSaved: false };
20648
21010
  const savedPassword = await getSavedServerPassword();
20649
21011
  if (savedPassword) return { password: savedPassword, wasSaved: true };
20650
- p10.log.error("Network server quick-start needs a saved server password or `--password <value>`.");
20651
- p10.log.info("Run `clodex server` and choose Configure & start to save one, or pass a one-run password.");
21012
+ p11.log.error("Network server quick-start needs a saved server password or `--password <value>`.");
21013
+ p11.log.info("Run `clodex server` and choose Configure & start to save one, or pass a one-run password.");
20652
21014
  return void 0;
20653
21015
  }
20654
21016
  function savedServerRunConfig() {
@@ -20682,16 +21044,16 @@ function shouldUseQuickServerMode(options) {
20682
21044
  return Boolean(options.quick || hasServerRunOverrides(options) || !process.stdin.isTTY);
20683
21045
  }
20684
21046
  async function configureExposedProviders() {
20685
- p10.log.info("Add providers to expose. Listed providers are removed when selected \u2014 like favorites.");
20686
- const spinner5 = p10.spinner();
20687
- spinner5.start("Loading providers...");
21047
+ p11.log.info("Add providers to expose. Listed providers are removed when selected \u2014 like favorites.");
21048
+ const spinner6 = p11.spinner();
21049
+ spinner6.start("Loading providers...");
20688
21050
  const catalog = await fetchProviderCatalog({ agent: "server" });
20689
- spinner5.stop("");
21051
+ spinner6.stop("");
20690
21052
  const available = providerOptionsFromCatalog(catalog);
20691
21053
  const picked = await selectServerProviders(available, getServerExposedProviders() ?? void 0);
20692
21054
  if (!picked) return void 0;
20693
21055
  setServerExposedProviders(picked);
20694
- p10.log.success(`Saved ${picked.length} provider${picked.length !== 1 ? "s" : ""} for future server runs.`);
21056
+ p11.log.success(`Saved ${picked.length} provider${picked.length !== 1 ? "s" : ""} for future server runs.`);
20695
21057
  return picked;
20696
21058
  }
20697
21059
  async function runServerWizard() {
@@ -20705,7 +21067,7 @@ async function runServerWizard() {
20705
21067
  if (favoritesOnly === null) return void 0;
20706
21068
  setServerFavoritesOnly(favoritesOnly);
20707
21069
  if (favoritesOnly) {
20708
- p10.log.info("Manage favorites with `clodex models`.");
21070
+ p11.log.info("Manage favorites with `clodex models`.");
20709
21071
  }
20710
21072
  let exposedProviders = null;
20711
21073
  if (!favoritesOnly) {
@@ -20735,14 +21097,14 @@ async function runServerCommand(options = {}) {
20735
21097
  if (options.httpProxy) {
20736
21098
  const hasGatewayOptions = options.quick || options.listenMode !== void 0 || options.providersMode !== void 0 || options.maskGatewayIds !== void 0 || options.password !== void 0;
20737
21099
  if (hasGatewayOptions) {
20738
- p10.log.error("--proxy is a local-only server mode and cannot be combined with endpoint-mode server options.");
21100
+ p11.log.error("--proxy is a local-only server mode and cannot be combined with endpoint-mode server options.");
20739
21101
  return 1;
20740
21102
  }
20741
21103
  return runHttpProxyServerCommand(false, options.wsDiagnostics, options.port, noDiscovery);
20742
21104
  }
20743
21105
  const apiKey = await resolveServerUpstreamApiKey();
20744
21106
  if (!apiKey) {
20745
- p10.log.error("No providers configured. Run `clodex providers` to add OpenAI.");
21107
+ p11.log.error("No providers configured. Run `clodex providers` to add OpenAI.");
20746
21108
  return 1;
20747
21109
  }
20748
21110
  const quickMode = shouldUseQuickServerMode(options);
@@ -20757,8 +21119,8 @@ async function runServerCommand(options = {}) {
20757
21119
  const { password: serverPassword, wasSaved: passwordWasSaved } = pwResult;
20758
21120
  const mode = runConfig.listenMode;
20759
21121
  const host = mode === "network" ? "0.0.0.0" : "127.0.0.1";
20760
- const spinner5 = p10.spinner();
20761
- spinner5.start("Fetching available models...");
21122
+ const spinner6 = p11.spinner();
21123
+ spinner6.start("Fetching available models...");
20762
21124
  let models;
20763
21125
  try {
20764
21126
  models = await loadServerModels();
@@ -20768,26 +21130,26 @@ async function runServerCommand(options = {}) {
20768
21130
  if (runConfig.favoritesOnly) {
20769
21131
  const favorites = loadPreferences().favoriteModels ?? [];
20770
21132
  if (favorites.length === 0) {
20771
- spinner5.stop(pc11.red("No favorite models configured"));
20772
- p10.log.error("Run `clodex models` to add favorites, or turn off favorites-only in the server wizard.");
21133
+ spinner6.stop(pc11.red("No favorite models configured"));
21134
+ p11.log.error("Run `clodex models` to add favorites, or turn off favorites-only in the server wizard.");
20773
21135
  return 1;
20774
21136
  }
20775
21137
  models = filterServerModelsByFavorites(models, favorites).slice(0, MAX_MODEL_CATALOG);
20776
21138
  if (models.length === 0) {
20777
- spinner5.stop(pc11.red("No favorite models matched the current provider filter"));
20778
- p10.log.error("Adjust favorites with `clodex models` or change exposed providers in the server wizard.");
21139
+ spinner6.stop(pc11.red("No favorite models matched the current provider filter"));
21140
+ p11.log.error("Adjust favorites with `clodex models` or change exposed providers in the server wizard.");
20779
21141
  return 1;
20780
21142
  }
20781
21143
  }
20782
21144
  if (runConfig.favoritesOnly) {
20783
- p10.log.info(
21145
+ p11.log.info(
20784
21146
  `Favorites-only mode active \u2014 GET /anthropic/v1/models returns ${models.length} favorites.`
20785
21147
  );
20786
- p10.log.info("Desktop/Cowork picker will only show these. Edit with `clodex models`.");
21148
+ p11.log.info("Desktop/Cowork picker will only show these. Edit with `clodex models`.");
20787
21149
  }
20788
21150
  if (models.length === 0) {
20789
- spinner5.stop(pc11.red("No models to expose"));
20790
- p10.log.error("Add providers with `clodex providers add` or configure exposed providers in the server wizard.");
21151
+ spinner6.stop(pc11.red("No models to expose"));
21152
+ p11.log.error("Add providers with `clodex providers add` or configure exposed providers in the server wizard.");
20791
21153
  return 1;
20792
21154
  }
20793
21155
  const localCount = models.filter((m) => m.apiKey !== void 0).length;
@@ -20795,10 +21157,10 @@ async function runServerCommand(options = {}) {
20795
21157
  const filterNote = runConfig.exposedProviders ? ` \u2014 ${runConfig.exposedProviders.length} provider${runConfig.exposedProviders.length !== 1 ? "s" : ""}` : "";
20796
21158
  const favoritesNote = runConfig.favoritesOnly ? " \u2014 favorites only" : "";
20797
21159
  const maskNote = runConfig.maskGatewayIds ? " \u2014 discovery ids masked" : "";
20798
- spinner5.stop(`Loaded ${models.length} models (${localCount} from registry providers)${filterNote}${favoritesNote}${maskNote}`);
20799
- if (summary) p10.log.info(summary);
21160
+ spinner6.stop(`Loaded ${models.length} models (${localCount} from registry providers)${filterNote}${favoritesNote}${maskNote}`);
21161
+ if (summary) p11.log.info(summary);
20800
21162
  } catch (err) {
20801
- spinner5.stop(pc11.red("Failed to load models"));
21163
+ spinner6.stop(pc11.red("Failed to load models"));
20802
21164
  console.error(pc11.red(String(err instanceof Error ? err.message : err)));
20803
21165
  return 1;
20804
21166
  }
@@ -20820,7 +21182,7 @@ async function runServerCommand(options = {}) {
20820
21182
  ...collidingAliases.flatMap(({ sources }) => sources.map((source) => `${JSON.stringify(source.name)} \u2014 conflicts with a catalog model id`))
20821
21183
  ];
20822
21184
  if (aliasWarnings.length > 0) {
20823
- p10.log.warn(
21185
+ p11.log.warn(
20824
21186
  `${aliasWarnings.length} saved model alias${aliasWarnings.length === 1 ? "" : "es"} inactive. Saved entries were preserved.
20825
21187
  ${aliasWarnings.join("\n ")}`
20826
21188
  );
@@ -20902,7 +21264,7 @@ function removeFavorite(list, fav) {
20902
21264
  }
20903
21265
 
20904
21266
  // src/favorites-picker.ts
20905
- import * as p11 from "@clack/prompts";
21267
+ import * as p12 from "@clack/prompts";
20906
21268
  import pc12 from "picocolors";
20907
21269
 
20908
21270
  // src/favorite-provider-display.ts
@@ -20980,25 +21342,25 @@ async function pickGlobalFavoriteModel(providers, favorites, opts) {
20980
21342
  if (index.length === 0) return null;
20981
21343
  const freeOnly = opts?.freeOnly === true;
20982
21344
  while (true) {
20983
- const searchInput = await p11.text({
21345
+ const searchInput = await p12.text({
20984
21346
  message: freeOnly ? `Search free models (${filterGlobalFavoriteIndex(index, "", { freeOnly: true }).length} models):` : `Search all providers (${index.length} models):`,
20985
21347
  placeholder: "e.g. deepseek, claude, sonnet"
20986
21348
  });
20987
- if (p11.isCancel(searchInput)) {
20988
- const fallback = await p11.select({
21349
+ if (p12.isCancel(searchInput)) {
21350
+ const fallback = await p12.select({
20989
21351
  message: "Add a favorite",
20990
21352
  options: [
20991
21353
  { value: "back", label: pc12.cyan("\u2190 Back to favorites"), hint: "" },
20992
21354
  { value: ADD_BY_PROVIDER, label: pc12.cyan("Browse by provider \u2192"), hint: "Pick one provider first" }
20993
21355
  ]
20994
21356
  });
20995
- if (p11.isCancel(fallback) || fallback === "back") return null;
21357
+ if (p12.isCancel(fallback) || fallback === "back") return null;
20996
21358
  if (fallback === ADD_BY_PROVIDER) return ADD_BY_PROVIDER;
20997
21359
  continue;
20998
21360
  }
20999
21361
  const matched = filterGlobalFavoriteIndex(index, String(searchInput), { freeOnly });
21000
21362
  if (matched.length === 0) {
21001
- p11.log.warn("No models match \u2014 try a different search");
21363
+ p12.log.warn("No models match \u2014 try a different search");
21002
21364
  continue;
21003
21365
  }
21004
21366
  const result = await pickModelFromPagedList(
@@ -21016,7 +21378,7 @@ async function pickGlobalFavoriteModel(providers, favorites, opts) {
21016
21378
  const picked = parseGlobalFavoritePickKey(result.id, matched);
21017
21379
  if (!picked) continue;
21018
21380
  if (isFavorite(favorites, { providerId: picked.providerId, modelId: picked.model.id })) {
21019
- p11.log.warn(`${picked.model.name || picked.model.id} (${picked.providerName}) is already in your favorites.`);
21381
+ p12.log.warn(`${picked.model.name || picked.model.id} (${picked.providerName}) is already in your favorites.`);
21020
21382
  continue;
21021
21383
  }
21022
21384
  return picked;
@@ -21086,7 +21448,7 @@ function resolveLaunchTarget(explicit, prefs, _agent = "claude") {
21086
21448
  }
21087
21449
  function findProviderAndModel(providers, target) {
21088
21450
  if (!target.providerId || !target.modelId) return null;
21089
- const provider = providers.find((p13) => p13.id === target.providerId);
21451
+ const provider = providers.find((p14) => p14.id === target.providerId);
21090
21452
  if (!provider) return null;
21091
21453
  const model = provider.models.find((m) => m.id === target.modelId);
21092
21454
  if (!model) return null;
@@ -21292,7 +21654,7 @@ function buildAndPublish(step) {
21292
21654
  const compiled = step.compile(cscPath, CSC_ARGS, scratch);
21293
21655
  const builtExe = join9(scratch, LAUNCHER_EXE_NAME);
21294
21656
  if (compiled.error || compiled.status !== 0 || !existsSync7(builtExe)) {
21295
- const output = [compiled.stdout, compiled.stderr].map((text5) => text5.trim()).filter(Boolean).join("\n");
21657
+ const output = [compiled.stdout, compiled.stderr].map((text6) => text6.trim()).filter(Boolean).join("\n");
21296
21658
  return {
21297
21659
  ok: false,
21298
21660
  reason: "compile-failed",
@@ -21703,6 +22065,7 @@ ${pc13.bold("Providers:")}
21703
22065
  openai OpenAI API key (platform.openai.com)
21704
22066
  openai-oauth ChatGPT/Codex plan OAuth \u2014 sign in with clodex providers auth openai
21705
22067
  opencode-go OpenCode Go API key \u2014 add with clodex providers add
22068
+ custom-<name> Any OpenAI-compatible server (OpenRouter, LM Studio, \u2026) \u2014 add with clodex providers add
21706
22069
 
21707
22070
  ${pc13.bold("Model switching:")}
21708
22071
  Run clodex models to save favorites (max ${MAX_MODEL_CATALOG}).
@@ -21927,9 +22290,9 @@ ${pc13.bold("Behavior:")}
21927
22290
  ${pc13.bold("Setup guide:")}
21928
22291
  docs/windows-setup.md in the clodex package (also on GitHub).`;
21929
22292
  }
21930
- function printHelp(text5) {
22293
+ function printHelp(text6) {
21931
22294
  console.log(`
21932
- ${text5}
22295
+ ${text6}
21933
22296
  `);
21934
22297
  }
21935
22298
  function reportInactiveCatalogAliases(modelAliases) {
@@ -21938,7 +22301,7 @@ function reportInactiveCatalogAliases(modelAliases) {
21938
22301
  const warningLines = unavailableAliases.flatMap((alias) => alias.sourceNames?.length ? alias.sourceNames.map((name) => ` ${JSON.stringify(name)} \u2014 ${alias.unavailableReason}`) : [
21939
22302
  ` ${JSON.stringify(alias.savedName ?? alias.name)} \u2014 ${alias.unavailableReason}`
21940
22303
  ]);
21941
- p12.log.warn(
22304
+ p13.log.warn(
21942
22305
  `${warningLines.length} saved model alias${warningLines.length === 1 ? "" : "es"} inactive. Saved entries were preserved.
21943
22306
  ` + warningLines.join("\n")
21944
22307
  );
@@ -21956,11 +22319,11 @@ async function launchClaudeViaCatalog(catalogRoutes, startingRoute, modelAliases
21956
22319
  void 0,
21957
22320
  modelAliases
21958
22321
  );
21959
- p12.log.info(
22322
+ p13.log.info(
21960
22323
  `Switch menu active \u2014 proxy on port ${proxyHandle.port} ` + pc13.dim(`(${catalogRoutes.length} model${catalogRoutes.length !== 1 ? "s" : ""} in /model)`)
21961
22324
  );
21962
22325
  } catch (err) {
21963
- p12.log.error(`Failed to start proxy: ${err instanceof Error ? err.message : String(err)}`);
22326
+ p13.log.error(`Failed to start proxy: ${err instanceof Error ? err.message : String(err)}`);
21964
22327
  return 1;
21965
22328
  }
21966
22329
  const childEnv = buildChildEnv(
@@ -21973,7 +22336,7 @@ async function launchClaudeViaCatalog(catalogRoutes, startingRoute, modelAliases
21973
22336
  );
21974
22337
  const debugLogPath = prepareClaudeTraceLog();
21975
22338
  const traceArgs = trace ? ["--debug-file", debugLogPath] : [];
21976
- if (trace) p12.log.info(`Debug log: ${debugLogPath}`);
22339
+ if (trace) p13.log.info(`Debug log: ${debugLogPath}`);
21977
22340
  const exitCode = await launchClaude(
21978
22341
  childEnv,
21979
22342
  claudeCodeClientModelId(startingRoute.aliasId, contextWindow),
@@ -21984,9 +22347,9 @@ async function launchClaudeViaCatalog(catalogRoutes, startingRoute, modelAliases
21984
22347
  return exitCode;
21985
22348
  }
21986
22349
  var CLACK_CONTEXT_REPORTER = {
21987
- info: (message) => p12.log.info(message),
21988
- warn: (message) => p12.log.warn(message),
21989
- success: (message) => p12.log.success(message)
22350
+ info: (message) => p13.log.info(message),
22351
+ warn: (message) => p13.log.warn(message),
22352
+ success: (message) => p13.log.success(message)
21990
22353
  };
21991
22354
  var STDERR_CONTEXT_REPORTER = {
21992
22355
  info: (message) => console.error(message),
@@ -22001,12 +22364,12 @@ function applyContextStopAssignments(assignments, save, report, saveHint = "Add
22001
22364
  for (const assignment of assignments) {
22002
22365
  const cached = modelsByProvider.get(assignment.providerId)?.find((model) => model.id === assignment.modelId);
22003
22366
  if (!cached) {
22004
- p12.log.error(
22367
+ p13.log.error(
22005
22368
  `${modelAliasTarget(assignment)} is not in the saved provider catalog. Add the provider and refresh its models first.`
22006
22369
  );
22007
22370
  return 1;
22008
22371
  }
22009
- const limits = contextLimitsFrom(cached, resolveContextWindow(assignment.modelId));
22372
+ const limits = contextLimitsFrom(cached, lookupKnownContextWindow(assignment.modelId));
22010
22373
  const resolved = resolveContextStop(limits, assignment.stop ?? "standard");
22011
22374
  const target = modelAliasTarget(assignment);
22012
22375
  const label = assignment.label === target ? target : `${assignment.label} (${target})`;
@@ -22035,12 +22398,12 @@ function applyContextStopAssignments(assignments, save, report, saveHint = "Add
22035
22398
  async function runModelsCommand(opts = {}) {
22036
22399
  const changesAlias = opts.alias !== void 0 || opts.unalias !== void 0;
22037
22400
  if (changesAlias && (opts.list || opts.alias !== void 0 && opts.unalias !== void 0)) {
22038
- p12.log.error("--alias/--unalias apply one at a time to proxy-mode favorites.");
22401
+ p13.log.error("--alias/--unalias apply one at a time to proxy-mode favorites.");
22039
22402
  return 1;
22040
22403
  }
22041
22404
  const assignments = opts.contextAssignments ?? [];
22042
22405
  if (opts.saveContext && assignments.length === 0) {
22043
- p12.log.error("--save applies to --context assignments; none were given.");
22406
+ p13.log.error("--save applies to --context assignments; none were given.");
22044
22407
  return 1;
22045
22408
  }
22046
22409
  if (assignments.length > 0) {
@@ -22059,7 +22422,7 @@ async function runModelsCommand(opts = {}) {
22059
22422
  if (opts.alias !== void 0) {
22060
22423
  const parsed = parseModelAliasAssignment(opts.alias);
22061
22424
  if ("error" in parsed) {
22062
- p12.log.error(parsed.error);
22425
+ p13.log.error(parsed.error);
22063
22426
  return 1;
22064
22427
  }
22065
22428
  const prefs2 = loadPreferences();
@@ -22067,19 +22430,19 @@ async function runModelsCommand(opts = {}) {
22067
22430
  (favorite) => favorite.providerId === parsed.providerId && favorite.modelId === parsed.modelId
22068
22431
  );
22069
22432
  if (!isSavedFavorite) {
22070
- p12.log.error(`${modelAliasTarget(parsed)} is not a saved favorite.`);
22071
- p12.log.info("Add it with `clodex models`, then save the alias.");
22433
+ p13.log.error(`${modelAliasTarget(parsed)} is not a saved favorite.`);
22434
+ p13.log.info("Add it with `clodex models`, then save the alias.");
22072
22435
  return 1;
22073
22436
  }
22074
22437
  if (prefs2.modelAliases !== void 0 && !Array.isArray(prefs2.modelAliases)) {
22075
- p12.log.error('Saved model aliases are malformed: "modelAliases" must be an array.');
22438
+ p13.log.error('Saved model aliases are malformed: "modelAliases" must be an array.');
22076
22439
  return 1;
22077
22440
  }
22078
22441
  const aliases = prefs2.modelAliases ?? [];
22079
22442
  const modelAliases = aliases.filter((alias) => !modelAliasMatchesName(alias, parsed.name));
22080
22443
  modelAliases.push(parsed);
22081
22444
  savePreferences({ modelAliases });
22082
- p12.log.success(`Saved model alias ${parsed.name} \u2192 ${modelAliasTarget(parsed)}.`);
22445
+ p13.log.success(`Saved model alias ${parsed.name} \u2192 ${modelAliasTarget(parsed)}.`);
22083
22446
  return 0;
22084
22447
  }
22085
22448
  if (opts.unalias !== void 0) {
@@ -22087,18 +22450,18 @@ async function runModelsCommand(opts = {}) {
22087
22450
  const name = canonicalModelAliasName(requestedName);
22088
22451
  const prefs2 = loadPreferences();
22089
22452
  if (prefs2.modelAliases !== void 0 && !Array.isArray(prefs2.modelAliases)) {
22090
- p12.log.error('Saved model aliases are malformed: "modelAliases" must be an array.');
22453
+ p13.log.error('Saved model aliases are malformed: "modelAliases" must be an array.');
22091
22454
  return 1;
22092
22455
  }
22093
22456
  const aliases = prefs2.modelAliases ?? [];
22094
22457
  const modelAliases = aliases.filter((alias) => !modelAliasMatchesStoredName(alias, requestedName));
22095
22458
  const removedCount = aliases.length - modelAliases.length;
22096
22459
  if (removedCount === 0) {
22097
- p12.log.error(`No model alias named ${JSON.stringify(requestedName)} is saved.`);
22460
+ p13.log.error(`No model alias named ${JSON.stringify(requestedName)} is saved.`);
22098
22461
  return 1;
22099
22462
  }
22100
22463
  savePreferences({ modelAliases });
22101
- p12.log.success(
22464
+ p13.log.success(
22102
22465
  removedCount === 1 ? `Removed model alias ${name || JSON.stringify(requestedName)}.` : `Removed ${removedCount} model aliases named ${name || JSON.stringify(requestedName)}.`
22103
22466
  );
22104
22467
  return 0;
@@ -22110,25 +22473,25 @@ async function runModelsCommand(opts = {}) {
22110
22473
  reportSkippedHttpProxyFavorites(loaded);
22111
22474
  return 0;
22112
22475
  } catch (err) {
22113
- p12.log.error(`Could not load proxy models: ${err instanceof Error ? err.message : String(err)}`);
22476
+ p13.log.error(`Could not load proxy models: ${err instanceof Error ? err.message : String(err)}`);
22114
22477
  return 1;
22115
22478
  }
22116
22479
  }
22117
22480
  const maxFavorites = MAX_MODEL_CATALOG;
22118
22481
  const scopeName = "Favorite Models";
22119
22482
  relayIntro(scopeName);
22120
- const spinner5 = p12.spinner();
22121
- spinner5.start("Loading providers...");
22483
+ const spinner6 = p13.spinner();
22484
+ spinner6.start("Loading providers...");
22122
22485
  const catalog = await fetchProviderCatalog();
22123
- spinner5.stop("");
22486
+ spinner6.stop("");
22124
22487
  const allProviders = providersForPicker(catalog);
22125
22488
  const favoriteProviders = allProviders.map((provider) => ({
22126
22489
  ...provider,
22127
22490
  name: favoriteProviderDisplayName(provider)
22128
22491
  }));
22129
22492
  if (favoriteProviders.length === 0) {
22130
- p12.log.warn("No providers found.");
22131
- p12.log.info(`${pc13.dim("Add a provider with ")}${pc13.cyan("clodex providers")}${pc13.dim(".")}`);
22493
+ p13.log.warn("No providers found.");
22494
+ p13.log.info(`${pc13.dim("Add a provider with ")}${pc13.cyan("clodex providers")}${pc13.dim(".")}`);
22132
22495
  relayOutro("Done");
22133
22496
  return 0;
22134
22497
  }
@@ -22157,19 +22520,19 @@ async function runModelsCommand(opts = {}) {
22157
22520
  });
22158
22521
  options.push({ value: "__done__", label: "Done", hint: "" });
22159
22522
  const header = favorites.length === 0 ? `${scopeName} (0/${maxFavorites})` : `${scopeName} (${favorites.length}/${maxFavorites}) \u2014 select to remove`;
22160
- const choice = await p12.select({
22523
+ const choice = await p13.select({
22161
22524
  message: header,
22162
22525
  options,
22163
22526
  initialValue: "__done__"
22164
22527
  });
22165
- if (p12.isCancel(choice) || choice === "__done__") break;
22528
+ if (p13.isCancel(choice) || choice === "__done__") break;
22166
22529
  if (choice === "__add__") {
22167
22530
  if (atCap) {
22168
- p12.log.warn(`Limit of ${maxFavorites} favorites reached \u2014 remove one first.`);
22531
+ p13.log.warn(`Limit of ${maxFavorites} favorites reached \u2014 remove one first.`);
22169
22532
  continue;
22170
22533
  }
22171
22534
  const globalCount = buildGlobalFavoriteIndex(favoriteProviders).length;
22172
- const addPath = await p12.select({
22535
+ const addPath = await p13.select({
22173
22536
  message: "Add a favorite",
22174
22537
  options: [
22175
22538
  {
@@ -22184,7 +22547,7 @@ async function runModelsCommand(opts = {}) {
22184
22547
  }
22185
22548
  ]
22186
22549
  });
22187
- if (p12.isCancel(addPath)) continue;
22550
+ if (p13.isCancel(addPath)) continue;
22188
22551
  let provider;
22189
22552
  let browsedMultiple = [];
22190
22553
  if (addPath === "global") {
@@ -22199,12 +22562,12 @@ async function runModelsCommand(opts = {}) {
22199
22562
  let currentInitialProvider = void 0;
22200
22563
  while (true) {
22201
22564
  const providerOptions = favoriteProviders.map((ap) => providerSelectOption(ap));
22202
- const pickedProviderId = await p12.select({
22565
+ const pickedProviderId = await p13.select({
22203
22566
  message: "Which provider?",
22204
22567
  options: providerOptions,
22205
22568
  initialValue: currentInitialProvider
22206
22569
  });
22207
- if (p12.isCancel(pickedProviderId)) break;
22570
+ if (p13.isCancel(pickedProviderId)) break;
22208
22571
  provider = favoriteProviders.find((ap) => ap.id === pickedProviderId);
22209
22572
  const options2 = provider.models.map((m) => {
22210
22573
  const favorited = isFavorite(favorites, { providerId: provider.id, modelId: m.id });
@@ -22215,12 +22578,12 @@ async function runModelsCommand(opts = {}) {
22215
22578
  hint: favorited ? pc13.yellow("\u2605 already favorite") : ""
22216
22579
  };
22217
22580
  });
22218
- const pickedModelIds = await p12.multiselect({
22581
+ const pickedModelIds = await p13.multiselect({
22219
22582
  message: `Select models to add from ${provider.name} ${pc13.dim("(Space to select, Enter to confirm)")}`,
22220
22583
  options: options2,
22221
22584
  required: false
22222
22585
  });
22223
- if (p12.isCancel(pickedModelIds)) {
22586
+ if (p13.isCancel(pickedModelIds)) {
22224
22587
  currentInitialProvider = provider.id;
22225
22588
  continue;
22226
22589
  }
@@ -22255,27 +22618,27 @@ async function runModelsCommand(opts = {}) {
22255
22618
  if (addedModels.length > 0) {
22256
22619
  if (addedModels.length === 1) {
22257
22620
  const modelName = addedModels[0].name || addedModels[0].id;
22258
- p12.log.success(`Added ${modelName} (${provider.name}) to favorites.`);
22621
+ p13.log.success(`Added ${modelName} (${provider.name}) to favorites.`);
22259
22622
  } else {
22260
- p12.log.success(`Added ${addedModels.length} models from ${provider.name} to favorites.`);
22623
+ p13.log.success(`Added ${addedModels.length} models from ${provider.name} to favorites.`);
22261
22624
  }
22262
22625
  }
22263
22626
  if (duplicateCount > 0) {
22264
- p12.log.warn(`${duplicateCount} selected model(s) were already in your favorites.`);
22627
+ p13.log.warn(`${duplicateCount} selected model(s) were already in your favorites.`);
22265
22628
  }
22266
22629
  if (limitReached) {
22267
- p12.log.warn(`Limit of ${maxFavorites} favorites reached \u2014 some selected models could not be added.`);
22630
+ p13.log.warn(`Limit of ${maxFavorites} favorites reached \u2014 some selected models could not be added.`);
22268
22631
  }
22269
22632
  } else if (choice.startsWith("fav-")) {
22270
22633
  const idx = parseInt(choice.slice(4), 10);
22271
22634
  const fav = favorites[idx];
22272
22635
  const entry = modelLookup.get(`${fav.providerId}:${fav.modelId}`);
22273
22636
  const label = entry ? `${entry.modelName} (${entry.providerName})` : fav.modelId;
22274
- const confirmed = await p12.confirm({ message: `Remove ${label} from favorites?` });
22275
- if (p12.isCancel(confirmed) || !confirmed) continue;
22637
+ const confirmed = await p13.confirm({ message: `Remove ${label} from favorites?` });
22638
+ if (p13.isCancel(confirmed) || !confirmed) continue;
22276
22639
  favorites = removeFavorite(favorites, fav);
22277
22640
  favoritesDirty = true;
22278
- p12.log.success(`Removed ${label} from favorites.`);
22641
+ p13.log.success(`Removed ${label} from favorites.`);
22279
22642
  }
22280
22643
  }
22281
22644
  if (favoritesDirty) {
@@ -22289,8 +22652,8 @@ async function runModelsCommand(opts = {}) {
22289
22652
  }
22290
22653
  async function runClaudeHttpProxyCommand(parsed, claudeArgs, agentStdout) {
22291
22654
  if (parsed.launchProvider || parsed.launchModel) {
22292
- p12.log.error("--provider/--model select endpoint-mode routes and cannot be combined with --proxy.");
22293
- p12.log.info("Use `-- --model clodex:<provider-id>:<model-id>` to start on a listed proxy-mode favorite.");
22655
+ p13.log.error("--provider/--model select endpoint-mode routes and cannot be combined with --proxy.");
22656
+ p13.log.info("Use `-- --model clodex:<provider-id>:<model-id>` to start on a listed proxy-mode favorite.");
22294
22657
  return 1;
22295
22658
  }
22296
22659
  if (!agentStdout) relayIntro("Claude Code \u2014 Proxy Mode");
@@ -22308,7 +22671,7 @@ async function runClaudeHttpProxyCommand(parsed, claudeArgs, agentStdout) {
22308
22671
  console.log("");
22309
22672
  return 0;
22310
22673
  } catch (err) {
22311
- p12.log.error(`Could not load proxy models: ${err instanceof Error ? err.message : String(err)}`);
22674
+ p13.log.error(`Could not load proxy models: ${err instanceof Error ? err.message : String(err)}`);
22312
22675
  return 1;
22313
22676
  }
22314
22677
  }
@@ -22318,13 +22681,13 @@ async function runClaudeHttpProxyCommand(parsed, claudeArgs, agentStdout) {
22318
22681
  try {
22319
22682
  started = await startConfiguredHttpProxy(0, parsed.trace, inferenceLogPath, proxyDebugLogPath);
22320
22683
  } catch (err) {
22321
- p12.log.error(`Failed to start proxy: ${err instanceof Error ? err.message : String(err)}`);
22684
+ p13.log.error(`Failed to start proxy: ${err instanceof Error ? err.message : String(err)}`);
22322
22685
  return 1;
22323
22686
  }
22324
22687
  const { handle, loaded, caWarning } = started;
22325
22688
  if (caWarning) {
22326
22689
  if (agentStdout) emitParentNotice(`clodex: ${caWarning}`);
22327
- else p12.log.warn(caWarning);
22690
+ else p13.log.warn(caWarning);
22328
22691
  }
22329
22692
  const inheritedProxyPort = (() => {
22330
22693
  const value = process.env["HTTPS_PROXY"] ?? process.env["HTTP_PROXY"] ?? process.env["https_proxy"] ?? process.env["http_proxy"];
@@ -22358,20 +22721,20 @@ async function runClaudeHttpProxyCommand(parsed, claudeArgs, agentStdout) {
22358
22721
  };
22359
22722
  process.once("exit", onProcessExit);
22360
22723
  if (!agentStdout) {
22361
- p12.log.info(`Proxy started on port ${handle.port}; Claude Code's Anthropic auth remains active.`);
22362
- p12.log.info(`Inference request log: ${handle.inferenceLogPath}`);
22724
+ p13.log.info(`Proxy started on port ${handle.port}; Claude Code's Anthropic auth remains active.`);
22725
+ p13.log.info(`Inference request log: ${handle.inferenceLogPath}`);
22363
22726
  printHttpProxyModels(loaded.routes, loaded.aliases);
22364
22727
  reportSkippedHttpProxyFavorites(loaded);
22365
22728
  if (loaded.routes.length > 0) {
22366
- p12.log.info("Switch with `/model <listed-name>`.");
22729
+ p13.log.info("Switch with `/model <listed-name>`.");
22367
22730
  }
22368
22731
  }
22369
22732
  const childEnv = buildHttpProxyChildEnv(handle.port, handle.caCertPath);
22370
22733
  const debugLogPath = parsed.trace ? prepareClaudeTraceLog(getSessionLogPath("claude-debug")) : void 0;
22371
22734
  const traceArgs = debugLogPath ? ["--debug-file", debugLogPath] : [];
22372
22735
  if (debugLogPath && !agentStdout) {
22373
- p12.log.info(`Claude debug log: ${debugLogPath}`);
22374
- if (proxyDebugLogPath) p12.log.info(`Adapter debug log: ${proxyDebugLogPath}`);
22736
+ p13.log.info(`Claude debug log: ${debugLogPath}`);
22737
+ if (proxyDebugLogPath) p13.log.info(`Adapter debug log: ${proxyDebugLogPath}`);
22375
22738
  }
22376
22739
  try {
22377
22740
  const exitCode = await launchClaude(childEnv, void 0, [...traceArgs, ...claudeArgs]);
@@ -22456,7 +22819,7 @@ Error: ${launchPlan.error}
22456
22819
  return 1;
22457
22820
  }
22458
22821
  } else {
22459
- const catalogSpinner = p12.spinner();
22822
+ const catalogSpinner = p13.spinner();
22460
22823
  catalogSpinner.start("Loading your providers...");
22461
22824
  try {
22462
22825
  catalog = await fetchProviderCatalog();
@@ -22471,11 +22834,11 @@ Error: ${launchPlan.error}
22471
22834
  const blockedLaunchReason = launchPlan.skip && launchPlan.target?.providerId ? catalog.blockedProviders.get(launchPlan.target.providerId) : void 0;
22472
22835
  if (allProviders.length === 0) {
22473
22836
  if (blockedLaunchReason) {
22474
- p12.log.error(blockedLaunchReason);
22837
+ p13.log.error(blockedLaunchReason);
22475
22838
  return 1;
22476
22839
  }
22477
- p12.log.warn("No providers available.");
22478
- p12.log.info(pc13.dim("Run clodex providers to get started."));
22840
+ p13.log.warn("No providers available.");
22841
+ p13.log.info(pc13.dim("Run clodex providers to get started."));
22479
22842
  return 0;
22480
22843
  }
22481
22844
  const providerOptions = allProviders.map((lp) => providerSelectOption(lp));
@@ -22492,7 +22855,7 @@ Error: ${launchPlan.error}
22492
22855
  if (launchPlan.skip && launchPlan.target) {
22493
22856
  const resolved = findProviderAndModel(allProviders, launchPlan.target);
22494
22857
  if (!resolved) {
22495
- p12.log.error(
22858
+ p13.log.error(
22496
22859
  blockedLaunchReason ?? `Provider/model not found: ${launchPlan.target.providerId} / ${launchPlan.target.modelId}`
22497
22860
  );
22498
22861
  return 1;
@@ -22500,19 +22863,19 @@ Error: ${launchPlan.error}
22500
22863
  activeProvider = resolved.provider;
22501
22864
  selectedModel = resolved.model;
22502
22865
  if (!agentStdout) {
22503
- p12.log.step(`Using ${selectedModel.name || selectedModel.id} (${activeProvider.name})`);
22866
+ p13.log.step(`Using ${selectedModel.name || selectedModel.id} (${activeProvider.name})`);
22504
22867
  }
22505
22868
  if (!dryRun) recordLaunchSelection("claude", activeProvider.id, selectedModel.id, prefs);
22506
22869
  } else {
22507
22870
  let currentInitialProvider = initialProvider;
22508
22871
  while (true) {
22509
- const chosen = await p12.select({
22872
+ const chosen = await p13.select({
22510
22873
  message: "Which provider?",
22511
22874
  options: providerOptions,
22512
22875
  initialValue: currentInitialProvider
22513
22876
  });
22514
- if (p12.isCancel(chosen)) {
22515
- p12.cancel("Cancelled.");
22877
+ if (p13.isCancel(chosen)) {
22878
+ p13.cancel("Cancelled.");
22516
22879
  return 0;
22517
22880
  }
22518
22881
  const providerChoice = chosen;
@@ -22524,7 +22887,7 @@ Error: ${launchPlan.error}
22524
22887
  if (prov && mod) available.push({ provider: prov, model: mod });
22525
22888
  }
22526
22889
  if (available.length === 0) {
22527
- p12.log.warn("No saved favorites are currently available.");
22890
+ p13.log.warn("No saved favorites are currently available.");
22528
22891
  return 0;
22529
22892
  }
22530
22893
  const favOptions = available.map((f, i) => ({
@@ -22532,13 +22895,13 @@ Error: ${launchPlan.error}
22532
22895
  label: `${f.model.name || f.model.id} \u2014 ${f.provider.name}`,
22533
22896
  hint: f.model.id
22534
22897
  }));
22535
- const pickedIdx = await p12.select({
22898
+ const pickedIdx = await p13.select({
22536
22899
  message: "Starting model?",
22537
22900
  options: favOptions,
22538
22901
  initialValue: "0"
22539
22902
  });
22540
- if (p12.isCancel(pickedIdx)) {
22541
- p12.cancel("Cancelled.");
22903
+ if (p13.isCancel(pickedIdx)) {
22904
+ p13.cancel("Cancelled.");
22542
22905
  return 0;
22543
22906
  }
22544
22907
  const sel = available[Number(pickedIdx)];
@@ -22567,12 +22930,12 @@ Error: ${launchPlan.error}
22567
22930
  );
22568
22931
  const startingRoute = resolveRoute(activeProvider.id, selectedModel.id) ?? null;
22569
22932
  if (!startingRoute) {
22570
- p12.log.error("Could not resolve a proxy route for the selected model.");
22933
+ p13.log.error("Could not resolve a proxy route for the selected model.");
22571
22934
  return 1;
22572
22935
  }
22573
22936
  const { routes: catalogRoutes, droppedFavorites } = buildCatalogRoutes(startingRoute, favorites, resolveRoute);
22574
22937
  if (droppedFavorites.length > 0) {
22575
- p12.log.warn(
22938
+ p13.log.warn(
22576
22939
  `Skipping ${droppedFavorites.length} favorite${droppedFavorites.length === 1 ? "" : "s"} that are no longer available in /model`
22577
22940
  );
22578
22941
  }
@@ -22626,7 +22989,7 @@ Error: ${launchPlan.error}
22626
22989
  }
22627
22990
  const launchApiKey = await resolveLocalProviderApiKey(activeProvider);
22628
22991
  if (!anonymousProvider && !launchApiKey?.trim()) {
22629
- p12.log.error(
22992
+ p13.log.error(
22630
22993
  `No credential found for ${activeProvider.name}. Add a key or sign in with clodex providers.`
22631
22994
  );
22632
22995
  return 1;
@@ -22652,9 +23015,9 @@ Error: ${launchPlan.error}
22652
23015
  },
22653
23016
  launchApiKey ?? ""
22654
23017
  );
22655
- if (!isAgentStdoutMode()) p12.log.info(`Anthropic proxy started on port ${proxyHandle.port}`);
23018
+ if (!isAgentStdoutMode()) p13.log.info(`Anthropic proxy started on port ${proxyHandle.port}`);
22656
23019
  } catch (err) {
22657
- p12.log.error(`Failed to start Anthropic proxy: ${err instanceof Error ? err.message : String(err)}`);
23020
+ p13.log.error(`Failed to start Anthropic proxy: ${err instanceof Error ? err.message : String(err)}`);
22658
23021
  return 1;
22659
23022
  }
22660
23023
  childEnv = buildChildEnv(
@@ -22698,12 +23061,12 @@ Error: ${launchPlan.error}
22698
23061
  launchApiKey ?? ""
22699
23062
  );
22700
23063
  if (!isAgentStdoutMode()) {
22701
- p12.log.info(
23064
+ p13.log.info(
22702
23065
  `SDK adapter proxy started on port ${proxyHandle.port}` + (selectedModel.npm ? pc13.dim(` (${selectedModel.npm})`) : "")
22703
23066
  );
22704
23067
  }
22705
23068
  } catch (err) {
22706
- p12.log.error(`Failed to start SDK adapter proxy: ${err instanceof Error ? err.message : String(err)}`);
23069
+ p13.log.error(`Failed to start SDK adapter proxy: ${err instanceof Error ? err.message : String(err)}`);
22707
23070
  return 1;
22708
23071
  }
22709
23072
  childEnv = buildChildEnv(
@@ -22719,7 +23082,7 @@ Error: ${launchPlan.error}
22719
23082
  }
22720
23083
  const debugLogPath = prepareClaudeTraceLog();
22721
23084
  const traceArgs = trace ? ["--debug-file", debugLogPath] : [];
22722
- if (trace) p12.log.info(`Debug log: ${debugLogPath}`);
23085
+ if (trace) p13.log.info(`Debug log: ${debugLogPath}`);
22723
23086
  const exitCode = await launchClaude(
22724
23087
  childEnv,
22725
23088
  claudeCodeClientModelId(selectedModel.id, selectedModel.contextWindow),