claudish 7.19.2 → 7.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +190 -114
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -651,7 +651,7 @@ var init_onepassword_config = __esm(() => {
651
651
  });
652
652
 
653
653
  // src/version.ts
654
- var VERSION = "7.19.2";
654
+ var VERSION = "7.20.0";
655
655
 
656
656
  // src/logger.ts
657
657
  var exports_logger = {};
@@ -27650,9 +27650,6 @@ function loadConfig() {
27650
27650
  if (config2.onepasswordEnvironments !== undefined) {
27651
27651
  merged.onepasswordEnvironments = config2.onepasswordEnvironments;
27652
27652
  }
27653
- if (config2.anthropicApiBilling !== undefined) {
27654
- merged.anthropicApiBilling = config2.anthropicApiBilling;
27655
- }
27656
27653
  if (config2.localProviders !== undefined) {
27657
27654
  merged.localProviders = Array.from(new Set(config2.localProviders)).sort();
27658
27655
  }
@@ -28326,8 +28323,8 @@ var init_provider_definitions = __esm(() => {
28326
28323
  shortcuts: ["kc"],
28327
28324
  shortestPrefix: "kc",
28328
28325
  legacyPrefixes: [{ prefix: "kc/", stripPrefix: true }],
28329
- nativeModelPatterns: [{ pattern: /^kimi-for-coding$/i }],
28330
- fixedModel: "kimi-for-coding",
28326
+ nativeModelPatterns: [{ pattern: /^kimi-for-coding/i }, { pattern: /^k3(-|$)/i }],
28327
+ modelDiscovery: { path: "/models", format: "openai-models-list" },
28331
28328
  isDirectApi: true,
28332
28329
  description: "Kimi Coding Plan (kc@)"
28333
28330
  },
@@ -30475,6 +30472,22 @@ function lookupModelForProvider(modelId, provider, cachePath) {
30475
30472
  return;
30476
30473
  return entry.aggregators?.find((a) => a.provider === provider)?.contextWindow ?? entry.contextWindow;
30477
30474
  }
30475
+ function resolveSubscriptionRouting(modelId, provider, cachePath) {
30476
+ const entry = findCacheEntry(modelId, cachePath);
30477
+ if (!entry)
30478
+ return { kind: "unknown" };
30479
+ if (entry.subscriptionPlans?.includes(provider)) {
30480
+ const agg = entry.aggregators?.find((a) => a.provider === provider);
30481
+ return agg?.externalId ? { kind: "serves", externalId: agg.externalId } : { kind: "unknown" };
30482
+ }
30483
+ return isSubscriptionPlan(provider, cachePath) ? { kind: "not-served" } : { kind: "unknown" };
30484
+ }
30485
+ function isSubscriptionPlan(provider, cachePath) {
30486
+ const cache = readAllModelsCache(cachePath);
30487
+ if (!cache)
30488
+ return false;
30489
+ return cache.entries.some((e) => e.subscriptionPlans?.includes(provider));
30490
+ }
30478
30491
  function findCacheEntry(modelId, cachePath) {
30479
30492
  if (modelId.includes("@")) {
30480
30493
  throw new Error(`model-catalog lookup received provider-routed ID "${modelId}" \u2014 callers must strip the "@" prefix before calling`);
@@ -31745,14 +31758,6 @@ var init_web_search_detector = __esm(() => {
31745
31758
  WEB_SEARCH_NAMES = new Set(["web_search", "brave_web_search", "tavily_search"]);
31746
31759
  });
31747
31760
 
31748
- // src/handlers/shared/stream-parsers/message-start-usage.ts
31749
- function messageStartUsage(priorInputTokens) {
31750
- return {
31751
- input_tokens: priorInputTokens && priorInputTokens > 0 ? priorInputTokens : 100,
31752
- output_tokens: 1
31753
- };
31754
- }
31755
-
31756
31761
  // src/handlers/shared/stream-parsers/openai-sse.ts
31757
31762
  function validateToolArguments(toolName, argsStr, toolSchemas, textContent) {
31758
31763
  const result = validateAndRepairToolCall(toolName, argsStr, toolSchemas, textContent);
@@ -31782,7 +31787,7 @@ function createStreamingState() {
31782
31787
  accumulatedText: ""
31783
31788
  };
31784
31789
  }
31785
- function createStreamingResponseHandler(c, response, adapter, target, middlewareManager, onTokenUpdate, toolSchemas, toolNameMap, priorInputTokens) {
31790
+ function createStreamingResponseHandler(c, response, adapter, target, middlewareManager, onTokenUpdate, toolSchemas, toolNameMap) {
31786
31791
  log(`[Streaming] ===== HANDLER STARTED for ${target} =====`);
31787
31792
  let isClosed = false;
31788
31793
  let ping = null;
@@ -31811,7 +31816,7 @@ data: ${JSON.stringify(d)}
31811
31816
  model: target,
31812
31817
  stop_reason: null,
31813
31818
  stop_sequence: null,
31814
- usage: messageStartUsage(priorInputTokens)
31819
+ usage: { input_tokens: 100, output_tokens: 1 }
31815
31820
  }
31816
31821
  });
31817
31822
  send("ping", { type: "ping" });
@@ -31924,10 +31929,7 @@ data: ${JSON.stringify(d)}
31924
31929
  send("message_delta", {
31925
31930
  type: "message_delta",
31926
31931
  delta: { stop_reason: stopReason, stop_sequence: null },
31927
- usage: {
31928
- ...state.usage?.prompt_tokens ? { input_tokens: state.usage.prompt_tokens } : {},
31929
- output_tokens: state.usage?.completion_tokens || 0
31930
- }
31932
+ usage: { output_tokens: state.usage?.completion_tokens || 0 }
31931
31933
  });
31932
31934
  send("message_stop", { type: "message_stop" });
31933
31935
  }
@@ -31938,7 +31940,7 @@ data: ${JSON.stringify(d)}
31938
31940
  } else {
31939
31941
  const estimatedOutputTokens = Math.ceil(state.accumulatedText.length / 4);
31940
31942
  log(`[Streaming] No usage data from provider, estimating: ~${estimatedOutputTokens} output tokens`);
31941
- onTokenUpdate(priorInputTokens || 100, estimatedOutputTokens);
31943
+ onTokenUpdate(100, estimatedOutputTokens);
31942
31944
  }
31943
31945
  }
