claudish 7.19.1 → 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 -107
  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.1";
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" });
@@ -38272,7 +38268,6 @@ class TokenTracker {
38272
38268
  sessionTotalCost = 0;
38273
38269
  sessionInputTokens = 0;
38274
38270
  sessionOutputTokens = 0;
38275
- lastInputTokens = 0;
38276
38271
  modelNameOverride;
38277
38272
  quotaRemaining;
38278
38273
  constructor(port, config2) {
@@ -38289,11 +38284,10 @@ class TokenTracker {
38289
38284
  this.quotaRemaining = fraction;
38290
38285
  }
38291
38286
  rewrite() {
38292
- this.writeFile(this.getLastInputTokens(), this.sessionOutputTokens);
38287
+ this.writeFile(this.sessionInputTokens, this.sessionOutputTokens);
38293
38288
  }
38294
38289
  update(inputTokens, outputTokens) {
38295
38290
  this.sessionInputTokens = inputTokens;
38296
- this.lastInputTokens = inputTokens;
38297
38291
  this.sessionOutputTokens += outputTokens;
38298
38292
  const pricing = this.getPricing();
38299
38293
  const cost = inputTokens / 1e6 * pricing.inputCostPer1M + outputTokens / 1e6 * pricing.outputCostPer1M;
@@ -38302,7 +38296,6 @@ class TokenTracker {
38302
38296
  }
38303
38297
  accumulateBoth(inputTokens, outputTokens) {
38304
38298
  this.sessionInputTokens += inputTokens;
38305
- this.lastInputTokens = this.sessionInputTokens;
38306
38299
  this.sessionOutputTokens += outputTokens;
38307
38300
  const pricing = this.getPricing();
38308
38301
  const cost = this.sessionInputTokens / 1e6 * pricing.inputCostPer1M + this.sessionOutputTokens / 1e6 * pricing.outputCostPer1M;
@@ -38311,7 +38304,6 @@ class TokenTracker {
38311
38304
  }
38312
38305
  updateWithDelta(inputTokens, outputTokens) {
38313
38306
  let incrementalInputTokens;
38314
- this.lastInputTokens = inputTokens;
38315
38307
  if (inputTokens >= this.sessionInputTokens) {
38316
38308
  incrementalInputTokens = inputTokens - this.sessionInputTokens;
38317
38309
  this.sessionInputTokens = inputTokens;
@@ -38327,11 +38319,10 @@ class TokenTracker {
38327
38319
  const pricing = this.getPricing();
38328
38320
  const cost = incrementalInputTokens / 1e6 * pricing.inputCostPer1M + outputTokens / 1e6 * pricing.outputCostPer1M;
38329
38321
  this.sessionTotalCost += cost;
38330
- this.writeFile(inputTokens, this.sessionOutputTokens, pricing.isEstimate);
38322
+ this.writeFile(Math.max(inputTokens, this.sessionInputTokens), this.sessionOutputTokens, pricing.isEstimate);
38331
38323
  }
38332
38324
  updateWithActualCost(inputTokens, outputTokens, actualCost) {
38333
38325
  this.sessionInputTokens = inputTokens;
38334
- this.lastInputTokens = inputTokens;
38335
38326
  this.sessionOutputTokens += outputTokens;
38336
38327
  if (typeof actualCost === "number" && actualCost > 0) {
38337
38328
  this.sessionTotalCost += actualCost;
@@ -38347,7 +38338,6 @@ class TokenTracker {
38347
38338
  updateLocal(inputTokens, outputTokens) {
38348
38339
  if (inputTokens > 0) {
38349
38340
  this.sessionInputTokens = inputTokens;
38350
- this.lastInputTokens = inputTokens;
38351
38341
  }
38352
38342
  this.sessionOutputTokens += outputTokens;
38353
38343
  this.writeFile(this.sessionInputTokens, this.sessionOutputTokens);
@@ -38361,9 +38351,6 @@ class TokenTracker {
38361
38351
  getInputTokens() {
38362
38352
  return this.sessionInputTokens;
38363
38353
  }
38364
- getLastInputTokens() {
38365
- return this.lastInputTokens || this.sessionInputTokens;
38366
- }
38367
38354
  getOutputTokens() {
38368
38355
  return this.sessionOutputTokens;
38369
38356
  }
@@ -38905,18 +38892,16 @@ class ComposedHandler {
38905
38892
  }
38906
38893
  };
38907
38894
  const streamFormat = this.provider.overrideStreamFormat?.() ?? this.explicitAdapter?.getStreamFormat() ?? this.modelAdapter?.getStreamFormat() ?? this.getAdapter().getStreamFormat();
38908
- const priorInputTokens = this.tokenTracker.getLastInputTokens();
38909
38895
  switch (streamFormat) {
38910
38896
  case "openai-sse":
38911
- 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);
38912
38898
  case "openai-responses-sse":
38913
38899
  return createResponsesStreamHandler(c, response, {
38914
38900
  modelName: this.bareModelName,
38915
38901
  onTokenUpdate,
38916
38902
  toolNameMap: adapter.getToolNameMap(),
38917
38903
  contextWindow: lookupModelForProvider(this.bareModelName, this.provider.name),
38918
- onApiError,
38919
- priorInputTokens
38904
+ onApiError
38920
38905
  });
38921
38906
  case "anthropic-sse":
38922
38907
  return createAnthropicPassthroughStream(c, response, {
@@ -38936,15 +38921,13 @@ class ComposedHandler {
38936
38921
  middlewareManager: this.middlewareManager,
38937
38922
  onTokenUpdate,
38938
38923
  onToolCall,
38939
- unwrapResponse: this.options.unwrapGeminiResponse,
38940
- priorInputTokens
38924
+ unwrapResponse: this.options.unwrapGeminiResponse
38941
38925
  });
38942
38926
  }
38943
38927
  case "ollama-jsonl":
38944
38928
  return createOllamaJsonlStream(c, response, {
38945
38929
  modelName: this.bareModelName,
38946
- onTokenUpdate,
38947
- priorInputTokens
38930
+ onTokenUpdate
38948
38931
  });
38949
38932
  default:
38950
38933
  throw new Error(`Unknown stream format: ${streamFormat}`);
@@ -42584,7 +42567,8 @@ var init_default_routing_rules = __esm(() => {
42584
42567
  "o3-*": ["openai-codex", "openai", "openrouter"],
42585
42568
  "gemini-*": ["gemini-codeassist", "google", "openrouter"],
42586
42569
  "grok-*": ["x-ai", "openrouter"],
42587
- "kimi-*": ["kimi-coding@kimi-for-coding", "kimi", "openrouter"],
42570
+ "kimi-*": ["kimi-coding", "kimi", "openrouter"],
42571
+ "k3*": ["kimi-coding", "kimi", "openrouter"],
42588
42572
  "minimax-*": ["minimax-coding", "minimax", "openrouter"],
42589
42573
  "glm-*": ["glm-coding", "glm", "openrouter"],
42590
42574
  "z-ai-*": ["z-ai", "openrouter"],
@@ -42638,7 +42622,7 @@ function matchRoutingRule(modelName, rules) {
42638
42622
  return rules["*"];
42639
42623
  return null;
42640
42624
  }
42641
- function buildRoutingChain(entries, originalModelName) {
42625
+ function buildRoutingChain(entries, originalModelName, cachePath) {
42642
42626
  const routes = [];
42643
42627
  for (const entry of entries) {
42644
42628
  const atIdx = entry.indexOf("@");
@@ -42652,6 +42636,13 @@ function buildRoutingChain(entries, originalModelName) {
42652
42636
  modelName = originalModelName;
42653
42637
  }
42654
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
+ }
42655
42646
  let modelSpec;
42656
42647
  if (provider === "openrouter") {
42657
42648
  const resolution = resolveModelNameSync(modelName, "openrouter");
@@ -42678,7 +42669,7 @@ function globMatch(pattern, value) {
42678
42669
  async function hasCredentialsForProvider(provider) {
42679
42670
  return credentials.isAvailable(provider);
42680
42671
  }
42681
- async function routeExplicit(modelSpec, model, provider) {
42672
+ async function routeExplicit(modelSpec, model, provider, cachePath) {
42682
42673
  if (!await hasCredentialsForProvider(provider)) {
42683
42674
  return {
42684
42675
  kind: "no-route",
@@ -42686,7 +42677,7 @@ async function routeExplicit(modelSpec, model, provider) {
42686
42677
  hint: buildCredentialHint(model, [provider]) ?? undefined
42687
42678
  };
42688
42679
  }
42689
- const built = buildRoutingChain([modelSpec], model)[0];
42680
+ const built = buildRoutingChain([modelSpec], model, cachePath)[0];
42690
42681
  if (!built) {
42691
42682
  return {
42692
42683
  kind: "no-route",
@@ -42695,7 +42686,7 @@ async function routeExplicit(modelSpec, model, provider) {
42695
42686
  }
42696
42687
  return { kind: "ok", primary: built, fallbacks: [] };
42697
42688
  }
42698
- async function routeBare(model, nativeProvider, rules, defaultProvider) {
42689
+ async function routeBare(model, nativeProvider, rules, defaultProvider, cachePath) {
42699
42690
  const matched = matchRoutingRule(model, rules) ?? [];
42700
42691
  const entries = [...matched];
42701
42692
  if (defaultProvider && defaultProvider.length > 0) {
@@ -42716,7 +42707,7 @@ async function routeBare(model, nativeProvider, rules, defaultProvider) {
42716
42707
  hint: buildCredentialHint(model, [nativeProvider]) ?? undefined
42717
42708
  };
42718
42709
  }
42719
- const candidates = buildRoutingChain(entries, model);
42710
+ const candidates = buildRoutingChain(entries, model, cachePath);
42720
42711
  const credentialed = [];
42721
42712
  const skipped = [];
42722
42713
  const checks4 = await Promise.all(candidates.map((candidate) => hasCredentialsForProvider(candidate.provider)));
@@ -42737,16 +42728,17 @@ async function routeBare(model, nativeProvider, rules, defaultProvider) {
42737
42728
  const [primary, ...fallbacks] = credentialed;
42738
42729
  return { kind: "ok", primary, fallbacks };
42739
42730
  }
42740
- async function route(modelSpec, rulesOverride, defaultProviderOverride) {
42731
+ async function route(modelSpec, rulesOverride, defaultProviderOverride, cachePath) {
42741
42732
  const parsed = parseModelSpec(modelSpec);
42742
42733
  if (parsed.isExplicitProvider) {
42743
- return routeExplicit(modelSpec, parsed.model, parsed.provider);
42734
+ return routeExplicit(modelSpec, parsed.model, parsed.provider, cachePath);
42744
42735
  }
42745
42736
  const rules = rulesOverride ?? loadRoutingRules();
42746
42737
  const defaultProvider = defaultProviderOverride !== undefined ? defaultProviderOverride : rulesOverride !== undefined ? undefined : loadConfig().defaultProvider;
42747
- return routeBare(parsed.model, parsed.provider, rules, defaultProvider);
42738
+ return routeBare(parsed.model, parsed.provider, rules, defaultProvider, cachePath);
42748
42739
  }
42749
42740
  var init_routing_rules = __esm(() => {
42741
+ init_model_catalog();
42750
42742
  init_authority();
42751
42743
  init_profile_config();
42752
42744
  init_auto_route();
@@ -58419,8 +58411,7 @@ var init_config = __esm(() => {
58419
58411
  OPENAI_BASE_URL: "OPENAI_BASE_URL",
58420
58412
  CLAUDISH_SUMMARIZE_TOOLS: "CLAUDISH_SUMMARIZE_TOOLS",
58421
58413
  CLAUDISH_DIAG_MODE: "CLAUDISH_DIAG_MODE",
58422
- CLAUDISH_DEBUG: "CLAUDISH_DEBUG",
58423
- CLAUDISH_ANTHROPIC_API_BILLING: "CLAUDISH_ANTHROPIC_API_BILLING"
58414
+ CLAUDISH_DEBUG: "CLAUDISH_DEBUG"
58424
58415
  };
58425
58416
  OPENROUTER_HEADERS = {
58426
58417
  "HTTP-Referer": "https://claudish.com",
@@ -58577,6 +58568,114 @@ var init_model_catalog2 = __esm(() => {
58577
58568
  NO_CATALOG_VENDOR_SLUGS = new Set(["litellm", "ollama", "lmstudio", "lm-studio"]);
58578
58569
  });
58579
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
+
58580
58679
  // src/providers/ollama-discovery.ts
58581
58680
  function ollamaBaseUrl() {
58582
58681
  return process.env.OLLAMA_HOST || process.env.OLLAMA_BASE_URL || "http://localhost:11434";
@@ -59146,8 +59245,22 @@ async function selectModelFromProvider(provider, tierName, recommendedModels, _f
59146
59245
  const prefix = PROVIDER_MODEL_PREFIX[provider] || `${provider}@`;
59147
59246
  const displayName = getPickerDisplayName(provider);
59148
59247
  const def = getProviderByName(provider);
59149
- if (def?.fixedModel) {
59150
- 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
+ }
59151
59264
  }
59152
59265
  if (provider === "ollama") {
59153
59266
  const ollamaModels = await fetchOllamaModels2({ enrichCapabilities: false });
@@ -59326,6 +59439,7 @@ var init_model_selector = __esm(() => {
59326
59439
  init_authority();
59327
59440
  init_model_loader();
59328
59441
  init_model_catalog2();
59442
+ init_model_discovery();
59329
59443
  init_provider_definitions();
59330
59444
  pickerProviderToFirebaseSlug = {
59331
59445
  openrouter: "openrouter",
@@ -62377,8 +62491,6 @@ async function parseArgs(args) {
62377
62491
  process.exit(1);
62378
62492
  }
62379
62493
  config3.defaultProvider = dpArg;
62380
- } else if (arg === "--anthropic-api-billing") {
62381
- config3.anthropicApiBilling = true;
62382
62494
  } else if (arg === "--op-env" || arg.startsWith("--op-env=")) {
62383
62495
  const v = arg.startsWith("--op-env=") ? arg.slice("--op-env=".length) : args[++i];
62384
62496
  if (!v) {
@@ -63428,10 +63540,6 @@ ${h("OPTIONS")}
63428
63540
  ${green("--profile")} ${yellow("<name>")} Use named profile for model mapping (default profile if omitted)
63429
63541
  ${green("--default-provider")} ${yellow("<name>")} Fallback provider for bare model names (builtin or customEndpoints key)
63430
63542
  ${dim("Precedence: this flag > CLAUDISH_DEFAULT_PROVIDER env > config.json")}
63431
- ${green("--anthropic-api-billing")} Use your real ANTHROPIC_API_KEY for native Claude models
63432
- ${dim("(metered API billing). Default: the key is hidden so Claude Code")}
63433
- ${dim("uses your claude.ai subscription. Env: CLAUDISH_ANTHROPIC_API_BILLING")}
63434
- ${dim("Config: anthropicApiBilling: true")}
63435
63543
  ${green("--config")} ${yellow("<file>")} Use THIS config file for the run, fully replacing the machine
63436
63544
  ${dim("global (~/.claudish/config.json) AND project (.claudish.json).")}
63437
63545
  ${dim("A file naming no op:// source never touches 1Password (no prompt).")}
@@ -63607,7 +63715,6 @@ ${h("ENVIRONMENT VARIABLES")}
63607
63715
  ${blue("CLAUDISH_CONTEXT_WINDOW")} Override context window size
63608
63716
  ${blue("CLAUDISH_DIAG_MODE")} Diagnostic output: auto / logfile / off
63609
63717
  ${blue("CLAUDISH_DEBUG")} Always enable debug logging: 1 / true ${dim("(same as -d)")}
63610
- ${blue("CLAUDISH_ANTHROPIC_API_BILLING")} Bill native Claude to your API key ${dim("(see --anthropic-api-billing)")}
63611
63718
  ${blue("CLAUDISH_MCP_TOOLS")} MCP tool gating: all / low-level / agentic / channel
63612
63719
  ${blue("CLAUDISH_MODEL_OPUS")} Override model for Opus role
63613
63720
  ${blue("CLAUDISH_MODEL_SONNET")} Override model for Sonnet role
@@ -64733,7 +64840,7 @@ function writeProbeModelsCache(data, path2 = PROBE_MODELS_CACHE_PATH) {
64733
64840
  mkdirSync14(dirname7(path2), { recursive: true });
64734
64841
  writeFileSync16(path2, JSON.stringify(data), "utf-8");
64735
64842
  }
64736
- function isCacheFresh(data, ttlMs = CACHE_TTL_MS3) {
64843
+ function isCacheFresh(data, ttlMs = CACHE_TTL_MS4) {
64737
64844
  if (!data?.generatedAt)
64738
64845
  return false;
64739
64846
  const generatedMs = Date.parse(data.generatedAt);
@@ -64741,7 +64848,7 @@ function isCacheFresh(data, ttlMs = CACHE_TTL_MS3) {
64741
64848
  return false;
64742
64849
  return Date.now() - generatedMs < ttlMs;
64743
64850
  }
64744
- async function fetchProbeModels(url2 = PROBE_MODELS_URL, timeoutMs = FETCH_TIMEOUT_MS2) {
64851
+ async function fetchProbeModels(url2 = PROBE_MODELS_URL, timeoutMs = FETCH_TIMEOUT_MS3) {
64745
64852
  let response;
64746
64853
  try {
64747
64854
  response = await fetch(url2, { signal: AbortSignal.timeout(timeoutMs) });
@@ -64847,9 +64954,9 @@ function isValidResponse(raw2) {
64847
64954
  return false;
64848
64955
  return true;
64849
64956
  }
64850
- 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;
64851
64958
  var init_probe_catalog = __esm(() => {
64852
- CACHE_TTL_MS3 = 60 * 60 * 1000;
64959
+ CACHE_TTL_MS4 = 60 * 60 * 1000;
64853
64960
  PROBE_MODELS_CACHE_PATH = join24(homedir23(), ".claudish", "probe-models.json");
64854
64961
  });
64855
64962
 
@@ -71177,8 +71284,7 @@ __export(exports_claude_runner, {
71177
71284
  isProxyAuthMode: () => isProxyAuthMode,
71178
71285
  computeMainThreadContextWindow: () => computeMainThreadContextWindow,
71179
71286
  checkClaudeInstalled: () => checkClaudeInstalled,
71180
- buildClaudishSettingsOverlay: () => buildClaudishSettingsOverlay,
71181
- MIN_AUTO_COMPACT_WINDOW: () => MIN_AUTO_COMPACT_WINDOW
71287
+ buildClaudishSettingsOverlay: () => buildClaudishSettingsOverlay
71182
71288
  });
71183
71289
  import { spawn as spawn4 } from "child_process";
71184
71290
  import {
@@ -71209,18 +71315,6 @@ function hasNativeAnthropicMapping(config3) {
71209
71315
  ];
71210
71316
  return models.some((m) => m && parseModelSpec(m).provider === "native-anthropic");
71211
71317
  }
71212
- function wantsAnthropicApiBilling(config3) {
71213
- if (config3.anthropicApiBilling)
71214
- return true;
71215
- const raw2 = process.env[ENV.CLAUDISH_ANTHROPIC_API_BILLING];
71216
- if (raw2 !== undefined && raw2 !== "" && raw2 !== "0" && raw2.toLowerCase() !== "false")
71217
- return true;
71218
- try {
71219
- return loadConfig().anthropicApiBilling === true;
71220
- } catch {
71221
- return false;
71222
- }
71223
- }
71224
71318
  function isProxyAuthMode(config3) {
71225
71319
  return !config3.monitor && !hasNativeAnthropicMapping(config3);
71226
71320
  }
@@ -71428,7 +71522,7 @@ async function computeMainThreadContextWindow(config3, cachePath) {
71428
71522
  continue;
71429
71523
  provider = plan.primary.provider;
71430
71524
  }
71431
- const win = lookupModelForProvider(parsed.model, provider, cachePath);
71525
+ const win = await discoverContextWindow(provider, parsed.model) ?? lookupModelForProvider(parsed.model, provider, cachePath);
71432
71526
  if (typeof win === "number" && win > 0) {
71433
71527
  min = Math.min(min, win);
71434
71528
  }
@@ -71484,7 +71578,6 @@ async function runClaudeWithProxy(config3, proxyUrl, onCleanup) {
71484
71578
  [ENV.CLAUDISH_ACTIVE_MODEL_NAME]: modelDisplayName,
71485
71579
  CLAUDISH_IS_LOCAL: isLocalModel2 ? "true" : "false"
71486
71580
  };
71487
- let hidAnthropicApiKey = false;
71488
71581
  delete env.CLAUDECODE;
71489
71582
  if (config3.monitor) {
71490
71583
  delete env.ANTHROPIC_API_KEY;
@@ -71498,23 +71591,16 @@ async function runClaudeWithProxy(config3, proxyUrl, onCleanup) {
71498
71591
  env[ENV.ANTHROPIC_MODEL] = modelId;
71499
71592
  env[ENV.ANTHROPIC_SMALL_FAST_MODEL] = modelId;
71500
71593
  }
71501
- if (hasNativeAnthropicMapping(config3)) {
71502
- if (process.env.ANTHROPIC_API_KEY && !wantsAnthropicApiBilling(config3)) {
71503
- delete env.ANTHROPIC_API_KEY;
71504
- hidAnthropicApiKey = true;
71505
- }
71506
- } else {
71507
- env.ANTHROPIC_API_KEY = "sk-ant-api03-placeholder-not-used-proxy-handles-auth-with-openrouter-key-xxxxxxxxxxxxxxxxxxxxx";
71508
- 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";
71509
71597
  if (!process.env[ENV.CLAUDE_CODE_AUTO_COMPACT_WINDOW]) {
71510
71598
  const autoCompactWindow = await computeMainThreadContextWindow(config3);
71511
- if (autoCompactWindow >= MIN_AUTO_COMPACT_WINDOW) {
71599
+ if (autoCompactWindow > 0) {
71512
71600
  env[ENV.CLAUDE_CODE_AUTO_COMPACT_WINDOW] = String(autoCompactWindow);
71513
71601
  if (!config3.quiet) {
71514
71602
  console.error(`[claudish] Auto-compact window: ${autoCompactWindow.toLocaleString()} tokens ` + "(Claude Code compacts before the backend's real limit)");
71515
71603
  }
71516
- } else if (autoCompactWindow > 0 && !config3.quiet) {
71517
- 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.");
71518
71604
  }
71519
71605
  }
71520
71606
  }
@@ -71530,9 +71616,6 @@ async function runClaudeWithProxy(config3, proxyUrl, onCleanup) {
71530
71616
  };
71531
71617
  if (!config3.monitor && hasNativeAnthropicMapping(config3)) {
71532
71618
  log2("[claudish] Native Claude model detected \u2014 using Claude Code subscription credentials");
71533
- if (hidAnthropicApiKey) {
71534
- 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");
71535
- }
71536
71619
  }
71537
71620
  if (config3.interactive) {
71538
71621
  log2(`
@@ -71699,12 +71782,12 @@ async function checkClaudeInstalled() {
71699
71782
  const binary = await findClaudeBinary();
71700
71783
  return binary !== null;
71701
71784
  }
71702
- var restoreTerminal = null, MIN_AUTO_COMPACT_WINDOW = 200000;
71785
+ var restoreTerminal = null;
71703
71786
  var init_claude_runner = __esm(() => {
71704
71787
  init_model_catalog();
71705
71788
  init_config();
71706
71789
  init_logger();
71707
- init_profile_config();
71790
+ init_model_discovery();
71708
71791
  init_model_parser();
71709
71792
  init_routing_rules();
71710
71793
  init_telemetry();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "7.19.1",
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.1",
64
- "@claudish/magmux-darwin-x64": "7.19.1",
65
- "@claudish/magmux-linux-arm64": "7.19.1",
66
- "@claudish/magmux-linux-x64": "7.19.1"
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",