claudish 7.56.0 → 7.58.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 +694 -406
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -729,7 +729,7 @@ var init_onepassword_config = __esm(() => {
729
729
  });
730
730
 
731
731
  // src/version.ts
732
- var VERSION = "7.56.0";
732
+ var VERSION = "7.58.0";
733
733
 
734
734
  // src/logger.ts
735
735
  var exports_logger = {};
@@ -28077,6 +28077,7 @@ var init_provider_definitions = __esm(() => {
28077
28077
  { prefix: "dv/", stripPrefix: true },
28078
28078
  { prefix: "devin/", stripPrefix: true }
28079
28079
  ],
28080
+ nativeModelPatterns: [{ pattern: /^swe-/i }],
28080
28081
  modelDiscovery: { path: "", format: "devin-connect" },
28081
28082
  isDirectApi: true,
28082
28083
  description: "Devin subscription (dv@, devin@)"
@@ -28214,6 +28215,7 @@ var init_provider_definitions = __esm(() => {
28214
28215
  baseUrl: "https://api.minimax.io",
28215
28216
  baseUrlEnvVars: ["MINIMAX_CODING_BASE_URL"],
28216
28217
  apiPath: "/anthropic/v1/messages",
28218
+ modelDiscovery: { path: "/v1/models", format: "openai-models-list" },
28217
28219
  apiKeyEnvVar: "MINIMAX_CODING_API_KEY",
28218
28220
  apiKeyDescription: "MiniMax Coding Plan API Key",
28219
28221
  apiKeyUrl: "https://platform.minimax.io/user-center/basic-information/interface-key",
@@ -28250,6 +28252,7 @@ var init_provider_definitions = __esm(() => {
28250
28252
  baseUrl: "https://api.moonshot.ai",
28251
28253
  baseUrlEnvVars: ["MOONSHOT_BASE_URL", "KIMI_BASE_URL"],
28252
28254
  apiPath: "/anthropic/v1/messages",
28255
+ modelDiscovery: { path: "/v1/models", format: "openai-models-list" },
28253
28256
  apiKeyEnvVar: "MOONSHOT_API_KEY",
28254
28257
  apiKeyAliases: ["KIMI_API_KEY"],
28255
28258
  apiKeyDescription: "Kimi/Moonshot API Key",
@@ -28301,6 +28304,7 @@ var init_provider_definitions = __esm(() => {
28301
28304
  tokenStrategy: "delta-aware",
28302
28305
  baseUrl: "https://api.z.ai",
28303
28306
  apiPath: "/api/coding/paas/v4/chat/completions",
28307
+ modelDiscovery: { path: "/api/coding/paas/v4/models", format: "openai-models-list" },
28304
28308
  apiKeyEnvVar: "GLM_CODING_API_KEY",
28305
28309
  apiKeyAliases: ["ZAI_CODING_API_KEY"],
28306
28310
  apiKeyDescription: "GLM Coding Plan API Key",
@@ -28377,6 +28381,7 @@ var init_provider_definitions = __esm(() => {
28377
28381
  baseUrl: "https://opencode.ai/zen/go",
28378
28382
  baseUrlEnvVars: ["OPENCODE_GO_BASE_URL"],
28379
28383
  apiPath: "/v1/chat/completions",
28384
+ modelDiscovery: { path: "/v1/models", format: "openai-models-list" },
28380
28385
  apiKeyEnvVar: "OPENCODE_GO_API_KEY",
28381
28386
  apiKeyAliases: ["OPENCODE_API_KEY"],
28382
28387
  apiKeyDescription: "OpenCode Zen Go (Lite Plan) API Key",
@@ -28594,6 +28599,7 @@ var init_provider_definitions = __esm(() => {
28594
28599
  baseUrl: "https://api.sakana.ai",
28595
28600
  baseUrlEnvVars: ["SAKANA_BASE_URL"],
28596
28601
  apiPath: "/v1/chat/completions",
28602
+ modelDiscovery: { path: "/v1/models", format: "openai-models-list" },
28597
28603
  apiKeyEnvVar: "SAKANA_SUBSCRIPTION_API_KEY",
28598
28604
  apiKeyAliases: ["SAKANA_CODING_API_KEY"],
28599
28605
  apiKeyDescription: "Sakana Fugu Subscription API Key",
@@ -28741,6 +28747,8 @@ var init_all_models_cache = __esm(() => {
28741
28747
 
28742
28748
  // src/providers/catalog-client.ts
28743
28749
  function getCatalogEntries() {
28750
+ if (_catalogEntriesForTest !== undefined)
28751
+ return _catalogEntriesForTest;
28744
28752
  if (_memCache)
28745
28753
  return _memCache;
28746
28754
  const cache = readAllModelsCache();
@@ -28760,6 +28768,26 @@ function getCatalogEntries() {
28760
28768
  }
28761
28769
  return null;
28762
28770
  }
28771
+ function latestAnthropicTierModelId(tier) {
28772
+ const entries = getCatalogEntries();
28773
+ if (!entries)
28774
+ return null;
28775
+ const family = new RegExp(`^claude-${tier}-`, "i");
28776
+ const opus = entries.filter((e) => family.test(e.modelId));
28777
+ if (opus.length === 0)
28778
+ return null;
28779
+ opus.sort((a, b) => {
28780
+ const byDate = (b.releaseDate ?? "").localeCompare(a.releaseDate ?? "");
28781
+ if (byDate !== 0)
28782
+ return byDate;
28783
+ const aFast = /-fast$/i.test(a.modelId) ? 1 : 0;
28784
+ const bFast = /-fast$/i.test(b.modelId) ? 1 : 0;
28785
+ if (aFast !== bFast)
28786
+ return aFast - bFast;
28787
+ return b.modelId.localeCompare(a.modelId);
28788
+ });
28789
+ return opus[0].modelId;
28790
+ }
28763
28791
  function isCatalogWarm() {
28764
28792
  return _memCache !== null && _memCache.length > 0;
28765
28793
  }
@@ -28902,7 +28930,7 @@ async function ensureCatalogReady(timeoutMs = 5000) {
28902
28930
  new Promise((resolve) => setTimeout(resolve, timeoutMs))
28903
28931
  ]);
28904
28932
  }
28905
- var FIREBASE_CATALOG_URL, _memCache = null, _warmPromise = null;
28933
+ var FIREBASE_CATALOG_URL, _memCache = null, _catalogEntriesForTest, _warmPromise = null;
28906
28934
  var init_catalog_client = __esm(() => {
28907
28935
  init_all_models_cache();
28908
28936
  FIREBASE_CATALOG_URL = process.env.CLAUDISH_CATALOG_URL ?? process.env.FIREBASE_CATALOG_URL ?? "https://us-central1-claudish-6da10.cloudfunctions.net/queryModels?status=active&catalog=slim&limit=1000";
@@ -39714,6 +39742,24 @@ var init_devin_stream_head_sniffer = __esm(() => {
39714
39742
  QUOTA_MESSAGE_RE = /quota|out of credits|credit balance|billing|plan limit|exceeded your/i;
39715
39743
  });
39716
39744
 
39745
+ // src/handlers/shared/model-unsupported.ts
39746
+ function hasModelUnsupportedWording(errorBody) {
39747
+ const lower = (errorBody || "").toLowerCase();
39748
+ return UNSUPPORTED_PHRASES.some((phrase) => lower.includes(phrase));
39749
+ }
39750
+ var UNSUPPORTED_PHRASES;
39751
+ var init_model_unsupported = __esm(() => {
39752
+ UNSUPPORTED_PHRASES = [
39753
+ "not supported",
39754
+ "unsupported model",
39755
+ "unsupported_model",
39756
+ "model not found",
39757
+ "model_not_found",
39758
+ "unknown model",
39759
+ "no such model"
39760
+ ];
39761
+ });
39762
+
39717
39763
  // src/handlers/shared/stream-head-sniffer.ts
39718
39764
  function isRetryableStreamError(code, type, message) {
39719
39765
  if (RETRYABLE_ERROR_CODES.has(code))
@@ -41668,6 +41714,33 @@ var init_token_tracker = __esm(() => {
41668
41714
  init_remote_provider_types();
41669
41715
  });
41670
41716
 
41717
+ // src/handlers/shared/upstream-error-capture.ts
41718
+ import { appendFileSync as appendFileSync4 } from "fs";
41719
+ function captureUpstreamError(record4) {
41720
+ const path = process.env[UPSTREAM_ERROR_LOG_ENV];
41721
+ if (!path)
41722
+ return false;
41723
+ try {
41724
+ const raw = record4.body ?? "";
41725
+ const truncated = raw.length > MAX_CAPTURED_BODY_BYTES;
41726
+ const line = JSON.stringify({
41727
+ at: record4.at ?? new Date().toISOString(),
41728
+ provider: record4.provider,
41729
+ model: record4.model,
41730
+ status: record4.status,
41731
+ body: truncated ? raw.slice(0, MAX_CAPTURED_BODY_BYTES) : raw,
41732
+ ...truncated ? { truncated: true, original_bytes: raw.length } : {}
41733
+ });
41734
+ appendFileSync4(path, `${line}
41735
+ `);
41736
+ return true;
41737
+ } catch {
41738
+ return false;
41739
+ }
41740
+ }
41741
+ var UPSTREAM_ERROR_LOG_ENV = "CLAUDISH_UPSTREAM_ERROR_LOG", MAX_CAPTURED_BODY_BYTES = 2048;
41742
+ var init_upstream_error_capture = () => {};
41743
+
41671
41744
  // src/handlers/composed-handler.ts
41672
41745
  function extractAuthHeaders(c) {
41673
41746
  const headers = c.req.header();
@@ -42067,6 +42140,12 @@ class ComposedHandler {
42067
42140
  } else {
42068
42141
  const errorText = await response.text();
42069
42142
  log(`[${this.provider.displayName}] Error: ${errorText}`);
42143
+ captureUpstreamError({
42144
+ provider: this.provider.displayName,
42145
+ model: this.bareModelName,
42146
+ status: response.status,
42147
+ body: errorText
42148
+ });
42070
42149
  const transportTerminal = this.provider.classifyTerminalError?.(response.status, errorText);
42071
42150
  const hint = getRecoveryHint(response.status, errorText, this.provider.displayName, transportTerminal);
42072
42151
  let parsedErrorBody;
@@ -42515,7 +42594,7 @@ function getRecoveryHint(status, errorText, providerName, transportTerminal429)
42515
42594
  return "Rate limited. Wait, reduce concurrency, or check plan limits.";
42516
42595
  }
42517
42596
  if (status === 401 || status === 403) {
42518
- if (lower.includes("not supported") || lower.includes("unsupported model") || lower.includes("model not found")) {
42597
+ if (hasModelUnsupportedWording(errorText)) {
42519
42598
  return "Model not supported by this provider. Verify model name.";
42520
42599
  }
42521
42600
  if (isQuotaExhaustionError(status, errorText)) {
@@ -42558,6 +42637,7 @@ var init_composed_handler = __esm(() => {
42558
42637
  init_collect_sse_message();
42559
42638
  init_connection_error();
42560
42639
  init_devin_stream_head_sniffer();
42640
+ init_model_unsupported();
42561
42641
  init_openai_compat();
42562
42642
  init_quota_exhaustion();
42563
42643
  init_stream_head_sniffer();
@@ -42568,6 +42648,7 @@ var init_composed_handler = __esm(() => {
42568
42648
  init_openai_responses_sse();
42569
42649
  init_openai_sse();
42570
42650
  init_token_tracker();
42651
+ init_upstream_error_capture();
42571
42652
  STREAM_RETRY_DELAYS_MS = [3000, 15000, 30000];
42572
42653
  });
42573
42654
 
@@ -43277,6 +43358,11 @@ async function discoverProviderModels(providerName) {
43277
43358
  return recordFailure({ kind: "no-credentials", provider: providerName, endpoint });
43278
43359
  }
43279
43360
  }
43361
+ headers = {
43362
+ "User-Agent": `claudish/${VERSION}`,
43363
+ ...def.headers ?? {},
43364
+ ...headers
43365
+ };
43280
43366
  let response;
43281
43367
  try {
43282
43368
  response = await fetch(endpoint, {
@@ -44954,6 +45040,7 @@ var init_default_routing_rules = __esm(() => {
44954
45040
  "ministral-*": ["mistralai", "openrouter"],
44955
45041
  "codestral-*": ["mistralai", "openrouter"],
44956
45042
  "labs-*": ["mistralai"],
45043
+ "swe-*": ["devin"],
44957
45044
  fugu: ["sakana-subscription", "sakana"],
44958
45045
  "fugu-*": ["sakana-subscription", "sakana"],
44959
45046
  "*-zen": ["opencode-zen"],
@@ -45558,7 +45645,7 @@ var init_prehydrate = __esm(() => {
45558
45645
  });
45559
45646
 
45560
45647
  // src/channel/diagnostics.ts
45561
- import { appendFileSync as appendFileSync4 } from "fs";
45648
+ import { appendFileSync as appendFileSync5 } from "fs";
45562
45649
  function traceEnabled() {
45563
45650
  return process.env[TRACE_ENV] === "1";
45564
45651
  }
@@ -45569,7 +45656,7 @@ function emit(line) {
45569
45656
  const filePath = process.env[TRACE_FILE_ENV];
45570
45657
  if (filePath) {
45571
45658
  try {
45572
- appendFileSync4(filePath, formatted);
45659
+ appendFileSync5(filePath, formatted);
45573
45660
  } catch {}
45574
45661
  }
45575
45662
  }
@@ -46369,6 +46456,15 @@ function normalizePricingDisplay(raw) {
46369
46456
  return "FREE";
46370
46457
  return pricing;
46371
46458
  }
46459
+ function formatListingPrice(entry, opts) {
46460
+ const rate = normalizePricingDisplay(entry.pricing?.average);
46461
+ if (rate !== "N/A")
46462
+ return rate;
46463
+ const plan = entry.subscription?.plan;
46464
+ if (!plan)
46465
+ return "N/A";
46466
+ return opts?.compact ? "SUB" : `SUB (${plan})`;
46467
+ }
46372
46468
  function computeQuickPicks(primaries) {
46373
46469
  if (primaries.length === 0) {
46374
46470
  return {
@@ -46603,6 +46699,412 @@ async function isPortAvailable(port) {
46603
46699
  }
46604
46700
  var init_port_manager = () => {};
46605
46701
 
46702
+ // src/providers/probe-live.ts
46703
+ function effortForProvider(provider) {
46704
+ return MINIMAL_EFFORT_UNSUPPORTED.has(provider) ? "low" : "minimal";
46705
+ }
46706
+ async function probeLink(proxyUrl, link, timeoutMs) {
46707
+ const isOAuth = OAUTH_PROVIDERS2.has(link.provider);
46708
+ if (!link.hasCredentials && !isOAuth) {
46709
+ return {
46710
+ state: "key-missing",
46711
+ latencyMs: 0,
46712
+ errorMessage: link.credentialHint
46713
+ };
46714
+ }
46715
+ const startedAt = Date.now();
46716
+ let response;
46717
+ try {
46718
+ response = await fetch(`${proxyUrl}/v1/messages`, {
46719
+ method: "POST",
46720
+ headers: {
46721
+ "Content-Type": "application/json"
46722
+ },
46723
+ body: JSON.stringify({
46724
+ model: link.modelSpec,
46725
+ system: "You are a helpful assistant.",
46726
+ messages: [{ role: "user", content: PROBE_PROMPT }],
46727
+ max_tokens: PROBE_MAX_TOKENS,
46728
+ output_config: { effort: effortForProvider(link.provider) },
46729
+ stream: true
46730
+ }),
46731
+ signal: AbortSignal.timeout(timeoutMs)
46732
+ });
46733
+ } catch (e) {
46734
+ const latencyMs = Date.now() - startedAt;
46735
+ const name = e?.name || "";
46736
+ const msg2 = String(e?.message || e);
46737
+ if (name === "TimeoutError" || name === "AbortError" || /timeout/i.test(msg2)) {
46738
+ return { state: "timeout", latencyMs, errorMessage: msg2 };
46739
+ }
46740
+ return { state: "network-error", latencyMs, errorMessage: msg2 };
46741
+ }
46742
+ const ttfbMs = Date.now() - startedAt;
46743
+ if (!response.ok) {
46744
+ const body = await safeReadBody(response);
46745
+ return annotateOAuthHint(classifyHttpError(response.status, body, ttfbMs), link.provider, isOAuth);
46746
+ }
46747
+ const streamResult = await consumeProbeStream(response, timeoutMs, startedAt);
46748
+ const totalMs = Date.now() - startedAt;
46749
+ let timing2;
46750
+ if (streamResult.state === "live" && streamResult.ttftMs !== undefined && !streamResult.truncated) {
46751
+ const ttftMs = streamResult.ttftMs;
46752
+ const tokens = streamResult.tokens ?? 0;
46753
+ const streamMs = Math.max(STREAM_MS_FLOOR, totalMs - ttftMs);
46754
+ const tokensPerSec = tokens > 0 ? tokens / streamMs * 1000 : 0;
46755
+ timing2 = { ttfbMs, ttftMs, totalMs, tokens, tokensPerSec };
46756
+ }
46757
+ const { ttftMs: _ttft, tokens: _tok, truncated: _trunc, ...rest } = streamResult;
46758
+ return annotateOAuthHint({
46759
+ ...rest,
46760
+ latencyMs: totalMs,
46761
+ timing: timing2
46762
+ }, link.provider, isOAuth);
46763
+ }
46764
+ function annotateOAuthHint(result, provider, isOAuth) {
46765
+ if (!isOAuth)
46766
+ return result;
46767
+ if (result.state === "live")
46768
+ return result;
46769
+ const loginCommand = provider === "antigravity" ? "claudish login antigravity" : provider === "vertex" ? "gcloud auth application-default login" : provider === "devin" ? "devin login" : undefined;
46770
+ if (!loginCommand)
46771
+ return result;
46772
+ if (result.httpStatus === 403)
46773
+ return result;
46774
+ const looksLikeAuthFailure = result.state === "auth-failed" || /auth|token|login|credential|unauthor/i.test(result.errorMessage || "");
46775
+ if (!looksLikeAuthFailure)
46776
+ return result;
46777
+ return {
46778
+ ...result,
46779
+ state: "auth-failed",
46780
+ actionHint: `run: ${loginCommand}`
46781
+ };
46782
+ }
46783
+ async function safeReadBody(response) {
46784
+ try {
46785
+ const text = await response.text();
46786
+ return text.slice(0, 500);
46787
+ } catch {
46788
+ return "";
46789
+ }
46790
+ }
46791
+ function extractUpstreamStatus(body) {
46792
+ if (!body)
46793
+ return;
46794
+ try {
46795
+ const parsed = JSON.parse(body);
46796
+ const status = parsed?.error?.upstream_status;
46797
+ return typeof status === "number" ? status : undefined;
46798
+ } catch {
46799
+ return;
46800
+ }
46801
+ }
46802
+ function extractErrorType(body) {
46803
+ if (!body)
46804
+ return;
46805
+ try {
46806
+ const parsed = JSON.parse(body);
46807
+ const t = parsed?.error?.type;
46808
+ return typeof t === "string" ? t : undefined;
46809
+ } catch {
46810
+ return;
46811
+ }
46812
+ }
46813
+ function classifyHttpError(status, body, latencyMs) {
46814
+ const lowered = body.toLowerCase();
46815
+ if (extractErrorType(body) === "connection_error") {
46816
+ return {
46817
+ state: "network-error",
46818
+ latencyMs,
46819
+ httpStatus: status,
46820
+ errorMessage: extractErrorMessage(body) || "Cannot reach provider"
46821
+ };
46822
+ }
46823
+ const upstream = status === 400 ? extractUpstreamStatus(body) : undefined;
46824
+ if (status === 401 || status === 403 || upstream === 401 || upstream === 403) {
46825
+ const authStatus = upstream ?? status;
46826
+ if (hasModelUnsupportedWording(body)) {
46827
+ return {
46828
+ state: "model-not-found",
46829
+ latencyMs,
46830
+ httpStatus: authStatus,
46831
+ errorMessage: extractErrorMessage(body) || `HTTP ${authStatus}`
46832
+ };
46833
+ }
46834
+ return {
46835
+ state: "auth-failed",
46836
+ latencyMs,
46837
+ httpStatus: authStatus,
46838
+ errorMessage: extractErrorMessage(body) || `HTTP ${authStatus}`
46839
+ };
46840
+ }
46841
+ if (status === 404 || /model[_ ]not[_ ]found|no such model|unknown model/.test(lowered)) {
46842
+ return {
46843
+ state: "model-not-found",
46844
+ latencyMs,
46845
+ httpStatus: status,
46846
+ errorMessage: extractErrorMessage(body) || `HTTP ${status}`
46847
+ };
46848
+ }
46849
+ if (status === 429) {
46850
+ return {
46851
+ state: "rate-limited",
46852
+ latencyMs,
46853
+ httpStatus: status,
46854
+ errorMessage: extractErrorMessage(body) || "Rate limited"
46855
+ };
46856
+ }
46857
+ if (upstream === 429 || status === 402) {
46858
+ return {
46859
+ state: "out-of-credit",
46860
+ latencyMs,
46861
+ httpStatus: upstream ?? status,
46862
+ errorMessage: extractErrorMessage(body) || "Out of credit \u2014 account balance or plan exhausted"
46863
+ };
46864
+ }
46865
+ if (status >= 500) {
46866
+ return {
46867
+ state: "server-error",
46868
+ latencyMs,
46869
+ httpStatus: status,
46870
+ errorMessage: extractErrorMessage(body) || `HTTP ${status}`
46871
+ };
46872
+ }
46873
+ return {
46874
+ state: "error",
46875
+ latencyMs,
46876
+ httpStatus: status,
46877
+ errorMessage: extractErrorMessage(body) || `HTTP ${status}`
46878
+ };
46879
+ }
46880
+ function extractErrorMessage(body) {
46881
+ if (!body)
46882
+ return;
46883
+ try {
46884
+ const parsed = JSON.parse(body);
46885
+ const msg2 = parsed?.error?.message || parsed?.error?.error?.message || parsed?.message || parsed?.detail;
46886
+ if (typeof msg2 === "string" && msg2.length > 0) {
46887
+ return msg2.length > 160 ? `${msg2.slice(0, 157)}...` : msg2;
46888
+ }
46889
+ } catch {}
46890
+ const trimmed2 = body.trim();
46891
+ if (!trimmed2)
46892
+ return;
46893
+ return trimmed2.length > 160 ? `${trimmed2.slice(0, 157)}...` : trimmed2;
46894
+ }
46895
+ async function consumeProbeStream(response, timeoutMs, startedAt) {
46896
+ const body = response.body;
46897
+ if (!body) {
46898
+ return { state: "error", errorMessage: "empty response body" };
46899
+ }
46900
+ const reader = body.getReader();
46901
+ const decoder = new TextDecoder;
46902
+ let buffered = "";
46903
+ const deadline = Date.now() + timeoutMs;
46904
+ let ttftMs;
46905
+ let sawContent = false;
46906
+ let textChars = 0;
46907
+ let reportedTokens;
46908
+ let stopReason;
46909
+ let errorVerdict = null;
46910
+ let completed = false;
46911
+ try {
46912
+ while (Date.now() < deadline) {
46913
+ const { value, done } = await reader.read();
46914
+ if (done) {
46915
+ completed = true;
46916
+ break;
46917
+ }
46918
+ buffered += decoder.decode(value, { stream: true });
46919
+ const events = buffered.split(`
46920
+
46921
+ `);
46922
+ buffered = events.pop() ?? "";
46923
+ for (const event of events) {
46924
+ const verdict = interpretSseEvent(event);
46925
+ if (verdict && typeof verdict === "object" && verdict.state !== "live") {
46926
+ errorVerdict = verdict;
46927
+ break;
46928
+ }
46929
+ const acct = accountStreamEvent(event);
46930
+ if (acct.contentDelta) {
46931
+ if (ttftMs === undefined)
46932
+ ttftMs = Date.now() - startedAt;
46933
+ sawContent = true;
46934
+ }
46935
+ if (acct.textChars)
46936
+ textChars += acct.textChars;
46937
+ if (acct.outputTokens !== undefined)
46938
+ reportedTokens = acct.outputTokens;
46939
+ if (acct.stopReason)
46940
+ stopReason = acct.stopReason;
46941
+ }
46942
+ if (errorVerdict)
46943
+ break;
46944
+ }
46945
+ } catch (e) {
46946
+ if (!sawContent) {
46947
+ return { state: "network-error", errorMessage: String(e?.message || e) };
46948
+ }
46949
+ } finally {
46950
+ try {
46951
+ await reader.cancel();
46952
+ } catch {}
46953
+ }
46954
+ if (errorVerdict)
46955
+ return errorVerdict;
46956
+ if (sawContent) {
46957
+ const tokens = reportedTokens ?? Math.max(1, Math.round(textChars / 4));
46958
+ return { state: "live", ttftMs, tokens, truncated: !completed };
46959
+ }
46960
+ const truncationReason = stopReason === "max_tokens" || stopReason === "length" ? stopReason : undefined;
46961
+ if (truncationReason || reportedTokens !== undefined && reportedTokens >= PROBE_MAX_TOKENS) {
46962
+ const cause = truncationReason ? `finish: ${truncationReason}` : `${reportedTokens} tokens consumed, none visible`;
46963
+ return {
46964
+ state: "error",
46965
+ errorMessage: `no visible output within probe budget (${cause})`
46966
+ };
46967
+ }
46968
+ return { state: "error", errorMessage: "stream ended without content" };
46969
+ }
46970
+ function accountStreamEvent(rawEvent) {
46971
+ let dataPayload = "";
46972
+ for (const line of rawEvent.split(`
46973
+ `)) {
46974
+ if (line.startsWith("data:"))
46975
+ dataPayload += line.slice(5).trim();
46976
+ }
46977
+ if (!dataPayload || dataPayload === "[DONE]") {
46978
+ return { contentDelta: false, textChars: 0 };
46979
+ }
46980
+ let parsed;
46981
+ try {
46982
+ parsed = JSON.parse(dataPayload);
46983
+ } catch {
46984
+ return { contentDelta: false, textChars: 0 };
46985
+ }
46986
+ let textChars = 0;
46987
+ let contentDelta = false;
46988
+ const text = parsed?.delta?.text ?? (Array.isArray(parsed?.choices) ? parsed.choices[0]?.delta?.content : undefined);
46989
+ if (typeof text === "string" && text.length > 0) {
46990
+ contentDelta = true;
46991
+ textChars = text.length;
46992
+ } else if (parsed?.type === "content_block_delta" || parsed?.type === "content_block_start") {
46993
+ contentDelta = true;
46994
+ }
46995
+ const outputTokens = parsed?.usage?.output_tokens ?? parsed?.message?.usage?.output_tokens ?? parsed?.usage?.completion_tokens;
46996
+ const stopReason = parsed?.delta?.stop_reason ?? (Array.isArray(parsed?.choices) ? parsed.choices[0]?.finish_reason : undefined);
46997
+ return {
46998
+ contentDelta,
46999
+ textChars,
47000
+ outputTokens: typeof outputTokens === "number" ? outputTokens : undefined,
47001
+ stopReason: typeof stopReason === "string" ? stopReason : undefined
47002
+ };
47003
+ }
47004
+ function interpretSseEvent(rawEvent) {
47005
+ const lines = rawEvent.split(`
47006
+ `);
47007
+ let eventType = "";
47008
+ let dataPayload = "";
47009
+ for (const line of lines) {
47010
+ if (line.startsWith("event:"))
47011
+ eventType = line.slice(6).trim();
47012
+ else if (line.startsWith("data:"))
47013
+ dataPayload += line.slice(5).trim();
47014
+ }
47015
+ if (!dataPayload)
47016
+ return null;
47017
+ if (dataPayload === "[DONE]")
47018
+ return null;
47019
+ let parsed;
47020
+ try {
47021
+ parsed = JSON.parse(dataPayload);
47022
+ } catch {
47023
+ return null;
47024
+ }
47025
+ if (parsed?.type === "error" || eventType === "error" || parsed?.error) {
47026
+ const message = parsed?.error?.message || parsed?.error?.error?.message || parsed?.message || "provider returned error event";
47027
+ const status = parsed?.error?.status || parsed?.status;
47028
+ if (typeof status === "number") {
47029
+ return {
47030
+ state: status === 401 || status === 403 ? "auth-failed" : "error",
47031
+ httpStatus: status,
47032
+ errorMessage: message
47033
+ };
47034
+ }
47035
+ return { state: "error", errorMessage: message };
47036
+ }
47037
+ if (isContentEvent(parsed, eventType)) {
47038
+ return "live";
47039
+ }
47040
+ return null;
47041
+ }
47042
+ function isContentEvent(parsed, eventType) {
47043
+ if (eventType === "content_block_start" || eventType === "content_block_delta")
47044
+ return true;
47045
+ if (eventType === "message_start")
47046
+ return true;
47047
+ if (parsed?.type === "content_block_start")
47048
+ return true;
47049
+ if (parsed?.type === "content_block_delta")
47050
+ return true;
47051
+ if (parsed?.type === "message_start")
47052
+ return true;
47053
+ if (parsed?.type === "message_delta")
47054
+ return true;
47055
+ if (Array.isArray(parsed?.choices) && parsed.choices.length > 0) {
47056
+ const choice = parsed.choices[0];
47057
+ if (choice?.delta || choice?.message || choice?.text || choice?.finish_reason)
47058
+ return true;
47059
+ }
47060
+ if (parsed?.candidates)
47061
+ return true;
47062
+ return false;
47063
+ }
47064
+ function withDetail(base, message) {
47065
+ return message ? `${base} \u2014 ${message}` : base;
47066
+ }
47067
+ function describeProbeState(result) {
47068
+ const status = result.httpStatus ?? "";
47069
+ const latency = result.latencyMs ? ` \xB7 ${result.latencyMs}ms` : "";
47070
+ switch (result.state) {
47071
+ case "live":
47072
+ return `live \xB7 ${result.latencyMs}ms`;
47073
+ case "key-missing":
47074
+ return result.errorMessage ? `missing (${result.errorMessage})` : "missing";
47075
+ case "auth-failed":
47076
+ return withDetail(`auth failed \xB7 ${status}${latency}`.trim(), result.errorMessage);
47077
+ case "model-not-found":
47078
+ return withDetail(`model not found \xB7 ${status}${latency}`.trim(), result.errorMessage);
47079
+ case "rate-limited":
47080
+ return withDetail(`rate limited \xB7 ${result.latencyMs}ms`, result.errorMessage);
47081
+ case "out-of-credit":
47082
+ return withDetail(`out of credit \xB7 ${status}${latency}`.trim(), result.errorMessage);
47083
+ case "server-error":
47084
+ return withDetail(`server error \xB7 ${status} \xB7 ${result.latencyMs}ms`, result.errorMessage);
47085
+ case "timeout":
47086
+ return withDetail(`timeout \xB7 ${result.latencyMs}ms`, result.errorMessage);
47087
+ case "network-error":
47088
+ return withDetail(`network error \xB7 ${result.latencyMs}ms`, result.errorMessage);
47089
+ case "error": {
47090
+ const base = `error${result.httpStatus ? ` \xB7 ${result.httpStatus}` : ""}${latency}`;
47091
+ return withDetail(base, result.errorMessage);
47092
+ }
47093
+ }
47094
+ }
47095
+ function isReadyState(state) {
47096
+ return state === "live";
47097
+ }
47098
+ function isFailureState(state) {
47099
+ return state === "auth-failed" || state === "model-not-found" || state === "rate-limited" || state === "out-of-credit" || state === "server-error" || state === "timeout" || state === "network-error" || state === "error";
47100
+ }
47101
+ var STREAM_MS_FLOOR = 50, OAUTH_PROVIDERS2, PROBE_PROMPT = "Count from one to twenty in words, one per line.", PROBE_MAX_TOKENS = 512, MINIMAL_EFFORT_UNSUPPORTED;
47102
+ var init_probe_live = __esm(() => {
47103
+ init_model_unsupported();
47104
+ OAUTH_PROVIDERS2 = new Set(["vertex", "antigravity", "devin"]);
47105
+ MINIMAL_EFFORT_UNSUPPORTED = new Set(["native-anthropic", "anthropic"]);
47106
+ });
47107
+
46606
47108
  // ../../node_modules/.bun/hono@4.10.6/node_modules/hono/dist/compose.js
46607
47109
  var compose = (middleware, onError, onNotFound) => {
46608
47110
  return (context, next) => {
@@ -48707,7 +49209,7 @@ var init_fallback_handler = __esm(() => {
48707
49209
  });
48708
49210
 
48709
49211
  // src/handlers/native-handler-advisor.ts
48710
- import { appendFileSync as appendFileSync5 } from "fs";
49212
+ import { appendFileSync as appendFileSync6 } from "fs";
48711
49213
  function loadAdvisorSwapConfig(cliModels, cliCollector) {
48712
49214
  return {
48713
49215
  enabled: process.env.CLAUDISH_SWAP_ADVISOR === "1" || (cliModels?.length ?? 0) > 0,
@@ -48762,7 +49264,7 @@ function logAdvisorEvent(cfg, event) {
48762
49264
  const line = `${JSON.stringify({ ts: new Date().toISOString(), ...event })}
48763
49265
  `;
48764
49266
  try {
48765
- appendFileSync5(cfg.logPath, line);
49267
+ appendFileSync6(cfg.logPath, line);
48766
49268
  } catch {}
48767
49269
  }
48768
49270
  function recordAdvisorEventsFromChunk(cfg, chunkText) {
@@ -51563,11 +52065,30 @@ ${plan.hint}` : `[Route] ${plan.reason}`;
51563
52065
  }));
51564
52066
  app.get("/health", (c) => c.json({ status: "ok" }));
51565
52067
  const servedSlotIds = options.servedSlotIds ?? [];
52068
+ const discoverableModelIds = (() => {
52069
+ const seen = new Set;
52070
+ try {
52071
+ const cfg = loadConfig();
52072
+ for (const name of Object.keys(cfg.routing ?? {})) {
52073
+ if (name === "*")
52074
+ continue;
52075
+ seen.add(name);
52076
+ }
52077
+ for (const ep of Object.values(cfg.customEndpoints ?? {})) {
52078
+ for (const m of ep.models ?? [])
52079
+ seen.add(m);
52080
+ }
52081
+ } catch (err) {
52082
+ log(`[Proxy] /v1/models discovery skipped: ${err instanceof Error ? err.message : err}`);
52083
+ }
52084
+ return [...seen];
52085
+ })();
51566
52086
  app.get("/v1/models", (c) => {
52087
+ const ids = servedSlotIds.length > 0 ? servedSlotIds : discoverableModelIds;
51567
52088
  return c.json({
51568
52089
  object: "list",
51569
52090
  has_more: false,
51570
- data: servedSlotIds.map((id) => ({
52091
+ data: ids.map((id) => ({
51571
52092
  id,
51572
52093
  object: "model",
51573
52094
  type: "model",
@@ -53052,7 +53573,7 @@ Tokens: ${result.usage.input} input, ${result.usage.output} output`;
53052
53573
  };
53053
53574
  const renderGroup = (group) => {
53054
53575
  const m = group.primary;
53055
- const pricing = normalizePricingDisplay(m.pricing?.average);
53576
+ const pricing = formatListingPrice(m);
53056
53577
  const ctx = m.context || "N/A";
53057
53578
  const caps = [];
53058
53579
  if (m.supportsTools)
@@ -53257,6 +53778,112 @@ Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
53257
53778
  return { content: [{ type: "text", text: output }] };
53258
53779
  }
53259
53780
  });
53781
+ tools.push({
53782
+ name: "preflight",
53783
+ description: "Check a roster of models BEFORE spending a run on it. For each model: which provider " + "will actually serve it, whether that hop is covered by a SUBSCRIPTION or billed per " + "token, and whether it is reachable right now. Call this before `team` or a batch of " + "`create_session` calls \u2014 a dead or unexpectedly-metered model is then caught while " + "the roster can still be adjusted, instead of costing a slot minutes into the run.",
53784
+ inputSchema: {
53785
+ type: "object",
53786
+ properties: {
53787
+ models: {
53788
+ type: "array",
53789
+ items: { type: "string" },
53790
+ description: "Model ids to check \u2014 bare (`glm-5.2`) or explicit (`dv@swe-1.7`). Bare names go " + "through the SAME routing rules and credential filter a real run would use, so " + "the provider reported here is the provider that would serve it."
53791
+ },
53792
+ probe: {
53793
+ type: "boolean",
53794
+ description: "Send a real short request to each resolved route (default true). Set false for " + "a routing/billing answer only \u2014 far faster, but it cannot tell you the provider " + "is actually reachable, which is the failure this tool exists to catch."
53795
+ },
53796
+ timeout_ms: {
53797
+ type: "number",
53798
+ description: "Per-model probe timeout in ms (default 20000)."
53799
+ }
53800
+ },
53801
+ required: ["models"]
53802
+ },
53803
+ group: "agentic",
53804
+ heartbeat: true,
53805
+ handler: async (args, ctx) => {
53806
+ const models = Array.isArray(args.models) ? args.models : [];
53807
+ if (models.length === 0) {
53808
+ return {
53809
+ content: [{ type: "text", text: "preflight: no models given." }],
53810
+ isError: true
53811
+ };
53812
+ }
53813
+ const doProbe = args.probe !== false;
53814
+ const timeoutMs = typeof args.timeout_ms === "number" ? args.timeout_ms : 20000;
53815
+ const proxy = doProbe ? await getProxy() : null;
53816
+ const rows = [];
53817
+ const readyModels = [];
53818
+ const failedModels = [];
53819
+ let subCount = 0;
53820
+ let meteredCount = 0;
53821
+ for (const model of models) {
53822
+ ctx.reportProgress(`preflight: ${model}`);
53823
+ let plan;
53824
+ try {
53825
+ plan = await route(model);
53826
+ } catch (err) {
53827
+ failedModels.push(model);
53828
+ rows.push(`| \`${model}\` | \u2014 | \u2014 | \u274C route error | ${err instanceof Error ? err.message : String(err)} |`);
53829
+ continue;
53830
+ }
53831
+ if (plan.kind === "no-route") {
53832
+ failedModels.push(model);
53833
+ rows.push(`| \`${model}\` | \u2014 | \u2014 | \u274C no route | ${plan.reason} |`);
53834
+ continue;
53835
+ }
53836
+ const primary = plan.primary;
53837
+ const billing = isLocalProviderName(primary.provider) ? "local" : isSubscriptionProvider(primary.provider) ? "SUB" : "metered";
53838
+ if (billing === "SUB")
53839
+ subCount++;
53840
+ else if (billing === "metered")
53841
+ meteredCount++;
53842
+ let status = "not probed";
53843
+ let ok = true;
53844
+ if (proxy) {
53845
+ try {
53846
+ const result = await probeLink(proxy.url, {
53847
+ provider: primary.provider,
53848
+ modelSpec: primary.modelSpec,
53849
+ hasCredentials: true
53850
+ }, timeoutMs);
53851
+ ok = isReadyState(result.state);
53852
+ status = ok ? `\u2705 ${result.state}` : `\u274C ${result.state}${result.errorMessage ? ` \u2014 ${result.errorMessage}` : ""}`;
53853
+ } catch (err) {
53854
+ ok = false;
53855
+ status = `\u274C probe error \u2014 ${err instanceof Error ? err.message : String(err)}`;
53856
+ }
53857
+ }
53858
+ if (ok)
53859
+ readyModels.push(model);
53860
+ else
53861
+ failedModels.push(model);
53862
+ const fallbacks = plan.fallbacks.length > 0 ? ` (+${plan.fallbacks.length} fallback)` : "";
53863
+ rows.push(`| \`${model}\` | ${primary.displayName}${fallbacks} | ${billing} | ${status} | \`${primary.modelSpec}\` |`);
53864
+ }
53865
+ const lines = [
53866
+ `# Preflight \u2014 ${models.length} model${models.length === 1 ? "" : "s"}`,
53867
+ "",
53868
+ `**Ready: ${readyModels.length}** \xB7 **Failed: ${failedModels.length}** \xB7 ` + `subscription: ${subCount} \xB7 metered: ${meteredCount}`,
53869
+ "",
53870
+ "| Model | Provider | Billing | Status | Wire id |",
53871
+ "|---|---|---|---|---|",
53872
+ ...rows
53873
+ ];
53874
+ if (failedModels.length > 0) {
53875
+ lines.push("", `\u26A0\uFE0F Drop or replace before running: ${failedModels.map((m) => `\`${m}\``).join(", ")}`);
53876
+ }
53877
+ if (meteredCount > 0) {
53878
+ lines.push("", `\uD83D\uDCB8 ${meteredCount} model${meteredCount === 1 ? "" : "s"} will be billed PER TOKEN. ` + "A bare name can land on a metered provider when the subscription that covers it " + "has no credential configured \u2014 name the provider explicitly to pin it.");
53879
+ }
53880
+ if (!doProbe) {
53881
+ lines.push("", "\u2139\uFE0F `probe: false` \u2014 routing and billing only. Reachability was NOT checked.");
53882
+ }
53883
+ return { content: [{ type: "text", text: lines.join(`
53884
+ `) }] };
53885
+ }
53886
+ });
53260
53887
  tools.push({
53261
53888
  name: "team",
53262
53889
  description: "Run AI models on a task with anonymized outputs and optional blind judging. Modes: 'run' (execute models), 'judge' (blind-vote on existing outputs), 'run-and-judge' (full pipeline), 'status' (check progress).",
@@ -53851,11 +54478,15 @@ var init_mcp_server = __esm(() => {
53851
54478
  init_prehydrate();
53852
54479
  init_diagnostics();
53853
54480
  init_channel();
54481
+ init_remote_provider_types();
53854
54482
  init_progress_heartbeat();
53855
54483
  init_model_loader();
53856
54484
  init_port_manager();
54485
+ init_model_parser();
53857
54486
  init_onepassword();
54487
+ init_probe_live();
53858
54488
  init_provider_definitions();
54489
+ init_routing_rules();
53859
54490
  init_proxy_server();
53860
54491
  init_redact();
53861
54492
  init_team_orchestrator();
@@ -68684,399 +69315,6 @@ var init_model_selector = __esm(() => {
68684
69315
  };
68685
69316
  });
68686
69317
 
68687
- // src/providers/probe-live.ts
68688
- async function probeLink(proxyUrl, link, timeoutMs) {
68689
- const isOAuth = OAUTH_PROVIDERS2.has(link.provider);
68690
- if (!link.hasCredentials && !isOAuth) {
68691
- return {
68692
- state: "key-missing",
68693
- latencyMs: 0,
68694
- errorMessage: link.credentialHint
68695
- };
68696
- }
68697
- const startedAt = Date.now();
68698
- let response;
68699
- try {
68700
- response = await fetch(`${proxyUrl}/v1/messages`, {
68701
- method: "POST",
68702
- headers: {
68703
- "Content-Type": "application/json"
68704
- },
68705
- body: JSON.stringify({
68706
- model: link.modelSpec,
68707
- system: "You are a helpful assistant.",
68708
- messages: [{ role: "user", content: PROBE_PROMPT }],
68709
- max_tokens: PROBE_MAX_TOKENS,
68710
- output_config: { effort: "minimal" },
68711
- stream: true
68712
- }),
68713
- signal: AbortSignal.timeout(timeoutMs)
68714
- });
68715
- } catch (e) {
68716
- const latencyMs = Date.now() - startedAt;
68717
- const name = e?.name || "";
68718
- const msg2 = String(e?.message || e);
68719
- if (name === "TimeoutError" || name === "AbortError" || /timeout/i.test(msg2)) {
68720
- return { state: "timeout", latencyMs, errorMessage: msg2 };
68721
- }
68722
- return { state: "network-error", latencyMs, errorMessage: msg2 };
68723
- }
68724
- const ttfbMs = Date.now() - startedAt;
68725
- if (!response.ok) {
68726
- const body = await safeReadBody(response);
68727
- return annotateOAuthHint(classifyHttpError(response.status, body, ttfbMs), link.provider, isOAuth);
68728
- }
68729
- const streamResult = await consumeProbeStream(response, timeoutMs, startedAt);
68730
- const totalMs = Date.now() - startedAt;
68731
- let timing2;
68732
- if (streamResult.state === "live" && streamResult.ttftMs !== undefined && !streamResult.truncated) {
68733
- const ttftMs = streamResult.ttftMs;
68734
- const tokens = streamResult.tokens ?? 0;
68735
- const streamMs = Math.max(STREAM_MS_FLOOR, totalMs - ttftMs);
68736
- const tokensPerSec = tokens > 0 ? tokens / streamMs * 1000 : 0;
68737
- timing2 = { ttfbMs, ttftMs, totalMs, tokens, tokensPerSec };
68738
- }
68739
- const { ttftMs: _ttft, tokens: _tok, truncated: _trunc, ...rest } = streamResult;
68740
- return annotateOAuthHint({
68741
- ...rest,
68742
- latencyMs: totalMs,
68743
- timing: timing2
68744
- }, link.provider, isOAuth);
68745
- }
68746
- function annotateOAuthHint(result, provider, isOAuth) {
68747
- if (!isOAuth)
68748
- return result;
68749
- if (result.state === "live")
68750
- return result;
68751
- const loginCommand2 = provider === "antigravity" ? "claudish login antigravity" : provider === "vertex" ? "gcloud auth application-default login" : provider === "devin" ? "devin login" : undefined;
68752
- if (!loginCommand2)
68753
- return result;
68754
- if (result.httpStatus === 403)
68755
- return result;
68756
- const looksLikeAuthFailure = result.state === "auth-failed" || /auth|token|login|credential|unauthor/i.test(result.errorMessage || "");
68757
- if (!looksLikeAuthFailure)
68758
- return result;
68759
- return {
68760
- ...result,
68761
- state: "auth-failed",
68762
- actionHint: `run: ${loginCommand2}`
68763
- };
68764
- }
68765
- async function safeReadBody(response) {
68766
- try {
68767
- const text = await response.text();
68768
- return text.slice(0, 500);
68769
- } catch {
68770
- return "";
68771
- }
68772
- }
68773
- function extractUpstreamStatus(body) {
68774
- if (!body)
68775
- return;
68776
- try {
68777
- const parsed = JSON.parse(body);
68778
- const status = parsed?.error?.upstream_status;
68779
- return typeof status === "number" ? status : undefined;
68780
- } catch {
68781
- return;
68782
- }
68783
- }
68784
- function extractErrorType(body) {
68785
- if (!body)
68786
- return;
68787
- try {
68788
- const parsed = JSON.parse(body);
68789
- const t = parsed?.error?.type;
68790
- return typeof t === "string" ? t : undefined;
68791
- } catch {
68792
- return;
68793
- }
68794
- }
68795
- function classifyHttpError(status, body, latencyMs) {
68796
- const lowered = body.toLowerCase();
68797
- if (extractErrorType(body) === "connection_error") {
68798
- return {
68799
- state: "network-error",
68800
- latencyMs,
68801
- httpStatus: status,
68802
- errorMessage: extractErrorMessage(body) || "Cannot reach provider"
68803
- };
68804
- }
68805
- const upstream = status === 400 ? extractUpstreamStatus(body) : undefined;
68806
- if (status === 401 || status === 403 || upstream === 401 || upstream === 403) {
68807
- const authStatus = upstream ?? status;
68808
- return {
68809
- state: "auth-failed",
68810
- latencyMs,
68811
- httpStatus: authStatus,
68812
- errorMessage: extractErrorMessage(body) || `HTTP ${authStatus}`
68813
- };
68814
- }
68815
- if (status === 404 || /model[_ ]not[_ ]found|no such model|unknown model/.test(lowered)) {
68816
- return {
68817
- state: "model-not-found",
68818
- latencyMs,
68819
- httpStatus: status,
68820
- errorMessage: extractErrorMessage(body) || `HTTP ${status}`
68821
- };
68822
- }
68823
- if (status === 429) {
68824
- return {
68825
- state: "rate-limited",
68826
- latencyMs,
68827
- httpStatus: status,
68828
- errorMessage: extractErrorMessage(body) || "Rate limited"
68829
- };
68830
- }
68831
- if (upstream === 429 || status === 402) {
68832
- return {
68833
- state: "out-of-credit",
68834
- latencyMs,
68835
- httpStatus: upstream ?? status,
68836
- errorMessage: extractErrorMessage(body) || "Out of credit \u2014 account balance or plan exhausted"
68837
- };
68838
- }
68839
- if (status >= 500) {
68840
- return {
68841
- state: "server-error",
68842
- latencyMs,
68843
- httpStatus: status,
68844
- errorMessage: extractErrorMessage(body) || `HTTP ${status}`
68845
- };
68846
- }
68847
- return {
68848
- state: "error",
68849
- latencyMs,
68850
- httpStatus: status,
68851
- errorMessage: extractErrorMessage(body) || `HTTP ${status}`
68852
- };
68853
- }
68854
- function extractErrorMessage(body) {
68855
- if (!body)
68856
- return;
68857
- try {
68858
- const parsed = JSON.parse(body);
68859
- const msg2 = parsed?.error?.message || parsed?.error?.error?.message || parsed?.message || parsed?.detail;
68860
- if (typeof msg2 === "string" && msg2.length > 0) {
68861
- return msg2.length > 160 ? `${msg2.slice(0, 157)}...` : msg2;
68862
- }
68863
- } catch {}
68864
- const trimmed2 = body.trim();
68865
- if (!trimmed2)
68866
- return;
68867
- return trimmed2.length > 160 ? `${trimmed2.slice(0, 157)}...` : trimmed2;
68868
- }
68869
- async function consumeProbeStream(response, timeoutMs, startedAt) {
68870
- const body = response.body;
68871
- if (!body) {
68872
- return { state: "error", errorMessage: "empty response body" };
68873
- }
68874
- const reader = body.getReader();
68875
- const decoder = new TextDecoder;
68876
- let buffered = "";
68877
- const deadline = Date.now() + timeoutMs;
68878
- let ttftMs;
68879
- let sawContent = false;
68880
- let textChars = 0;
68881
- let reportedTokens;
68882
- let stopReason;
68883
- let errorVerdict = null;
68884
- let completed = false;
68885
- try {
68886
- while (Date.now() < deadline) {
68887
- const { value, done } = await reader.read();
68888
- if (done) {
68889
- completed = true;
68890
- break;
68891
- }
68892
- buffered += decoder.decode(value, { stream: true });
68893
- const events = buffered.split(`
68894
-
68895
- `);
68896
- buffered = events.pop() ?? "";
68897
- for (const event of events) {
68898
- const verdict = interpretSseEvent(event);
68899
- if (verdict && typeof verdict === "object" && verdict.state !== "live") {
68900
- errorVerdict = verdict;
68901
- break;
68902
- }
68903
- const acct = accountStreamEvent(event);
68904
- if (acct.contentDelta) {
68905
- if (ttftMs === undefined)
68906
- ttftMs = Date.now() - startedAt;
68907
- sawContent = true;
68908
- }
68909
- if (acct.textChars)
68910
- textChars += acct.textChars;
68911
- if (acct.outputTokens !== undefined)
68912
- reportedTokens = acct.outputTokens;
68913
- if (acct.stopReason)
68914
- stopReason = acct.stopReason;
68915
- }
68916
- if (errorVerdict)
68917
- break;
68918
- }
68919
- } catch (e) {
68920
- if (!sawContent) {
68921
- return { state: "network-error", errorMessage: String(e?.message || e) };
68922
- }
68923
- } finally {
68924
- try {
68925
- await reader.cancel();
68926
- } catch {}
68927
- }
68928
- if (errorVerdict)
68929
- return errorVerdict;
68930
- if (sawContent) {
68931
- const tokens = reportedTokens ?? Math.max(1, Math.round(textChars / 4));
68932
- return { state: "live", ttftMs, tokens, truncated: !completed };
68933
- }
68934
- const truncationReason = stopReason === "max_tokens" || stopReason === "length" ? stopReason : undefined;
68935
- if (truncationReason || reportedTokens !== undefined && reportedTokens >= PROBE_MAX_TOKENS) {
68936
- const cause = truncationReason ? `finish: ${truncationReason}` : `${reportedTokens} tokens consumed, none visible`;
68937
- return {
68938
- state: "error",
68939
- errorMessage: `no visible output within probe budget (${cause})`
68940
- };
68941
- }
68942
- return { state: "error", errorMessage: "stream ended without content" };
68943
- }
68944
- function accountStreamEvent(rawEvent) {
68945
- let dataPayload = "";
68946
- for (const line of rawEvent.split(`
68947
- `)) {
68948
- if (line.startsWith("data:"))
68949
- dataPayload += line.slice(5).trim();
68950
- }
68951
- if (!dataPayload || dataPayload === "[DONE]") {
68952
- return { contentDelta: false, textChars: 0 };
68953
- }
68954
- let parsed;
68955
- try {
68956
- parsed = JSON.parse(dataPayload);
68957
- } catch {
68958
- return { contentDelta: false, textChars: 0 };
68959
- }
68960
- let textChars = 0;
68961
- let contentDelta = false;
68962
- const text = parsed?.delta?.text ?? (Array.isArray(parsed?.choices) ? parsed.choices[0]?.delta?.content : undefined);
68963
- if (typeof text === "string" && text.length > 0) {
68964
- contentDelta = true;
68965
- textChars = text.length;
68966
- } else if (parsed?.type === "content_block_delta" || parsed?.type === "content_block_start") {
68967
- contentDelta = true;
68968
- }
68969
- const outputTokens = parsed?.usage?.output_tokens ?? parsed?.message?.usage?.output_tokens ?? parsed?.usage?.completion_tokens;
68970
- const stopReason = parsed?.delta?.stop_reason ?? (Array.isArray(parsed?.choices) ? parsed.choices[0]?.finish_reason : undefined);
68971
- return {
68972
- contentDelta,
68973
- textChars,
68974
- outputTokens: typeof outputTokens === "number" ? outputTokens : undefined,
68975
- stopReason: typeof stopReason === "string" ? stopReason : undefined
68976
- };
68977
- }
68978
- function interpretSseEvent(rawEvent) {
68979
- const lines = rawEvent.split(`
68980
- `);
68981
- let eventType = "";
68982
- let dataPayload = "";
68983
- for (const line of lines) {
68984
- if (line.startsWith("event:"))
68985
- eventType = line.slice(6).trim();
68986
- else if (line.startsWith("data:"))
68987
- dataPayload += line.slice(5).trim();
68988
- }
68989
- if (!dataPayload)
68990
- return null;
68991
- if (dataPayload === "[DONE]")
68992
- return null;
68993
- let parsed;
68994
- try {
68995
- parsed = JSON.parse(dataPayload);
68996
- } catch {
68997
- return null;
68998
- }
68999
- if (parsed?.type === "error" || eventType === "error" || parsed?.error) {
69000
- const message = parsed?.error?.message || parsed?.error?.error?.message || parsed?.message || "provider returned error event";
69001
- const status = parsed?.error?.status || parsed?.status;
69002
- if (typeof status === "number") {
69003
- return {
69004
- state: status === 401 || status === 403 ? "auth-failed" : "error",
69005
- httpStatus: status,
69006
- errorMessage: message
69007
- };
69008
- }
69009
- return { state: "error", errorMessage: message };
69010
- }
69011
- if (isContentEvent(parsed, eventType)) {
69012
- return "live";
69013
- }
69014
- return null;
69015
- }
69016
- function isContentEvent(parsed, eventType) {
69017
- if (eventType === "content_block_start" || eventType === "content_block_delta")
69018
- return true;
69019
- if (eventType === "message_start")
69020
- return true;
69021
- if (parsed?.type === "content_block_start")
69022
- return true;
69023
- if (parsed?.type === "content_block_delta")
69024
- return true;
69025
- if (parsed?.type === "message_start")
69026
- return true;
69027
- if (parsed?.type === "message_delta")
69028
- return true;
69029
- if (Array.isArray(parsed?.choices) && parsed.choices.length > 0) {
69030
- const choice = parsed.choices[0];
69031
- if (choice?.delta || choice?.message || choice?.text || choice?.finish_reason)
69032
- return true;
69033
- }
69034
- if (parsed?.candidates)
69035
- return true;
69036
- return false;
69037
- }
69038
- function withDetail(base, message) {
69039
- return message ? `${base} \u2014 ${message}` : base;
69040
- }
69041
- function describeProbeState(result) {
69042
- const status = result.httpStatus ?? "";
69043
- const latency = result.latencyMs ? ` \xB7 ${result.latencyMs}ms` : "";
69044
- switch (result.state) {
69045
- case "live":
69046
- return `live \xB7 ${result.latencyMs}ms`;
69047
- case "key-missing":
69048
- return result.errorMessage ? `missing (${result.errorMessage})` : "missing";
69049
- case "auth-failed":
69050
- return withDetail(`auth failed \xB7 ${status}${latency}`.trim(), result.errorMessage);
69051
- case "model-not-found":
69052
- return withDetail(`model not found \xB7 ${status}${latency}`.trim(), result.errorMessage);
69053
- case "rate-limited":
69054
- return withDetail(`rate limited \xB7 ${result.latencyMs}ms`, result.errorMessage);
69055
- case "out-of-credit":
69056
- return withDetail(`out of credit \xB7 ${status}${latency}`.trim(), result.errorMessage);
69057
- case "server-error":
69058
- return withDetail(`server error \xB7 ${status} \xB7 ${result.latencyMs}ms`, result.errorMessage);
69059
- case "timeout":
69060
- return withDetail(`timeout \xB7 ${result.latencyMs}ms`, result.errorMessage);
69061
- case "network-error":
69062
- return withDetail(`network error \xB7 ${result.latencyMs}ms`, result.errorMessage);
69063
- case "error": {
69064
- const base = `error${result.httpStatus ? ` \xB7 ${result.httpStatus}` : ""}${latency}`;
69065
- return withDetail(base, result.errorMessage);
69066
- }
69067
- }
69068
- }
69069
- function isReadyState(state) {
69070
- return state === "live";
69071
- }
69072
- function isFailureState(state) {
69073
- return state === "auth-failed" || state === "model-not-found" || state === "rate-limited" || state === "out-of-credit" || state === "server-error" || state === "timeout" || state === "network-error" || state === "error";
69074
- }
69075
- var STREAM_MS_FLOOR = 50, OAUTH_PROVIDERS2, PROBE_PROMPT = "Count from one to twenty in words, one per line.", PROBE_MAX_TOKENS = 512;
69076
- var init_probe_live = __esm(() => {
69077
- OAUTH_PROVIDERS2 = new Set(["vertex", "antigravity", "devin"]);
69078
- });
69079
-
69080
69318
  // src/tui/theme.ts
69081
69319
  import { createTextAttributes } from "@opentui/core";
69082
69320
  function latencyBucket(ms) {
@@ -71350,6 +71588,21 @@ var init_api_key_map = __esm(() => {
71350
71588
  };
71351
71589
  });
71352
71590
 
71591
+ // src/providers/claude-code-aliases.ts
71592
+ function claudeCodeTierAlias(model) {
71593
+ return TIER_ALIASES[model.trim().toLowerCase()] ?? null;
71594
+ }
71595
+ var TIER_ALIASES;
71596
+ var init_claude_code_aliases = __esm(() => {
71597
+ TIER_ALIASES = {
71598
+ opus: "opus",
71599
+ sonnet: "sonnet",
71600
+ haiku: "haiku",
71601
+ internal: "opus",
71602
+ default: "opus"
71603
+ };
71604
+ });
71605
+
71353
71606
  // src/providers/probe-runner.ts
71354
71607
  function pinProbeModelSpec(link) {
71355
71608
  if (link.provider === "native-anthropic")
@@ -72041,7 +72294,7 @@ async function printRecommendedModels(jsonOutput, forceUpdate) {
72041
72294
  const rawId = m.id;
72042
72295
  const modelId = rawId.length > 28 ? `${rawId.substring(0, 25)}...` : rawId;
72043
72296
  const modelIdPadded = modelId.padEnd(28);
72044
- const pricing = normalizePricingDisplay(m.pricing?.average);
72297
+ const pricing = formatListingPrice(m, { compact: true });
72045
72298
  const pricingPadded = pricing.padEnd(10);
72046
72299
  const context = m.context || "N/A";
72047
72300
  const contextPadded = context.padEnd(6);
@@ -72133,13 +72386,19 @@ async function probeModelRouting(models, jsonOutput, options = { live: true, tim
72133
72386
  };
72134
72387
  }
72135
72388
  if (parsed.provider === "native-anthropic") {
72136
- const opusModel = process.env[ENV.CLAUDISH_MODEL_OPUS] || process.env[ENV.ANTHROPIC_DEFAULT_OPUS_MODEL] || "claude-opus-4-1";
72389
+ const tier = claudeCodeTierAlias(parsed.model);
72390
+ const tierEnv = {
72391
+ opus: process.env[ENV.CLAUDISH_MODEL_OPUS] || process.env[ENV.ANTHROPIC_DEFAULT_OPUS_MODEL],
72392
+ sonnet: process.env[ENV.CLAUDISH_MODEL_SONNET] || process.env[ENV.ANTHROPIC_DEFAULT_SONNET_MODEL],
72393
+ haiku: process.env[ENV.CLAUDISH_MODEL_HAIKU] || process.env[ENV.ANTHROPIC_DEFAULT_HAIKU_MODEL]
72394
+ };
72395
+ const opusModel = tier ? tierEnv[tier] || latestAnthropicTierModelId(tier) || "claude-opus-5" : parsed.model;
72137
72396
  return {
72138
72397
  routes: [
72139
72398
  {
72140
72399
  provider: "native-anthropic",
72141
72400
  modelSpec: opusModel,
72142
- displayName: "Claude Code (Opus)"
72401
+ displayName: tier ? `Claude Code (${tier})` : "Claude Code"
72143
72402
  }
72144
72403
  ],
72145
72404
  source: "auto-chain",
@@ -72233,6 +72492,33 @@ async function probeModelRouting(models, jsonOutput, options = { live: true, tim
72233
72492
  }
72234
72493
  return chain.source;
72235
72494
  }
72495
+ function buildDirectChainEntry(parsed, directProbe) {
72496
+ const providerDef = getProviderByName(parsed.provider);
72497
+ const keyInfo = API_KEY_MAP[parsed.provider];
72498
+ if (!providerDef && !keyInfo)
72499
+ return [];
72500
+ let hasCredentials;
72501
+ let provenance;
72502
+ if (providerDef?.isLocal) {
72503
+ hasCredentials = isLocalProviderEnabled(parsed.provider);
72504
+ } else if (!keyInfo?.envVar) {
72505
+ hasCredentials = true;
72506
+ } else {
72507
+ provenance = resolveApiKeyProvenance(keyInfo.envVar, keyInfo.aliases);
72508
+ hasCredentials = provenance.hasValue || (keyInfo.aliases?.some((a) => !!process.env[a]) ?? false);
72509
+ }
72510
+ return [
72511
+ {
72512
+ provider: parsed.provider,
72513
+ displayName: providerDef?.displayName ?? parsed.provider,
72514
+ modelSpec: parsed.model,
72515
+ hasCredentials,
72516
+ credentialHint: !hasCredentials ? providerDef?.isLocal ? "enable local provider in global config" : keyInfo?.envVar : undefined,
72517
+ provenance,
72518
+ probe: directProbe
72519
+ }
72520
+ ];
72521
+ }
72236
72522
  function buildResultLinks(parsed, chainDetails, directProbe) {
72237
72523
  if (chainDetails.length === 0) {
72238
72524
  const directProviderDef = getProviderByName(parsed.provider);
@@ -72383,7 +72669,7 @@ async function probeModelRouting(models, jsonOutput, options = { live: true, tim
72383
72669
  isExplicit: parsed.isExplicitProvider,
72384
72670
  routingSource: chain.source,
72385
72671
  matchedPattern: chain.matchedPattern,
72386
- chain: chainDetails,
72672
+ chain: chainDetails.length > 0 ? chainDetails : buildDirectChainEntry(parsed, directProbeResult),
72387
72673
  directProbe: directProbeResult,
72388
72674
  wiring
72389
72675
  });
@@ -73010,6 +73296,8 @@ var init_cli = __esm(() => {
73010
73296
  init_profile_config();
73011
73297
  init_api_key_map();
73012
73298
  init_api_key_provenance();
73299
+ init_catalog_client();
73300
+ init_claude_code_aliases();
73013
73301
  init_endpoint_registration();
73014
73302
  init_model_parser();
73015
73303
  init_probe_live();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "7.56.0",
3
+ "version": "7.58.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.56.0",
64
- "@claudish/magmux-darwin-x64": "7.56.0",
65
- "@claudish/magmux-linux-arm64": "7.56.0",
66
- "@claudish/magmux-linux-x64": "7.56.0"
63
+ "@claudish/magmux-darwin-arm64": "7.58.0",
64
+ "@claudish/magmux-darwin-x64": "7.58.0",
65
+ "@claudish/magmux-linux-arm64": "7.58.0",
66
+ "@claudish/magmux-linux-x64": "7.58.0"
67
67
  },
68
68
  "author": "Jack Rudenko <i@madappgang.com>",
69
69
  "license": "MIT",