31944
31946
  if (!isClosed) {
@@ -37556,7 +37558,7 @@ data: ${JSON.stringify(data)}
37556
37558
  model: opts.modelName,
37557
37559
  stop_reason: null,
37558
37560
  stop_sequence: null,
37559
- usage: messageStartUsage(opts.priorInputTokens)
37561
+ usage: { input_tokens: 100, output_tokens: 1 }
37560
37562
  }
37561
37563
  });
37562
37564
  send("ping", { type: "ping" });
@@ -37600,10 +37602,7 @@ data: ${JSON.stringify(data)}
37600
37602
  send("message_delta", {
37601
37603
  type: "message_delta",
37602
37604
  delta: { stop_reason: hasToolCalls ? "tool_use" : "end_turn", stop_sequence: null },
37603
- usage: {
37604
- ...inputTokens > 0 ? { input_tokens: inputTokens } : {},
37605
- output_tokens: outputTokens
37606
- }
37605
+ usage: { output_tokens: outputTokens }
37607
37606
  });
37608
37607
  send("message_stop", { type: "message_stop" });
37609
37608
  }
@@ -37806,7 +37805,7 @@ data: ${JSON.stringify(data)}
37806
37805
  model: opts.modelName,
37807
37806
  stop_reason: null,
37808
37807
  stop_sequence: null,
37809
- usage: messageStartUsage(opts.priorInputTokens)
37808
+ usage: { input_tokens: 100, output_tokens: 1 }
37810
37809
  }
37811
37810
  });
37812
37811
  send("ping", { type: "ping" });
@@ -37827,10 +37826,7 @@ data: ${JSON.stringify(data)}
37827
37826
  send("message_delta", {
37828
37827
  type: "message_delta",
37829
37828
  delta: { stop_reason: "end_turn", stop_sequence: null },
37830
- usage: {
37831
- ...promptTokens > 0 ? { input_tokens: promptTokens } : {},
37832
- output_tokens: completionTokens
37833
- }
37829
+ usage: { output_tokens: completionTokens }
37834
37830
  });
37835
37831
  send("message_stop", { type: "message_stop" });
37836
37832
  }
@@ -37982,7 +37978,7 @@ data: ${JSON.stringify(data)}
37982
37978
  model: opts.modelName,
37983
37979
  stop_reason: null,
37984
37980
  stop_sequence: null,
37985
- usage: messageStartUsage(opts.priorInputTokens)
37981
+ usage: { input_tokens: 100, output_tokens: 1 }
37986
37982
  }
37987
37983
  });
37988
37984
  send("ping", { type: "ping" });
@@ -38245,13 +38241,6 @@ data: ${JSON.stringify(data)}
38245
38241
  } catch {}
38246
38242
  }
38247
38243
  }
38248
- },
38249
- cancel() {
38250
- isClosed = true;
38251
- if (pingInterval) {
38252
- clearInterval(pingInterval);
38253
- pingInterval = null;
38254
- }
38255
38244
  }
38256
38245
  });
38257
38246
  return new Response(stream, {
@@ -38279,7 +38268,6 @@ class TokenTracker {
38279
38268
  sessionTotalCost = 0;
38280
38269
  sessionInputTokens = 0;
38281
38270
  sessionOutputTokens = 0;
38282
- lastInputTokens = 0;
38283
38271
  modelNameOverride;
38284
38272
  quotaRemaining;
38285
38273
  constructor(port, config2) {
@@ -38296,11 +38284,10 @@ class TokenTracker {
38296
38284
  this.quotaRemaining = fraction;
38297
38285
  }
38298
38286
  rewrite() {
38299
- this.writeFile(this.getLastInputTokens(), this.sessionOutputTokens);
38287
+ this.writeFile(this.sessionInputTokens, this.sessionOutputTokens);
38300
38288
  }
38301
38289
  update(inputTokens, outputTokens) {
38302
38290
  this.sessionInputTokens = inputTokens;
38303
- this.lastInputTokens = inputTokens;
38304
38291
  this.sessionOutputTokens += outputTokens;
38305
38292
  const pricing = this.getPricing();
38306
38293
  const cost = inputTokens / 1e6 * pricing.inputCostPer1M + outputTokens / 1e6 * pricing.outputCostPer1M;
@@ -38309,7 +38296,6 @@ class TokenTracker {
38309
38296
  }
38310
38297
  accumulateBoth(inputTokens, outputTokens) {
38311
38298
  this.sessionInputTokens += inputTokens;
38312
- this.lastInputTokens = this.sessionInputTokens;
38313
38299
  this.sessionOutputTokens += outputTokens;
38314
38300
  const pricing = this.getPricing();
38315
38301
  const cost = this.sessionInputTokens / 1e6 * pricing.inputCostPer1M + this.sessionOutputTokens / 1e6 * pricing.outputCostPer1M;
@@ -38318,7 +38304,6 @@ class TokenTracker {
38318
38304
  }
38319
38305
  updateWithDelta(inputTokens, outputTokens) {
38320
38306
  let incrementalInputTokens;
38321
- this.lastInputTokens = inputTokens;
38322
38307
  if (inputTokens >= this.sessionInputTokens) {
38323
38308
  incrementalInputTokens = inputTokens - this.sessionInputTokens;
38324
38309
  this.sessionInputTokens = inputTokens;
@@ -38334,11 +38319,10 @@ class TokenTracker {
38334
38319
  const pricing = this.getPricing();
38335
38320
  const cost = incrementalInputTokens / 1e6 * pricing.inputCostPer1M + outputTokens / 1e6 * pricing.outputCostPer1M;
38336
38321
  this.sessionTotalCost += cost;
38337
- this.writeFile(inputTokens, this.sessionOutputTokens, pricing.isEstimate);
38322
+ this.writeFile(Math.max(inputTokens, this.sessionInputTokens), this.sessionOutputTokens, pricing.isEstimate);
38338
38323
  }
38339
38324
  updateWithActualCost(inputTokens, outputTokens, actualCost) {
38340
38325
  this.sessionInputTokens = inputTokens;
38341
- this.lastInputTokens = inputTokens;
38342
38326
  this.sessionOutputTokens += outputTokens;
38343
38327
  if (typeof actualCost === "number" && actualCost > 0) {
38344
38328
  this.sessionTotalCost += actualCost;
@@ -38354,7 +38338,6 @@ class TokenTracker {
38354
38338
  updateLocal(inputTokens, outputTokens) {
38355
38339
  if (inputTokens > 0) {
38356
38340
  this.sessionInputTokens = inputTokens;
38357
- this.lastInputTokens = inputTokens;
38358
38341
  }
38359
38342
  this.sessionOutputTokens += outputTokens;
38360
38343
  this.writeFile(this.sessionInputTokens, this.sessionOutputTokens);
@@ -38368,9 +38351,6 @@ class TokenTracker {
38368
38351
  getInputTokens() {
38369
38352
  return this.sessionInputTokens;
38370
38353
  }
38371
- getLastInputTokens() {
38372
- return this.lastInputTokens || this.sessionInputTokens;
38373
- }
38374
38354
  getOutputTokens() {
38375
38355
  return this.sessionOutputTokens;
38376
38356
  }
@@ -38912,18 +38892,16 @@ class ComposedHandler {
38912
38892
  }
38913
38893
  };
38914
38894
  const streamFormat = this.provider.overrideStreamFormat?.() ?? this.explicitAdapter?.getStreamFormat() ?? this.modelAdapter?.getStreamFormat() ?? this.getAdapter().getStreamFormat();
38915
- const priorInputTokens = this.tokenTracker.getLastInputTokens();
38916
38895
  switch (streamFormat) {
38917
38896
  case "openai-sse":
38918
- return createStreamingResponseHandler(c, response, adapter, this.bareModelName, this.middlewareManager, onTokenUpdate, claudeRequest.tools, toolNameMap, priorInputTokens);
38897
+ return createStreamingResponseHandler(c, response, adapter, this.bareModelName, this.middlewareManager, onTokenUpdate, claudeRequest.tools, toolNameMap);
38919
38898
  case "openai-responses-sse":
38920
38899
  return createResponsesStreamHandler(c, response, {
38921
38900
  modelName: this.bareModelName,
38922
38901
  onTokenUpdate,
38923
38902
  toolNameMap: adapter.getToolNameMap(),
38924
38903
  contextWindow: lookupModelForProvider(this.bareModelName, this.provider.name),
38925
- onApiError,
38926
- priorInputTokens
38904
+ onApiError
38927
38905
  });
38928
38906
  case "anthropic-sse":
38929
38907
  return createAnthropicPassthroughStream(c, response, {
@@ -38943,15 +38921,13 @@ class ComposedHandler {
38943
38921
  middlewareManager: this.middlewareManager,
38944
38922
  onTokenUpdate,
38945
38923
  onToolCall,
38946
- unwrapResponse: this.options.unwrapGeminiResponse,
38947
- priorInputTokens
38924
+ unwrapResponse: this.options.unwrapGeminiResponse
38948
38925
  });
38949
38926
  }
38950
38927
  case "ollama-jsonl":
38951
38928
  return createOllamaJsonlStream(c, response, {
38952
38929
  modelName: this.bareModelName,
38953
- onTokenUpdate,
38954
- priorInputTokens
38930
+ onTokenUpdate
38955
38931
  });
38956
38932
  default:
38957
38933
  throw new Error(`Unknown stream format: ${streamFormat}`);
@@ -42591,7 +42567,8 @@ var init_default_routing_rules = __esm(() => {
42591
42567
  "o3-*": ["openai-codex", "openai", "openrouter"],
42592
42568
  "gemini-*": ["gemini-codeassist", "google", "openrouter"],
42593
42569
  "grok-*": ["x-ai", "openrouter"],
42594
- "kimi-*": ["kimi-coding@kimi-for-coding", "kimi", "openrouter"],
42570
+ "kimi-*": ["kimi-coding", "kimi", "openrouter"],
42571
+ "k3*": ["kimi-coding", "kimi", "openrouter"],
42595
42572
  "minimax-*": ["minimax-coding", "minimax", "openrouter"],
42596
42573
  "glm-*": ["glm-coding", "glm", "openrouter"],
42597
42574
  "z-ai-*": ["z-ai", "openrouter"],
@@ -42645,7 +42622,7 @@ function matchRoutingRule(modelName, rules) {
42645
42622
  return rules["*"];
42646
42623
  return null;
42647
42624
  }
42648
- function buildRoutingChain(entries, originalModelName) {
42625
+ function buildRoutingChain(entries, originalModelName, cachePath) {
42649
42626
  const routes = [];
42650
42627
  for (const entry of entries) {
42651
42628
  const atIdx = entry.indexOf("@");
@@ -42659,6 +42636,13 @@ function buildRoutingChain(entries, originalModelName) {
42659
42636
  modelName = originalModelName;
42660
42637
  }
42661
42638
  const provider = PROVIDER_SHORTCUTS[providerRaw.toLowerCase()] ?? providerRaw.toLowerCase();
42639
+ if (atIdx === -1) {
42640
+ const routing = resolveSubscriptionRouting(modelName, provider, cachePath);
42641
+ if (routing.kind === "not-served")
42642
+ continue;
42643
+ if (routing.kind === "serves")
42644
+ modelName = routing.externalId;
42645
+ }
42662
42646
  let modelSpec;
42663
42647
  if (provider === "openrouter") {
42664
42648
  const resolution = resolveModelNameSync(modelName, "openrouter");
@@ -42685,7 +42669,7 @@ function globMatch(pattern, value) {
42685
42669
  async function hasCredentialsForProvider(provider) {
42686
42670
  return credentials.isAvailable(provider);
42687
42671
  }
42688
- async function routeExplicit(modelSpec, model, provider) {
42672
+ async function routeExplicit(modelSpec, model, provider, cachePath) {
42689
42673
  if (!await hasCredentialsForProvider(provider)) {
42690
42674
  return {
42691
42675
  kind: "no-route",
@@ -42693,7 +42677,7 @@ async function routeExplicit(modelSpec, model, provider) {
42693
42677
  hint: buildCredentialHint(model, [provider]) ?? undefined
42694
42678
  };
42695
42679
  }
42696
- const built = buildRoutingChain([modelSpec], model)[0];
42680
+ const built = buildRoutingChain([modelSpec], model, cachePath)[0];
42697
42681
  if (!built) {
42698
42682
  return {
42699
42683
  kind: "no-route",
@@ -42702,7 +42686,7 @@ async function routeExplicit(modelSpec, model, provider) {
42702
42686
  }
42703
42687
  return { kind: "ok", primary: built, fallbacks: [] };
42704
42688
  }
42705
- async function routeBare(model, nativeProvider, rules, defaultProvider) {
42689
+ async function routeBare(model, nativeProvider, rules, defaultProvider, cachePath) {
42706
42690
  const matched = matchRoutingRule(model, rules) ?? [];
42707
42691
  const entries = [...matched];
42708
42692
  if (defaultProvider && defaultProvider.length > 0) {
@@ -42723,7 +42707,7 @@ async function routeBare(model, nativeProvider, rules, defaultProvider) {
42723
42707
  hint: buildCredentialHint(model, [nativeProvider]) ?? undefined
42724
42708
  };
42725
42709
  }
42726
- const candidates = buildRoutingChain(entries, model);
42710
+ const candidates = buildRoutingChain(entries, model, cachePath);
42727
42711
  const credentialed = [];
42728
42712
  const skipped = [];
42729
42713
  const checks4 = await Promise.all(candidates.map((candidate) => hasCredentialsForProvider(candidate.provider)));
@@ -42744,16 +42728,17 @@ async function routeBare(model, nativeProvider, rules, defaultProvider) {
42744
42728
  const [primary, ...fallbacks] = credentialed;
42745
42729
  return { kind: "ok", primary, fallbacks };
42746
42730
  }
42747
- async function route(modelSpec, rulesOverride, defaultProviderOverride) {
42731
+ async function route(modelSpec, rulesOverride, defaultProviderOverride, cachePath) {
42748
42732
  const parsed = parseModelSpec(modelSpec);
42749
42733
  if (parsed.isExplicitProvider) {
42750
- return routeExplicit(modelSpec, parsed.model, parsed.provider);
42734
+ return routeExplicit(modelSpec, parsed.model, parsed.provider, cachePath);
42751
42735
  }
42752
42736
  const rules = rulesOverride ?? loadRoutingRules();
42753
42737
  const defaultProvider = defaultProviderOverride !== undefined ? defaultProviderOverride : rulesOverride !== undefined ? undefined : loadConfig().defaultProvider;
42754
- return routeBare(parsed.model, parsed.provider, rules, defaultProvider);
42738
+ return routeBare(parsed.model, parsed.provider, rules, defaultProvider, cachePath);
42755
42739
  }
42756
42740
  var init_routing_rules = __esm(() => {
42741
+ init_model_catalog();
42757
42742
  init_authority();
42758
42743
  init_profile_config();
42759
42744
  init_auto_route();
@@ -58426,8 +58411,7 @@ var init_config = __esm(() => {
58426
58411
  OPENAI_BASE_URL: "OPENAI_BASE_URL",
58427
58412
  CLAUDISH_SUMMARIZE_TOOLS: "CLAUDISH_SUMMARIZE_TOOLS",
58428
58413
  CLAUDISH_DIAG_MODE: "CLAUDISH_DIAG_MODE",
58429
- CLAUDISH_DEBUG: "CLAUDISH_DEBUG",
58430
- CLAUDISH_ANTHROPIC_API_BILLING: "CLAUDISH_ANTHROPIC_API_BILLING"
58414
+ CLAUDISH_DEBUG: "CLAUDISH_DEBUG"
58431
58415
  };
58432
58416
  OPENROUTER_HEADERS = {
58433
58417
  "HTTP-Referer": "https://claudish.com",
@@ -58584,6 +58568,114 @@ var init_model_catalog2 = __esm(() => {
58584
58568
  NO_CATALOG_VENDOR_SLUGS = new Set(["litellm", "ollama", "lmstudio", "lm-studio"]);
58585
58569
  });
58586
58570
 
58571
+ // src/providers/model-discovery.ts
58572
+ function resolveBaseUrl2(catalogName) {
58573
+ const def = getProviderByName(catalogName);
58574
+ if (!def)
58575
+ return null;
58576
+ for (const envVar of def.baseUrlEnvVars ?? []) {
58577
+ const v = process.env[envVar];
58578
+ if (v)
58579
+ return v.replace(/\/+$/, "");
58580
+ }
58581
+ return (def.baseUrl || "").replace(/\/+$/, "") || null;
58582
+ }
58583
+ function readContextWindow(row) {
58584
+ for (const field of ["context_length", "context_window", "max_context_length"]) {
58585
+ const v = row[field];
58586
+ if (typeof v === "number" && Number.isFinite(v) && v > 0)
58587
+ return v;
58588
+ }
58589
+ return;
58590
+ }
58591
+ function parseOpenAIModelsList(body) {
58592
+ const data = body?.data;
58593
+ if (!Array.isArray(data))
58594
+ return [];
58595
+ const models = [];
58596
+ for (const raw2 of data) {
58597
+ if (!raw2 || typeof raw2 !== "object")
58598
+ continue;
58599
+ const row = raw2;
58600
+ const id = row.id;
58601
+ if (typeof id !== "string" || id.trim().length === 0)
58602
+ continue;
58603
+ const displayName = typeof row.display_name === "string" ? row.display_name : undefined;
58604
+ models.push({ id, displayName, contextWindow: readContextWindow(row) });
58605
+ }
58606
+ return models;
58607
+ }
58608
+ async function discoverProviderModels(providerName) {
58609
+ const cached2 = _cache2.get(providerName);
58610
+ if (cached2 && cached2.expiresAt > Date.now())
58611
+ return cached2.models;
58612
+ const def = getProviderByName(providerName);
58613
+ const descriptor = def?.modelDiscovery;
58614
+ if (!def || !descriptor)
58615
+ return [];
58616
+ const baseUrl = resolveBaseUrl2(providerName);
58617
+ if (!baseUrl)
58618
+ return [];
58619
+ const endpoint = `${baseUrl}${descriptor.path}`;
58620
+ let headers = {};
58621
+ try {
58622
+ const auth = await credentials.getRequestAuth(providerName, { model: "" });
58623
+ headers = { ...auth.headers };
58624
+ } catch (e) {
58625
+ log(`[model-discovery:${providerName}] no credentials, skipping: ${e?.message}`);
58626
+ return [];
58627
+ }
58628
+ let response;
58629
+ try {
58630
+ response = await fetch(endpoint, {
58631
+ method: "GET",
58632
+ headers,
58633
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS2)
58634
+ });
58635
+ } catch (e) {
58636
+ log(`[model-discovery:${providerName}] fetch failed: ${e?.message}`);
58637
+ return [];
58638
+ }
58639
+ if (!response.ok) {
58640
+ log(`[model-discovery:${providerName}] HTTP ${response.status} from ${endpoint}`);
58641
+ return [];
58642
+ }
58643
+ let body;
58644
+ try {
58645
+ body = await response.json();
58646
+ } catch {
58647
+ log(`[model-discovery:${providerName}] response was not JSON`);
58648
+ return [];
58649
+ }
58650
+ const models = parseOpenAIModelsList(body);
58651
+ if (models.length === 0) {
58652
+ log(`[model-discovery:${providerName}] endpoint reachable but listed no models`);
58653
+ return [];
58654
+ }
58655
+ log(`[model-discovery:${providerName}] discovered ${models.length} models: ` + models.map((m) => `${m.id}(${m.contextWindow ?? "?"})`).join(", "));
58656
+ _cache2.set(providerName, { models, expiresAt: Date.now() + CACHE_TTL_MS3 });
58657
+ return models;
58658
+ }
58659
+ async function discoverContextWindow(providerName, modelId) {
58660
+ const models = await discoverProviderModels(providerName);
58661
+ const match2 = models.find((m) => m.id.toLowerCase() === modelId.toLowerCase());
58662
+ return match2?.contextWindow;
58663
+ }
58664
+ function rankDiscoveredModels(models) {
58665
+ return [...models].sort((a, b) => {
58666
+ const diff = (b.contextWindow ?? 0) - (a.contextWindow ?? 0);
58667
+ return diff !== 0 ? diff : a.id.localeCompare(b.id);
58668
+ });
58669
+ }
58670
+ var CACHE_TTL_MS3, FETCH_TIMEOUT_MS2 = 5000, _cache2;
58671
+ var init_model_discovery = __esm(() => {
58672
+ init_authority();
58673
+ init_logger();
58674
+ init_provider_definitions();
58675
+ CACHE_TTL_MS3 = 5 * 60 * 1000;
58676
+ _cache2 = new Map;
58677
+ });
58678
+
58587
58679
  // src/providers/ollama-discovery.ts
58588
58680
  function ollamaBaseUrl() {
58589
58681
  return process.env.OLLAMA_HOST || process.env.OLLAMA_BASE_URL || "http://localhost:11434";
@@ -59153,8 +59245,22 @@ async function selectModelFromProvider(provider, tierName, recommendedModels, _f
59153
59245
  const prefix = PROVIDER_MODEL_PREFIX[provider] || `${provider}@`;
59154
59246
  const displayName = getPickerDisplayName(provider);
59155
59247
  const def = getProviderByName(provider);
59156
- if (def?.fixedModel) {
59157
- return buildExplicitModelSpec(provider, def.fixedModel);
59248
+ if (def?.modelDiscovery) {
59249
+ const discovered = rankDiscoveredModels(await discoverProviderModels(provider));
59250
+ if (discovered.length > 0) {
59251
+ const discoveredModels = discovered.map((m) => ({
59252
+ id: m.id,
59253
+ name: m.displayName || m.id,
59254
+ description: m.contextWindow ? `${m.displayName ?? m.id} \xB7 ${Math.round(m.contextWindow / 1024)}K context` : m.displayName ?? m.id,
59255
+ provider: displayName,
59256
+ supportsTools: true,
59257
+ isFree: true,
59258
+ source: displayName
59259
+ }));
59260
+ const picked = await pickModelFromList(provider, displayName, tierName, discoveredModels);
59261
+ if (picked)
59262
+ return picked;
59263
+ }
59158
59264
  }
59159
59265
  if (provider === "ollama") {
59160
59266
  const ollamaModels = await fetchOllamaModels2({ enrichCapabilities: false });
@@ -59333,6 +59439,7 @@ var init_model_selector = __esm(() => {
59333
59439
  init_authority();
59334
59440
  init_model_loader();
59335
59441
  init_model_catalog2();
59442
+ init_model_discovery();
59336
59443
  init_provider_definitions();
59337
59444
  pickerProviderToFirebaseSlug = {
59338
59445
  openrouter: "openrouter",
@@ -62384,8 +62491,6 @@ async function parseArgs(args) {
62384
62491
  process.exit(1);
62385
62492
  }
62386
62493
  config3.defaultProvider = dpArg;
62387
- } else if (arg === "--anthropic-api-billing") {
62388
- config3.anthropicApiBilling = true;
62389
62494
  } else if (arg === "--op-env" || arg.startsWith("--op-env=")) {
62390
62495
  const v = arg.startsWith("--op-env=") ? arg.slice("--op-env=".length) : args[++i];
62391
62496
  if (!v) {
@@ -63435,10 +63540,6 @@ ${h("OPTIONS")}
63435
63540
  ${green("--profile")} ${yellow("<name>")} Use named profile for model mapping (default profile if omitted)
63436
63541
  ${green("--default-provider")} ${yellow("<name>")} Fallback provider for bare model names (builtin or customEndpoints key)
63437
63542
  ${dim("Precedence: this flag > CLAUDISH_DEFAULT_PROVIDER env > config.json")}
63438
- ${green("--anthropic-api-billing")} Use your real ANTHROPIC_API_KEY for native Claude models
63439
- ${dim("(metered API billing). Default: the key is hidden so Claude Code")}
63440
- ${dim("uses your claude.ai subscription. Env: CLAUDISH_ANTHROPIC_API_BILLING")}
63441
- ${dim("Config: anthropicApiBilling: true")}
63442
63543
  ${green("--config")} ${yellow("<file>")} Use THIS config file for the run, fully replacing the machine
63443
63544
  ${dim("global (~/.claudish/config.json) AND project (.claudish.json).")}
63444
63545
  ${dim("A file naming no op:// source never touches 1Password (no prompt).")}
@@ -63614,7 +63715,6 @@ ${h("ENVIRONMENT VARIABLES")}
63614
63715
  ${blue("CLAUDISH_CONTEXT_WINDOW")} Override context window size
63615
63716
  ${blue("CLAUDISH_DIAG_MODE")} Diagnostic output: auto / logfile / off
63616
63717
  ${blue("CLAUDISH_DEBUG")} Always enable debug logging: 1 / true ${dim("(same as -d)")}
63617
- ${blue("CLAUDISH_ANTHROPIC_API_BILLING")} Bill native Claude to your API key ${dim("(see --anthropic-api-billing)")}
63618
63718
  ${blue("CLAUDISH_MCP_TOOLS")} MCP tool gating: all / low-level / agentic / channel
63619
63719
  ${blue("CLAUDISH_MODEL_OPUS")} Override model for Opus role
63620
63720
  ${blue("CLAUDISH_MODEL_SONNET")} Override model for Sonnet role
@@ -64740,7 +64840,7 @@ function writeProbeModelsCache(data, path2 = PROBE_MODELS_CACHE_PATH) {
64740
64840
  mkdirSync14(dirname7(path2), { recursive: true });
64741
64841
  writeFileSync16(path2, JSON.stringify(data), "utf-8");
64742
64842
  }
64743
- function isCacheFresh(data, ttlMs = CACHE_TTL_MS3) {
64843
+ function isCacheFresh(data, ttlMs = CACHE_TTL_MS4) {
64744
64844
  if (!data?.generatedAt)
64745
64845
  return false;
64746
64846
  const generatedMs = Date.parse(data.generatedAt);
@@ -64748,7 +64848,7 @@ function isCacheFresh(data, ttlMs = CACHE_TTL_MS3) {
64748
64848
  return false;
64749
64849
  return Date.now() - generatedMs < ttlMs;
64750
64850
  }
64751
- async function fetchProbeModels(url2 = PROBE_MODELS_URL, timeoutMs = FETCH_TIMEOUT_MS2) {
64851
+ async function fetchProbeModels(url2 = PROBE_MODELS_URL, timeoutMs = FETCH_TIMEOUT_MS3) {
64752
64852
  let response;
64753
64853
  try {
64754
64854
  response = await fetch(url2, { signal: AbortSignal.timeout(timeoutMs) });
@@ -64854,9 +64954,9 @@ function isValidResponse(raw2) {
64854
64954
  return false;
64855
64955
  return true;
64856
64956
  }
64857
- var PROBE_MODELS_URL = "https://us-central1-claudish-6da10.cloudfunctions.net/probeModels", CACHE_TTL_MS3, FETCH_TIMEOUT_MS2 = 15000, PROBE_MODELS_CACHE_PATH, _inFlight = null;
64957
+ var PROBE_MODELS_URL = "https://us-central1-claudish-6da10.cloudfunctions.net/probeModels", CACHE_TTL_MS4, FETCH_TIMEOUT_MS3 = 15000, PROBE_MODELS_CACHE_PATH, _inFlight = null;
64858
64958
  var init_probe_catalog = __esm(() => {
64859
- CACHE_TTL_MS3 = 60 * 60 * 1000;
64959
+ CACHE_TTL_MS4 = 60 * 60 * 1000;
64860
64960
  PROBE_MODELS_CACHE_PATH = join24(homedir23(), ".claudish", "probe-models.json");
64861
64961
  });
64862
64962
 
@@ -71184,8 +71284,7 @@ __export(exports_claude_runner, {
71184
71284
  isProxyAuthMode: () => isProxyAuthMode,
71185
71285
  computeMainThreadContextWindow: () => computeMainThreadContextWindow,
71186
71286
  checkClaudeInstalled: () => checkClaudeInstalled,
71187
- buildClaudishSettingsOverlay: () => buildClaudishSettingsOverlay,
71188
- MIN_AUTO_COMPACT_WINDOW: () => MIN_AUTO_COMPACT_WINDOW
71287
+ buildClaudishSettingsOverlay: () => buildClaudishSettingsOverlay
71189
71288
  });
71190
71289
  import { spawn as spawn4 } from "child_process";
71191
71290
  import {
@@ -71216,18 +71315,6 @@ function hasNativeAnthropicMapping(config3) {
71216
71315
  ];
71217
71316
  return models.some((m) => m && parseModelSpec(m).provider === "native-anthropic");
71218
71317
  }
71219
- function wantsAnthropicApiBilling(config3) {
71220
- if (config3.anthropicApiBilling)
71221
- return true;
71222
- const raw2 = process.env[ENV.CLAUDISH_ANTHROPIC_API_BILLING];
71223
- if (raw2 !== undefined && raw2 !== "" && raw2 !== "0" && raw2.toLowerCase() !== "false")
71224
- return true;
71225
- try {
71226
- return loadConfig().anthropicApiBilling === true;
71227
- } catch {
71228
- return false;
71229
- }
71230
- }
71231
71318
  function isProxyAuthMode(config3) {
71232
71319
  return !config3.monitor && !hasNativeAnthropicMapping(config3);
71233
71320
  }
@@ -71435,7 +71522,7 @@ async function computeMainThreadContextWindow(config3, cachePath) {
71435
71522
  continue;
71436
71523
  provider = plan.primary.provider;
71437
71524
  }
71438
- const win = lookupModelForProvider(parsed.model, provider, cachePath);
71525
+ const win = await discoverContextWindow(provider, parsed.model) ?? lookupModelForProvider(parsed.model, provider, cachePath);
71439
71526
  if (typeof win === "number" && win > 0) {
71440
71527
  min = Math.min(min, win);
71441
71528
  }
@@ -71491,7 +71578,6 @@ async function runClaudeWithProxy(config3, proxyUrl, onCleanup) {
71491
71578
  [ENV.CLAUDISH_ACTIVE_MODEL_NAME]: modelDisplayName,
71492
71579
  CLAUDISH_IS_LOCAL: isLocalModel2 ? "true" : "false"
71493
71580
  };
71494
- let hidAnthropicApiKey = false;
71495
71581
  delete env.CLAUDECODE;
71496
71582
  if (config3.monitor) {
71497
71583
  delete env.ANTHROPIC_API_KEY;
@@ -71505,23 +71591,16 @@ async function runClaudeWithProxy(config3, proxyUrl, onCleanup) {
71505
71591
  env[ENV.ANTHROPIC_MODEL] = modelId;
71506
71592
  env[ENV.ANTHROPIC_SMALL_FAST_MODEL] = modelId;
71507
71593
  }
71508
- if (hasNativeAnthropicMapping(config3)) {
71509
- if (process.env.ANTHROPIC_API_KEY && !wantsAnthropicApiBilling(config3)) {
71510
- delete env.ANTHROPIC_API_KEY;
71511
- hidAnthropicApiKey = true;
71512
- }
71513
- } else {
71514
- env.ANTHROPIC_API_KEY = "sk-ant-api03-placeholder-not-used-proxy-handles-auth-with-openrouter-key-xxxxxxxxxxxxxxxxxxxxx";
71515
- env.ANTHROPIC_AUTH_TOKEN = "placeholder-token-not-used-proxy-handles-auth";
71594
+ if (hasNativeAnthropicMapping(config3)) {} else {
71595
+ env.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY || "sk-ant-api03-placeholder-not-used-proxy-handles-auth-with-openrouter-key-xxxxxxxxxxxxxxxxxxxxx";
71596
+ env.ANTHROPIC_AUTH_TOKEN = process.env.ANTHROPIC_AUTH_TOKEN || "placeholder-token-not-used-proxy-handles-auth";
71516
71597
  if (!process.env[ENV.CLAUDE_CODE_AUTO_COMPACT_WINDOW]) {
71517
71598
  const autoCompactWindow = await computeMainThreadContextWindow(config3);
71518
- if (autoCompactWindow >= MIN_AUTO_COMPACT_WINDOW) {
71599
+ if (autoCompactWindow > 0) {
71519
71600
  env[ENV.CLAUDE_CODE_AUTO_COMPACT_WINDOW] = String(autoCompactWindow);
71520
71601
  if (!config3.quiet) {
71521
71602
  console.error(`[claudish] Auto-compact window: ${autoCompactWindow.toLocaleString()} tokens ` + "(Claude Code compacts before the backend's real limit)");
71522
71603
  }
71523
- } else if (autoCompactWindow > 0 && !config3.quiet) {
71524
- console.error(`[claudish] Model's real context window (${autoCompactWindow.toLocaleString()}) is below ` + `Claude Code's ${MIN_AUTO_COMPACT_WINDOW.toLocaleString()}-token auto-compact floor \u2014 ` + "leaving CLAUDE_CODE_AUTO_COMPACT_WINDOW unset so native auto-compaction stays on.");
71525
71604
  }
71526
71605
  }
71527
71606
  }
@@ -71537,9 +71616,6 @@ async function runClaudeWithProxy(config3, proxyUrl, onCleanup) {
71537
71616
  };
71538
71617
  if (!config3.monitor && hasNativeAnthropicMapping(config3)) {
71539
71618
  log2("[claudish] Native Claude model detected \u2014 using Claude Code subscription credentials");
71540
- if (hidAnthropicApiKey) {
71541
- log2("[claudish] ANTHROPIC_API_KEY found but hidden so it can't override that subscription \xB7 " + "use --anthropic-api-billing (or anthropicApiBilling: true) to bill the API instead");
71542
- }
71543
71619
  }
71544
71620
  if (config3.interactive) {
71545
71621
  log2(`
@@ -71706,12 +71782,12 @@ async function checkClaudeInstalled() {
71706
71782
  const binary = await findClaudeBinary();
71707
71783
  return binary !== null;
71708
71784
  }
71709
- var restoreTerminal = null, MIN_AUTO_COMPACT_WINDOW = 200000;
71785
+ var restoreTerminal = null;
71710
71786
  var init_claude_runner = __esm(() => {
71711
71787
  init_model_catalog();
71712
71788
  init_config();
71713
71789
  init_logger();
71714
- init_profile_config();
71790
+ init_model_discovery();
71715
71791
  init_model_parser();
71716
71792
  init_routing_rules();
71717
71793
  init_telemetry();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "7.19.2",
3
+ "version": "7.20.0",
4
4
  "description": "Run Claude Code with any model - OpenRouter, Ollama, LM Studio & local models",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -60,10 +60,10 @@
60
60
  "ai"
61
61
  ],
62
62
  "optionalDependencies": {
63
- "@claudish/magmux-darwin-arm64": "7.19.2",
64
- "@claudish/magmux-darwin-x64": "7.19.2",
65
- "@claudish/magmux-linux-arm64": "7.19.2",
66
- "@claudish/magmux-linux-x64": "7.19.2"
63
+ "@claudish/magmux-darwin-arm64": "7.20.0",
64
+ "@claudish/magmux-darwin-x64": "7.20.0",
65
+ "@claudish/magmux-linux-arm64": "7.20.0",
66
+ "@claudish/magmux-linux-x64": "7.20.0"
67
67
  },
68
68
  "author": "Jack Rudenko <i@madappgang.com>",
69
69
  "license": "MIT",