claudish 10.2.0 → 10.3.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 +1538 -998
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -715,7 +715,7 @@ var init_onepassword_config = __esm(() => {
715
715
  });
716
716
 
717
717
  // src/version.ts
718
- var VERSION = "10.2.0";
718
+ var VERSION = "10.3.0";
719
719
 
720
720
  // src/logger.ts
721
721
  import { appendFile, existsSync as existsSync2, mkdirSync, readdirSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
@@ -5328,6 +5328,56 @@ var init_onepassword_command = __esm(() => {
5328
5328
  ` + " claudish --op 'op://Jack/My Item/*/*_API_KEY' --model gpt-4o 'task'";
5329
5329
  });
5330
5330
 
5331
+ // src/default-provider.ts
5332
+ function resolveDefaultProvider(opts) {
5333
+ const env = opts.env ?? process.env;
5334
+ if (opts.cliFlag && opts.cliFlag.length > 0) {
5335
+ return { provider: opts.cliFlag, source: "cli-flag", legacyAutoPromoted: false };
5336
+ }
5337
+ const envVal = env.CLAUDISH_DEFAULT_PROVIDER;
5338
+ if (envVal !== undefined) {
5339
+ return { provider: envVal, source: "env-var", legacyAutoPromoted: false };
5340
+ }
5341
+ const configured = opts.config.defaultProvider;
5342
+ if (typeof configured === "string") {
5343
+ return { provider: configured, source: "config-file", legacyAutoPromoted: false };
5344
+ }
5345
+ if (env.OPENROUTER_API_KEY) {
5346
+ return { provider: "openrouter", source: "openrouter-key", legacyAutoPromoted: false };
5347
+ }
5348
+ return { provider: "openrouter", source: "hardcoded", legacyAutoPromoted: false };
5349
+ }
5350
+ function planDefaultProviderFlag(argv) {
5351
+ const rest = [];
5352
+ let value;
5353
+ for (let i = 0;i < argv.length; i++) {
5354
+ const arg = argv[i];
5355
+ if (arg === "--") {
5356
+ rest.push(...argv.slice(i));
5357
+ break;
5358
+ }
5359
+ if (arg === DEFAULT_PROVIDER_FLAG) {
5360
+ const next = argv[i + 1];
5361
+ if (next === undefined || next.startsWith("-")) {
5362
+ return {
5363
+ kind: "error",
5364
+ message: `${DEFAULT_PROVIDER_FLAG} requires a provider name ("" for no fallback provider)`
5365
+ };
5366
+ }
5367
+ value = next;
5368
+ i++;
5369
+ continue;
5370
+ }
5371
+ if (arg.startsWith(`${DEFAULT_PROVIDER_FLAG}=`)) {
5372
+ value = arg.slice(DEFAULT_PROVIDER_FLAG.length + 1);
5373
+ continue;
5374
+ }
5375
+ rest.push(arg);
5376
+ }
5377
+ return value === undefined ? { kind: "none" } : { kind: "apply", value, argv: rest };
5378
+ }
5379
+ var DEFAULT_PROVIDER_FLAG = "--default-provider";
5380
+
5331
5381
  // ../../node_modules/.bun/zod@4.1.13/node_modules/zod/v4/core/core.js
5332
5382
  function $constructor(name, initializer, params) {
5333
5383
  function init(inst, def) {
@@ -18304,30 +18354,37 @@ var init_all_models_cache = __esm(() => {
18304
18354
 
18305
18355
  // src/providers/catalog-route-bindings.ts
18306
18356
  function catalogRouteForProvider(provider) {
18307
- return CATALOG_ROUTE_BINDINGS[provider];
18308
- }
18309
- function providerForCatalogRoute(route) {
18310
- return providersForCatalogRoute(route)[0];
18357
+ return CATALOG_ROUTE_BINDINGS[provider] ?? LOOKUP_ONLY_ROUTE_BINDINGS[provider];
18311
18358
  }
18312
- function providersForCatalogRoute(route) {
18359
+ function namesBoundTo(table, route) {
18313
18360
  if (!route)
18314
18361
  return [];
18315
- return Object.entries(CATALOG_ROUTE_BINDINGS).filter(([, binding]) => binding.routeId === route.routeId && binding.routeProfileId === route.routeProfileId).map(([provider]) => provider);
18362
+ return Object.entries(table).filter(([, binding]) => binding.routeId === route.routeId && binding.routeProfileId === route.routeProfileId).map(([name]) => name);
18363
+ }
18364
+ function routingProvidersForRoute(route) {
18365
+ return namesBoundTo(CATALOG_ROUTE_BINDINGS, route);
18366
+ }
18367
+ function catalogReadProvidersForRoute(route) {
18368
+ return [
18369
+ ...namesBoundTo(CATALOG_ROUTE_BINDINGS, route),
18370
+ ...namesBoundTo(LOOKUP_ONLY_ROUTE_BINDINGS, route)
18371
+ ];
18372
+ }
18373
+ function providerForCatalogRoute(route) {
18374
+ return catalogReadProvidersForRoute(route)[0];
18316
18375
  }
18317
18376
  function catalogRouteMatchesProvider(route, provider) {
18318
18377
  const binding = catalogRouteForProvider(provider);
18319
18378
  return route !== undefined && binding !== undefined && route.routeId === binding.routeId && route.routeProfileId === binding.routeProfileId;
18320
18379
  }
18321
- var CATALOG_ROUTE_BINDINGS;
18380
+ var CATALOG_ROUTE_BINDINGS, LOOKUP_ONLY_ROUTE_BINDINGS;
18322
18381
  var init_catalog_route_bindings = __esm(() => {
18323
18382
  CATALOG_ROUTE_BINDINGS = {
18324
18383
  "native-anthropic": { routeId: "anthropic", routeProfileId: "claude-code-subscription" },
18325
- anthropic: { routeId: "anthropic", routeProfileId: "direct-api" },
18326
18384
  "openai-codex": { routeId: "openai", routeProfileId: "codex-subscription" },
18327
18385
  openai: { routeId: "openai", routeProfileId: "direct-api" },
18328
18386
  "kimi-coding": { routeId: "moonshotai", routeProfileId: "kimi-code-subscription" },
18329
18387
  kimi: { routeId: "moonshotai", routeProfileId: "direct-api" },
18330
- moonshotai: { routeId: "moonshotai", routeProfileId: "direct-api" },
18331
18388
  "glm-coding": { routeId: "z-ai", routeProfileId: "glm-coding-subscription" },
18332
18389
  "z-ai": { routeId: "z-ai", routeProfileId: "direct-api" },
18333
18390
  glm: { routeId: "z-ai", routeProfileId: "direct-api" },
@@ -18345,7 +18402,6 @@ var init_catalog_route_bindings = __esm(() => {
18345
18402
  "qwen-payg": { routeId: "qwen", routeProfileId: "dashscope-direct" },
18346
18403
  "opencode-zen-go": { routeId: "opencode", routeProfileId: "go-subscription" },
18347
18404
  "opencode-zen": { routeId: "opencode", routeProfileId: "zen" },
18348
- zen: { routeId: "opencode", routeProfileId: "zen" },
18349
18405
  ollamacloud: { routeId: "ollama", routeProfileId: "cloud" },
18350
18406
  openrouter: { routeId: "openrouter", routeProfileId: "gateway" },
18351
18407
  together: { routeId: "together-ai", routeProfileId: "gateway" },
@@ -18355,6 +18411,11 @@ var init_catalog_route_bindings = __esm(() => {
18355
18411
  deepseek: { routeId: "deepseek", routeProfileId: "direct-api" },
18356
18412
  mistralai: { routeId: "mistralai", routeProfileId: "direct-api" }
18357
18413
  };
18414
+ LOOKUP_ONLY_ROUTE_BINDINGS = {
18415
+ anthropic: { routeId: "anthropic", routeProfileId: "direct-api" },
18416
+ moonshotai: { routeId: "moonshotai", routeProfileId: "direct-api" },
18417
+ zen: { routeId: "opencode", routeProfileId: "zen" }
18418
+ };
18358
18419
  });
18359
18420
 
18360
18421
  // src/providers/model-ordering.ts
@@ -28360,7 +28421,7 @@ class AntigravityProviderTransport {
28360
28421
  rewriteModelNotFound(response, capacityFallbacksExhausted = false) {
28361
28422
  const served = this.servedModels;
28362
28423
  if (!capacityFallbacksExhausted && served.includes(this.servedModelName)) {
28363
- log(`[Antigravity] 404 for ${this.servedModelName}, which IS in the served set \u2014 passing through unmodified`);
28424
+ log(`[Antigravity] 404 for ${this.servedModelName}, which IS in the dynamic models catalog \u2014 passing through unmodified`);
28364
28425
  return response;
28365
28426
  }
28366
28427
  response.text().catch(() => {});
@@ -32662,6 +32723,40 @@ var init_pro_injection = __esm(() => {
32662
32723
  init_session_events();
32663
32724
  });
32664
32725
 
32726
+ // src/providers/claude-code-aliases.ts
32727
+ function claudeCodeTierAlias(model) {
32728
+ return TIER_ALIASES[model.trim().toLowerCase()] ?? null;
32729
+ }
32730
+ function isClaudeCodeModelName(name) {
32731
+ const normalized = name.trim().toLowerCase();
32732
+ if (normalized.startsWith("claude-"))
32733
+ return true;
32734
+ const alias = normalized.endsWith(ONE_MILLION_CONTEXT_SUFFIX) ? normalized.slice(0, -ONE_MILLION_CONTEXT_SUFFIX.length) : normalized;
32735
+ return CLAUDE_CODE_MODEL_ALIASES.includes(alias);
32736
+ }
32737
+ function normalizeNativeModelSpec(spec) {
32738
+ return claudeCodeTierAlias(spec) ?? spec;
32739
+ }
32740
+ var TIER_ALIASES, CLAUDE_CODE_MODEL_ALIASES, ONE_MILLION_CONTEXT_SUFFIX = "[1m]";
32741
+ var init_claude_code_aliases = __esm(() => {
32742
+ TIER_ALIASES = {
32743
+ opus: "opus",
32744
+ sonnet: "sonnet",
32745
+ haiku: "haiku",
32746
+ internal: "opus",
32747
+ default: "opus"
32748
+ };
32749
+ CLAUDE_CODE_MODEL_ALIASES = [
32750
+ "opus",
32751
+ "sonnet",
32752
+ "haiku",
32753
+ "internal",
32754
+ "default",
32755
+ "opusplan",
32756
+ "best"
32757
+ ];
32758
+ });
32759
+
32665
32760
  // src/providers/model-parser.ts
32666
32761
  function parseModelChain(modelSpec) {
32667
32762
  const parts = modelSpec.split(MODEL_CHAIN_SEPARATOR).map((s) => s.trim()).filter(Boolean);
@@ -32744,13 +32839,18 @@ function parseModelSpec(modelSpec) {
32744
32839
  };
32745
32840
  }
32746
32841
  return {
32747
- provider: "native-anthropic",
32842
+ provider: lastResortProvider(modelSpec),
32748
32843
  model: modelSpec,
32749
32844
  original,
32750
32845
  isLegacySyntax: false,
32751
32846
  isExplicitProvider: false
32752
32847
  };
32753
32848
  }
32849
+ function lastResortProvider(modelSpec) {
32850
+ const name = modelSpec.trim();
32851
+ const isNative = name === "" || name.startsWith("@") || isClaudeCodeModelName(name);
32852
+ return isNative ? "native-anthropic" : AUTO_ROUTE_PROVIDER;
32853
+ }
32754
32854
  function isLocalProviderName(provider) {
32755
32855
  return LOCAL_PROVIDERS.has(provider.toLowerCase());
32756
32856
  }
@@ -32762,8 +32862,9 @@ function getLegacySyntaxWarning(parsed) {
32762
32862
  return `Deprecation warning: "${parsed.original}" uses legacy prefix syntax.
32763
32863
  ` + ` Consider using: ${newSyntax}`;
32764
32864
  }
32765
- var PROVIDER_SHORTCUTS, LOCAL_PROVIDERS, NATIVE_MODEL_PATTERNS, LEGACY_PREFIX_PATTERNS, MODEL_CHAIN_SEPARATOR = "+";
32865
+ var AUTO_ROUTE_PROVIDER = "auto-route", PROVIDER_SHORTCUTS, LOCAL_PROVIDERS, NATIVE_MODEL_PATTERNS, LEGACY_PREFIX_PATTERNS, MODEL_CHAIN_SEPARATOR = "+";
32766
32866
  var init_model_parser = __esm(() => {
32867
+ init_claude_code_aliases();
32767
32868
  init_provider_definitions();
32768
32869
  PROVIDER_SHORTCUTS = getShortcuts();
32769
32870
  LOCAL_PROVIDERS = {
@@ -37129,7 +37230,7 @@ class ComposedHandler {
37129
37230
  ...this.recoveryStats(recoveryTally)
37130
37231
  });
37131
37232
  } catch {}
37132
- return c.json(wrapAnthropicError(503, surfaced, "overloaded_error", undefined, settled.message), 503);
37233
+ return c.json(wrapAnthropicError(503, surfaced, "overloaded_error", undefined, settled.message), 503, settled.unreachable ? connectionFaultHeaders() : undefined);
37133
37234
  }
37134
37235
  response = settled.response;
37135
37236
  }
@@ -37172,7 +37273,7 @@ class ComposedHandler {
37172
37273
  ...this.recoveryStats(recoveryTally)
37173
37274
  });
37174
37275
  } catch {}
37175
- return isTerminal ? c.json(wrapAnthropicError(400, surfaced, "invalid_request_error", undefined, settled.message), 400) : c.json(wrapAnthropicError(503, surfaced, "overloaded_error", undefined, settled.message), 503);
37276
+ return isTerminal ? c.json(wrapAnthropicError(400, surfaced, "invalid_request_error", undefined, settled.message), 400) : c.json(wrapAnthropicError(503, surfaced, "overloaded_error", undefined, settled.message), 503, settled.kind === "exhausted" && settled.unreachable ? connectionFaultHeaders() : undefined);
37176
37277
  }
37177
37278
  response = settled.response;
37178
37279
  }
@@ -37242,7 +37343,8 @@ class ComposedHandler {
37242
37343
  kind: "exhausted",
37243
37344
  code: verdict.code,
37244
37345
  message: `${verdict.message} (retry could not reach the provider: ${error})`,
37245
- attempts: attempt + 1
37346
+ attempts: attempt + 1,
37347
+ unreachable: true
37246
37348
  };
37247
37349
  }
37248
37350
  if (!next.ok) {
@@ -37291,7 +37393,8 @@ class ComposedHandler {
37291
37393
  kind: "exhausted",
37292
37394
  code: verdict.code,
37293
37395
  message: `${verdict.message} (retry could not reach the provider: ${error})`,
37294
- attempts: attempt + 1
37396
+ attempts: attempt + 1,
37397
+ unreachable: true
37295
37398
  };
37296
37399
  }
37297
37400
  if (!next.ok) {
@@ -40116,16 +40219,9 @@ function requireKeyUnlessNoAuth(ep, ctx) {
40116
40219
  });
40117
40220
  }
40118
40221
  }
40119
- var BuiltinDefaultProviderSchema, CustomEndpointAuthSchemeSchema, CustomEndpointSimpleSchema, CustomEndpointComplexSchema, CustomEndpointSchema, PredefinedEndpointsConfigSchema, DefaultProviderSchema;
40222
+ var CustomEndpointAuthSchemeSchema, CustomEndpointSimpleSchema, CustomEndpointComplexSchema, CustomEndpointSchema, PredefinedEndpointsConfigSchema;
40120
40223
  var init_config_schema = __esm(() => {
40121
40224
  init_zod();
40122
- BuiltinDefaultProviderSchema = _enum([
40123
- "openrouter",
40124
- "litellm",
40125
- "openai",
40126
- "anthropic",
40127
- "google"
40128
- ]);
40129
40225
  CustomEndpointAuthSchemeSchema = _enum(["bearer", "x-api-key", "none"]);
40130
40226
  CustomEndpointSimpleSchema = object2({
40131
40227
  kind: literal("simple"),
@@ -40158,7 +40254,6 @@ var init_config_schema = __esm(() => {
40158
40254
  disable: array(string2()).optional(),
40159
40255
  enable: array(string2()).optional()
40160
40256
  });
40161
- DefaultProviderSchema = union([BuiltinDefaultProviderSchema, string2().min(1)]);
40162
40257
  });
40163
40258
 
40164
40259
  // src/providers/endpoint-diagnostics.ts
@@ -40991,6 +41086,54 @@ var init_endpoint_registration = __esm(() => {
40991
41086
  warnedMessages4 = new Set;
40992
41087
  });
40993
41088
 
41089
+ // src/providers/native-route.ts
41090
+ function proxyRouteDecision(target) {
41091
+ const parsed = parseModelSpec(target);
41092
+ if (parsed.isExplicitProvider) {
41093
+ return {
41094
+ type: "explicit",
41095
+ provider: parsed.provider,
41096
+ model: parsed.model,
41097
+ spec: target,
41098
+ via: "model-spec"
41099
+ };
41100
+ }
41101
+ if (target.startsWith(POE_PREFIX)) {
41102
+ return { type: "poe", model: target.slice(POE_PREFIX.length) };
41103
+ }
41104
+ if (parsed.provider === "native-anthropic") {
41105
+ if (target.includes("/")) {
41106
+ return {
41107
+ type: "explicit",
41108
+ provider: "openrouter",
41109
+ model: target,
41110
+ spec: target,
41111
+ via: "vendor-qualified-id"
41112
+ };
41113
+ }
41114
+ return { type: "native", route: nativeRouteOf(target) };
41115
+ }
41116
+ return { type: "bare", model: parsed.model };
41117
+ }
41118
+ function nativeRouteOf(model) {
41119
+ return {
41120
+ provider: "native-anthropic",
41121
+ modelSpec: normalizeNativeModelSpec(model),
41122
+ displayName: getProviderByName("native-anthropic")?.displayName ?? "Anthropic (Native)",
41123
+ isTierAlias: claudeCodeTierAlias(model) !== null
41124
+ };
41125
+ }
41126
+ function nativeRouteFor(model) {
41127
+ const decision = proxyRouteDecision(model);
41128
+ return decision.type === "native" ? decision.route : null;
41129
+ }
41130
+ var NATIVE_NOT_PROBED = "not probed \u2014 served on Claude Code's own auth, which this process cannot forward", POE_PREFIX = "poe:";
41131
+ var init_native_route = __esm(() => {
41132
+ init_claude_code_aliases();
41133
+ init_model_parser();
41134
+ init_provider_definitions();
41135
+ });
41136
+
40994
41137
  // src/providers/provider-registry.ts
40995
41138
  function resolveBaseUrl2(envVar, fallbackEnvVars, staticDefault) {
40996
41139
  for (const v of [envVar, ...fallbackEnvVars]) {
@@ -41158,7 +41301,7 @@ var init_remote_provider_registry = __esm(() => {
41158
41301
  });
41159
41302
 
41160
41303
  // src/providers/routing-hints.ts
41161
- function buildCredentialHint(modelName, providers) {
41304
+ function buildCredentialHint(modelName, providers, options = {}) {
41162
41305
  const seen = new Set;
41163
41306
  const lines = [`No credentials found for "${modelName}". Options:`];
41164
41307
  let hasOption = false;
@@ -41182,7 +41325,7 @@ function buildCredentialHint(modelName, providers) {
41182
41325
  hasOption = true;
41183
41326
  }
41184
41327
  }
41185
- if (!seen.has("openrouter")) {
41328
+ if (!seen.has("openrouter") && options.suggestOpenRouter !== false) {
41186
41329
  lines.push(` Use: claudish --model or@${modelName} (route via OpenRouter)`);
41187
41330
  hasOption = true;
41188
41331
  }
@@ -41415,17 +41558,17 @@ async function fetchProbeModels(url = PROBE_MODELS_URL, timeoutMs = FETCH_TIMEOU
41415
41558
  for (const route of Object.values(envelope.data.routes)) {
41416
41559
  if (!route || typeof route !== "object" || typeof route.externalModelId !== "string")
41417
41560
  continue;
41418
- const provider = providerForCatalogRoute(route.route);
41419
- if (provider)
41561
+ for (const provider of catalogReadProvidersForRoute(route.route)) {
41420
41562
  providers[provider] = route.externalModelId;
41563
+ }
41421
41564
  }
41422
41565
  const unavailable = {};
41423
41566
  for (const route of Object.values(envelope.data.unavailableRoutes)) {
41424
41567
  if (!route || typeof route !== "object" || typeof route.reason !== "string")
41425
41568
  continue;
41426
- const provider = providerForCatalogRoute(route.route);
41427
- if (provider)
41569
+ for (const provider of catalogReadProvidersForRoute(route.route)) {
41428
41570
  unavailable[provider] = route.reason;
41571
+ }
41429
41572
  }
41430
41573
  if (Object.keys(providers).length + Object.keys(unavailable).length === 0) {
41431
41574
  return { kind: "invalid", reason: "no supported probe routes" };
@@ -41518,6 +41661,9 @@ var init_probe_catalog = __esm(() => {
41518
41661
  });
41519
41662
 
41520
41663
  // src/providers/route-candidates.ts
41664
+ function bandRank(tier) {
41665
+ return tier === "dynamic-subscription" ? TIER_RANK.subscription : TIER_RANK[tier];
41666
+ }
41521
41667
  function routeLabel(route) {
41522
41668
  return route ? `${route.routeId}/${route.routeProfileId}` : "(no route)";
41523
41669
  }
@@ -41538,7 +41684,7 @@ function gatherFromConnections(entry, candidates, unmappedRoutes) {
41538
41684
  for (const connection of entry.aggregators ?? []) {
41539
41685
  if (connection.routeStatus !== "mapped")
41540
41686
  continue;
41541
- const bound = providersForCatalogRoute(connection.route);
41687
+ const bound = routingProvidersForRoute(connection.route);
41542
41688
  const routable = bound.filter((name) => getProviderByName(name)?.tier !== undefined);
41543
41689
  if (routable.length === 0) {
41544
41690
  unmappedRoutes.add(routeLabel(connection.route));
@@ -41566,6 +41712,15 @@ function gatherFromConnections(entry, candidates, unmappedRoutes) {
41566
41712
  function isVendorOwnRoute(route, entry) {
41567
41713
  return route !== undefined && entry?.provider !== undefined && route.routeId === entry.provider;
41568
41714
  }
41715
+ function nativeProviderForVendor(vendorSlug) {
41716
+ for (const [provider, binding] of Object.entries(CATALOG_ROUTE_BINDINGS)) {
41717
+ if (binding.routeId !== vendorSlug)
41718
+ continue;
41719
+ if (getProviderByName(provider)?.tier === "native")
41720
+ return provider;
41721
+ }
41722
+ return;
41723
+ }
41569
41724
  function gatherFromNamespaceClaims(model, entry, candidates) {
41570
41725
  for (const def of getAllProviders()) {
41571
41726
  if (def.tier !== "dynamic-subscription")
@@ -41583,11 +41738,14 @@ function gatherFromNamespaceClaims(model, entry, candidates) {
41583
41738
  }
41584
41739
  }
41585
41740
  function compareRouteCandidates(a, b) {
41741
+ const byBand = bandRank(a.tier) - bandRank(b.tier);
41742
+ if (byBand !== 0)
41743
+ return byBand;
41744
+ if (a.isVendorOwn !== b.isVendorOwn)
41745
+ return a.isVendorOwn ? -1 : 1;
41586
41746
  const byTier = TIER_RANK[a.tier] - TIER_RANK[b.tier];
41587
41747
  if (byTier !== 0)
41588
41748
  return byTier;
41589
- if (a.isVendorOwn !== b.isVendorOwn)
41590
- return a.isVendorOwn ? -1 : 1;
41591
41749
  const byPrice = compareByConnectionPrice(a.price, b.price);
41592
41750
  if (byPrice !== 0)
41593
41751
  return byPrice;
@@ -41666,44 +41824,79 @@ var init_route_candidates = __esm(() => {
41666
41824
 
41667
41825
  // src/providers/routing-rules.ts
41668
41826
  function loadRoutingRules(sources) {
41669
- const local = sources ? sources.localRules : loadLocalConfig()?.routing ?? {};
41670
- const global_ = sources ? sources.globalRules : loadConfig().routing ?? {};
41671
- validateRoutingRules(local);
41672
- validateRoutingRules(global_);
41827
+ const { localRules: local, globalRules: global_ } = sources ?? loadRoutingRuleSources();
41673
41828
  return { ...global_, ...local };
41674
41829
  }
41675
- function validateRoutingRules(rules) {
41830
+ function loadRoutingRuleSources() {
41831
+ const localRules = loadLocalConfig()?.routing ?? {};
41832
+ const globalRules = loadConfig().routing ?? {};
41833
+ return { globalRules, localRules };
41834
+ }
41835
+ function routingRuleProblems(sources) {
41836
+ return [
41837
+ ...ruleTableProblems(sources.globalRules, "global"),
41838
+ ...ruleTableProblems(sources.localRules, "project")
41839
+ ];
41840
+ }
41841
+ function ruleTableProblems(rules, scope) {
41842
+ const problems = [];
41676
41843
  const seenLower = new Map;
41677
41844
  for (const key of Object.keys(rules)) {
41678
41845
  if (key !== "*" && (key.match(/\*/g) || []).length > 1) {
41679
- console.error(`[claudish] Warning: routing pattern "${key}" has multiple wildcards \u2014 only single * is supported. This pattern may not match as expected.`);
41846
+ problems.push({ scope, pattern: key, problem: "multiple-wildcards" });
41680
41847
  }
41681
41848
  const lower = key.toLowerCase();
41682
41849
  const prior = seenLower.get(lower);
41683
41850
  if (prior !== undefined && prior !== key) {
41684
- console.error(`[claudish] Warning: routing patterns "${prior}" and "${key}" collide case-insensitively. Matching is case-insensitive, so one will silently shadow the other. Pick one casing and remove the duplicate.`);
41851
+ problems.push({ scope, pattern: key, problem: "case-collision", collidesWith: prior });
41685
41852
  } else {
41686
41853
  seenLower.set(lower, key);
41687
41854
  }
41855
+ for (const entry of rules[key] ?? []) {
41856
+ if (!isKnownProvider(ruleEntryProvider(entry))) {
41857
+ problems.push({ scope, pattern: key, problem: "unknown-provider", entry });
41858
+ }
41859
+ }
41860
+ }
41861
+ return problems;
41862
+ }
41863
+ function ruleEntryProvider(entry) {
41864
+ const atIdx = entry.indexOf("@");
41865
+ const providerRaw = atIdx === -1 ? entry : entry.slice(0, atIdx);
41866
+ return PROVIDER_SHORTCUTS[providerRaw.toLowerCase()] ?? providerRaw.toLowerCase();
41867
+ }
41868
+ function isKnownProvider(provider) {
41869
+ return getProviderByName(provider) !== undefined || PREDEFINED_ENDPOINTS.some((row) => row.name.toLowerCase() === provider);
41870
+ }
41871
+ function describeRoutingRuleProblem(problem) {
41872
+ switch (problem.problem) {
41873
+ case "multiple-wildcards":
41874
+ return `routing pattern "${problem.pattern}" (${problem.scope}) has more than one * \u2014 ` + "only a single * is supported, so it may not match as expected.";
41875
+ case "case-collision":
41876
+ return `routing patterns "${problem.collidesWith}" and "${problem.pattern}" (${problem.scope}) ` + "differ only in case. Matching ignores case, so one silently shadows the other: " + "pick one casing and remove the duplicate.";
41877
+ case "unknown-provider": {
41878
+ const entry = problem.entry ?? "";
41879
+ return `routing rule "${problem.pattern}" (${problem.scope}) names "${entry}", but no ` + `provider "${ruleEntryProvider(entry)}" exists, so that entry never routes.`;
41880
+ }
41688
41881
  }
41689
41882
  }
41690
- function matchRoutingRule(modelName, rules) {
41883
+ function matchRoutingRuleKey(modelName, rules) {
41691
41884
  const lowered = modelName.toLowerCase();
41692
- for (const [key, entries] of Object.entries(rules)) {
41885
+ for (const key of Object.keys(rules)) {
41693
41886
  if (!key.includes("*") && key.toLowerCase() === lowered)
41694
- return entries;
41887
+ return key;
41695
41888
  }
41696
41889
  const globKeys = Object.keys(rules).filter((k) => k !== "*" && k.includes("*")).sort((a, b) => b.length - a.length);
41697
41890
  for (const pattern of globKeys) {
41698
41891
  if (globMatch(pattern, modelName))
41699
- return rules[pattern];
41892
+ return pattern;
41700
41893
  }
41701
41894
  if (rules["*"] !== undefined)
41702
- return rules["*"];
41895
+ return "*";
41703
41896
  return null;
41704
41897
  }
41705
- function buildRoutingChain(entries, originalModelName, cachePath) {
41706
- const routes = [];
41898
+ function resolveRoutingEntries(entries, originalModelName, cachePath) {
41899
+ const resolved = [];
41707
41900
  for (const entry of entries) {
41708
41901
  const atIdx = entry.indexOf("@");
41709
41902
  let providerRaw;
@@ -41719,8 +41912,10 @@ function buildRoutingChain(entries, originalModelName, cachePath) {
41719
41912
  let wireIdResolved = false;
41720
41913
  if (atIdx === -1) {
41721
41914
  const routing = resolveSubscriptionRouting(modelName, provider, cachePath);
41722
- if (routing.kind === "not-served")
41915
+ if (routing.kind === "not-served") {
41916
+ resolved.push({ route: routeFor(provider, modelName), excludedByMembership: true });
41723
41917
  continue;
41918
+ }
41724
41919
  if (routing.kind === "serves") {
41725
41920
  modelName = routing.externalId;
41726
41921
  wireIdResolved = true;
@@ -41729,9 +41924,9 @@ function buildRoutingChain(entries, originalModelName, cachePath) {
41729
41924
  if (!wireIdResolved) {
41730
41925
  modelName = resolveExternalId(modelName, provider, cachePath) ?? modelName;
41731
41926
  }
41732
- routes.push(routeFor(provider, modelName));
41927
+ resolved.push({ route: routeFor(provider, modelName), excludedByMembership: false });
41733
41928
  }
41734
- return routes;
41929
+ return resolved;
41735
41930
  }
41736
41931
  function routeFor(provider, wireId) {
41737
41932
  const modelSpec = provider === "openrouter" ? wireId : `${PROVIDER_TO_PREFIX[provider] ?? provider}@${wireId}`;
@@ -41750,29 +41945,67 @@ function globMatch(pattern, value) {
41750
41945
  async function hasCredentialsForProvider(provider) {
41751
41946
  return credentials.isAvailable(provider);
41752
41947
  }
41753
- async function routeExplicit(modelSpec, model, provider, cachePath) {
41754
- if (!await hasCredentialsForProvider(provider)) {
41755
- return {
41948
+ function explainCandidate(route, position, outcome, gathered) {
41949
+ const tier = gathered ? gathered.tier : getProviderByName(route.provider)?.tier;
41950
+ return {
41951
+ provider: route.provider,
41952
+ displayName: route.displayName,
41953
+ modelSpec: route.modelSpec,
41954
+ wireId: wireIdOf(route),
41955
+ position,
41956
+ ...tier !== undefined ? { tier } : {},
41957
+ ...gathered ? { isVendorOwn: gathered.isVendorOwn, price: gathered.price } : {},
41958
+ ...gathered?.contextWindow !== undefined ? { contextWindow: gathered.contextWindow } : {},
41959
+ outcome
41960
+ };
41961
+ }
41962
+ function entryOutcome(resolved) {
41963
+ return resolved.excludedByMembership ? "excluded-by-membership" : "kept";
41964
+ }
41965
+ async function explainExplicitSpec(requestedModel, modelSpec, model, provider, cachePath) {
41966
+ const credentialed = await hasCredentialsForProvider(provider);
41967
+ const [resolved] = resolveRoutingEntries([modelSpec], model, cachePath);
41968
+ const candidate = explainCandidate(resolved.route, "candidate", entryOutcome(resolved));
41969
+ const explanation = {
41970
+ requestedModel,
41971
+ routedModel: model,
41972
+ source: "explicit",
41973
+ via: "model-spec",
41974
+ candidates: [candidate],
41975
+ outcome: { kind: "ok" },
41976
+ warnings: []
41977
+ };
41978
+ if (!credentialed) {
41979
+ candidate.outcome = "no-credential";
41980
+ explanation.outcome = {
41756
41981
  kind: "no-route",
41982
+ cause: "explicit-no-credential",
41757
41983
  reason: `No credentials configured for "${provider}".`,
41758
41984
  hint: buildCredentialHint(model, [provider]) ?? undefined
41759
41985
  };
41986
+ return explanation;
41760
41987
  }
41761
- const built = buildRoutingChain([modelSpec], model, cachePath)[0];
41762
- if (!built) {
41763
- return {
41988
+ if (resolved.excludedByMembership) {
41989
+ explanation.outcome = {
41764
41990
  kind: "no-route",
41991
+ cause: "explicit-unbuildable",
41765
41992
  reason: `Could not build a route for "${modelSpec}".`
41766
41993
  };
41994
+ return explanation;
41767
41995
  }
41768
- if (await providerServesModel(built.provider, wireIdOf(built)) === "not-served") {
41769
- return {
41996
+ const availability = await providerServesModel(candidate.provider, candidate.wireId);
41997
+ if (availability === "not-served") {
41998
+ candidate.outcome = "not-served";
41999
+ explanation.outcome = {
41770
42000
  kind: "no-route",
41771
- reason: `${built.displayName} does not serve "${model}".`,
42001
+ cause: "explicit-not-served",
42002
+ reason: `${candidate.displayName} does not serve "${model}".`,
41772
42003
  hint: `Check the model id, or use a bare \`${model}\` to let claudish pick a provider ` + "that carries it."
41773
42004
  };
42005
+ return explanation;
41774
42006
  }
41775
- return { kind: "ok", primary: built, fallbacks: [] };
42007
+ candidate.availability = availability;
42008
+ return explanation;
41776
42009
  }
41777
42010
  function fallbackProviderFor(defaultProvider) {
41778
42011
  if (defaultProvider !== undefined && defaultProvider.length === 0)
@@ -41780,39 +42013,100 @@ function fallbackProviderFor(defaultProvider) {
41780
42013
  const named = defaultProvider ?? DEFAULT_FALLBACK_PROVIDER;
41781
42014
  return PROVIDER_SHORTCUTS[named.toLowerCase()] ?? named.toLowerCase();
41782
42015
  }
41783
- function buildCatalogChain(model, defaultProvider, cachePath) {
42016
+ function effectiveDefaultProvider() {
42017
+ return resolveDefaultProvider({ config: loadConfig(), env: process.env }).provider;
42018
+ }
42019
+ function explainCatalogChain(model, defaultProvider, cachePath) {
41784
42020
  ensureEndpointsRegistered();
41785
42021
  const gathering = gatherRouteCandidates(model, cachePath);
41786
- const routes = gathering.candidates.map((candidate) => routeFor(candidate.provider, candidate.wireId));
42022
+ const candidates = gathering.candidates.map((candidate) => explainCandidate(routeFor(candidate.provider, candidate.wireId), "candidate", "kept", candidate));
42023
+ const catalog = !gathering.catalogReadable ? "unreadable" : gathering.catalogMiss ? "absent" : "found";
41787
42024
  const fallback = fallbackProviderFor(defaultProvider);
41788
- if (fallback && gathering.catalogReadable && !routes.some((route) => route.provider === fallback) && !catalogDeniesProvider(fallback, model, cachePath)) {
41789
- routes.push(...buildRoutingChain([fallback], model, cachePath));
42025
+ let fallbackWithheld;
42026
+ if (!fallback) {
42027
+ fallbackWithheld = "disabled";
42028
+ } else if (!gathering.catalogReadable) {
42029
+ fallbackWithheld = "catalog-unreadable";
42030
+ } else if (candidates.some((candidate) => candidate.provider === fallback)) {
42031
+ fallbackWithheld = "already-gathered";
42032
+ } else if (catalogDeniesProvider(fallback, model, cachePath)) {
42033
+ fallbackWithheld = "catalog-denies";
42034
+ } else {
42035
+ for (const resolved of resolveRoutingEntries([fallback], model, cachePath)) {
42036
+ candidates.push(explainCandidate(resolved.route, "fallback", entryOutcome(resolved)));
42037
+ }
42038
+ }
42039
+ return { candidates, catalog, ...fallbackWithheld ? { fallbackWithheld } : {} };
42040
+ }
42041
+ function emptyChainOutcome(model, nativeProvider, cause, cachePath) {
42042
+ return {
42043
+ kind: "no-route",
42044
+ cause,
42045
+ reason: cause === "rule-empty" ? `A routing rule matched "${model}" and named no provider.` : `No provider in the catalog serves "${model}".`,
42046
+ hint: emptyChainHint(model, nativeProvider, cause, cachePath)
42047
+ };
42048
+ }
42049
+ function emptyChainHint(model, nativeProvider, cause, cachePath) {
42050
+ if (nativeProvider !== AUTO_ROUTE_PROVIDER) {
42051
+ return buildCredentialHint(model, [nativeProvider]) ?? undefined;
41790
42052
  }
41791
- return { routes, catalogReadable: gathering.catalogReadable };
42053
+ const suggestOpenRouter = cause === "rule-empty" || !catalogDeniesProvider("openrouter", model, cachePath);
42054
+ return buildCredentialHint(model, [], { suggestOpenRouter }) ?? undefined;
41792
42055
  }
41793
- async function routeBare(model, nativeProvider, rules, defaultProvider, cachePath) {
41794
- const matched = matchRoutingRule(model, rules);
41795
- let candidates;
41796
- if (matched !== null) {
41797
- candidates = buildRoutingChain(matched, model, cachePath);
42056
+ function ruleProblemWarnings(problems) {
42057
+ return (problems ?? []).map((problem) => ({
42058
+ type: "rule-problem",
42059
+ problem,
42060
+ message: describeRoutingRuleProblem(problem)
42061
+ }));
42062
+ }
42063
+ async function explainBareName(requestedModel, model, nativeProvider, routing, cachePath) {
42064
+ const matchedKey = matchRoutingRuleKey(model, routing.rules);
42065
+ const matched = matchedKey === null ? null : routing.rules[matchedKey];
42066
+ let explanation;
42067
+ if (matchedKey !== null && matched !== null) {
42068
+ explanation = {
42069
+ requestedModel,
42070
+ routedModel: model,
42071
+ source: "user-rule",
42072
+ matchedPattern: matchedKey,
42073
+ ...routing.scopeOf ? { ruleScope: routing.scopeOf(matchedKey) } : {},
42074
+ candidates: resolveRoutingEntries(matched, model, cachePath).map((resolved) => explainCandidate(resolved.route, "candidate", entryOutcome(resolved))),
42075
+ outcome: { kind: "ok" },
42076
+ warnings: ruleProblemWarnings(routing.ruleProblems)
42077
+ };
41798
42078
  } else {
41799
- const gathered = buildCatalogChain(model, defaultProvider, cachePath);
41800
- if (!gathered.catalogReadable && gathered.routes.length === 0) {
41801
- return {
41802
- kind: "no-route",
41803
- reason: `No model catalog available, so "${model}" cannot be routed by name.`,
41804
- hint: "Run `claudish --models-refresh` to fetch the catalog, or name the provider " + `explicitly (e.g. \`openrouter@${model}\`).`
41805
- };
41806
- }
41807
- candidates = gathered.routes;
42079
+ const chain = explainCatalogChain(model, routing.defaultProvider, cachePath);
42080
+ explanation = {
42081
+ requestedModel,
42082
+ routedModel: model,
42083
+ source: "catalog",
42084
+ catalog: chain.catalog,
42085
+ candidates: chain.candidates,
42086
+ ...chain.fallbackWithheld ? { fallbackWithheld: chain.fallbackWithheld } : {},
42087
+ outcome: { kind: "ok" },
42088
+ warnings: ruleProblemWarnings(routing.ruleProblems)
42089
+ };
41808
42090
  }
41809
- if (candidates.length === 0) {
41810
- return {
42091
+ const remaining = explanation.candidates.filter((candidate) => candidate.outcome !== "excluded-by-membership");
42092
+ if (explanation.catalog === "unreadable" && remaining.length === 0) {
42093
+ explanation.outcome = {
41811
42094
  kind: "no-route",
41812
- reason: matched !== null ? `A routing rule matched "${model}" and named no provider.` : `No provider in the catalog serves "${model}".`,
41813
- hint: buildCredentialHint(model, [nativeProvider]) ?? undefined
42095
+ cause: "catalog-unreadable",
42096
+ reason: `No model catalog available, so "${model}" cannot be routed by name.`,
42097
+ hint: "Run `claudish --models-refresh` to fetch the catalog, or name the provider " + `explicitly (e.g. \`openrouter@${model}\`).`
41814
42098
  };
42099
+ return explanation;
42100
+ }
42101
+ if (remaining.length === 0) {
42102
+ explanation.outcome = emptyChainOutcome(model, nativeProvider, explanation.source === "user-rule" ? "rule-empty" : "catalog-empty", cachePath);
42103
+ return explanation;
41815
42104
  }
42105
+ await applyCandidateFilters(explanation, remaining);
42106
+ return explanation;
42107
+ }
42108
+ async function applyCandidateFilters(explanation, candidates) {
42109
+ const model = explanation.routedModel;
41816
42110
  const credentialed = [];
41817
42111
  const skipped = [];
41818
42112
  const skippedFailed = [];
@@ -41830,45 +42124,63 @@ async function routeBare(model, nativeProvider, rules, defaultProvider, cachePat
41830
42124
  return;
41831
42125
  }
41832
42126
  skipped.push(candidate.provider);
41833
- if (verdict.readiness === "failed")
42127
+ if (verdict.readiness === "failed") {
41834
42128
  skippedFailed.push(candidate.provider);
42129
+ candidate.outcome = "credential-unreadable";
42130
+ } else {
42131
+ candidate.outcome = "no-credential";
42132
+ }
41835
42133
  });
41836
42134
  if (credentialed.length === 0) {
41837
- return {
42135
+ explanation.outcome = {
41838
42136
  kind: "no-route",
42137
+ cause: "no-credential",
41839
42138
  reason: skipped.length > 0 ? `No credentialed providers in chain for "${model}" (tried: ${skipped.join(", ")}).` : `No providers available for "${model}".`,
41840
42139
  hint: buildCredentialHint(model, skipped) ?? undefined
41841
42140
  };
42141
+ return;
41842
42142
  }
41843
- const availability = await Promise.all(credentialed.map((candidate) => providerServesModel(candidate.provider, wireIdOf(candidate))));
42143
+ const availability = await Promise.all(credentialed.map((candidate) => providerServesModel(candidate.provider, candidate.wireId)));
41844
42144
  const serving = [];
41845
42145
  const notServing = [];
41846
42146
  credentialed.forEach((candidate, i) => {
41847
- if (availability[i] === "not-served") {
42147
+ const verdict = availability[i];
42148
+ if (verdict === "not-served") {
41848
42149
  notServing.push(candidate.provider);
42150
+ candidate.outcome = "not-served";
41849
42151
  } else {
41850
42152
  serving.push(candidate);
42153
+ candidate.availability = verdict;
41851
42154
  }
41852
42155
  });
41853
42156
  if (serving.length === 0) {
41854
- return {
42157
+ explanation.outcome = {
41855
42158
  kind: "no-route",
42159
+ cause: "not-served",
41856
42160
  reason: `No provider serves "${model}" (checked: ${notServing.join(", ")}).`,
41857
42161
  hint: buildCredentialHint(model, notServing) ?? undefined
41858
42162
  };
42163
+ return;
41859
42164
  }
41860
42165
  if (notServing.length > 0) {
41861
- log(`[routing] ${model}: skipped ${notServing.join(", ")} \u2014 does not serve this model`);
41862
42166
  const droppedSubscription = notServing.filter((p) => isSubscriptionProvider(p));
41863
42167
  if (droppedSubscription.length > 0 && !isSubscriptionProvider(serving[0].provider)) {
41864
- logStderr(`${droppedSubscription.join(", ")} does not serve ${model} \u2014 ` + `using ${serving[0].displayName}, which bills per token.`);
42168
+ explanation.warnings.push({
42169
+ type: "subscription-not-served",
42170
+ providers: droppedSubscription,
42171
+ usedInstead: serving[0].provider,
42172
+ message: `${droppedSubscription.join(", ")} does not serve ${model} \u2014 ` + `using ${serving[0].displayName}, which bills per token.`
42173
+ });
41865
42174
  }
41866
42175
  }
41867
42176
  if (skippedFailed.length > 0 && !isSubscriptionProvider(serving[0].provider)) {
41868
- logStderr(`${skippedFailed.join(", ")}: the credential could not be READ (not "no key") \u2014 ` + `using ${serving[0].displayName}, which bills per token.`);
42177
+ explanation.warnings.push({
42178
+ type: "subscription-credential-unreadable",
42179
+ providers: skippedFailed,
42180
+ usedInstead: serving[0].provider,
42181
+ message: `${skippedFailed.join(", ")}: the credential could not be READ (not "no key") \u2014 ` + `using ${serving[0].displayName}, which bills per token.`
42182
+ });
41869
42183
  }
41870
- const [primary, ...fallbacks] = serving;
41871
- return { kind: "ok", primary, fallbacks };
41872
42184
  }
41873
42185
  function wireIdOf(route) {
41874
42186
  const at = route.modelSpec.indexOf("@");
@@ -41878,15 +42190,174 @@ function normalizeGlmSlug(model) {
41878
42190
  return model.replace(/^glm-(\d+)-(\d+)(-.*)?$/i, (_m, major, minor, suffix) => `glm-${major}.${minor}${suffix ?? ""}`);
41879
42191
  }
41880
42192
  async function route(modelSpec, rulesOverride, defaultProviderOverride, cachePath) {
42193
+ const explanation = await explainRoutePlan(modelSpec, () => {
42194
+ const rules = rulesOverride ?? loadRoutingRules();
42195
+ const defaultProvider = defaultProviderOverride !== undefined ? defaultProviderOverride : rulesOverride !== undefined ? undefined : effectiveDefaultProvider();
42196
+ return { rules, defaultProvider };
42197
+ }, cachePath);
42198
+ emitRouteNotices(explanation);
42199
+ return toRoutePlan(explanation);
42200
+ }
42201
+ async function explainRoutePlan(modelSpec, bareRouting, cachePath, requestedModel = modelSpec) {
41881
42202
  const parsed = parseModelSpec(modelSpec);
41882
42203
  if (parsed.isExplicitProvider) {
41883
- return routeExplicit(modelSpec, parsed.model, parsed.provider, cachePath);
42204
+ return explainExplicitSpec(requestedModel, modelSpec, parsed.model, parsed.provider, cachePath);
42205
+ }
42206
+ return explainBareName(requestedModel, normalizeGlmSlug(parsed.model), parsed.provider, bareRouting(), cachePath);
42207
+ }
42208
+ async function explainRoute(target, opts = {}) {
42209
+ const decision = proxyRouteDecision(target);
42210
+ if (decision.type === "native") {
42211
+ return {
42212
+ requestedModel: target,
42213
+ routedModel: target,
42214
+ source: "native",
42215
+ candidates: [],
42216
+ outcome: { kind: "ok" },
42217
+ warnings: [],
42218
+ native: decision.route
42219
+ };
42220
+ }
42221
+ if (decision.type === "poe") {
42222
+ return explainUnroutedExplicit(target, "poe", "poe", decision.model);
41884
42223
  }
41885
- const rules = rulesOverride ?? loadRoutingRules();
41886
- const defaultProvider = defaultProviderOverride !== undefined ? defaultProviderOverride : rulesOverride !== undefined ? undefined : loadConfig().defaultProvider;
41887
- return routeBare(normalizeGlmSlug(parsed.model), parsed.provider, rules, defaultProvider, cachePath);
42224
+ if (decision.type === "explicit" && decision.via === "vendor-qualified-id") {
42225
+ return explainUnroutedExplicit(target, "vendor-qualified-id", decision.provider, decision.model);
42226
+ }
42227
+ const routeTarget = decision.type === "explicit" ? decision.spec : decision.model;
42228
+ return explainRoutePlan(routeTarget, () => bareRoutingFor(opts), opts.cachePath, target);
41888
42229
  }
41889
- var DEFAULT_FALLBACK_PROVIDER = "openrouter";
42230
+ async function explainUnroutedExplicit(requestedModel, via, provider, wireId) {
42231
+ const credentialed = await hasCredentialsForProvider(provider);
42232
+ const tier = getProviderByName(provider)?.tier;
42233
+ return {
42234
+ requestedModel,
42235
+ routedModel: wireId,
42236
+ source: "explicit",
42237
+ via,
42238
+ candidates: [
42239
+ {
42240
+ provider,
42241
+ displayName: DISPLAY_NAMES[provider] ?? provider,
42242
+ modelSpec: requestedModel,
42243
+ wireId,
42244
+ position: "candidate",
42245
+ ...tier !== undefined ? { tier } : {},
42246
+ outcome: credentialed ? "kept" : "no-credential"
42247
+ }
42248
+ ],
42249
+ outcome: credentialed ? { kind: "ok" } : {
42250
+ kind: "no-route",
42251
+ cause: "explicit-no-credential",
42252
+ reason: `No credentials configured for "${provider}".`,
42253
+ hint: buildCredentialHint(wireId, [provider]) ?? undefined
42254
+ },
42255
+ warnings: []
42256
+ };
42257
+ }
42258
+ function bareRoutingFor(opts) {
42259
+ if (opts.rules !== undefined) {
42260
+ return { rules: opts.rules, defaultProvider: opts.defaultProvider };
42261
+ }
42262
+ const sources = loadRoutingRuleSources();
42263
+ return {
42264
+ rules: loadRoutingRules(sources),
42265
+ defaultProvider: opts.defaultProvider !== undefined ? opts.defaultProvider : effectiveDefaultProvider(),
42266
+ scopeOf: (ruleKey) => Object.hasOwn(sources.localRules, ruleKey) ? "project" : "global",
42267
+ ruleProblems: ruleProblemsAfterRegistration(sources)
42268
+ };
42269
+ }
42270
+ function ruleProblemsAfterRegistration(sources) {
42271
+ ensureEndpointsRegistered();
42272
+ return routingRuleProblems(sources);
42273
+ }
42274
+ function toRoutePlan(explanation) {
42275
+ const { outcome } = explanation;
42276
+ if (outcome.kind === "no-route") {
42277
+ return "hint" in outcome ? { kind: "no-route", reason: outcome.reason, hint: outcome.hint } : { kind: "no-route", reason: outcome.reason };
42278
+ }
42279
+ const [primary, ...fallbacks] = explanation.candidates.filter((candidate) => candidate.outcome === "kept").map(routeOf);
42280
+ if (!primary) {
42281
+ throw new Error(`toRoutePlan: "${explanation.requestedModel}" (${explanation.source}) has no kept candidate, so it has no RoutePlan.`);
42282
+ }
42283
+ return { kind: "ok", primary, fallbacks };
42284
+ }
42285
+ function routeOf(candidate) {
42286
+ return {
42287
+ provider: candidate.provider,
42288
+ modelSpec: candidate.modelSpec,
42289
+ displayName: candidate.displayName
42290
+ };
42291
+ }
42292
+ function emitRouteNotices(explanation) {
42293
+ if (explanation.outcome.kind !== "ok")
42294
+ return;
42295
+ const notServing = explanation.candidates.filter((candidate) => candidate.outcome === "not-served").map((candidate) => candidate.provider);
42296
+ if (notServing.length > 0) {
42297
+ log(`[routing] ${explanation.routedModel}: skipped ${notServing.join(", ")} \u2014 does not serve this model`);
42298
+ }
42299
+ for (const warning of explanation.warnings) {
42300
+ if (warning.type === "rule-problem")
42301
+ continue;
42302
+ logStderr(warning.message);
42303
+ }
42304
+ }
42305
+ function hopLabel(candidate) {
42306
+ if (candidate.position === "fallback")
42307
+ return TIER_LABEL.fallback;
42308
+ return candidate.tier ? TIER_LABEL[candidate.tier] : "unregistered provider";
42309
+ }
42310
+ function describeRouteExplanation(explanation) {
42311
+ const line = describeOrigin(explanation);
42312
+ const routed = explanation.source === "user-rule" || explanation.source === "catalog";
42313
+ return routed && explanation.requestedModel !== explanation.routedModel ? `${line} \xB7 asked as ${explanation.routedModel}` : line;
42314
+ }
42315
+ function describeOrigin(explanation) {
42316
+ switch (explanation.source) {
42317
+ case "native":
42318
+ return "native \xB7 Claude Code's own auth \xB7 not probed";
42319
+ case "explicit": {
42320
+ const name = explanation.candidates[0]?.displayName ?? explanation.routedModel;
42321
+ return explanation.via === "vendor-qualified-id" ? `explicit \xB7 ${name} \xB7 vendor-qualified id sent verbatim` : `explicit \xB7 ${name}`;
42322
+ }
42323
+ case "user-rule": {
42324
+ const scope = explanation.ruleScope ? ` (${explanation.ruleScope})` : "";
42325
+ return `user rule "${explanation.matchedPattern}"${scope}`;
42326
+ }
42327
+ case "catalog":
42328
+ return describeCatalogOrigin(explanation);
42329
+ }
42330
+ }
42331
+ function describeCatalogOrigin(explanation) {
42332
+ if (explanation.catalog === "unreadable") {
42333
+ const refresh = "run claudish --models-refresh";
42334
+ return explanation.candidates.length > 0 ? `no cloud models catalog \xB7 ${firstHopPhrase(explanation)} \xB7 ${refresh}` : `no cloud models catalog \xB7 ${refresh}`;
42335
+ }
42336
+ if (explanation.catalog === "absent")
42337
+ return describeAbsentEntry(explanation);
42338
+ return `catalog \xB7 ${firstHopPhrase(explanation)}`;
42339
+ }
42340
+ function describeAbsentEntry(explanation) {
42341
+ const head = `catalog has no entry for "${explanation.routedModel}"`;
42342
+ const withheld = explanation.fallbackWithheld;
42343
+ if (explanation.candidates.some((candidate) => candidate.position === "candidate")) {
42344
+ const noFallback = withheld && withheld !== "already-gathered" ? ` \xB7 no fallback (${withheld})` : "";
42345
+ return `${head} \xB7 ${firstHopPhrase(explanation)}${noFallback}`;
42346
+ }
42347
+ if (withheld)
42348
+ return `${head} \xB7 no fallback (${withheld})`;
42349
+ const fallback = explanation.candidates.find((candidate) => candidate.position === "fallback");
42350
+ if (!fallback)
42351
+ return `${head} \xB7 ${firstHopPhrase(explanation)}`;
42352
+ return fallback.outcome === "kept" ? `${head} \xB7 fallback only` : `${head} \xB7 fallback ${fallback.displayName} ${DROPPED_FALLBACK_PHRASE[fallback.outcome]}`;
42353
+ }
42354
+ function firstHopPhrase(explanation) {
42355
+ if (explanation.outcome.kind === "no-route")
42356
+ return `no route (${explanation.outcome.cause})`;
42357
+ const first = explanation.candidates.find((candidate) => candidate.outcome === "kept");
42358
+ return first ? `${hopLabel(first)} first` : "no route";
42359
+ }
42360
+ var DEFAULT_FALLBACK_PROVIDER = "openrouter", TIER_LABEL, DROPPED_FALLBACK_PHRASE;
41890
42361
  var init_routing_rules = __esm(() => {
41891
42362
  init_model_catalog();
41892
42363
  init_authority();
@@ -41899,9 +42370,24 @@ var init_routing_rules = __esm(() => {
41899
42370
  init_model_availability();
41900
42371
  init_model_parser();
41901
42372
  init_model_parser();
42373
+ init_native_route();
42374
+ init_predefined_catalog();
41902
42375
  init_provider_definitions();
41903
42376
  init_route_candidates();
41904
42377
  init_routing_hints();
42378
+ TIER_LABEL = {
42379
+ subscription: "subscription",
42380
+ "dynamic-subscription": "subscription \xB7 account decides models",
42381
+ native: "native API",
42382
+ gateway: "gateway",
42383
+ fallback: "fallback"
42384
+ };
42385
+ DROPPED_FALLBACK_PHRASE = {
42386
+ "no-credential": "has no credential",
42387
+ "credential-unreadable": "has a credential that could not be read",
42388
+ "not-served": "does not serve it",
42389
+ "excluded-by-membership": "is outside its plan's membership"
42390
+ };
41905
42391
  });
41906
42392
 
41907
42393
  // src/providers/provider-resolver.ts
@@ -41947,6 +42433,19 @@ function resolveModelProvider(modelId) {
41947
42433
  deprecationWarning: deprecationWarning || undefined,
41948
42434
  concurrency: parsed.concurrency
41949
42435
  });
42436
+ if (parsed.provider === AUTO_ROUTE_PROVIDER) {
42437
+ return addCommonFields({
42438
+ category: "auto-route",
42439
+ catalogName: null,
42440
+ providerName: "unresolved (routed on the first request)",
42441
+ modelName: parsed.model,
42442
+ fullModelId: modelId,
42443
+ requiredApiKeyEnvVar: null,
42444
+ apiKeyAvailable: true,
42445
+ apiKeyDescription: null,
42446
+ apiKeyUrl: null
42447
+ });
42448
+ }
41950
42449
  if (isLocalProviderName(parsed.provider)) {
41951
42450
  const resolved = resolveProvider(modelId);
41952
42451
  const urlParsed = parseUrlModel(modelId);
@@ -42124,7 +42623,7 @@ function getMissingKeyError(resolution) {
42124
42623
  }
42125
42624
  {
42126
42625
  const parsed = resolution.parsed;
42127
- if (parsed && !parsed.isExplicitProvider && parsed.provider !== "unknown" && parsed.provider !== "native-anthropic") {
42626
+ if (parsed && !parsed.isExplicitProvider && parsed.provider !== "unknown" && parsed.provider !== "native-anthropic" && parsed.provider !== AUTO_ROUTE_PROVIDER) {
42128
42627
  const hint = buildCredentialHint(parsed.model, [parsed.provider]);
42129
42628
  if (hint) {
42130
42629
  lines.push("");
@@ -42233,10 +42732,7 @@ async function pinRoutes(models, into, router) {
42233
42732
  } catch {}
42234
42733
  }
42235
42734
  function isRoutablyPinnable(model) {
42236
- if (model.startsWith("poe:"))
42237
- return false;
42238
- const parsed = parseModelSpec(model);
42239
- return !parsed.isExplicitProvider && parsed.provider !== "native-anthropic";
42735
+ return proxyRouteDecision(model).type === "bare";
42240
42736
  }
42241
42737
  async function prepareParentRoutingContext() {
42242
42738
  if (!parentRoutingContextReady) {
@@ -42294,6 +42790,7 @@ var init_prehydrate = __esm(() => {
42294
42790
  init_catalog_client();
42295
42791
  init_endpoint_registration();
42296
42792
  init_model_parser();
42793
+ init_native_route();
42297
42794
  init_onepassword();
42298
42795
  init_provider_resolver();
42299
42796
  init_routing_rules();
@@ -45743,7 +46240,7 @@ function getAvailableModels() {
45743
46240
  _cachedModelIds = result;
45744
46241
  return result;
45745
46242
  }
45746
- var _cachedModelInfo = null, _cachedModelIds = null, _cachedRecommendedModels = null, FIREBASE_BASE_URL = "https://us-central1-claudish-6da10.cloudfunctions.net/queryModels", FIREBASE_RECOMMENDED_URL, RECOMMENDED_MODELS_CACHE_PATH, RECOMMENDED_FETCH_TIMEOUT_MS = 5000, SEARCH_FETCH_TIMEOUT_MS = 1e4, FIREBASE_SLUG_TO_PROVIDER_NAME, RECOMMENDED_ROUTE_TIER_ORDER;
46243
+ var _cachedModelInfo = null, _cachedModelIds = null, _cachedRecommendedModels = null, FIREBASE_BASE_URL = "https://us-central1-claudish-6da10.cloudfunctions.net/queryModels", FIREBASE_RECOMMENDED_URL, RECOMMENDED_MODELS_CACHE_PATH, RECOMMENDED_FETCH_TIMEOUT_MS = 5000, SEARCH_FETCH_TIMEOUT_MS = 1e4, RECOMMENDED_ROUTE_TIER_ORDER;
45747
46244
  var init_model_loader = __esm(() => {
45748
46245
  init_all_models_cache();
45749
46246
  init_auto_route();
@@ -45752,18 +46249,6 @@ var init_model_loader = __esm(() => {
45752
46249
  init_catalog_route_bindings();
45753
46250
  FIREBASE_RECOMMENDED_URL = `${FIREBASE_BASE_URL}?catalog=recommended`;
45754
46251
  RECOMMENDED_MODELS_CACHE_PATH = join32(homedir30(), ".claudish", "recommended-models-cache.json");
45755
- FIREBASE_SLUG_TO_PROVIDER_NAME = {
45756
- openai: "openai",
45757
- google: "google",
45758
- "x-ai": "x-ai",
45759
- "z-ai": "z-ai",
45760
- moonshotai: "kimi",
45761
- minimax: "minimax",
45762
- qwen: "qwen",
45763
- deepseek: "deepseek",
45764
- mistralai: "mistralai",
45765
- sakana: "sakana"
45766
- };
45767
46252
  RECOMMENDED_ROUTE_TIER_ORDER = {
45768
46253
  native: 0,
45769
46254
  general: 1,
@@ -45801,44 +46286,6 @@ async function isPortAvailable(port) {
45801
46286
  }
45802
46287
  var init_port_manager = () => {};
45803
46288
 
45804
- // src/providers/claude-code-aliases.ts
45805
- function claudeCodeTierAlias(model) {
45806
- return TIER_ALIASES[model.trim().toLowerCase()] ?? null;
45807
- }
45808
- function normalizeNativeModelSpec(spec) {
45809
- return claudeCodeTierAlias(spec) ?? spec;
45810
- }
45811
- var TIER_ALIASES;
45812
- var init_claude_code_aliases = __esm(() => {
45813
- TIER_ALIASES = {
45814
- opus: "opus",
45815
- sonnet: "sonnet",
45816
- haiku: "haiku",
45817
- internal: "opus",
45818
- default: "opus"
45819
- };
45820
- });
45821
-
45822
- // src/providers/native-route.ts
45823
- function nativeRouteFor(model) {
45824
- if (model.includes("/"))
45825
- return null;
45826
- const parsed = parseModelSpec(model);
45827
- if (parsed.isExplicitProvider || parsed.provider !== "native-anthropic")
45828
- return null;
45829
- return {
45830
- provider: "native-anthropic",
45831
- modelSpec: normalizeNativeModelSpec(model),
45832
- displayName: getProviderByName("native-anthropic")?.displayName ?? "Anthropic (Native)",
45833
- isTierAlias: claudeCodeTierAlias(model) !== null
45834
- };
45835
- }
45836
- var init_native_route = __esm(() => {
45837
- init_claude_code_aliases();
45838
- init_model_parser();
45839
- init_provider_definitions();
45840
- });
45841
-
45842
46289
  // src/providers/probe-live.ts
45843
46290
  function effortForProvider(provider) {
45844
46291
  if (EFFORT_OMITTED.has(provider))
@@ -48859,7 +49306,7 @@ function prepareAdvisorPanelMessages(messages, toolUseId) {
48859
49306
  });
48860
49307
  return prepared;
48861
49308
  }
48862
- function routeOf(kind, wireModel) {
49309
+ function routeOf2(kind, wireModel) {
48863
49310
  const url = ADVISOR_ENDPOINTS[kind];
48864
49311
  return { kind, host: new URL(url).host, url, credential: kind, wireModel };
48865
49312
  }
@@ -48869,21 +49316,21 @@ function advisorRouteFor(modelSpec, role) {
48869
49316
  const model = parsed.model;
48870
49317
  const tier = anthropicTierAlias(model);
48871
49318
  if (!tier)
48872
- return routeOf("anthropic", model);
49319
+ return routeOf2("anthropic", model);
48873
49320
  const resolved = findEntryByAlias(model)?.modelId ?? latestAnthropicTierModelId(tier);
48874
49321
  if (!resolved)
48875
- return { ...routeOf("anthropic", model), unresolvedAlias: model };
48876
- return routeOf("anthropic", resolved);
49322
+ return { ...routeOf2("anthropic", model), unresolvedAlias: model };
49323
+ return routeOf2("anthropic", resolved);
48877
49324
  }
48878
49325
  const provider = parsed.provider;
48879
49326
  if (provider === "google" || provider === "gemini")
48880
- return routeOf("google", parsed.model);
49327
+ return routeOf2("google", parsed.model);
48881
49328
  if (provider === "openai" || provider === "oai")
48882
- return routeOf("openai", parsed.model);
49329
+ return routeOf2("openai", parsed.model);
48883
49330
  if (!parsed.isExplicitProvider || provider === "openrouter") {
48884
- return routeOf("openrouter", openRouterWireModelFor(null, parsed.model));
49331
+ return routeOf2("openrouter", openRouterWireModelFor(null, parsed.model));
48885
49332
  }
48886
- return routeOf("openrouter", openRouterWireModelFor(provider, parsed.model));
49333
+ return routeOf2("openrouter", openRouterWireModelFor(provider, parsed.model));
48887
49334
  }
48888
49335
  function openRouterIdOf(entry) {
48889
49336
  return entry.aggregators?.find((a) => catalogRouteMatchesProvider(a.route, "openrouter"))?.externalModelId ?? null;
@@ -49858,6 +50305,8 @@ function isRetryableError(status, errorBody, provider, headers) {
49858
50305
  return true;
49859
50306
  if (status === 429)
49860
50307
  return true;
50308
+ if (status === 502 || status === 503 || status === 504)
50309
+ return true;
49861
50310
  const lower = errorBody.toLowerCase();
49862
50311
  if (status === 422) {
49863
50312
  if (lower.includes("not available") || lower.includes("model not found") || lower.includes("not supported")) {
@@ -49885,13 +50334,14 @@ function isRetryableError(status, errorBody, provider, headers) {
49885
50334
  function exhaustedChainStatus(errors) {
49886
50335
  if (errors.length === 0)
49887
50336
  return 400;
50337
+ const TRANSIENT = new Set([429, 502, 503, 504]);
49888
50338
  const isTransient = (e) => {
49889
- if (e.status === 429 || e.status === 503)
50339
+ if (TRANSIENT.has(e.status))
49890
50340
  return true;
49891
50341
  if (hasQuotaExhaustionWording(e.message))
49892
50342
  return true;
49893
50343
  const upstream = e.status === 400 ? extractUpstreamStatus(e.message) : undefined;
49894
- return upstream === 429 || upstream === 503;
50344
+ return upstream !== undefined && TRANSIENT.has(upstream);
49895
50345
  };
49896
50346
  return errors.every(isTransient) ? 503 : 400;
49897
50347
  }
@@ -51104,7 +51554,12 @@ async function createProxyServer(port, _openrouterApiKey, model, monitorMode = f
51104
51554
  }
51105
51555
  return null;
51106
51556
  };
51107
- const effectiveRoutingRules = loadRoutingRules();
51557
+ const routingRuleSources = loadRoutingRuleSources();
51558
+ const effectiveRoutingRules = loadRoutingRules(routingRuleSources);
51559
+ for (const problem of routingRuleProblems(routingRuleSources)) {
51560
+ logStderr(`Warning: ${describeRoutingRuleProblem(problem)}`);
51561
+ }
51562
+ const effectiveFallbackProvider = effectiveDefaultProvider();
51108
51563
  const fallbackHandlerCache = new Map;
51109
51564
  const detectInvocationMode = (target, wasFromModelMap) => {
51110
51565
  if (wasFromModelMap)
@@ -51187,14 +51642,14 @@ async function createProxyServer(port, _openrouterApiKey, model, monitorMode = f
51187
51642
  }
51188
51643
  }
51189
51644
  {
51190
- const parsedForFallback = parseModelSpec(target);
51191
- if (!parsedForFallback.isExplicitProvider && parsedForFallback.provider !== "native-anthropic" && !isPoeModel(target)) {
51645
+ const decision = proxyRouteDecision(target);
51646
+ if (decision.type === "bare") {
51192
51647
  const cacheKey = `fallback:${target}`;
51193
51648
  if (fallbackHandlerCache.has(cacheKey)) {
51194
51649
  return fallbackHandlerCache.get(cacheKey);
51195
51650
  }
51196
51651
  await ensureCatalogReady(5000);
51197
- const plan = await route(parsedForFallback.model, effectiveRoutingRules);
51652
+ const plan = await route(decision.model, effectiveRoutingRules, effectiveFallbackProvider);
51198
51653
  if (plan.kind === "ok") {
51199
51654
  const chain = [plan.primary, ...plan.fallbacks];
51200
51655
  const candidates = [];
@@ -51212,8 +51667,11 @@ async function createProxyServer(port, _openrouterApiKey, model, monitorMode = f
51212
51667
  if (candidates.length > 0) {
51213
51668
  const resultHandler = candidates.length > 1 ? new FallbackHandler(candidates) : candidates[0].handler;
51214
51669
  fallbackHandlerCache.set(cacheKey, resultHandler);
51670
+ const routeLine = `[Route] ${candidates.length} ${candidates.length === 1 ? "provider" : "providers"} for ${decision.model}: ${candidates.map((c) => c.name).join(" \u2192 ")}`;
51215
51671
  if (!options.quiet && candidates.length > 1) {
51216
- logStderr(`[Route] ${candidates.length} providers for ${parsedForFallback.model}: ${candidates.map((c) => c.name).join(" \u2192 ")}`);
51672
+ logStderr(routeLine);
51673
+ } else {
51674
+ log(routeLine);
51217
51675
  }
51218
51676
  return resultHandler;
51219
51677
  }
@@ -51442,6 +51900,7 @@ var init_proxy_server = __esm(() => {
51442
51900
  init_endpoint_diagnostics();
51443
51901
  init_endpoint_registration();
51444
51902
  init_model_parser();
51903
+ init_native_route();
51445
51904
  init_provider_definitions();
51446
51905
  init_provider_profiles();
51447
51906
  init_provider_registry();
@@ -51762,7 +52221,7 @@ Tokens: ${result.usage.input} input, ${result.usage.output} output`;
51762
52221
  const { flagship, fast } = groupRecommendedModels(doc.models);
51763
52222
  const providerByName = new Map(BUILTIN_PROVIDERS.map((p) => [p.name, p]));
51764
52223
  const getNativePrefix = (firebaseSlug) => {
51765
- const canonical = FIREBASE_SLUG_TO_PROVIDER_NAME[firebaseSlug];
52224
+ const canonical = nativeProviderForVendor(firebaseSlug);
51766
52225
  if (!canonical)
51767
52226
  return null;
51768
52227
  const def = providerByName.get(canonical);
@@ -52054,7 +52513,7 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
52054
52513
  const native = nativeRouteFor(model);
52055
52514
  if (native) {
52056
52515
  nativeModels.push(model);
52057
- rows.push(`| \`${model}\` | ${native.displayName} | native | ${"not probed \u2014 served on Claude Code's own auth, which this process cannot forward"} | \`${native.modelSpec}\` |`);
52516
+ rows.push(`| \`${model}\` | ${native.displayName} | native | ${NATIVE_NOT_PROBED} | \`${native.modelSpec}\` |`);
52058
52517
  continue;
52059
52518
  }
52060
52519
  let plan;
@@ -52849,6 +53308,7 @@ var init_mcp_server = __esm(() => {
52849
53308
  init_onepassword();
52850
53309
  init_probe_live();
52851
53310
  init_provider_definitions();
53311
+ init_route_candidates();
52852
53312
  init_routing_rules();
52853
53313
  init_proxy_server();
52854
53314
  init_redact();
@@ -53436,66 +53896,26 @@ import { existsSync as existsSync28, readFileSync as readFileSync28, writeFileSy
53436
53896
  import { connect as netConnect } from "net";
53437
53897
  import { join as join37 } from "path";
53438
53898
  import { setTimeout as wait } from "timers/promises";
53439
- function resolveRouteInfo(modelId) {
53440
- const parsed = parseModelSpec(modelId);
53441
- if (parsed.isExplicitProvider) {
53442
- return { chain: [parsed.provider], source: "direct" };
53443
- }
53444
- const local = loadLocalConfig();
53445
- if (local?.routing && Object.keys(local.routing).length > 0) {
53446
- const matched = matchRoutingRule(parsed.model, local.routing);
53447
- if (matched) {
53448
- const routes = buildRoutingChain(matched, parsed.model);
53449
- const pattern = Object.keys(local.routing).find((k) => {
53450
- if (k === parsed.model)
53451
- return true;
53452
- if (k.includes("*")) {
53453
- const star = k.indexOf("*");
53454
- return parsed.model.startsWith(k.slice(0, star)) && parsed.model.endsWith(k.slice(star + 1));
53455
- }
53456
- return false;
53457
- });
53458
- return {
53459
- chain: routes.map((r) => r.displayName),
53460
- source: "project routing",
53461
- sourceDetail: pattern
53462
- };
53463
- }
53464
- }
53465
- const global_ = loadConfig();
53466
- if (global_.routing && Object.keys(global_.routing).length > 0) {
53467
- const matched = matchRoutingRule(parsed.model, global_.routing);
53468
- if (matched) {
53469
- const routes = buildRoutingChain(matched, parsed.model);
53470
- const pattern = Object.keys(global_.routing).find((k) => {
53471
- if (k === parsed.model)
53472
- return true;
53473
- if (k.includes("*")) {
53474
- const star = k.indexOf("*");
53475
- return parsed.model.startsWith(k.slice(0, star)) && parsed.model.endsWith(k.slice(star + 1));
53476
- }
53477
- return false;
53478
- });
53479
- return {
53480
- chain: routes.map((r) => r.displayName),
53481
- source: "user routing",
53482
- sourceDetail: pattern
53483
- };
53899
+ function paneRouteLine(explanation) {
53900
+ const origin = describeRouteExplanation(explanation);
53901
+ if (explanation.outcome.kind === "no-route") {
53902
+ return `no route \u2014 ${explanation.outcome.reason} (${origin})`;
53903
+ }
53904
+ if (explanation.native)
53905
+ return `${explanation.native.displayName} (${origin})`;
53906
+ const hops = explanation.candidates.filter((candidate) => candidate.outcome === "kept").map((candidate) => candidate.displayName);
53907
+ return `${hops.join(" \u2192 ")} (${origin})`;
53908
+ }
53909
+ async function resolvePaneRouteLines(models) {
53910
+ const unique = [...new Set(models)];
53911
+ const entries = await Promise.all(unique.map(async (model) => {
53912
+ try {
53913
+ return [model, paneRouteLine(await explainRoute(model))];
53914
+ } catch (err) {
53915
+ return [model, `unknown \u2014 ${err instanceof Error ? err.message : String(err)}`];
53484
53916
  }
53485
- }
53486
- const merged = loadRoutingRules();
53487
- const matched = matchRoutingRule(parsed.model, merged);
53488
- if (matched) {
53489
- const routes = buildRoutingChain(matched, parsed.model);
53490
- return {
53491
- chain: routes.map((r) => r.displayName),
53492
- source: "auto"
53493
- };
53494
- }
53495
- return {
53496
- chain: [],
53497
- source: "auto"
53498
- };
53917
+ }));
53918
+ return new Map(entries);
53499
53919
  }
53500
53920
  function pickBannerColor(model, used) {
53501
53921
  let hash = 0;
@@ -53510,14 +53930,11 @@ function pickBannerColor(model, used) {
53510
53930
  used.add(idx);
53511
53931
  return BANNER_BG_COLORS[idx];
53512
53932
  }
53513
- function buildPaneHeader(model, prompt, bg) {
53514
- const route = resolveRouteInfo(model);
53933
+ function buildPaneHeader(model, routeLine, prompt, bg) {
53515
53934
  const esc = (s) => s.replace(/'/g, "'\\''");
53516
- const chainStr = route.chain.join(" \u2192 ");
53517
- const sourceLabel = route.sourceDetail ? `${route.source}: ${route.sourceDetail}` : route.source;
53518
53935
  const lines = [];
53519
53936
  lines.push(`printf '\\033[1;97;${bg}m %s \\033[0m\\n' '${esc(model)}';`);
53520
- lines.push(`printf '\\033[2m route: ${esc(chainStr)} (${esc(sourceLabel)})\\033[0m\\n' ;`);
53937
+ lines.push(`printf '\\033[2m route: %s\\033[0m\\n' '${esc(routeLine)}';`);
53521
53938
  lines.push(`printf '\\033[2m %s\\033[0m\\n' '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500';`);
53522
53939
  const promptForShell = esc(prompt).replace(/\n/g, "\\n");
53523
53940
  lines.push(`printf '%b\\n' '${promptForShell}' | fold -s -w 78 | sed 's/^/ /';`);
@@ -53623,13 +54040,14 @@ async function runWithGrid(sessionPath, models, input, opts) {
53623
54040
  const prompt = readFileSync28(join37(sessionPath, "input.md"), "utf-8").replace(/'/g, "'\\''").replace(/\n/g, " ");
53624
54041
  const rawPrompt = readFileSync28(join37(sessionPath, "input.md"), "utf-8");
53625
54042
  const usedBannerColors = new Set;
54043
+ const routeLines = mode === "interactive" ? new Map : await resolvePaneRouteLines(Object.values(manifest.models).map((entry) => entry.model));
53626
54044
  const gridLines = Object.entries(manifest.models).map(([anonId]) => {
53627
54045
  const model = manifest.models[anonId].model;
53628
54046
  if (mode === "interactive") {
53629
54047
  return `claudish --model ${model} -i --dangerously-skip-permissions '${prompt}'`;
53630
54048
  }
53631
54049
  const bg = pickBannerColor(model, usedBannerColors);
53632
- const header = buildPaneHeader(model, rawPrompt, bg);
54050
+ const header = buildPaneHeader(model, routeLines.get(model) ?? "", rawPrompt, bg);
53633
54051
  return `${header} claudish --model ${model} -y --quiet '${prompt}'`;
53634
54052
  });
53635
54053
  writeFileSync17(gridfilePath, `${gridLines.join(`
@@ -53659,8 +54077,6 @@ async function runWithGrid(sessionPath, models, input, opts) {
53659
54077
  var BANNER_BG_COLORS;
53660
54078
  var init_team_grid = __esm(() => {
53661
54079
  init_magmux_binary();
53662
- init_profile_config();
53663
- init_model_parser();
53664
54080
  init_routing_rules();
53665
54081
  init_team_orchestrator();
53666
54082
  BANNER_BG_COLORS = [
@@ -57112,32 +57528,6 @@ var init_quota_command = __esm(() => {
57112
57528
  };
57113
57529
  });
57114
57530
 
57115
- // src/default-provider.ts
57116
- function resolveDefaultProvider(opts) {
57117
- const env = opts.env ?? process.env;
57118
- if (opts.cliFlag && opts.cliFlag.length > 0) {
57119
- return { provider: opts.cliFlag, source: "cli-flag", legacyAutoPromoted: false };
57120
- }
57121
- const envVal = env.CLAUDISH_DEFAULT_PROVIDER;
57122
- if (envVal && envVal.length > 0) {
57123
- return { provider: envVal, source: "env-var", legacyAutoPromoted: false };
57124
- }
57125
- if (opts.config.defaultProvider && opts.config.defaultProvider.length > 0) {
57126
- return {
57127
- provider: opts.config.defaultProvider,
57128
- source: "config-file",
57129
- legacyAutoPromoted: false
57130
- };
57131
- }
57132
- if (env.OPENROUTER_API_KEY) {
57133
- return { provider: "openrouter", source: "openrouter-key", legacyAutoPromoted: false };
57134
- }
57135
- return { provider: "openrouter", source: "hardcoded", legacyAutoPromoted: false };
57136
- }
57137
- function buildLegacyHint(_resolved) {
57138
- return null;
57139
- }
57140
-
57141
57531
  // src/providers/model-catalog.ts
57142
57532
  function aggregatorProviderSlugs(readSlimCache) {
57143
57533
  const cache = readSlimCache();
@@ -57293,9 +57683,9 @@ var init_model_catalog2 = __esm(() => {
57293
57683
  "opencode-zen",
57294
57684
  "opencode-zen-go",
57295
57685
  "fireworks",
57296
- "together-ai"
57686
+ "together"
57297
57687
  ]);
57298
- NO_CATALOG_VENDOR_SLUGS = new Set(["litellm", "ollama", "lmstudio", "lm-studio"]);
57688
+ NO_CATALOG_VENDOR_SLUGS = new Set(["litellm", "ollama", "lmstudio"]);
57299
57689
  });
57300
57690
 
57301
57691
  // src/theme/renderer-theme.ts
@@ -61275,21 +61665,141 @@ var init_model_selector = __esm(() => {
61275
61665
  _modelsCatalogCollapse = { collapse: collapseModelsCatalog };
61276
61666
  });
61277
61667
 
61668
+ // src/probe/probe-chain.ts
61669
+ function probeChainFrom(explanation, credentials) {
61670
+ if (explanation.source === "native" && explanation.native) {
61671
+ return { chain: [nativeLinkOf(explanation.native)], dropped: [] };
61672
+ }
61673
+ const chain = [];
61674
+ const dropped = [];
61675
+ for (const candidate of explanation.candidates) {
61676
+ if (candidate.outcome === "kept")
61677
+ chain.push(keptLinkOf(candidate, credentials));
61678
+ else
61679
+ dropped.push(droppedLinkOf(candidate, candidate.outcome, credentials));
61680
+ }
61681
+ return { chain, dropped };
61682
+ }
61683
+ function nativeLinkOf(native) {
61684
+ return {
61685
+ provider: native.provider,
61686
+ displayName: native.displayName,
61687
+ modelSpec: native.modelSpec,
61688
+ wireId: native.modelSpec,
61689
+ position: "candidate",
61690
+ label: NATIVE_LINK_LABEL,
61691
+ hasCredentials: true,
61692
+ notProbed: "native-auth"
61693
+ };
61694
+ }
61695
+ function keptLinkOf(candidate, credentials) {
61696
+ const provenance = credentials.provenanceFor(candidate.provider);
61697
+ return {
61698
+ provider: candidate.provider,
61699
+ displayName: candidate.displayName,
61700
+ modelSpec: candidate.modelSpec,
61701
+ wireId: candidate.wireId,
61702
+ position: candidate.position,
61703
+ ...candidate.tier !== undefined ? { tier: candidate.tier } : {},
61704
+ label: hopLabel(candidate),
61705
+ ...candidate.availability ? { availability: candidate.availability } : {},
61706
+ hasCredentials: true,
61707
+ ...provenance ? { provenance } : {}
61708
+ };
61709
+ }
61710
+ function droppedLinkOf(candidate, outcome, credentials) {
61711
+ const hint = outcome === "no-credential" ? credentials.hintFor(candidate.provider) : undefined;
61712
+ return {
61713
+ provider: candidate.provider,
61714
+ displayName: candidate.displayName,
61715
+ wireId: candidate.wireId,
61716
+ position: candidate.position,
61717
+ ...candidate.tier !== undefined ? { tier: candidate.tier } : {},
61718
+ label: hopLabel(candidate),
61719
+ outcome,
61720
+ ...hint ? { credentialHint: hint } : {}
61721
+ };
61722
+ }
61723
+ function routingFieldsFrom(explanation) {
61724
+ const { outcome } = explanation;
61725
+ return {
61726
+ routingSource: explanation.source,
61727
+ routingExplanation: describeRouteExplanation(explanation),
61728
+ ...explanation.matchedPattern !== undefined ? { matchedPattern: explanation.matchedPattern } : {},
61729
+ ...explanation.ruleScope ? { ruleScope: explanation.ruleScope } : {},
61730
+ ...outcome.kind === "no-route" ? {
61731
+ noRoute: {
61732
+ reason: outcome.reason,
61733
+ ...outcome.hint !== undefined ? { hint: outcome.hint } : {}
61734
+ }
61735
+ } : {},
61736
+ warnings: explanation.warnings
61737
+ };
61738
+ }
61739
+ function resultLinksFrom(chain, dropped) {
61740
+ return [
61741
+ ...chain.map((link) => ({
61742
+ provider: link.provider,
61743
+ displayName: link.displayName,
61744
+ modelId: link.wireId,
61745
+ label: link.label,
61746
+ hasCredentials: link.hasCredentials,
61747
+ ...link.credentialHint ? { credentialHint: link.credentialHint } : {},
61748
+ ...link.notProbed ? { notProbed: link.notProbed } : {},
61749
+ ...link.probe ? { probe: link.probe } : {}
61750
+ })),
61751
+ ...dropped.map((entry) => ({
61752
+ provider: entry.provider,
61753
+ displayName: entry.displayName,
61754
+ modelId: entry.wireId,
61755
+ label: entry.label,
61756
+ hasCredentials: entry.outcome === "not-served",
61757
+ ...entry.credentialHint ? { credentialHint: entry.credentialHint } : {},
61758
+ dropped: entry.outcome
61759
+ }))
61760
+ ];
61761
+ }
61762
+ var NATIVE_LINK_LABEL = "Claude Code's own auth";
61763
+ var init_probe_chain = __esm(() => {
61764
+ init_routing_rules();
61765
+ });
61766
+
61278
61767
  // src/providers/probe-runner.ts
61279
61768
  function pinProbeModelSpec(link) {
61280
61769
  if (link.provider === "native-anthropic")
61281
61770
  return link.modelSpec;
61282
61771
  return link.modelSpec.includes("@") ? link.modelSpec : `${link.provider}@${link.modelSpec}`;
61283
61772
  }
61773
+ function droppedRemedy(credentialHint) {
61774
+ if (!credentialHint)
61775
+ return;
61776
+ return /^[A-Z][A-Z0-9_]*$/.test(credentialHint) ? `set ${credentialHint}` : credentialHint;
61777
+ }
61778
+ function describeDropped(outcome, credentialHint) {
61779
+ const remedy = droppedRemedy(credentialHint);
61780
+ return remedy ? `${DROPPED_LABEL[outcome]} \xB7 ${remedy}` : DROPPED_LABEL[outcome];
61781
+ }
61782
+ function probeTargets(explanation) {
61783
+ return explanation.candidates.filter((candidate) => candidate.outcome === "kept").map((candidate) => ({
61784
+ ...candidate,
61785
+ probeSpec: explanation.source === "explicit" ? explanation.requestedModel : pinProbeModelSpec(candidate)
61786
+ }));
61787
+ }
61284
61788
  function probeProviderRoute(proxyUrl, link, timeoutMs) {
61285
61789
  return probeLink(proxyUrl, {
61286
61790
  ...link,
61287
61791
  modelSpec: pinProbeModelSpec(link)
61288
61792
  }, timeoutMs);
61289
61793
  }
61290
- var INTERACTIVE_PROBE_TIMEOUT_MS = 60000;
61794
+ var INTERACTIVE_PROBE_TIMEOUT_MS = 60000, DROPPED_LABEL;
61291
61795
  var init_probe_runner = __esm(() => {
61292
61796
  init_probe_live();
61797
+ DROPPED_LABEL = {
61798
+ "no-credential": "no credential",
61799
+ "credential-unreadable": "credential could not be read",
61800
+ "not-served": "account does not serve it",
61801
+ "excluded-by-membership": "not in the plan's membership"
61802
+ };
61293
61803
  });
61294
61804
 
61295
61805
  // src/probe/probe-results-printer.ts
@@ -61383,7 +61893,6 @@ function computeBarScales(results) {
61383
61893
  maxTokPerSec = scaledTps;
61384
61894
  };
61385
61895
  for (const r of results) {
61386
- consider(r.directProbe);
61387
61896
  for (const c of r.chain ?? [])
61388
61897
  consider(c.probe);
61389
61898
  }
@@ -61607,6 +62116,16 @@ function buildRowData(result, isLiveProbe) {
61607
62116
  return result.chain.map((entry, i) => {
61608
62117
  const isFastest = i === fastestIdx;
61609
62118
  const isSlowest = i === slowestIdx;
62119
+ if (entry.notProbed) {
62120
+ return {
62121
+ num: `${i + 1}`,
62122
+ provider: entry.displayName,
62123
+ label: entry.label ?? "",
62124
+ spec: entry.modelSpec,
62125
+ status: `${pc.cyan}\u25D0 native \u2014 not probed${pc.reset}`,
62126
+ note: NATIVE_NOT_PROBED
62127
+ };
62128
+ }
61610
62129
  let status = shortStatusLabel(entry.probe, entry.hasCredentials, entry.credentialHint);
61611
62130
  if (isFastest) {
61612
62131
  status = `${status} ${pc.brightGreen}\u25CF${pc.reset}`;
@@ -61619,6 +62138,7 @@ function buildRowData(result, isLiveProbe) {
61619
62138
  return {
61620
62139
  num: `${i + 1}`,
61621
62140
  provider: entry.displayName,
62141
+ label: entry.label ?? "",
61622
62142
  spec: entry.modelSpec,
61623
62143
  status,
61624
62144
  errorDetail,
@@ -61628,40 +62148,19 @@ function buildRowData(result, isLiveProbe) {
61628
62148
  };
61629
62149
  });
61630
62150
  }
61631
- function buildDirectRowData(result) {
61632
- const probe = result.directProbe;
61633
- let status;
61634
- if (!probe) {
61635
- status = `${pc.dim}\u2014 no probe \u2014${pc.reset}`;
61636
- } else {
61637
- status = shortStatusLabel(probe, true);
61638
- if (probe.state === "live") {
61639
- status = `${status} ${pc.brightGreen}\u25CF${pc.reset}`;
61640
- }
61641
- }
61642
- let errorDetail;
61643
- if (probe && isFailureState(probe.state) && probe.errorMessage) {
61644
- errorDetail = stripAnsi2(probe.errorMessage).replace(/\s+/g, " ").trim();
61645
- }
61646
- const barsTiming = probe?.state === "live" && probe.timing ? probe.timing : undefined;
61647
- return [
61648
- {
61649
- num: "1",
61650
- provider: result.nativeProvider,
61651
- spec: pinProbeModelSpec({ provider: result.nativeProvider, modelSpec: result.model }),
61652
- status,
61653
- errorDetail,
61654
- barsTiming
61655
- }
61656
- ];
62151
+ function buildDroppedRowData(result) {
62152
+ return (result.dropped ?? []).map((entry) => ({
62153
+ num: "\u2013",
62154
+ provider: entry.displayName,
62155
+ label: entry.label ?? "",
62156
+ spec: entry.wireId,
62157
+ status: `${pc.dim}\u2013 ${DROPPED_LABEL[entry.outcome]}${pc.reset}`,
62158
+ note: droppedRemedy(entry.credentialHint)
62159
+ }));
61657
62160
  }
61658
62161
  function computeColumnWidths(rows) {
61659
- const headers = ["#", "Provider", "Model Spec", "Status"];
61660
- const wNum = Math.max(headers[0].length, ...rows.map((r) => r.num.length));
61661
- const wProv = Math.max(headers[1].length, ...rows.map((r) => visibleLength(r.provider)));
61662
- const wSpec = Math.max(headers[2].length, ...rows.map((r) => visibleLength(r.spec)));
61663
- const wStatus = Math.max(headers[3].length, ...rows.map((r) => visibleLength(r.status)));
61664
- return [wNum, wProv, wSpec, wStatus];
62162
+ const cells = (r) => [r.num, r.provider, r.label, r.spec, r.status];
62163
+ return COLUMN_HEADERS.map((header, col) => Math.max(header.length, ...rows.map((r) => visibleLength(cells(r)[col]))));
61665
62164
  }
61666
62165
  function computeCardWidth(_rows, widths, topTitleVis, topSummaryVis, footerVis) {
61667
62166
  const tableRowWidth = 2 + CARD_PADDING_LEFT + widths.reduce((a, b) => a + b, 0) + (widths.length - 1) * 3 + CARD_PADDING_RIGHT;
@@ -61682,6 +62181,9 @@ function formatContextWindow(ctx) {
61682
62181
  return `${Math.round(ctx / 1000)}K`;
61683
62182
  }
61684
62183
  function buildKeyLine(activeEntry, directKeyVar) {
62184
+ if (activeEntry?.notProbed) {
62185
+ return `${pc.bold}Key${pc.reset} ${pc.dim}Claude Code's own auth (not visible to this process)${pc.reset}`;
62186
+ }
61685
62187
  if (activeEntry?.provenance) {
61686
62188
  const p = activeEntry.provenance;
61687
62189
  const subject = p.effectiveLabel ?? `$${p.envVar}`;
@@ -61702,21 +62204,29 @@ function buildWireLine(wiring, activeProvider) {
61702
62204
  const head = activeProvider ? `${activeProvider} \u2192 ` : "";
61703
62205
  return `${pc.bold}Wire${pc.reset} ${head}${wiring.effectiveStreamFormat} \xB7 ${wiring.modelTranslator} \xB7 ${ctx}`;
61704
62206
  }
62207
+ function cardSummary(result) {
62208
+ const first = result.chain[0];
62209
+ if (!first)
62210
+ return { text: result.routingExplanation || "no route", color: pc.red };
62211
+ if (first.notProbed)
62212
+ return { text: `${first.displayName} \xB7 not probed`, color: pc.cyan };
62213
+ const probed = result.chain.filter((c) => !c.notProbed);
62214
+ const live = probed.filter((c) => c.probe?.state === "live").length;
62215
+ return {
62216
+ text: `${first.displayName} \xB7 ${live}/${probed.length} live`,
62217
+ color: summaryColor(live, probed.length)
62218
+ };
62219
+ }
61705
62220
  function buildCardLayout(result, isLiveProbe, directKeyVar) {
61706
- const rows = result.routingSource === "direct" ? buildDirectRowData(result) : buildRowData(result, isLiveProbe);
61707
- const totalLinks = rows.length;
61708
- const liveCount = result.chain ? result.chain.filter((c) => c.probe?.state === "live").length : result.directProbe?.state === "live" ? 1 : 0;
61709
- const effLive = result.routingSource === "direct" ? liveCount : liveCount;
61710
- const effTotal = result.routingSource === "direct" ? totalLinks : result.chain.length;
61711
- const titleText = result.model;
61712
- const sumColor = summaryColor(effLive, effTotal);
61713
- const summaryPlain = `${result.nativeProvider} \xB7 ${effLive}/${effTotal} live`;
61714
- const titleStyled = `${pc.bold}${pc.cyan}${titleText}${pc.reset}`;
61715
- const summaryStyled = `${sumColor}${summaryPlain}${pc.reset}`;
61716
- const activeEntry = result.chain?.find((c) => c.probe?.state === "live") ?? result.chain?.find((c) => c.hasCredentials);
62221
+ const rows = [...buildRowData(result, isLiveProbe), ...buildDroppedRowData(result)];
62222
+ const summary = cardSummary(result);
62223
+ const titleStyled = `${pc.bold}${pc.cyan}${result.model}${pc.reset}`;
62224
+ const summaryStyled = `${summary.color}${summary.text}${pc.reset}`;
62225
+ const activeEntry = result.chain.find((c) => c.probe?.state === "live") ?? result.chain.find((c) => c.hasCredentials);
61717
62226
  const keyLine = buildKeyLine(activeEntry, directKeyVar);
61718
- const wireLine = result.wiring ? buildWireLine(result.wiring, activeEntry?.displayName ?? result.nativeProvider) : "";
61719
- const footerVis = Math.max(visibleLength(keyLine), visibleLength(wireLine));
62227
+ const wireLine = result.wiring && !activeEntry?.notProbed ? buildWireLine(result.wiring, activeEntry?.displayName) : "";
62228
+ const routeLine = result.routingExplanation ? `${pc.bold}Route${pc.reset} ${result.routingExplanation}` : "";
62229
+ const footerVis = Math.max(visibleLength(keyLine), visibleLength(wireLine), visibleLength(routeLine));
61720
62230
  const widths = computeColumnWidths(rows);
61721
62231
  return {
61722
62232
  rows,
@@ -61725,6 +62235,7 @@ function buildCardLayout(result, isLiveProbe, directKeyVar) {
61725
62235
  summaryStyled,
61726
62236
  keyLine,
61727
62237
  wireLine,
62238
+ routeLine,
61728
62239
  footerVis,
61729
62240
  activeEntry
61730
62241
  };
@@ -61736,17 +62247,12 @@ function computeRequiredWidth(result, isLiveProbe, directKeyVar) {
61736
62247
  }
61737
62248
  function renderCard(result, isLiveProbe, w, width, scales, directKeyVar) {
61738
62249
  const layout = buildCardLayout(result, isLiveProbe, directKeyVar);
61739
- const { rows, widths, titleStyled, summaryStyled, keyLine, wireLine } = layout;
62250
+ const { rows, widths, titleStyled, summaryStyled, keyLine, wireLine, routeLine } = layout;
61740
62251
  w(`${renderBorderTop(titleStyled, summaryStyled, width)}
61741
62252
  `);
61742
62253
  w(`${renderBlankLine(width)}
61743
62254
  `);
61744
- const headerCells = [
61745
- `${pc.dim}#${pc.reset}`,
61746
- `${pc.dim}Provider${pc.reset}`,
61747
- `${pc.dim}Model Spec${pc.reset}`,
61748
- `${pc.dim}Status${pc.reset}`
61749
- ];
62255
+ const headerCells = COLUMN_HEADERS.map((h) => `${pc.dim}${h}${pc.reset}`);
61750
62256
  w(`${renderRow(headerCells, widths, width)}
61751
62257
  `);
61752
62258
  w(`${renderSepRow(widths, width)}
@@ -61754,7 +62260,13 @@ function renderCard(result, isLiveProbe, w, width, scales, directKeyVar) {
61754
62260
  for (let rowIdx = 0;rowIdx < rows.length; rowIdx++) {
61755
62261
  const r = rows[rowIdx];
61756
62262
  const bg = r.fastest ? pc.bgFastest : r.slowest ? pc.bgSlowest : undefined;
61757
- const cells = [r.num, r.provider, `${pc.dim}${r.spec}${pc.reset}`, r.status];
62263
+ const cells = [
62264
+ r.num,
62265
+ r.provider,
62266
+ `${pc.dim}${r.label}${pc.reset}`,
62267
+ `${pc.dim}${r.spec}${pc.reset}`,
62268
+ r.status
62269
+ ];
61758
62270
  w(`${renderRow(cells, widths, width, bg)}
61759
62271
  `);
61760
62272
  if (r.barsTiming) {
@@ -61768,36 +62280,10 @@ function renderCard(result, isLiveProbe, w, width, scales, directKeyVar) {
61768
62280
  `);
61769
62281
  }
61770
62282
  }
61771
- if (r.errorDetail) {
61772
- const innerUsable = width - 2 - CARD_PADDING_LEFT - CARD_PADDING_RIGHT;
61773
- const errorIndent = 4;
61774
- const prefixVis = 2;
61775
- const textWidth = innerUsable - errorIndent - prefixVis;
61776
- const MAX_ERROR_LINES = 4;
61777
- if (textWidth > 0) {
61778
- let wrapped = wordWrap(r.errorDetail, textWidth);
61779
- let truncated = false;
61780
- if (wrapped.length > MAX_ERROR_LINES) {
61781
- wrapped = wrapped.slice(0, MAX_ERROR_LINES);
61782
- truncated = true;
61783
- }
61784
- if (truncated) {
61785
- const last = wrapped[wrapped.length - 1];
61786
- if (last.length >= textWidth) {
61787
- wrapped[wrapped.length - 1] = `${last.slice(0, textWidth - 1)}\u2026`;
61788
- } else {
61789
- wrapped[wrapped.length - 1] = `${last}\u2026`;
61790
- }
61791
- }
61792
- const indentStr = " ".repeat(errorIndent);
61793
- for (let i = 0;i < wrapped.length; i++) {
61794
- const prefix = i === 0 ? "\u2514 " : " ";
61795
- const body = `${indentStr}${pc.dim}${pc.red}${prefix}${wrapped[i]}${pc.reset}`;
61796
- w(`${renderTextLine(body, width, bg)}
61797
- `);
61798
- }
61799
- }
61800
- }
62283
+ if (r.errorDetail)
62284
+ writeSubRows(w, r.errorDetail, `${pc.dim}${pc.red}`, width, bg);
62285
+ if (r.note)
62286
+ writeSubRows(w, r.note, pc.dim, width, bg);
61801
62287
  }
61802
62288
  w(`${renderBlankLine(width)}
61803
62289
  `);
@@ -61809,14 +62295,61 @@ function renderCard(result, isLiveProbe, w, width, scales, directKeyVar) {
61809
62295
  w(`${renderTextLine(wireLine, width)}
61810
62296
  `);
61811
62297
  }
61812
- if (result.routingSource === "custom-rules" && result.matchedPattern) {
61813
- const note = `${pc.dim}Custom rule: ${pc.reset}${pc.cyan}${result.matchedPattern}${pc.reset}`;
61814
- w(`${renderTextLine(note, width)}
62298
+ if (visibleLength(routeLine) > 0) {
62299
+ w(`${renderTextLine(routeLine, width)}
61815
62300
  `);
61816
62301
  }
62302
+ writeDecisionNotes(w, result, width);
61817
62303
  w(`${renderBorderBottom(width)}
61818
62304
  `);
61819
62305
  }
62306
+ function writeDecisionNotes(w, result, width) {
62307
+ const textWidth = cardTextWidth(width);
62308
+ if (result.noRoute) {
62309
+ const head = "No route ";
62310
+ wordWrap(result.noRoute.reason, textWidth - head.length).forEach((line, i) => {
62311
+ const lead = i === 0 ? `${pc.red}No route${pc.reset} ` : " ".repeat(head.length);
62312
+ w(`${renderTextLine(`${lead}${line}`, width)}
62313
+ `);
62314
+ });
62315
+ const hintLines = (result.noRoute.hint ?? "").split(`
62316
+ `).filter((l) => l.trim());
62317
+ const fitted = hintLines.flatMap((l) => l.length <= textWidth ? [l] : wordWrap(l, textWidth));
62318
+ for (const line of fitted) {
62319
+ w(`${renderTextLine(`${pc.dim}${line}${pc.reset}`, width)}
62320
+ `);
62321
+ }
62322
+ }
62323
+ const notices = (result.warnings ?? []).filter((warning) => warning.type !== "rule-problem");
62324
+ for (const line of notices.flatMap((warning) => wordWrap(`\u26A0 ${warning.message}`, textWidth))) {
62325
+ w(`${renderTextLine(`${pc.yellow}${line}${pc.reset}`, width)}
62326
+ `);
62327
+ }
62328
+ }
62329
+ function cardTextWidth(width) {
62330
+ return Math.max(10, width - 2 - CARD_PADDING_LEFT - CARD_PADDING_RIGHT);
62331
+ }
62332
+ function writeSubRows(w, text, color, width, bg) {
62333
+ const indent = 4;
62334
+ const prefixVis = 2;
62335
+ const textWidth = cardTextWidth(width) - indent - prefixVis;
62336
+ const MAX_LINES = 4;
62337
+ if (textWidth <= 0)
62338
+ return;
62339
+ let wrapped = wordWrap(text, textWidth);
62340
+ if (wrapped.length > MAX_LINES) {
62341
+ wrapped = wrapped.slice(0, MAX_LINES);
62342
+ const last = wrapped[wrapped.length - 1];
62343
+ wrapped[wrapped.length - 1] = last.length >= textWidth ? `${last.slice(0, textWidth - 1)}\u2026` : `${last}\u2026`;
62344
+ }
62345
+ const indentStr = " ".repeat(indent);
62346
+ for (let i = 0;i < wrapped.length; i++) {
62347
+ const prefix = i === 0 ? "\u2514 " : " ";
62348
+ const body = `${indentStr}${color}${prefix}${wrapped[i]}${pc.reset}`;
62349
+ w(`${renderTextLine(body, width, bg)}
62350
+ `);
62351
+ }
62352
+ }
61820
62353
  function renderLegend(w) {
61821
62354
  const net = STAGE_BG_ANSI.network;
61822
62355
  const srv = STAGE_BG_ANSI.server;
@@ -61837,11 +62370,12 @@ function pickRepresentative(result) {
61837
62370
  return { model: result.model, provider: entry.displayName, timing: entry.probe.timing };
61838
62371
  }
61839
62372
  }
61840
- const direct = result.directProbe;
61841
- if (direct?.state === "live" && direct.timing) {
61842
- return { model: result.model, provider: result.nativeProvider, timing: direct.timing };
61843
- }
61844
- return { model: result.model, provider: result.nativeProvider };
62373
+ const first = result.chain?.[0];
62374
+ return {
62375
+ model: result.model,
62376
+ provider: first?.displayName ?? result.routingExplanation,
62377
+ missing: first?.notProbed ? "not probed (native)" : first ? "no live route" : "no route"
62378
+ };
61845
62379
  }
61846
62380
  function renderLeaderboard(results, scales, maxWidth, w) {
61847
62381
  const reps = results.map(pickRepresentative);
@@ -61935,7 +62469,7 @@ function renderLeaderboard(results, scales, maxWidth, w) {
61935
62469
  const rankStr = " ".repeat(rankW);
61936
62470
  const name = padEnd(`${pc.dim}${truncate3(row.model, nameW)}${pc.reset}`, nameW);
61937
62471
  const prov = padEnd(`${pc.dim}${truncate3(row.provider, provW)}${pc.reset}`, provW);
61938
- w(`${margin}${rankStr} ${name} ${prov} ${pc.dim}\u2014 no live route${pc.reset}
62472
+ w(`${margin}${rankStr} ${name} ${prov} ${pc.dim}\u2014 ${row.missing}${pc.reset}
61939
62473
  `);
61940
62474
  }
61941
62475
  const rowVis = leadW - MARGIN + LB_TIMELINE + (showBreakdown ? LB_BREAKDOWN : 0) + (showTokBar ? LB_TOKBAR : 2) + LB_TOK_VALUE;
@@ -61945,13 +62479,24 @@ function renderLeaderboard(results, scales, maxWidth, w) {
61945
62479
  w(`
61946
62480
  `);
61947
62481
  }
62482
+ function renderRuleProblems(results, w) {
62483
+ const messages = new Set(results.flatMap((r) => (r.warnings ?? []).filter((x) => x.type === "rule-problem").map((x) => x.message)));
62484
+ for (const message of messages) {
62485
+ w(` ${pc.yellow}\u26A0 routing rule: ${message}${pc.reset}
62486
+ `);
62487
+ }
62488
+ if (messages.size > 0)
62489
+ w(`
62490
+ `);
62491
+ }
61948
62492
  function printProbeResults(results, isLiveProbe) {
61949
62493
  refreshPc();
61950
62494
  const w = process.stderr.write.bind(process.stderr);
61951
62495
  w(`
61952
62496
  `);
61953
62497
  const scales = computeBarScales(results);
61954
- const anyTimedLive = results.some((r) => r.directProbe?.state === "live" && r.directProbe.timing !== undefined) || results.some((r) => (r.chain ?? []).some((c) => c.probe?.state === "live" && c.probe.timing !== undefined));
62498
+ const anyTimedLive = results.some((r) => (r.chain ?? []).some((c) => c.probe?.state === "live" && c.probe.timing !== undefined));
62499
+ renderRuleProblems(results, w);
61955
62500
  if (isLiveProbe && anyTimedLive) {
61956
62501
  renderLegend(w);
61957
62502
  }
@@ -61975,13 +62520,16 @@ function printProbeResults(results, isLiveProbe) {
61975
62520
  w(`
61976
62521
  `);
61977
62522
  }
61978
- w(` ${pc.dim}Tip: chain order is LiteLLM \u2192 Zen Go \u2192 Subscription \u2192 Native API \u2192 OpenRouter${pc.reset}
62523
+ w(` ${pc.dim}Tip: a matching user rule is used as written; otherwise the catalog orders hops by tier,${pc.reset}
62524
+ `);
62525
+ w(` ${pc.dim} subscription first, and the fallback hop comes last. Dropped candidates are never probed.${pc.reset}
61979
62526
  `);
61980
62527
  w(`
61981
62528
  `);
61982
62529
  }
61983
- var pc, ANSI_RE2, PRINTER_BAR_WIDTH = 24, PRINTER_TOK_WIDTH = 14, PRINTER_TRACK = "\xB7", PRINTER_BAR_FILL = "\u2588", STAGE_NUM_W = 6, PRINTER_TOK_VALUE_W = 9, PRINTER_BARS_FULL_WIDTH, PRINTER_BARS_NOTOK_WIDTH, PRINTER_BARS_MIN_WIDTH, MIN_CARD_WIDTH = 60, CARD_PADDING_LEFT = 2, CARD_PADDING_RIGHT = 2;
62530
+ var pc, ANSI_RE2, PRINTER_BAR_WIDTH = 24, PRINTER_TOK_WIDTH = 14, PRINTER_TRACK = "\xB7", PRINTER_BAR_FILL = "\u2588", STAGE_NUM_W = 6, PRINTER_TOK_VALUE_W = 9, PRINTER_BARS_FULL_WIDTH, PRINTER_BARS_NOTOK_WIDTH, PRINTER_BARS_MIN_WIDTH, MIN_CARD_WIDTH = 60, CARD_PADDING_LEFT = 2, CARD_PADDING_RIGHT = 2, COLUMN_HEADERS;
61984
62531
  var init_probe_results_printer = __esm(() => {
62532
+ init_native_route();
61985
62533
  init_probe_live();
61986
62534
  init_probe_runner();
61987
62535
  init_ansi();
@@ -61991,6 +62539,7 @@ var init_probe_results_printer = __esm(() => {
61991
62539
  PRINTER_BARS_FULL_WIDTH = 24 + 2 + 7 + 34 + 17 + 9;
61992
62540
  PRINTER_BARS_NOTOK_WIDTH = 24 + 2 + 7 + 34 + 2 + 9;
61993
62541
  PRINTER_BARS_MIN_WIDTH = 24 + 2 + 7 + 2 + 9;
62542
+ COLUMN_HEADERS = ["#", "Provider", "Tier", "Model Spec", "Status"];
61994
62543
  });
61995
62544
 
61996
62545
  // src/probe/probe-tui-app.tsx
@@ -62183,7 +62732,7 @@ function ProgressBar({
62183
62732
  isRunFastest
62184
62733
  }) {
62185
62734
  const elapsedMs = link.status === "waiting" ? 0 : link.startTime ? (link.endTime ?? Date.now()) - link.startTime : 0;
62186
- const elapsed = formatElapsed(elapsedMs);
62735
+ const elapsed = link.status === "not-probed" ? " \u2013 " : formatElapsed(elapsedMs);
62187
62736
  const displayName = padEndSafe(link.displayName, maxNameLen);
62188
62737
  const prefix = /* @__PURE__ */ jsxs9(Fragment6, {
62189
62738
  children: [
@@ -62201,6 +62750,19 @@ function ProgressBar({
62201
62750
  })
62202
62751
  ]
62203
62752
  });
62753
+ if (link.status === "not-probed") {
62754
+ const used = ELAPSED_COL + maxNameLen + 2;
62755
+ const fg = link.tone === "native" ? C.cyan : link.tone === "ready" ? C.green : C.dim;
62756
+ return /* @__PURE__ */ jsxs9("text", {
62757
+ children: [
62758
+ prefix,
62759
+ /* @__PURE__ */ jsx12("span", {
62760
+ fg,
62761
+ children: clipReason(stripAnsi3(link.note ?? "not probed"), layout.width - used)
62762
+ })
62763
+ ]
62764
+ });
62765
+ }
62204
62766
  if (layout.pillFallback) {
62205
62767
  if (link.status === "live") {
62206
62768
  const latency = link.timing?.totalMs ?? elapsedMs;
@@ -62562,6 +63124,16 @@ function shortFailureReason(probe, hasCreds) {
62562
63124
  return "key missing";
62563
63125
  return stripAnsi3(describeProbeState(probe));
62564
63126
  }
63127
+ function notProbedRow(link) {
63128
+ if (link.notProbed)
63129
+ return { mark: "\u25D0 ", text: `native \u2014 ${NATIVE_NOT_PROBED}`, fg: C.cyan };
63130
+ if (link.dropped) {
63131
+ return { mark: "\u2013 ", text: describeDropped(link.dropped, link.credentialHint), fg: C.dim };
63132
+ }
63133
+ if (!link.probe && link.hasCredentials)
63134
+ return { mark: "\u25CB ", text: "not probed", fg: C.dim };
63135
+ return null;
63136
+ }
62565
63137
  function DetailLinkRow({
62566
63138
  link,
62567
63139
  isWinner,
@@ -62598,8 +63170,24 @@ function DetailLinkRow({
62598
63170
  })
62599
63171
  ]
62600
63172
  });
63173
+ const used = 2 + 1 + 1 + provW + 2 + 3;
63174
+ const idle = notProbedRow(link);
63175
+ if (idle) {
63176
+ return /* @__PURE__ */ jsxs9("text", {
63177
+ children: [
63178
+ lead,
63179
+ /* @__PURE__ */ jsx12("span", {
63180
+ fg: idle.fg,
63181
+ children: idle.mark
63182
+ }),
63183
+ /* @__PURE__ */ jsx12("span", {
63184
+ fg: idle.fg,
63185
+ children: clipReason(idle.text, layout.width - used)
63186
+ })
63187
+ ]
63188
+ });
63189
+ }
62601
63190
  if (!isLive || !probe?.timing) {
62602
- const used = 2 + 1 + 1 + provW + 2 + 3;
62603
63191
  return /* @__PURE__ */ jsxs9("text", {
62604
63192
  children: [
62605
63193
  lead,
@@ -62732,7 +63320,7 @@ function DetailModel({
62732
63320
  const gap = Math.max(2, headerW - result.model.length - result.routingExplanation.length - 2);
62733
63321
  const wiring = result.wiring;
62734
63322
  const wireLine = winner && wiring ? `wire (${winner.displayName}): ${wiring.effectiveStreamFormat} \xB7 ${wiring.modelTranslator} \xB7 ${formatContextWindow2(wiring.contextWindow)} ctx` : "wire: \u2014";
62735
- const routeChain = result.links.map((l) => l.displayName).join(" \u2192 ");
63323
+ const routeChain = result.links.filter((l) => !l.dropped).map((l) => l.label ? `${l.displayName} (${l.label})` : l.displayName).join(" \u2192 ");
62736
63324
  const liveLinks = result.links.filter((l) => l.probe?.state === "live" && !!l.probe.timing);
62737
63325
  let fastestByLatency;
62738
63326
  let fastestByTput;
@@ -62791,6 +63379,24 @@ function DetailModel({
62791
63379
  })
62792
63380
  ]
62793
63381
  }),
63382
+ result.noRoute && /* @__PURE__ */ jsxs9("text", {
63383
+ children: [
63384
+ /* @__PURE__ */ jsx12("span", {
63385
+ fg: C.red,
63386
+ children: " no route "
63387
+ }),
63388
+ /* @__PURE__ */ jsx12("span", {
63389
+ fg: C.fg,
63390
+ children: clipReason(result.noRoute.reason, layout.width - 12)
63391
+ })
63392
+ ]
63393
+ }),
63394
+ (result.warnings ?? []).filter((w) => w.type !== "rule-problem").map((w) => /* @__PURE__ */ jsx12("text", {
63395
+ children: /* @__PURE__ */ jsx12("span", {
63396
+ fg: C.yellow,
63397
+ children: ` \u26A0 ${clipReason(w.message, layout.width - 4)}`
63398
+ })
63399
+ }, `${result.model}:warn:${w.message}`)),
62794
63400
  result.links.map((link, i) => /* @__PURE__ */ jsx12(DetailLinkRow, {
62795
63401
  link,
62796
63402
  isWinner: i === winnerIdx,
@@ -62874,17 +63480,28 @@ function DetailsView({
62874
63480
  }) {
62875
63481
  const provW = Math.min(22, Math.max(8, ...results.flatMap((r) => r.links.map((l) => l.displayName.length))));
62876
63482
  const headerW = Math.max(24, Math.min(detailRowWidth(provW, layout), (termWidth || 100) - 3));
62877
- return /* @__PURE__ */ jsx12("box", {
63483
+ const ruleProblems = [
63484
+ ...new Set(results.flatMap((r) => (r.warnings ?? []).filter((w) => w.type === "rule-problem").map((w) => w.message)))
63485
+ ];
63486
+ return /* @__PURE__ */ jsxs9("box", {
62878
63487
  flexDirection: "column",
62879
- children: results.map((r, idx) => /* @__PURE__ */ jsx12(DetailModel, {
62880
- result: r,
62881
- provW,
62882
- headerW,
62883
- layout,
62884
- maxTotalMs,
62885
- maxTokPerSec,
62886
- isLast: idx === results.length - 1
62887
- }, r.model))
63488
+ children: [
63489
+ ruleProblems.map((message) => /* @__PURE__ */ jsx12("text", {
63490
+ children: /* @__PURE__ */ jsx12("span", {
63491
+ fg: C.yellow,
63492
+ children: ` \u26A0 routing rule: ${clipReason(message, (termWidth || 100) - 20)}`
63493
+ })
63494
+ }, `rule-problem:${message}`)),
63495
+ results.map((r, idx) => /* @__PURE__ */ jsx12(DetailModel, {
63496
+ result: r,
63497
+ provW,
63498
+ headerW,
63499
+ layout,
63500
+ maxTotalMs,
63501
+ maxTokPerSec,
63502
+ isLast: idx === results.length - 1
63503
+ }, r.model))
63504
+ ]
62888
63505
  });
62889
63506
  }
62890
63507
  function pickRepresentativeLink(result) {
@@ -62893,7 +63510,12 @@ function pickRepresentativeLink(result) {
62893
63510
  return { model: result.model, provider: link.displayName, timing: link.probe.timing };
62894
63511
  }
62895
63512
  }
62896
- return { model: result.model, provider: result.nativeProvider };
63513
+ const first = result.links.find((link) => !link.dropped);
63514
+ return {
63515
+ model: result.model,
63516
+ provider: first?.displayName ?? result.routingExplanation,
63517
+ missing: first?.notProbed ? "not probed (native)" : first ? "no live route" : "no route"
63518
+ };
62897
63519
  }
62898
63520
  function LeaderLiveRow({
62899
63521
  row,
@@ -63167,7 +63789,7 @@ function LeaderboardView({
63167
63789
  }),
63168
63790
  /* @__PURE__ */ jsx12("span", {
63169
63791
  fg: C.dim,
63170
- children: " \u2014 no live route"
63792
+ children: ` \u2014 ${row.missing}`
63171
63793
  })
63172
63794
  ]
63173
63795
  }, `lb-na:${row.model}`))
@@ -63295,7 +63917,7 @@ function ProbeApp({
63295
63917
  isDone ? /* @__PURE__ */ jsx12(TabBar, {
63296
63918
  activeTab: state.activeTab
63297
63919
  }) : null,
63298
- groups.length > 0 ? /* @__PURE__ */ jsxs9(Fragment6, {
63920
+ groups.length > 0 || state.results.length > 0 ? /* @__PURE__ */ jsxs9(Fragment6, {
63299
63921
  children: [
63300
63922
  showSummary && /* @__PURE__ */ jsx12("box", {
63301
63923
  flexDirection: "column",
@@ -63347,7 +63969,9 @@ function ProbeApp({
63347
63969
  }
63348
63970
  var ANIM_FRAMES2, TIMELINE_BAR_FULL = 24, TIMELINE_BAR_NARROW = 12, TOK_BAR_FULL = 14, TOTAL_COL = 7, ELAPSED_COL = 11, STAGE_NUM_W2 = 6, BREAKDOWN_COL, TOK_VALUE_COL = 7, TRACK_CHAR = "\xB7", BAR_FILL = "\u2588", BANNER_ROWS = 7, SCROLL_HINT_ROWS = 1, LEGEND_ROWS = 2, MIN_LIST_H = 4, TAB_BAR_ROWS = 2;
63349
63971
  var init_probe_tui_app = __esm(() => {
63972
+ init_native_route();
63350
63973
  init_probe_live();
63974
+ init_probe_runner();
63351
63975
  init_theme_mode();
63352
63976
  init_useAnimationFrame();
63353
63977
  init_theme2();
@@ -63677,8 +64301,7 @@ import {
63677
64301
  mkdirSync as mkdirSync19,
63678
64302
  readFileSync as readFileSync32,
63679
64303
  readdirSync as readdirSync7,
63680
- unlinkSync as unlinkSync8,
63681
- writeFileSync as writeFileSync20
64304
+ unlinkSync as unlinkSync8
63682
64305
  } from "fs";
63683
64306
  import { homedir as homedir36 } from "os";
63684
64307
  import { dirname as dirname13, join as join42 } from "path";
@@ -63902,13 +64525,10 @@ async function parseArgs(args) {
63902
64525
  process.exit(1);
63903
64526
  }
63904
64527
  config.profile = profileArg;
63905
- } else if (arg === "--default-provider") {
63906
- const dpArg = args[++i];
63907
- if (!dpArg) {
63908
- console.error("--default-provider requires a provider name");
63909
- process.exit(1);
64528
+ } else if (arg === "--default-provider" || arg.startsWith("--default-provider=")) {
64529
+ if (arg === "--default-provider" && i + 1 < args.length && !args[i + 1].startsWith("-")) {
64530
+ i++;
63910
64531
  }
63911
- config.defaultProvider = dpArg;
63912
64532
  } else if (arg === "--anthropic-api-billing") {
63913
64533
  config.anthropicApiBilling = true;
63914
64534
  } else if (arg === "--classifier-model") {
@@ -64165,28 +64785,6 @@ Usage: claudish --models --provider <slug>`);
64165
64785
  config.modelSubagent = profileModels.subagent;
64166
64786
  }
64167
64787
  }
64168
- try {
64169
- const fileConfigForResolver = loadConfig();
64170
- const resolved = resolveDefaultProvider({
64171
- cliFlag: config.defaultProvider,
64172
- config: fileConfigForResolver,
64173
- env: process.env
64174
- });
64175
- config.resolvedDefaultProvider = resolved;
64176
- if (resolved.legacyAutoPromoted && !config.quiet) {
64177
- const markerFile = join42(homedir36(), ".claudish", ".legacy-litellm-hint-shown");
64178
- if (!existsSync32(markerFile)) {
64179
- const hint = buildLegacyHint(resolved);
64180
- if (hint) {
64181
- console.error(hint);
64182
- }
64183
- try {
64184
- mkdirSync19(dirname13(markerFile), { recursive: true });
64185
- writeFileSync20(markerFile, new Date().toISOString(), "utf-8");
64186
- } catch {}
64187
- }
64188
- }
64189
- } catch {}
64190
64788
  if (config.proOnUltracode === undefined) {
64191
64789
  const envVal = process.env.CLAUDISH_PRO_ON_ULTRACODE;
64192
64790
  if (envVal !== undefined) {
@@ -64428,7 +65026,7 @@ async function printRecommendedModels(jsonOutput, forceUpdate) {
64428
65026
  const { flagship, fast } = groupRecommendedModels(doc.models);
64429
65027
  const providerByName = new Map(BUILTIN_PROVIDERS.map((p) => [p.name, p]));
64430
65028
  const getNativePrefix = (firebaseSlug) => {
64431
- const canonical = FIREBASE_SLUG_TO_PROVIDER_NAME[firebaseSlug];
65029
+ const canonical = nativeProviderForVendor(firebaseSlug);
64432
65030
  if (!canonical)
64433
65031
  return null;
64434
65032
  const def = providerByName.get(canonical);
@@ -64530,204 +65128,62 @@ async function printVersion() {
64530
65128
  console.log("");
64531
65129
  }
64532
65130
  async function probeModelRouting(models, jsonOutput, options = { live: true, timeoutMs: 40000 }) {
64533
- const userRoutingKeys = new Set([
64534
- ...Object.keys(loadConfig().routing ?? {}),
64535
- ...Object.keys(loadLocalConfig()?.routing ?? {})
64536
- ]);
64537
65131
  function credentialHintFrom(provenance, envVar) {
64538
65132
  if (provenance?.effectiveLabel)
64539
65133
  return provenance.effectiveSource;
64540
65134
  return envVar;
64541
65135
  }
64542
- function credentialHintFor(provider, envVar, aliases) {
64543
- if (!envVar)
64544
- return;
64545
- return credentialHintFrom(resolveCredentialProvenance(provider, envVar, aliases), envVar);
64546
- }
64547
- function buildModelChain(modelInput) {
64548
- const parsed = parseModelSpec(modelInput);
64549
- const chain = (() => {
64550
- if (parsed.isExplicitProvider) {
64551
- return {
64552
- routes: [],
64553
- source: "direct",
64554
- matchedPattern: undefined
64555
- };
64556
- }
64557
- if (parsed.provider === "native-anthropic") {
64558
- const tier = claudeCodeTierAlias(parsed.model);
64559
- const tierEnv = {
64560
- opus: process.env[ENV.CLAUDISH_MODEL_OPUS] || process.env[ENV.ANTHROPIC_DEFAULT_OPUS_MODEL],
64561
- sonnet: process.env[ENV.CLAUDISH_MODEL_SONNET] || process.env[ENV.ANTHROPIC_DEFAULT_SONNET_MODEL],
64562
- haiku: process.env[ENV.CLAUDISH_MODEL_HAIKU] || process.env[ENV.ANTHROPIC_DEFAULT_HAIKU_MODEL]
64563
- };
64564
- const opusModel = tier ? tierEnv[tier] || latestAnthropicTierModelId(tier) || "claude-opus-5" : parsed.model;
64565
- return {
64566
- routes: [
64567
- {
64568
- provider: "native-anthropic",
64569
- modelSpec: opusModel,
64570
- displayName: tier ? `Claude Code (${tier})` : "Claude Code"
64571
- }
64572
- ],
64573
- source: "auto-chain",
64574
- matchedPattern: undefined
64575
- };
64576
- }
64577
- const routingRules = loadRoutingRules();
64578
- const matched = matchRoutingRule(parsed.model, routingRules);
64579
- if (matched) {
64580
- const matchedPattern = Object.keys(routingRules).find((k) => {
64581
- if (k === parsed.model)
64582
- return true;
64583
- if (k.includes("*")) {
64584
- const star = k.indexOf("*");
64585
- const prefix = k.slice(0, star);
64586
- const suffix = k.slice(star + 1);
64587
- return parsed.model.startsWith(prefix) && parsed.model.endsWith(suffix);
64588
- }
64589
- return false;
64590
- });
64591
- const isUserKey = !!matchedPattern && userRoutingKeys.has(matchedPattern);
64592
- return {
64593
- routes: buildRoutingChain(matched, parsed.model),
64594
- source: isUserKey ? "custom-rules" : "auto-chain",
64595
- matchedPattern
64596
- };
64597
- }
64598
- return {
64599
- routes: buildCatalogChain(parsed.model, loadConfig().defaultProvider).routes,
64600
- source: "auto-chain",
64601
- matchedPattern: undefined
64602
- };
64603
- })();
64604
- const chainDetails = chain.routes.map((route) => {
64605
- const keyInfo = API_KEY_MAP[route.provider];
64606
- const providerDef = getProviderByName(route.provider);
64607
- let hasCredentials = false;
64608
- let credentialHint;
64609
- let provenance;
64610
- if (route.provider === "native-anthropic") {
64611
- hasCredentials = !!process.env.ANTHROPIC_API_KEY;
64612
- if (!hasCredentials) {
64613
- credentialHint = "ANTHROPIC_API_KEY (required to probe Claude Code)";
64614
- }
64615
- } else if (providerDef?.isLocal) {
64616
- hasCredentials = isLocalProviderEnabled(route.provider);
64617
- if (!hasCredentials) {
64618
- credentialHint = "enable local provider in global config";
64619
- }
64620
- } else if (!keyInfo) {
64621
- hasCredentials = true;
64622
- } else if (!keyInfo.envVar) {
64623
- hasCredentials = true;
64624
- } else {
64625
- provenance = resolveCredentialProvenance(route.provider, keyInfo.envVar, keyInfo.aliases);
64626
- hasCredentials = provenance.hasValue;
64627
- if (!hasCredentials && keyInfo.aliases) {
64628
- hasCredentials = keyInfo.aliases.some((a) => !!process.env[a]);
64629
- }
64630
- if (!hasCredentials) {
64631
- credentialHint = credentialHintFrom(provenance, keyInfo.envVar);
64632
- }
64633
- }
64634
- return {
64635
- provider: route.provider,
64636
- displayName: route.displayName,
64637
- modelSpec: route.modelSpec,
64638
- hasCredentials,
64639
- credentialHint,
64640
- provenance,
64641
- probe: undefined
64642
- };
64643
- });
64644
- return { parsed, chain, chainDetails };
64645
- }
64646
- const probeCredentialReadiness = new Map;
64647
- async function credentialForProbe(provider) {
64648
- if (probeCredentialReadiness.has(provider))
64649
- return probeCredentialReadiness.get(provider);
64650
- const ready = provider === "native-anthropic" ? !!process.env.ANTHROPIC_API_KEY : await hasCredentialsForProvider(provider);
64651
- probeCredentialReadiness.set(provider, ready);
64652
- return ready;
64653
- }
64654
- async function prepareModelChain(modelInput) {
64655
- const result = buildModelChain(modelInput);
64656
- await Promise.all(result.chainDetails.map(async (link) => {
64657
- link.hasCredentials = await credentialForProbe(link.provider);
64658
- const keyInfo = API_KEY_MAP[link.provider];
64659
- if (keyInfo?.envVar)
64660
- link.provenance = resolveCredentialProvenance(link.provider, keyInfo.envVar, keyInfo.aliases);
64661
- link.credentialHint = link.hasCredentials ? undefined : link.provider === "native-anthropic" ? "ANTHROPIC_API_KEY (required to probe Claude Code)" : getProviderByName(link.provider)?.isLocal ? "enable local provider in global config" : credentialHintFrom(link.provenance, keyInfo?.envVar);
64662
- }));
64663
- if (result.chain.source === "direct")
64664
- await credentialForProbe(result.parsed.provider);
64665
- return result;
64666
- }
64667
- function buildRoutingExplanation(parsed, chain) {
64668
- if (parsed.provider === "native-anthropic") {
64669
- return "native passthrough \xB7 default Claude Code (Opus)";
64670
- }
64671
- if (chain.source === "direct") {
64672
- return `explicit \xB7 ${parsed.provider} (direct)`;
64673
- }
64674
- if (chain.source === "custom-rules" && chain.matchedPattern) {
64675
- return `custom-rules \xB7 matched \`${chain.matchedPattern}\``;
64676
- }
64677
- if (chain.source === "auto-chain") {
64678
- if (chain.matchedPattern && chain.matchedPattern !== "*") {
64679
- return `auto-chain \xB7 default rule \`${chain.matchedPattern}\``;
64680
- }
64681
- return "auto-chain \xB7 catch-all \u2192 openrouter";
65136
+ const probeCredentials = {
65137
+ hintFor(provider) {
65138
+ if (getProviderByName(provider)?.isLocal)
65139
+ return "enable local provider in global config";
65140
+ const keyInfo = API_KEY_MAP[provider];
65141
+ if (!keyInfo?.envVar)
65142
+ return;
65143
+ return credentialHintFrom(resolveCredentialProvenance(provider, keyInfo.envVar, keyInfo.aliases), keyInfo.envVar);
65144
+ },
65145
+ provenanceFor(provider) {
65146
+ const keyInfo = API_KEY_MAP[provider];
65147
+ return keyInfo?.envVar ? resolveCredentialProvenance(provider, keyInfo.envVar, keyInfo.aliases) : undefined;
64682
65148
  }
64683
- return chain.source;
64684
- }
64685
- function buildDirectChainEntry(parsed, directProbe) {
64686
- const providerDef = getProviderByName(parsed.provider);
64687
- const keyInfo = API_KEY_MAP[parsed.provider];
64688
- if (!providerDef && !keyInfo)
64689
- return [];
64690
- const hasCredentials = probeCredentialReadiness.get(parsed.provider) ?? false;
64691
- const provenance = keyInfo?.envVar ? resolveCredentialProvenance(parsed.provider, keyInfo.envVar, keyInfo.aliases) : undefined;
64692
- return [
64693
- {
64694
- provider: parsed.provider,
64695
- displayName: providerDef?.displayName ?? parsed.provider,
64696
- modelSpec: parsed.model,
64697
- hasCredentials,
64698
- credentialHint: !hasCredentials ? providerDef?.isLocal ? "enable local provider in global config" : credentialHintFrom(provenance, keyInfo?.envVar) : undefined,
64699
- provenance,
64700
- probe: directProbe
64701
- }
64702
- ];
65149
+ };
65150
+ async function explainForProbe(modelInput) {
65151
+ const explanation = await explainRoute(modelInput);
65152
+ const { chain, dropped } = probeChainFrom(explanation, probeCredentials);
65153
+ const targets = probeTargets(explanation).map((target, i) => ({ target, link: chain[i] }));
65154
+ return { modelInput, explanation, chain, dropped, targets };
64703
65155
  }
64704
- function buildResultLinks(parsed, chainDetails, directProbe) {
64705
- if (chainDetails.length === 0) {
64706
- const directProviderDef = getProviderByName(parsed.provider);
64707
- const directKeyInfo = API_KEY_MAP[parsed.provider];
64708
- const directHasCreds = probeCredentialReadiness.get(parsed.provider) ?? false;
64709
- return [
64710
- {
64711
- provider: parsed.provider,
64712
- displayName: directProviderDef?.displayName ?? parsed.provider,
64713
- modelId: parsed.model,
64714
- hasCredentials: directHasCreds,
64715
- credentialHint: !directHasCreds ? directProviderDef?.isLocal ? "enable local provider in global config" : credentialHintFor(parsed.provider, directKeyInfo?.envVar, directKeyInfo?.aliases) : undefined,
64716
- probe: directProbe
64717
- }
64718
- ];
64719
- }
64720
- return chainDetails.map((c) => ({
64721
- provider: c.provider,
64722
- displayName: c.displayName,
64723
- modelId: c.modelSpec.includes("@") ? c.modelSpec.slice(c.modelSpec.indexOf("@") + 1) : c.modelSpec,
64724
- hasCredentials: c.hasCredentials,
64725
- credentialHint: c.credentialHint,
64726
- probe: c.probe
65156
+ function probeTarget(proxyUrl, target) {
65157
+ return probeLink(proxyUrl, { provider: target.provider, modelSpec: target.probeSpec, hasCredentials: true }, options.timeoutMs).catch((e) => ({
65158
+ state: "error",
65159
+ latencyMs: 0,
65160
+ errorMessage: String(e instanceof Error ? e.message : e)
64727
65161
  }));
64728
65162
  }
64729
- async function computeWiring(chainDetails, parsedModel) {
64730
- const firstReadyRoute = chainDetails.find((c) => c.hasCredentials);
65163
+ function chainProbeOf(probe, wiring) {
65164
+ const exp = probe.explanation;
65165
+ const directProbe = exp.source === "explicit" ? probe.chain[0]?.probe : undefined;
65166
+ return {
65167
+ model: probe.modelInput,
65168
+ nativeProvider: parseModelSpec(probe.modelInput).provider,
65169
+ isExplicit: exp.source === "explicit",
65170
+ routingSource: exp.source,
65171
+ routingExplanation: routingFieldsFrom(exp).routingExplanation,
65172
+ ...exp.via ? { via: exp.via } : {},
65173
+ ...exp.matchedPattern !== undefined ? { matchedPattern: exp.matchedPattern } : {},
65174
+ ...exp.ruleScope ? { ruleScope: exp.ruleScope } : {},
65175
+ ...exp.catalog ? { catalog: exp.catalog } : {},
65176
+ ...exp.fallbackWithheld ? { fallbackWithheld: exp.fallbackWithheld } : {},
65177
+ outcome: exp.outcome,
65178
+ warnings: exp.warnings,
65179
+ chain: probe.chain,
65180
+ dropped: probe.dropped,
65181
+ ...directProbe ? { directProbe } : {},
65182
+ ...wiring ? { wiring } : {}
65183
+ };
65184
+ }
65185
+ async function computeWiring(chain, parsedModel) {
65186
+ const firstReadyRoute = chain.find((c) => c.hasCredentials && !c.notProbed);
64731
65187
  if (!firstReadyRoute)
64732
65188
  return;
64733
65189
  const providerName = firstReadyRoute.provider;
@@ -64808,52 +65264,16 @@ async function probeModelRouting(models, jsonOutput, options = { live: true, tim
64808
65264
  try {
64809
65265
  const results = [];
64810
65266
  for (const modelInput of models) {
64811
- const { parsed, chain, chainDetails } = await prepareModelChain(modelInput);
64812
- let directProbeResult;
64813
- if (liveProxy && chain.source === "direct") {
64814
- const directKeyInfo = API_KEY_MAP[parsed.provider];
64815
- const directProviderDef = getProviderByName(parsed.provider);
64816
- const directHasCreds = await credentialForProbe(parsed.provider);
64817
- const directCredentialHint = directProviderDef?.isLocal && !directHasCreds ? "enable local provider in global config" : credentialHintFor(parsed.provider, directKeyInfo?.envVar, directKeyInfo?.aliases);
64818
- directProbeResult = await probeProviderRoute(liveProxy.url, {
64819
- provider: parsed.provider,
64820
- modelSpec: modelInput,
64821
- hasCredentials: directHasCreds,
64822
- credentialHint: directCredentialHint
64823
- }, options.timeoutMs).catch((e) => ({
64824
- state: "error",
64825
- latencyMs: 0,
64826
- errorMessage: String(e instanceof Error ? e.message : e)
64827
- }));
64828
- }
65267
+ const probe = await explainForProbe(modelInput);
64829
65268
  if (liveProxy) {
64830
- const probes = await Promise.all(chainDetails.map((link) => {
64831
- return probeProviderRoute(liveProxy.url, {
64832
- provider: link.provider,
64833
- modelSpec: link.modelSpec,
64834
- hasCredentials: link.hasCredentials,
64835
- credentialHint: link.credentialHint
64836
- }, options.timeoutMs).catch((e) => ({
64837
- state: "error",
64838
- latencyMs: 0,
64839
- errorMessage: String(e instanceof Error ? e.message : e)
64840
- }));
64841
- }));
64842
- for (let i = 0;i < chainDetails.length; i++) {
64843
- chainDetails[i].probe = probes[i];
64844
- }
65269
+ const url = liveProxy.url;
65270
+ const probes = await Promise.all(probe.targets.map(({ target }) => probeTarget(url, target)));
65271
+ probe.targets.forEach(({ link }, i) => {
65272
+ link.probe = probes[i];
65273
+ });
64845
65274
  }
64846
- const wiring = await computeWiring(chainDetails, parsed.model);
64847
- results.push({
64848
- model: modelInput,
64849
- nativeProvider: parsed.provider,
64850
- isExplicit: parsed.isExplicitProvider,
64851
- routingSource: chain.source,
64852
- matchedPattern: chain.matchedPattern,
64853
- chain: chainDetails.length > 0 ? chainDetails : buildDirectChainEntry(parsed, directProbeResult),
64854
- directProbe: directProbeResult,
64855
- wiring
64856
- });
65275
+ const wiring = await computeWiring(probe.chain, probe.explanation.routedModel);
65276
+ results.push(chainProbeOf(probe, wiring));
64857
65277
  }
64858
65278
  console.log(JSON.stringify(results, null, 2));
64859
65279
  } finally {
@@ -64897,9 +65317,6 @@ async function probeModelRouting(models, jsonOutput, options = { live: true, tim
64897
65317
  };
64898
65318
  let liveProxy = null;
64899
65319
  try {
64900
- addStep("Loading routing rules", "running");
64901
- loadRoutingRules();
64902
- updateStep("Loading routing rules", "done");
64903
65320
  if (options.live) {
64904
65321
  addStep("Starting probe proxy", "running");
64905
65322
  try {
@@ -64914,122 +65331,99 @@ async function probeModelRouting(models, jsonOutput, options = { live: true, tim
64914
65331
  }
64915
65332
  }
64916
65333
  addStep("Resolving routing chains", "running");
64917
- const modelChains = [];
65334
+ const modelProbes = [];
64918
65335
  for (const modelInput of models) {
64919
- const { parsed, chain, chainDetails } = await prepareModelChain(modelInput);
64920
- modelChains.push({ modelInput, parsed, chain, chainDetails });
65336
+ modelProbes.push(await explainForProbe(modelInput));
64921
65337
  }
64922
65338
  updateStep("Resolving routing chains", "done");
64923
- const directProbeResults = new Map;
64924
- if (liveProxy) {
64925
- const allLinks = [];
64926
- for (const { modelInput, parsed, chain, chainDetails } of modelChains) {
64927
- if (chain.source === "direct") {
64928
- const directKeyInfo = API_KEY_MAP[parsed.provider];
64929
- const directProviderDef = getProviderByName(parsed.provider);
64930
- const directHasCreds = await credentialForProbe(parsed.provider);
64931
- const directCredentialHint = directProviderDef?.isLocal && !directHasCreds ? "enable local provider in global config" : credentialHintFor(parsed.provider, directKeyInfo?.envVar, directKeyInfo?.aliases);
64932
- allLinks.push({
64933
- id: `${modelInput}:direct`,
64934
- displayName: parsed.provider,
64935
- modelSpec: modelInput,
64936
- provider: parsed.provider,
64937
- pinnedSpec: modelInput,
64938
- hasCredentials: directHasCreds,
64939
- credentialHint: directCredentialHint,
64940
- chainDetail: null,
64941
- isDirect: true,
64942
- modelInput
65339
+ const liveRows = [];
65340
+ const rows = [];
65341
+ for (const probe of modelProbes) {
65342
+ const model = probe.modelInput;
65343
+ probe.targets.forEach(({ target, link }, i) => {
65344
+ const id = `${model}:${i}:${target.provider}`;
65345
+ if (liveProxy) {
65346
+ liveRows.push({ id, target, link });
65347
+ rows.push({
65348
+ id,
65349
+ model,
65350
+ displayName: target.displayName,
65351
+ modelSpec: target.probeSpec,
65352
+ status: "waiting"
64943
65353
  });
64944
- }
64945
- for (const link of chainDetails) {
64946
- const pinnedSpec = pinProbeModelSpec(link);
64947
- allLinks.push({
64948
- id: `${modelInput}:${link.provider}`,
64949
- displayName: link.displayName,
64950
- modelSpec: pinnedSpec,
64951
- provider: link.provider,
64952
- pinnedSpec,
64953
- hasCredentials: link.hasCredentials,
64954
- credentialHint: link.credentialHint,
64955
- chainDetail: link,
64956
- isDirect: false,
64957
- modelInput
65354
+ } else {
65355
+ rows.push({
65356
+ id,
65357
+ model,
65358
+ displayName: target.displayName,
65359
+ modelSpec: target.probeSpec,
65360
+ status: "not-probed",
65361
+ tone: "ready",
65362
+ note: `\u25CB ${link.label} \xB7 not probed (--no-probe)`
64958
65363
  });
64959
65364
  }
64960
- }
64961
- setLinks(allLinks.map((l) => ({
64962
- id: l.id,
64963
- model: l.modelInput,
64964
- displayName: l.displayName,
64965
- modelSpec: l.modelSpec,
64966
- status: "waiting"
64967
- })));
64968
- const probePromises = allLinks.map(async (link) => {
64969
- updateLink(link.id, { status: "probing", startTime: Date.now() });
64970
- const result = await probeProviderRoute(liveProxy.url, {
64971
- provider: link.provider,
65365
+ });
65366
+ for (const link of probe.chain) {
65367
+ if (!link.notProbed)
65368
+ continue;
65369
+ rows.push({
65370
+ id: `${model}:native`,
65371
+ model,
65372
+ displayName: link.displayName,
64972
65373
  modelSpec: link.modelSpec,
64973
- hasCredentials: link.hasCredentials,
64974
- credentialHint: link.credentialHint
64975
- }, options.timeoutMs).catch((e) => ({
64976
- state: "error",
64977
- latencyMs: 0,
64978
- errorMessage: String(e instanceof Error ? e.message : e)
64979
- }));
65374
+ status: "not-probed",
65375
+ tone: "native",
65376
+ note: `\u25D0 native \u2014 ${NATIVE_NOT_PROBED}`
65377
+ });
65378
+ }
65379
+ probe.dropped.forEach((entry, i) => {
65380
+ rows.push({
65381
+ id: `${model}:dropped:${i}:${entry.provider}`,
65382
+ model,
65383
+ displayName: entry.displayName,
65384
+ modelSpec: entry.wireId,
65385
+ status: "not-probed",
65386
+ tone: "dropped",
65387
+ note: `\u2013 ${describeDropped(entry.outcome, entry.credentialHint)}`
65388
+ });
65389
+ });
65390
+ }
65391
+ setLinks(rows);
65392
+ if (liveProxy) {
65393
+ const url = liveProxy.url;
65394
+ await Promise.all(liveRows.map(async ({ id, target, link }) => {
65395
+ updateLink(id, { status: "probing", startTime: Date.now() });
65396
+ const result = await probeTarget(url, target);
64980
65397
  if (result.state === "live") {
64981
- updateLink(link.id, {
64982
- status: "live",
64983
- endTime: Date.now(),
64984
- timing: result.timing
64985
- });
65398
+ updateLink(id, { status: "live", endTime: Date.now(), timing: result.timing });
64986
65399
  } else {
64987
- updateLink(link.id, {
65400
+ updateLink(id, {
64988
65401
  status: "failed",
64989
65402
  endTime: Date.now(),
64990
65403
  error: describeProbeState(result)
64991
65404
  });
64992
65405
  }
64993
- if (link.isDirect) {
64994
- directProbeResults.set(link.modelInput, result);
64995
- } else if (link.chainDetail) {
64996
- link.chainDetail.probe = result;
64997
- }
64998
- });
64999
- await Promise.all(probePromises);
65406
+ link.probe = result;
65407
+ }));
65000
65408
  }
65001
65409
  const isLiveProbe = !!liveProxy;
65002
65410
  const printable = [];
65003
65411
  const results = [];
65004
- for (const { modelInput, parsed, chain, chainDetails } of modelChains) {
65005
- const wiring = await computeWiring(chainDetails, parsed.model);
65006
- const directProbe = directProbeResults.get(modelInput);
65412
+ for (const probe of modelProbes) {
65413
+ const wiring = await computeWiring(probe.chain, probe.explanation.routedModel);
65414
+ const fields = routingFieldsFrom(probe.explanation);
65007
65415
  printable.push({
65008
- model: modelInput,
65009
- nativeProvider: parsed.provider,
65010
- isExplicit: parsed.isExplicitProvider,
65011
- routingSource: chain.source,
65012
- matchedPattern: chain.matchedPattern,
65013
- chain: chainDetails.map((c) => ({
65014
- provider: c.provider,
65015
- displayName: c.displayName,
65016
- modelSpec: c.modelSpec,
65017
- hasCredentials: c.hasCredentials,
65018
- credentialHint: c.credentialHint,
65019
- provenance: c.provenance,
65020
- probe: c.probe
65021
- })),
65022
- directProbe,
65416
+ model: probe.modelInput,
65417
+ ...fields,
65418
+ chain: probe.chain,
65419
+ dropped: probe.dropped,
65023
65420
  wiring
65024
65421
  });
65025
65422
  results.push({
65026
- model: modelInput,
65027
- nativeProvider: parsed.provider,
65028
- isExplicit: parsed.isExplicitProvider,
65029
- routingSource: chain.source,
65030
- matchedPattern: chain.matchedPattern,
65031
- routingExplanation: buildRoutingExplanation(parsed, chain),
65032
- links: buildResultLinks(parsed, chainDetails, directProbe),
65423
+ model: probe.modelInput,
65424
+ isExplicit: probe.explanation.source === "explicit",
65425
+ ...fields,
65426
+ links: resultLinksFrom(probe.chain, probe.dropped),
65033
65427
  wiring
65034
65428
  });
65035
65429
  }
@@ -65064,6 +65458,18 @@ async function probeModelRouting(models, jsonOutput, options = { live: true, tim
65064
65458
  setStderrQuiet(false);
65065
65459
  }
65066
65460
  }
65461
+ function providerShortcutRows(providers = BUILTIN_PROVIDERS) {
65462
+ const pickable = providers.filter(isPickableProvider);
65463
+ const ordered = [
65464
+ ...pickable.filter((def) => !def.isLocal),
65465
+ ...pickable.filter((def) => def.isLocal)
65466
+ ];
65467
+ return ordered.map((def) => ({
65468
+ shortcuts: [...def.shortcuts],
65469
+ displayName: def.displayName,
65470
+ kind: def.isLocal ? "local" : def.tier ? TIER_LABEL[def.tier] : ""
65471
+ }));
65472
+ }
65067
65473
  function printHelp2() {
65068
65474
  const useColor = !!process.stdout.isTTY && !process.env.NO_COLOR;
65069
65475
  const A = cliAnsi();
@@ -65076,6 +65482,12 @@ function printHelp2() {
65076
65482
  const magenta = c(A.MAGENTA);
65077
65483
  const blue = c(A.BLUE);
65078
65484
  const h = (title) => bold(cyan(`\u258C ${title}`));
65485
+ const shortcutRows = providerShortcutRows();
65486
+ const shortcutsOf = (row) => row.shortcuts.join(", ");
65487
+ const shortcutWidth = Math.max(...shortcutRows.map((row) => shortcutsOf(row).length));
65488
+ const nameWidth = Math.max(...shortcutRows.map((row) => row.displayName.length));
65489
+ const shortcutTable = shortcutRows.map((row) => ` ${magenta(shortcutsOf(row).padEnd(shortcutWidth))} ${dim("->")} ${row.displayName.padEnd(nameWidth)} ${dim(row.kind)}`).join(`
65490
+ `);
65079
65491
  console.log(`
65080
65492
  ${bold("claudish")} ${dim("\xB7")} Run Claude Code with any AI model
65081
65493
  ${dim("OpenRouter \xB7 Gemini \xB7 OpenAI \xB7 xAI \xB7 MiniMax \xB7 Kimi \xB7 GLM \xB7 Z.AI \xB7 Sakana \xB7 Poe \xB7 LiteLLM \xB7 Local")}
@@ -65088,60 +65500,29 @@ ${h("USAGE")}
65088
65500
 
65089
65501
  ${h("MODEL ROUTING")}
65090
65502
  ${bold("New syntax:")} ${yellow("provider@model[:concurrency]")}
65091
- ${magenta("google@gemini-3-pro")} ${dim("Direct Google API (explicit)")}
65092
- ${magenta("openrouter@google/gemini-3-pro")} ${dim("OpenRouter (explicit)")}
65093
- ${magenta("oai@gpt-5.3")} ${dim("Direct OpenAI API (shortcut)")}
65094
- ${magenta("ollama@llama3.2:3")} ${dim("Local Ollama, 3 concurrent requests")}
65095
- ${magenta("ollama@llama3.2:0")} ${dim("Local Ollama, no limits")}
65096
-
65097
- ${bold("Provider shortcuts:")}
65098
- ${magenta("g, gemini")} ${dim("->")} Google Gemini ${dim("google@gemini-3-pro")}
65099
- ${magenta("oai")} ${dim("->")} OpenAI Direct ${dim("oai@gpt-5.3")}
65100
- ${magenta("cx, codex")} ${dim("->")} OpenAI Codex ${dim("cx@gpt-5.3 (Responses API)")}
65101
- ${magenta("or")} ${dim("->")} OpenRouter ${dim("or@openai/gpt-5.3")}
65102
- ${magenta("x-ai, xai, grok")} ${dim("->")} xAI / Grok ${dim("x-ai@grok-3")}
65103
- ${magenta("mm, mmax")} ${dim("->")} MiniMax Direct ${dim("mm@MiniMax-M2.1")}
65104
- ${magenta("mmc")} ${dim("->")} MiniMax Coding ${dim("mmc@MiniMax-M2.1")}
65105
- ${magenta("kimi, moon")} ${dim("->")} Kimi Direct ${dim("kimi@kimi-k2-thinking-turbo")}
65106
- ${magenta("kc")} ${dim("->")} Kimi Coding ${dim("kc@kimi-k2-thinking-turbo")}
65107
- ${magenta("glm, zhipu")} ${dim("->")} GLM Direct ${dim("glm@glm-4.7")}
65108
- ${magenta("gc")} ${dim("->")} GLM Coding ${dim("gc@glm-4.7")}
65109
- ${magenta("z-ai, zai")} ${dim("->")} Z.AI Direct ${dim("z-ai@glm-4.7")}
65110
- ${magenta("oc, llama, lc, meta")} ${dim("->")} OllamaCloud ${dim("oc@llama-3.1")}
65111
- ${magenta("zen")} ${dim("->")} OpenCode Zen ${dim("zen@grok-code")}
65112
- ${magenta("zengo, zgo")} ${dim("->")} OpenCode Zen Go ${dim("zengo@grok-code")}
65113
- ${magenta("v, vertex")} ${dim("->")} Vertex AI ${dim("v@gemini-2.5-flash")}
65114
- ${magenta("poe")} ${dim("->")} Poe ${dim("poe@GPT-4o")}
65115
- ${magenta("litellm, ll")} ${dim("->")} LiteLLM ${dim("ll@gpt-4o (needs LITELLM_BASE_URL)")}
65116
- ${magenta("ds")} ${dim("->")} DeepSeek ${dim("ds@deepseek-chat")}
65117
- ${magenta("sakana, fugu")} ${dim("->")} Sakana Fugu ${dim("fugu@fugu-ultra")}
65118
- ${magenta("sc")} ${dim("->")} Sakana Subscription ${dim("sc@fugu-ultra")}
65119
- ${magenta("ollama")} ${dim("->")} Ollama (local) ${dim("ollama@llama3.2")}
65120
- ${magenta("lms, lmstudio")} ${dim("->")} LM Studio (local) ${dim("lms@qwen")}
65121
- ${magenta("vllm")} ${dim("->")} vLLM (local) ${dim("vllm@model")}
65122
- ${magenta("mlx")} ${dim("->")} MLX (local) ${dim("mlx@model")}
65123
-
65124
- ${bold("Native auto-detection")} ${dim("(when no provider specified):")}
65125
- ${yellow("google/*, gemini-*")} ${dim("->")} Google API
65126
- ${yellow("openai/*, gpt-*, o1-*")} ${dim("->")} OpenAI API
65127
- ${yellow("x-ai/*, grok-*")} ${dim("->")} xAI
65128
- ${yellow("meta-llama/*, llama-*")} ${dim("->")} OllamaCloud
65129
- ${yellow("minimax/*, abab-*")} ${dim("->")} MiniMax API
65130
- ${yellow("moonshot/*, kimi-*")} ${dim("->")} Kimi API
65131
- ${yellow("zhipu/*, glm-*")} ${dim("->")} GLM API
65132
- ${yellow("sakana/*, fugu-*")} ${dim("->")} Sakana Fugu
65133
- ${yellow("poe:*")} ${dim("->")} Poe
65134
- ${yellow("anthropic/*, claude-*")} ${dim("->")} Native Anthropic
65135
- ${yellow("(unknown vendor/)")} ${dim("->")} Error (use openrouter@vendor/model)
65136
-
65137
- ${dim("A defaultProvider (config / --default-provider) catches bare names that match no rule.")}
65503
+ ${magenta("google@<model>")} ${dim("Direct Google API (explicit)")}
65504
+ ${magenta("openrouter@<vendor>/<model>")} ${dim("OpenRouter (explicit)")}
65505
+ ${magenta("oai@<model>")} ${dim("Direct OpenAI API (shortcut)")}
65506
+ ${magenta("ollama@<model>:3")} ${dim("Local Ollama, 3 concurrent requests")}
65507
+ ${magenta("ollama@<model>:0")} ${dim("Local Ollama, no limits")}
65508
+
65509
+ ${bold("Provider shortcuts:")} ${dim("(<shortcut>@<model>)")}
65510
+ ${shortcutTable}
65511
+
65512
+ ${bold("Bare names")} are routed from the cloud models catalog: subscriptions first, then the vendor's own
65513
+ API, then gateways, then the fallback. ${green("claudish --probe")} ${yellow("<model>")} shows the chain a request uses.
65514
+
65515
+ ${dim("A defaultProvider (--default-provider / CLAUDISH_DEFAULT_PROVIDER / config) is the last hop for")}
65516
+ ${dim('bare names that match no rule. "" disables it.')}
65517
+ ${dim("Claude Code's own names (opus, sonnet, claude-*) are served on Claude Code's own auth.")}
65138
65518
 
65139
65519
  ${h("OPTIONS")}
65140
65520
  ${green("-i, --interactive")} Run in interactive mode (default when no prompt given)
65141
65521
  ${green("-m, --model")} ${yellow("<model>")} Model to use (required for single-shot mode)
65142
65522
  ${green("--profile")} ${yellow("<name>")} Use named profile for model mapping (default profile if omitted)
65143
- ${green("--default-provider")} ${yellow("<name>")} Fallback provider for bare model names (builtin or customEndpoints key)
65144
- ${dim("Precedence: this flag > CLAUDISH_DEFAULT_PROVIDER env > config.json")}
65523
+ ${green("--default-provider")} ${yellow("<name>")} Fallback provider for bare model names (claudish provider or customEndpoints key)
65524
+ ${dim('"" disables it. Precedence: this flag > CLAUDISH_DEFAULT_PROVIDER env > config.json')}
65525
+ ${dim("(the global config or the --config file; a project .claudish.json is not read)")}
65145
65526
  ${green("--anthropic-api-billing")} Use your real ANTHROPIC_API_KEY for native Claude models
65146
65527
  ${dim("(metered API billing). Default: the key is hidden so Claude Code")}
65147
65528
  ${dim("uses your claude.ai subscription. Env: CLAUDISH_ANTHROPIC_API_BILLING")}
@@ -65192,9 +65573,9 @@ ${h("MODEL DISCOVERY")}
65192
65573
  ${green("-s, --models-search")} ${yellow("<query>")} Fuzzy search: id, brand synonyms (chatgpt,
65193
65574
  ${dim("claude, grok), gateways (zen, oc, codex), caps")}
65194
65575
  ${green("--models-top")} Curated recommended models (flagship + fast)
65195
- ${green("--probe")} ${yellow("<models...>")} Probe each provider in the fallback chain with
65576
+ ${green("--probe")} ${yellow("<models...>")} Show each model's routing chain and send each hop
65196
65577
  ${dim("a real 1-token request (may incur tiny cost)")}
65197
- ${green("--no-probe")} Skip live requests, show static chain only
65578
+ ${green("--no-probe")} Show the routing chain without the 1-token requests
65198
65579
  ${green("--probe-timeout")} ${yellow("<secs>")} Per-link timeout for live probes (default: 40)
65199
65580
  ${green("--models-refresh")} Force refresh the slim model catalog from Firebase
65200
65581
  ${green("--models-skip-update")} Skip the launcher catalog warm step (offline)
@@ -65202,7 +65583,7 @@ ${h("MODEL DISCOVERY")}
65202
65583
 
65203
65584
  ${h("TEAM MODE")}
65204
65585
  ${green("--team")} ${yellow("<models>")} Run multiple models in parallel (comma-separated)
65205
- ${dim('Example: --team minimax-m2.5,kimi-k2.5 "prompt"')}
65586
+ ${dim('Example: --team <model>,<model> "prompt"')}
65206
65587
  ${green("--mode")} ${yellow("<mode>")} Team mode: default (grid), interactive, json
65207
65588
  ${green("-f, --file")} ${yellow("<path>")} Read prompt from file (use with --team or single-shot)
65208
65589
 
@@ -65240,7 +65621,7 @@ ${h("1PASSWORD")} ${dim("(SDK-based \u2014 no op CLI needed for secrets)")}
65240
65621
  ${green("--op")} ${yellow("<glob> --list")} Preview which fields a glob would import (names only)
65241
65622
  ${green("--op")} ${yellow("<glob>")} ${yellow("[...args]")} Resolve a glob into env vars, then run a session
65242
65623
  ${dim("Inline op import requires a GLOB (self-names via field labels)")}
65243
- ${dim('Example: claudish --op "op://Jack/Keys/**" --model gpt-4o "task"')}
65624
+ ${dim('Example: claudish --op "op://Jack/Keys/**" --model <model> "task"')}
65244
65625
  ${green("--op-env")} ${yellow("<id>")} Load a 1Password Environment (highest-priority source)
65245
65626
  ${dim("Persistent setup (single refs, sets, environments, account): claudish config -> 1Password tab")}
65246
65627
 
@@ -65257,17 +65638,17 @@ ${h("MACOS KEYCHAIN")} ${dim("(local, encrypted at rest, no desktop-app handshak
65257
65638
 
65258
65639
  ${h("CLAUDE CODE FLAG PASSTHROUGH")}
65259
65640
  ${dim("Any unrecognized flag is forwarded to Claude Code. Claudish flags can appear in any order.")}
65260
- ${green("claudish")} --model grok ${yellow("--agent test")} ${yellow('"task"')} ${dim("# --agent passes through")}
65261
- ${green("claudish")} --model grok ${yellow("--effort high")} --stdin ${yellow('"task"')} ${dim("# --effort passes, --stdin stays")}
65262
- ${green("claudish")} --model grok ${yellow("--permission-mode plan")} -i ${dim("# works in interactive too")}
65641
+ ${green("claudish")} --model ${yellow("<model>")} ${yellow("--agent test")} ${yellow('"task"')} ${dim("# --agent passes through")}
65642
+ ${green("claudish")} --model ${yellow("<model>")} ${yellow("--effort high")} --stdin ${yellow('"task"')} ${dim("# --effort passes, --stdin stays")}
65643
+ ${green("claudish")} --model ${yellow("<model>")} ${yellow("--permission-mode plan")} -i ${dim("# works in interactive too")}
65263
65644
  ${dim("Use -- when a Claude Code flag value starts with '-':")}
65264
- ${green("claudish")} --model grok ${green("--")} ${yellow('--system-prompt "-verbose mode" "task"')}
65645
+ ${green("claudish")} --model ${yellow("<model>")} ${green("--")} ${yellow('--system-prompt "-verbose mode" "task"')}
65265
65646
 
65266
65647
  ${h("CUSTOM MODELS & ENDPOINTS")}
65267
- ${dim("Claudish accepts ANY valid model ID from the Firebase catalog, even if not in --models:")}
65268
- ${green("claudish")} --model ${yellow("openrouter@your_provider/custom-model-123")} ${yellow('"task"')}
65648
+ ${dim("An explicit provider@ takes any model id, including one --models does not list:")}
65649
+ ${green("claudish")} --model ${yellow("openrouter@<vendor>/<model>")} ${yellow('"task"')}
65269
65650
  ${dim("Named custom endpoints live in ~/.claudish/config.json under 'customEndpoints' and route via @:")}
65270
- ${green("claudish")} --model ${yellow("my-vllm@llama3.1-70b")} ${yellow('"task"')}
65651
+ ${green("claudish")} --model ${yellow("my-vllm@<model>")} ${yellow('"task"')}
65271
65652
 
65272
65653
  ${h("MODES")}
65273
65654
  ${green("\u2022")} ${bold("Interactive")} ${dim("(default):")} shows model selector, starts a persistent session
@@ -65330,8 +65711,8 @@ ${h("ENVIRONMENT VARIABLES")}
65330
65711
  ${blue("MLX_BASE_URL")} MLX server ${dim("(default: http://127.0.0.1:8080)")}
65331
65712
 
65332
65713
  ${bold("Claudish settings:")}
65333
- ${blue("CLAUDISH_MODEL")} Default model ${dim("(default: openai/gpt-5.3)")}
65334
- ${blue("CLAUDISH_DEFAULT_PROVIDER")} Fallback provider for bare names ${dim("(see --default-provider)")}
65714
+ ${blue("CLAUDISH_MODEL")} Default model ${dim("(--model overrides it; ANTHROPIC_MODEL is read when unset)")}
65715
+ ${blue("CLAUDISH_DEFAULT_PROVIDER")} Fallback provider for bare names; empty disables it ${dim("(see --default-provider)")}
65335
65716
  ${blue("CLAUDISH_PORT")} Default proxy port
65336
65717
  ${blue("CLAUDISH_CONTEXT_WINDOW")} Override context window size
65337
65718
  ${blue("CLAUDISH_DIAG_MODE")} Diagnostic output: auto / logfile / off
@@ -65354,24 +65735,24 @@ ${h("EXAMPLES")}
65354
65735
  ${green("claudish")} --free ${dim("# only FREE models")}
65355
65736
 
65356
65737
  ${dim("# Explicit provider routing")}
65357
- ${green("claudish")} --model ${magenta("google@gemini-3-pro")} ${yellow('"implement auth"')}
65358
- ${green("claudish")} --model ${magenta("oai@gpt-5.3")} ${yellow('"add tests for login"')}
65359
- ${green("claudish")} --model ${magenta("openrouter@deepseek/deepseek-r1")} ${yellow('"unknown vendor"')}
65738
+ ${green("claudish")} --model ${magenta("google@<model>")} ${yellow('"implement auth"')}
65739
+ ${green("claudish")} --model ${magenta("oai@<model>")} ${yellow('"add tests for login"')}
65740
+ ${green("claudish")} --model ${magenta("openrouter@<vendor>/<model>")} ${yellow('"any vendor via OpenRouter"')}
65360
65741
 
65361
- ${dim("# Native auto-detection (provider inferred from model name)")}
65362
- ${green("claudish")} --model ${yellow("gpt-4o")} ${yellow('"routes to OpenAI"')}
65363
- ${green("claudish")} --model ${yellow("gemini-2.5-pro")} ${yellow('"routes to Google"')}
65742
+ ${dim("# Bare name: routed from the cloud models catalog")}
65743
+ ${green("claudish")} --probe ${yellow("<model>")} ${dim("# show the chain it gets")}
65744
+ ${green("claudish")} --model ${yellow("<model>")} ${yellow('"implement auth"')}
65364
65745
 
65365
65746
  ${dim("# Per-role model mapping")}
65366
- ${green("claudish")} --model-opus ${magenta("oai@gpt-5.3")} --model-sonnet ${magenta("google@gemini-3-pro")}
65747
+ ${green("claudish")} --model-opus ${magenta("oai@<model>")} --model-sonnet ${magenta("google@<model>")}
65367
65748
 
65368
65749
  ${dim("# stdin for large prompts (diffs, code review)")}
65369
- ${dim("git diff |")} ${green("claudish")} --stdin --model ${magenta("oai@gpt-5.3")} ${yellow('"Review these changes"')}
65750
+ ${dim("git diff |")} ${green("claudish")} --stdin --model ${magenta("oai@<model>")} ${yellow('"Review these changes"')}
65370
65751
 
65371
65752
  ${dim("# Local models with concurrency control")}
65372
- ${green("claudish")} --model ${magenta("ollama@llama3.2:3")} ${yellow('"3 concurrent requests"')}
65373
- ${green("claudish")} --model ${magenta("lms@qwen2.5-coder")} ${yellow('"LM Studio shortcut"')}
65374
- ${green("claudish")} --model ${yellow('"http://localhost:8000/mistral"')} ${yellow('"any OpenAI-compatible URL"')}
65753
+ ${green("claudish")} --model ${magenta("ollama@<model>:3")} ${yellow('"3 concurrent requests"')}
65754
+ ${green("claudish")} --model ${magenta("lms@<model>")} ${yellow('"LM Studio shortcut"')}
65755
+ ${green("claudish")} --model ${yellow('"http://localhost:8000/<model>"')} ${yellow('"any OpenAI-compatible URL"')}
65375
65756
 
65376
65757
  ${dim("# Autonomous (no prompts, no sandbox) \u2014 use with caution")}
65377
65758
  ${green("claudish")} -y --dangerous ${yellow('"refactor entire codebase"')}
@@ -65492,19 +65873,21 @@ var init_cli = __esm(() => {
65492
65873
  init_logger();
65493
65874
  init_model_loader();
65494
65875
  init_model_selector();
65876
+ init_probe_chain();
65495
65877
  init_probe_results_printer();
65496
65878
  init_probe_tui_runtime();
65497
65879
  init_profile_config();
65498
65880
  init_api_key_map();
65499
65881
  init_api_key_provenance();
65500
- init_catalog_client();
65501
65882
  init_claude_code_aliases();
65502
65883
  init_endpoint_registration();
65503
65884
  init_model_parser();
65885
+ init_native_route();
65504
65886
  init_probe_live();
65505
65887
  init_probe_runner();
65506
65888
  init_provider_definitions();
65507
65889
  init_provider_slug_resolve();
65890
+ init_route_candidates();
65508
65891
  init_routing_rules();
65509
65892
  init_settings();
65510
65893
  init_ansi();
@@ -67929,7 +68312,7 @@ function ProfileDetail({ config, profileIndex }) {
67929
68312
  }),
67930
68313
  /* @__PURE__ */ jsx20("span", {
67931
68314
  fg: C.dim,
67932
- children: " \u2014 uses routing table"
68315
+ children: " \u2014 calculated per model \xB7 Routing \u2192 p shows the chain"
67933
68316
  }),
67934
68317
  /* @__PURE__ */ jsx20("span", {
67935
68318
  fg: C.yellow,
@@ -68396,7 +68779,7 @@ function ProfilesContent({
68396
68779
  }),
68397
68780
  /* @__PURE__ */ jsx21("span", {
68398
68781
  fg: C.fgMuted,
68399
- children: "\u2014 uses routing table \xB7 "
68782
+ children: "\u2014 calculated per model (Routing \u2192 p) \xB7 "
68400
68783
  }),
68401
68784
  /* @__PURE__ */ jsxs17("span", {
68402
68785
  fg: C.green,
@@ -69244,14 +69627,158 @@ function deriveProbeOutcome(mode, results) {
69244
69627
  // src/tui/components/RoutingContent.tsx
69245
69628
  import { useEffect as useEffect10, useRef as useRef7 } from "react";
69246
69629
  import { jsx as jsx24, jsxs as jsxs20, Fragment as Fragment14 } from "@opentui/react/jsx-runtime";
69630
+ function rowStatusView(entry) {
69631
+ switch (entry.status) {
69632
+ case "unverified":
69633
+ return { icon: "\u25D0", color: C.cyan, text: "native \u2014 not probed" };
69634
+ case "success":
69635
+ return {
69636
+ icon: "\u25CF",
69637
+ color: C.green,
69638
+ text: entry.ms !== undefined ? `${entry.ms}ms` : "success"
69639
+ };
69640
+ case "failed":
69641
+ return { icon: "\u2717", color: C.red, text: entry.error ?? "failed" };
69642
+ case "testing":
69643
+ return { icon: "\u25CC", color: C.yellow, text: "testing..." };
69644
+ case "dropped":
69645
+ return {
69646
+ icon: "\u25CB",
69647
+ color: entry.outcome === "credential-unreadable" ? C.yellow : C.dim,
69648
+ text: entry.outcome ? `dropped \xB7 ${DROPPED_TEXT[entry.outcome]}` : "dropped"
69649
+ };
69650
+ case "skipped":
69651
+ return { icon: "\xB7", color: C.dim, text: "not reached" };
69652
+ case "no_key":
69653
+ return { icon: "\u25CB", color: C.dim, text: "not configured, skipping" };
69654
+ case "pending":
69655
+ return { icon: "\u25CB", color: C.dim, text: "waiting" };
69656
+ }
69657
+ }
69658
+ function hintLines(hint) {
69659
+ if (!hint)
69660
+ return [];
69661
+ return hint.split(`
69662
+ `).filter((text) => text.trim().length > 0).map((text, n) => ({ id: `hint-${n}`, text }));
69663
+ }
69247
69664
  function chainStr(chain) {
69248
69665
  return chain.join(" \u2192 ");
69249
69666
  }
69667
+ function ruleChainCell(chain, selected) {
69668
+ if (chain.length === 0)
69669
+ return { text: "no route", fg: C.yellow };
69670
+ return { text: chainStr(chain), fg: selected ? C.cyan : C.fgMuted };
69671
+ }
69672
+ function routingHeaderLines({
69673
+ globalRules,
69674
+ localRules,
69675
+ resolved
69676
+ }) {
69677
+ const catchAll = { ...globalRules, ...localRules }["*"];
69678
+ if (Array.isArray(catchAll)) {
69679
+ const scope = Object.hasOwn(localRules, "*") ? "project" : "global";
69680
+ return [
69681
+ [
69682
+ { text: ' "*" rule:', tone: "title" },
69683
+ { text: " decides every model no other rule matches", tone: "muted" }
69684
+ ],
69685
+ [
69686
+ { text: " \u2192 ", tone: "dim" },
69687
+ catchAll.length > 0 ? { text: chainStr(catchAll), tone: "value" } : { text: "no route", tone: "warn" },
69688
+ { text: ` (${scope})`, tone: "muted" }
69689
+ ],
69690
+ [{ text: " The catalog and the fallback hop are not used.", tone: "dim" }]
69691
+ ];
69692
+ }
69693
+ return fallbackHopLines(resolved);
69694
+ }
69695
+ function fallbackHopLines(resolved) {
69696
+ const source = ` (${FALLBACK_SOURCE_LABEL[resolved.source]})`;
69697
+ const note = [
69698
+ {
69699
+ text: " --default-provider overrides this for one run and the sessions it starts.",
69700
+ tone: "dim"
69701
+ }
69702
+ ];
69703
+ if (resolved.provider === "") {
69704
+ return [
69705
+ [
69706
+ { text: " Fallback hop:", tone: "title" },
69707
+ { text: " (none \u2014 a model the catalog maps to no provider gets no route)", tone: "muted" }
69708
+ ],
69709
+ [
69710
+ { text: " \u2192 ", tone: "dim" },
69711
+ { text: "disabled", tone: "warn" },
69712
+ { text: `, set to ""${source}`, tone: "muted" }
69713
+ ],
69714
+ note
69715
+ ];
69716
+ }
69717
+ return [
69718
+ [
69719
+ { text: " Fallback hop:", tone: "title" },
69720
+ { text: " (tried last, after every provider the catalog maps)", tone: "muted" }
69721
+ ],
69722
+ [
69723
+ { text: " \u2192 ", tone: "dim" },
69724
+ { text: resolved.provider, tone: "value" },
69725
+ { text: source, tone: "muted" }
69726
+ ],
69727
+ note
69728
+ ];
69729
+ }
69730
+ function resolveFallbackHop(config, env = process.env) {
69731
+ return resolveDefaultProvider({ config, env });
69732
+ }
69733
+ function toneColor(tone) {
69734
+ switch (tone) {
69735
+ case "title":
69736
+ return C.blue;
69737
+ case "value":
69738
+ return C.cyan;
69739
+ case "warn":
69740
+ return C.yellow;
69741
+ case "muted":
69742
+ return C.fgMuted;
69743
+ case "dim":
69744
+ return C.dim;
69745
+ }
69746
+ }
69747
+ function rulesOfScope(mergedRules, scope) {
69748
+ return Object.fromEntries(mergedRules.filter((rule) => rule.kind === scope).map((rule) => [rule.pattern, rule.chain]));
69749
+ }
69750
+ function hopLabel2(entry) {
69751
+ if (entry.status === "unverified")
69752
+ return NATIVE_HOP_LABEL;
69753
+ return hopLabel(entry);
69754
+ }
69755
+ function hopLabelColor(entry) {
69756
+ if (entry.status === "dropped")
69757
+ return C.dim;
69758
+ if (entry.status === "unverified")
69759
+ return C.cyan;
69760
+ if (entry.position === "fallback")
69761
+ return C.yellow;
69762
+ switch (entry.tier) {
69763
+ case "subscription":
69764
+ case "dynamic-subscription":
69765
+ return C.green;
69766
+ case "native":
69767
+ return C.cyan;
69768
+ case "gateway":
69769
+ return C.blue;
69770
+ case "fallback":
69771
+ return C.yellow;
69772
+ default:
69773
+ return C.dim;
69774
+ }
69775
+ }
69250
69776
  function RoutingContent({
69251
69777
  config,
69252
69778
  probeMode,
69253
69779
  probeModel,
69254
69780
  probeResults,
69781
+ probeSummary,
69255
69782
  mode,
69256
69783
  routingPattern,
69257
69784
  chainSelected,
@@ -69347,23 +69874,20 @@ function RoutingContent({
69347
69874
  }),
69348
69875
  /* @__PURE__ */ jsx24("text", {
69349
69876
  children: /* @__PURE__ */ jsx24("span", {
69350
- fg: C.dim,
69351
- children: "Examples: kimi-k2 deepseek-r1 gemini-2.0-flash gpt-4o"
69877
+ fg: C.fgMuted,
69878
+ children: "The probe shows the routing chain a request would use, then tests"
69352
69879
  })
69353
69880
  }),
69354
- /* @__PURE__ */ jsx24("text", {
69355
- children: " "
69356
- }),
69357
69881
  /* @__PURE__ */ jsx24("text", {
69358
69882
  children: /* @__PURE__ */ jsx24("span", {
69359
69883
  fg: C.fgMuted,
69360
- children: "The probe resolves the fallback chain and tests each provider's"
69884
+ children: "each kept hop in order, stopping at the first success. Dropped"
69361
69885
  })
69362
69886
  }),
69363
69887
  /* @__PURE__ */ jsx24("text", {
69364
69888
  children: /* @__PURE__ */ jsx24("span", {
69365
69889
  fg: C.fgMuted,
69366
- children: "API key in order, stopping at the first success."
69890
+ children: "candidates are listed with the reason, never tested."
69367
69891
  })
69368
69892
  })
69369
69893
  ]
@@ -69426,21 +69950,30 @@ function RoutingContent({
69426
69950
  /* @__PURE__ */ jsx24("text", {
69427
69951
  children: /* @__PURE__ */ jsx24("span", {
69428
69952
  fg: C.fgMuted,
69429
- children: probeResults[0]?.reason ?? `Chain (${probeResults.length} providers):`
69953
+ children: probeSummary?.line ?? ""
69430
69954
  })
69431
69955
  }),
69956
+ probeSummary?.warnings.map((warning) => /* @__PURE__ */ jsx24("text", {
69957
+ children: /* @__PURE__ */ jsx24("span", {
69958
+ fg: C.yellow,
69959
+ children: `! ${warning}`
69960
+ })
69961
+ }, `warning:${warning}`)),
69962
+ probeSummary?.notes.map((note) => /* @__PURE__ */ jsx24("text", {
69963
+ children: /* @__PURE__ */ jsx24("span", {
69964
+ fg: C.dim,
69965
+ children: note
69966
+ })
69967
+ }, `note:${note}`)),
69432
69968
  /* @__PURE__ */ jsx24("text", {
69433
69969
  children: " "
69434
69970
  }),
69435
69971
  probeResults.map((entry, idx) => {
69436
- const isNoKey = entry.status === "no_key";
69972
+ const isDropped = entry.status === "dropped";
69437
69973
  const isNotReached = entry.status === "skipped";
69438
69974
  const isSelected = entry.status === "success" && probeMode === "done";
69439
- const statusIcon = entry.status === "unverified" ? "\u25D0" : entry.status === "success" ? "\u25CF" : entry.status === "failed" ? "\u2717" : entry.status === "testing" ? "\u25CC" : isNoKey ? "\u25CB" : isNotReached ? "\xB7" : "\u25CB";
69440
- const statusColor = entry.status === "unverified" ? C.cyan : entry.status === "success" ? C.green : entry.status === "failed" ? C.red : entry.status === "testing" ? C.yellow : C.dim;
69975
+ const status = rowStatusView(entry);
69441
69976
  const nameCol = entry.displayName.padEnd(18).substring(0, 18);
69442
- const statusText = entry.status === "unverified" ? "native \u2014 not probed" : entry.status === "success" ? entry.ms !== undefined ? `${entry.ms}ms` : "success" : entry.status === "failed" ? entry.error ?? "failed" : entry.status === "testing" ? "testing..." : isNoKey ? "not configured, skipping" : isNotReached ? "not reached" : "waiting";
69443
- const reason = PROVIDER_REASONS[entry.provider] ?? entry.provider;
69444
69977
  return /* @__PURE__ */ jsxs20("box", {
69445
69978
  flexDirection: "column",
69446
69979
  children: [
@@ -69451,7 +69984,7 @@ function RoutingContent({
69451
69984
  children: `${idx + 1}. `
69452
69985
  }),
69453
69986
  /* @__PURE__ */ jsx24("span", {
69454
- fg: isNoKey ? C.dim : isSelected ? C.strong : isNotReached ? C.dim : C.fgMuted,
69987
+ fg: isDropped || isNotReached ? C.dim : isSelected ? C.strong : C.fgMuted,
69455
69988
  attributes: A.boldIf(isSelected),
69456
69989
  children: nameCol
69457
69990
  }),
@@ -69460,12 +69993,12 @@ function RoutingContent({
69460
69993
  children: " "
69461
69994
  }),
69462
69995
  /* @__PURE__ */ jsxs20("span", {
69463
- fg: statusColor,
69996
+ fg: status.color,
69464
69997
  attributes: A.boldIf(entry.status === "success"),
69465
69998
  children: [
69466
- statusIcon,
69999
+ status.icon,
69467
70000
  " ",
69468
- statusText
70001
+ status.text
69469
70002
  ]
69470
70003
  }),
69471
70004
  isSelected && /* @__PURE__ */ jsx24("span", {
@@ -69482,13 +70015,21 @@ function RoutingContent({
69482
70015
  children: " \u21B3 "
69483
70016
  }),
69484
70017
  /* @__PURE__ */ jsx24("span", {
69485
- fg: isNoKey ? C.dim : C.fgMuted,
69486
- children: reason
70018
+ fg: isDropped ? C.dim : C.fgMuted,
70019
+ children: entry.displayName
70020
+ }),
70021
+ /* @__PURE__ */ jsx24("span", {
70022
+ fg: C.dim,
70023
+ children: " \xB7 "
70024
+ }),
70025
+ /* @__PURE__ */ jsx24("span", {
70026
+ fg: hopLabelColor(entry),
70027
+ children: hopLabel2(entry)
69487
70028
  })
69488
70029
  ]
69489
70030
  })
69490
70031
  ]
69491
- }, entry.provider);
70032
+ }, `${idx}:${entry.provider}`);
69492
70033
  }),
69493
70034
  probeMode === "done" && /* @__PURE__ */ jsxs20(Fragment14, {
69494
70035
  children: [
@@ -69496,7 +70037,19 @@ function RoutingContent({
69496
70037
  children: " "
69497
70038
  }),
69498
70039
  /* @__PURE__ */ jsx24("text", {
69499
- children: allFailed ? /* @__PURE__ */ jsxs20(Fragment14, {
70040
+ children: probeSummary?.noRoute ? /* @__PURE__ */ jsxs20(Fragment14, {
70041
+ children: [
70042
+ /* @__PURE__ */ jsx24("span", {
70043
+ fg: C.red,
70044
+ attributes: A.bold,
70045
+ children: "Result: "
70046
+ }),
70047
+ /* @__PURE__ */ jsx24("span", {
70048
+ fg: C.red,
70049
+ children: `\u2717 No route \u2014 ${probeSummary.noRoute.reason}`
70050
+ })
70051
+ ]
70052
+ }) : allFailed ? /* @__PURE__ */ jsxs20(Fragment14, {
69500
70053
  children: [
69501
70054
  /* @__PURE__ */ jsx24("span", {
69502
70055
  fg: C.red,
@@ -69542,7 +70095,13 @@ function RoutingContent({
69542
70095
  })
69543
70096
  ]
69544
70097
  })
69545
- })
70098
+ }),
70099
+ hintLines(probeSummary?.noRoute?.hint).map((line) => /* @__PURE__ */ jsx24("text", {
70100
+ children: /* @__PURE__ */ jsx24("span", {
70101
+ fg: C.dim,
70102
+ children: ` ${line.text}`
70103
+ })
70104
+ }, line.id))
69546
70105
  ]
69547
70106
  })
69548
70107
  ]
@@ -69557,61 +70116,18 @@ function RoutingContent({
69557
70116
  flexDirection: "column",
69558
70117
  paddingX: 1,
69559
70118
  children: [
69560
- /* @__PURE__ */ jsxs20("text", {
70119
+ routingHeaderLines({
70120
+ globalRules: rulesOfScope(mergedRules, "global"),
70121
+ localRules: rulesOfScope(mergedRules, "project"),
70122
+ resolved: resolveFallbackHop(config)
70123
+ }).map((line) => /* @__PURE__ */ jsx24("text", {
69561
70124
  height: 1,
69562
- children: [
69563
- /* @__PURE__ */ jsx24("span", {
69564
- fg: C.blue,
69565
- attributes: A.bold,
69566
- children: " Fallback hop:"
69567
- }),
69568
- /* @__PURE__ */ jsx24("span", {
69569
- fg: C.fgMuted,
69570
- children: " (tried last, after every provider the catalog maps)"
69571
- })
69572
- ]
69573
- }),
69574
- /* @__PURE__ */ jsx24("text", {
69575
- height: 1,
69576
- children: (() => {
69577
- const configured = config.defaultProvider;
69578
- if (configured !== undefined && configured.length === 0) {
69579
- return /* @__PURE__ */ jsxs20(Fragment14, {
69580
- children: [
69581
- /* @__PURE__ */ jsx24("span", {
69582
- fg: C.dim,
69583
- children: " \u2192 "
69584
- }),
69585
- /* @__PURE__ */ jsx24("span", {
69586
- fg: C.yellow,
69587
- children: "disabled"
69588
- }),
69589
- /* @__PURE__ */ jsx24("span", {
69590
- fg: C.fgMuted,
69591
- children: " (defaultProvider is empty \u2014 an unroutable model errors instead)"
69592
- })
69593
- ]
69594
- });
69595
- }
69596
- const hasOverride = configured !== undefined && configured.length > 0;
69597
- return /* @__PURE__ */ jsxs20(Fragment14, {
69598
- children: [
69599
- /* @__PURE__ */ jsx24("span", {
69600
- fg: C.dim,
69601
- children: " \u2192 "
69602
- }),
69603
- /* @__PURE__ */ jsx24("span", {
69604
- fg: C.cyan,
69605
- children: hasOverride ? configured : DEFAULT_FALLBACK_PROVIDER
69606
- }),
69607
- /* @__PURE__ */ jsx24("span", {
69608
- fg: C.fgMuted,
69609
- children: hasOverride ? " (defaultProvider)" : " (default \u2014 set defaultProvider to change)"
69610
- })
69611
- ]
69612
- });
69613
- })()
69614
- }),
70125
+ children: line.map((segment) => /* @__PURE__ */ jsx24("span", {
70126
+ fg: toneColor(segment.tone),
70127
+ attributes: A.boldIf(segment.tone === "title"),
70128
+ children: segment.text
70129
+ }, `${segment.tone}:${segment.text}`))
70130
+ }, line.map((segment) => segment.text).join(""))),
69615
70131
  /* @__PURE__ */ jsx24("text", {
69616
70132
  height: 1,
69617
70133
  children: /* @__PURE__ */ jsx24("span", {
@@ -69726,7 +70242,7 @@ function RoutingContent({
69726
70242
  const scopeText = isProject ? "project " : "global ";
69727
70243
  const scopeFg = isProject ? C.cyan : C.green;
69728
70244
  const patFg = sel ? C.strong : C.cyan;
69729
- const chainFg = sel ? C.cyan : C.fgMuted;
70245
+ const chainCell = ruleChainCell(rule.chain, sel);
69730
70246
  return /* @__PURE__ */ jsx24("box", {
69731
70247
  height: 1,
69732
70248
  flexDirection: "row",
@@ -69748,8 +70264,8 @@ function RoutingContent({
69748
70264
  children: scopeText
69749
70265
  }),
69750
70266
  /* @__PURE__ */ jsx24("span", {
69751
- fg: chainFg,
69752
- children: chainStr(rule.chain)
70267
+ fg: chainCell.fg,
70268
+ children: chainCell.text
69753
70269
  })
69754
70270
  ]
69755
70271
  })
@@ -70055,32 +70571,24 @@ function RoutingContent({
70055
70571
  ]
70056
70572
  });
70057
70573
  }
70058
- var PROVIDER_REASONS;
70574
+ var DROPPED_TEXT, FALLBACK_SOURCE_LABEL, NATIVE_HOP_LABEL = "Claude Code's own auth";
70059
70575
  var init_RoutingContent = __esm(() => {
70060
70576
  init_routing_rules();
70061
70577
  init_constants3();
70062
70578
  init_providers();
70063
70579
  init_theme2();
70064
- PROVIDER_REASONS = {
70065
- litellm: "LiteLLM proxy",
70066
- "opencode-zen": "Free tier (OpenCode Zen)",
70067
- "opencode-zen-go": "Zen Go plan",
70068
- kimi: "Native Kimi API",
70069
- "kimi-coding": "Kimi Coding Plan",
70070
- minimax: "Native MiniMax API",
70071
- "minimax-coding": "MiniMax Coding Plan",
70072
- glm: "Native GLM API",
70073
- "glm-coding": "GLM Coding Plan",
70074
- "qwen-token-plan": "Alibaba Token Plan",
70075
- "qwen-coding": "Alibaba Coding Plan",
70076
- "qwen-payg": "Alibaba PAYG",
70077
- google: "Direct Gemini API",
70078
- openai: "Direct OpenAI API",
70079
- "openai-codex": "OpenAI Codex (Responses API)",
70080
- zai: "Z.AI API",
70081
- ollamacloud: "Cloud Ollama",
70082
- vertex: "Vertex AI (ADC)",
70083
- openrouter: "Fallback: 580+ models"
70580
+ DROPPED_TEXT = {
70581
+ "no-credential": "no credential",
70582
+ "credential-unreadable": "credential could not be read",
70583
+ "not-served": "the account does not serve it",
70584
+ "excluded-by-membership": "not in the plan's membership"
70585
+ };
70586
+ FALLBACK_SOURCE_LABEL = {
70587
+ "cli-flag": "--default-provider",
70588
+ "env-var": "CLAUDISH_DEFAULT_PROVIDER",
70589
+ "config-file": "config",
70590
+ "openrouter-key": "default",
70591
+ hardcoded: "default"
70084
70592
  };
70085
70593
  });
70086
70594
 
@@ -70092,6 +70600,7 @@ function RoutingDetail({ probeMode, mergedRules }) {
70092
70600
  }
70093
70601
  const globalCustom = mergedRules.filter((r) => r.kind === "global").length;
70094
70602
  const projectRules = mergedRules.filter((r) => r.kind === "project");
70603
+ const hasCatchAll = mergedRules.some((r) => r.pattern === "*");
70095
70604
  const fmtCount = (n) => String(n).padStart(2, " ");
70096
70605
  return /* @__PURE__ */ jsxs21("box", {
70097
70606
  height: DETAIL_H,
@@ -70157,7 +70666,7 @@ function RoutingDetail({ probeMode, mergedRules }) {
70157
70666
  children: /* @__PURE__ */ jsx25("text", {
70158
70667
  children: /* @__PURE__ */ jsx25("span", {
70159
70668
  fg: C.fgMuted,
70160
- children: " Anything with no rule here is routed from the models catalog."
70669
+ children: hasCatchAll ? ' The "*" rule takes every model no other rule here matches.' : " Anything with no rule here is routed from the cloud models catalog."
70161
70670
  })
70162
70671
  })
70163
70672
  })
@@ -70652,20 +71161,76 @@ var probeProxy = null, probeProxyStarting = null;
70652
71161
 
70653
71162
  // src/tui/hooks/useRouteProbe.ts
70654
71163
  import { useCallback as useCallback3, useState as useState10 } from "react";
70655
- async function routeForProbe(model) {
71164
+ function probeRowsFrom(explanation) {
71165
+ const summary = {
71166
+ line: describeRouteExplanation(explanation),
71167
+ warnings: explanation.warnings.map((warning) => warning.message),
71168
+ notes: []
71169
+ };
71170
+ if (explanation.source === "native") {
71171
+ summary.notes.push(NATIVE_NOT_PROBED);
71172
+ const native = explanation.native;
71173
+ const rows = native ? [
71174
+ {
71175
+ provider: native.provider,
71176
+ displayName: native.displayName,
71177
+ modelSpec: native.modelSpec,
71178
+ status: "unverified"
71179
+ }
71180
+ ] : [];
71181
+ return { summary, rows };
71182
+ }
71183
+ if (explanation.catalog === "found" && explanation.fallbackWithheld) {
71184
+ summary.notes.push(FALLBACK_WITHHELD_NOTE[explanation.fallbackWithheld]);
71185
+ }
71186
+ if (explanation.outcome.kind === "no-route") {
71187
+ const { reason, hint } = explanation.outcome;
71188
+ summary.noRoute = hint !== undefined ? { reason, hint } : { reason };
71189
+ }
71190
+ const rows = explanation.candidates.map((candidate) => {
71191
+ const row = {
71192
+ provider: candidate.provider,
71193
+ displayName: candidate.displayName,
71194
+ modelSpec: candidate.modelSpec,
71195
+ position: candidate.position,
71196
+ ...candidate.tier !== undefined ? { tier: candidate.tier } : {},
71197
+ status: "pending"
71198
+ };
71199
+ if (candidate.outcome !== "kept") {
71200
+ row.status = "dropped";
71201
+ row.outcome = candidate.outcome;
71202
+ }
71203
+ return row;
71204
+ });
71205
+ return { summary, rows };
71206
+ }
71207
+ function probeQueue(rows) {
71208
+ return rows.flatMap((row, index) => row.status === "pending" && row.modelSpec !== undefined ? [{ index, provider: row.provider, modelSpec: row.modelSpec }] : []);
71209
+ }
71210
+ async function probeViewFor(model) {
70656
71211
  try {
70657
- return await route(model);
71212
+ return probeRowsFrom(await explainRoute(model));
70658
71213
  } catch (err) {
70659
- return { kind: "no-route", reason: err instanceof Error ? err.message : String(err) };
71214
+ return {
71215
+ summary: {
71216
+ line: "the route could not be calculated",
71217
+ warnings: [],
71218
+ notes: [],
71219
+ noRoute: { reason: err instanceof Error ? err.message : String(err) }
71220
+ },
71221
+ rows: []
71222
+ };
70660
71223
  }
70661
71224
  }
70662
- function useRouteProbe(config) {
71225
+ function useRouteProbe() {
70663
71226
  const [probeMode, setProbeMode] = useState10("idle");
70664
71227
  const [probeModel, setProbeModel] = useState10("");
70665
71228
  const [probeResults, setProbeResults] = useState10([]);
71229
+ const [probeSummary, setProbeSummary] = useState10(null);
70666
71230
  const startInput = useCallback3(() => {
70667
71231
  setProbeModel("");
70668
71232
  setProbeResults([]);
71233
+ setProbeSummary(null);
70669
71234
  setProbeMode("input");
70670
71235
  }, []);
70671
71236
  const typeChar = useCallback3((ch) => {
@@ -70677,11 +71242,13 @@ function useRouteProbe(config) {
70677
71242
  const cancel = useCallback3(() => {
70678
71243
  setProbeModel("");
70679
71244
  setProbeResults([]);
71245
+ setProbeSummary(null);
70680
71246
  setProbeMode("idle");
70681
71247
  }, []);
70682
71248
  const enterFromDone = useCallback3(() => {
70683
71249
  setProbeModel("");
70684
71250
  setProbeResults([]);
71251
+ setProbeSummary(null);
70685
71252
  setProbeMode("input");
70686
71253
  }, []);
70687
71254
  const submit = useCallback3(() => {
@@ -70692,71 +71259,28 @@ function useRouteProbe(config) {
70692
71259
  return;
70693
71260
  }
70694
71261
  (async () => {
70695
- const native = nativeRouteFor(model);
70696
- let chain;
70697
- if (native) {
70698
- chain = [native];
70699
- } else {
70700
- const plan = await routeForProbe(model);
70701
- if (plan.kind !== "ok") {
70702
- setProbeResults([
70703
- {
70704
- provider: "none",
70705
- displayName: "No routes found",
70706
- status: "failed",
70707
- error: plan.hint ?? plan.reason
70708
- }
70709
- ]);
70710
- setProbeMode("done");
70711
- return;
70712
- }
70713
- chain = [plan.primary, ...plan.fallbacks];
71262
+ const { summary, rows } = await probeViewFor(model);
71263
+ setProbeSummary(summary);
71264
+ setProbeResults(rows);
71265
+ const queue = probeQueue(rows);
71266
+ if (queue.length === 0) {
71267
+ setProbeMode("done");
71268
+ return;
70714
71269
  }
70715
- const ruleEntries = Object.entries(config.routing ?? {});
70716
- const modelLower = model.toLowerCase();
70717
- const matchedRule = ruleEntries.find(([pat]) => {
70718
- if (pat.toLowerCase() === modelLower)
70719
- return true;
70720
- if (pat.includes("*")) {
70721
- const regex = new RegExp(`^${pat.replace(/\*/g, ".*")}$`, "i");
70722
- return regex.test(model);
70723
- }
70724
- return false;
70725
- });
70726
- const initial = chain.map((r) => {
70727
- return {
70728
- provider: r.provider,
70729
- displayName: r.displayName,
70730
- status: "pending",
70731
- hasKey: true,
70732
- reason: native ? "Native Claude Code auth \u2014 served by the proxy, never routed" : matchedRule ? `Custom rule: ${matchedRule[0]}` : "Default fallback chain"
70733
- };
70734
- });
70735
- setProbeResults(initial);
70736
71270
  setProbeMode("running");
70737
71271
  (async () => {
70738
- if (native) {
70739
- setProbeResults((prev) => prev.map((e) => ({ ...e, status: "unverified", reason: REASON_NATIVE })));
70740
- setProbeMode("done");
70741
- return;
70742
- }
70743
71272
  let proxyUrl;
70744
71273
  try {
70745
71274
  proxyUrl = await ensureProbeProxy();
70746
71275
  } catch (err) {
70747
71276
  const msg = err instanceof Error ? err.message : String(err);
70748
- setProbeResults((prev) => prev.map((e) => ({ ...e, status: "failed", error: `probe proxy: ${msg}` })));
71277
+ const queued = new Set(queue.map((item) => item.index));
71278
+ setProbeResults((prev) => prev.map((e, idx) => queued.has(idx) ? { ...e, status: "failed", error: `probe proxy: ${msg}` } : e));
70749
71279
  setProbeMode("done");
70750
71280
  return;
70751
71281
  }
70752
- for (let i = 0;i < chain.length; i++) {
70753
- const link = chain[i];
70754
- const provDef = getProviderDefs().find((p) => p.catalogName === link.provider);
70755
- const ready = provDef ? providerIsReady(provDef, config) : true;
70756
- if (!ready) {
70757
- setProbeResults((prev) => prev.map((e, idx) => idx === i ? { ...e, status: "no_key" } : e));
70758
- continue;
70759
- }
71282
+ for (const link of queue) {
71283
+ const i = link.index;
70760
71284
  setProbeResults((prev) => prev.map((e, idx) => idx === i ? { ...e, status: "testing" } : e));
70761
71285
  const startMs = Date.now();
70762
71286
  const result = await probeProviderRoute(proxyUrl, {
@@ -70778,7 +71302,7 @@ function useRouteProbe(config) {
70778
71302
  error: ok ? undefined : describeProbeState(result),
70779
71303
  ms
70780
71304
  };
70781
- if (idx > i && ok && e.status !== "no_key")
71305
+ if (idx > i && ok && e.status === "pending")
70782
71306
  return { ...e, status: "skipped" };
70783
71307
  return e;
70784
71308
  }));
@@ -70788,7 +71312,7 @@ function useRouteProbe(config) {
70788
71312
  setProbeMode("done");
70789
71313
  })();
70790
71314
  })();
70791
- }, [config, probeModel]);
71315
+ }, [probeModel]);
70792
71316
  let state;
70793
71317
  if (probeMode === "idle")
70794
71318
  state = { kind: "idle" };
@@ -70803,6 +71327,7 @@ function useRouteProbe(config) {
70803
71327
  probeMode,
70804
71328
  probeModel,
70805
71329
  probeResults,
71330
+ probeSummary,
70806
71331
  startInput,
70807
71332
  typeChar,
70808
71333
  backspace,
@@ -70811,13 +71336,18 @@ function useRouteProbe(config) {
70811
71336
  enterFromDone
70812
71337
  };
70813
71338
  }
70814
- var REASON_NATIVE = "Native Claude Code auth \u2014 not probed \u2014 served on Claude Code's own auth, which this process cannot forward";
71339
+ var FALLBACK_WITHHELD_NOTE;
70815
71340
  var init_useRouteProbe = __esm(() => {
70816
71341
  init_native_route();
70817
71342
  init_probe_live();
70818
71343
  init_probe_runner();
70819
71344
  init_routing_rules();
70820
- init_providers();
71345
+ FALLBACK_WITHHELD_NOTE = {
71346
+ disabled: 'No fallback hop: the default provider is set to "".',
71347
+ "catalog-unreadable": "No fallback hop: there is no cloud models catalog to check it against.",
71348
+ "already-gathered": "No separate fallback hop: that provider is already a catalog candidate.",
71349
+ "catalog-denies": "No fallback hop: the catalog maps that provider no connection to this model."
71350
+ };
70821
71351
  });
70822
71352
 
70823
71353
  // src/tui/onepassword-fields.ts
@@ -71114,8 +71644,8 @@ function App({ requestLogin } = {}) {
71114
71644
  return next;
71115
71645
  });
71116
71646
  }, []);
71117
- const probe = useRouteProbe(config);
71118
- const { probeMode, probeModel, probeResults } = probe;
71647
+ const probe = useRouteProbe();
71648
+ const { probeMode, probeModel, probeResults, probeSummary } = probe;
71119
71649
  const wizard = useProfileWizard({ mode, setMode, refreshConfig, setStatusMsg });
71120
71650
  const { editProfileValue, profileScope, suggestions, suggestionIndex, providerPickerIndex } = wizard;
71121
71651
  const hasCfgKey = !!config.apiKeys?.[selectedProvider.apiKeyEnvVar];
@@ -71139,14 +71669,10 @@ function App({ requestLogin } = {}) {
71139
71669
  const out = [];
71140
71670
  const localCfg = loadLocalConfig();
71141
71671
  for (const [pat, chain] of Object.entries(config.routing ?? {})) {
71142
- if (pat === "*")
71143
- continue;
71144
71672
  out.push({ kind: "global", pattern: pat, chain });
71145
71673
  }
71146
71674
  if (localCfg?.routing) {
71147
71675
  for (const [pat, chain] of Object.entries(localCfg.routing)) {
71148
- if (pat === "*")
71149
- continue;
71150
71676
  out.push({ kind: "project", pattern: pat, chain });
71151
71677
  }
71152
71678
  }
@@ -72634,6 +73160,7 @@ function App({ requestLogin } = {}) {
72634
73160
  probeMode,
72635
73161
  probeModel,
72636
73162
  probeResults,
73163
+ probeSummary,
72637
73164
  mode,
72638
73165
  routingPattern,
72639
73166
  chainSelected,
@@ -72851,7 +73378,7 @@ import {
72851
73378
  mkdtempSync,
72852
73379
  openSync as openSync8,
72853
73380
  rmSync as rmSync2,
72854
- writeFileSync as writeFileSync21
73381
+ writeFileSync as writeFileSync20
72855
73382
  } from "fs";
72856
73383
  import { connect as netConnect2 } from "net";
72857
73384
  import { join as join43 } from "path";
@@ -72925,7 +73452,7 @@ function planMagmuxWrap(input) {
72925
73452
  const deltas = envDeltas(parentEnv, input.childEnv);
72926
73453
  const fd = openSync8(scriptPath, "wx", 384);
72927
73454
  try {
72928
- writeFileSync21(fd, buildLauncherScript(input.claudeBinary, input.claudeArgs, deltas));
73455
+ writeFileSync20(fd, buildLauncherScript(input.claudeBinary, input.claudeArgs, deltas));
72929
73456
  } finally {
72930
73457
  closeSync8(fd);
72931
73458
  }
@@ -73483,7 +74010,7 @@ import {
73483
74010
  readdirSync as readdirSync8,
73484
74011
  statSync as statSync8,
73485
74012
  unlinkSync as unlinkSync9,
73486
- writeFileSync as writeFileSync22
74013
+ writeFileSync as writeFileSync21
73487
74014
  } from "fs";
73488
74015
  import { homedir as homedir37, tmpdir as tmpdir2 } from "os";
73489
74016
  import { dirname as dirname14, join as join44 } from "path";
@@ -73722,13 +74249,13 @@ process.stdin.on('end', () => {
73722
74249
  }
73723
74250
  });
73724
74251
  `;
73725
- writeFileSync22(scriptPath, script, "utf-8");
74252
+ writeFileSync21(scriptPath, script, "utf-8");
73726
74253
  return scriptPath;
73727
74254
  }
73728
74255
  function initializeTokenFile(tokenFilePath) {
73729
74256
  try {
73730
74257
  mkdirSync21(dirname14(tokenFilePath), { recursive: true });
73731
- writeFileSync22(tokenFilePath, JSON.stringify({
74258
+ writeFileSync21(tokenFilePath, JSON.stringify({
73732
74259
  input_tokens: 0,
73733
74260
  output_tokens: 0,
73734
74261
  total_tokens: 0,
@@ -73871,7 +74398,7 @@ function createTempSettingsFile(_modelDisplay, port, proxyAuthMode, userStatusLi
73871
74398
  padding: 0
73872
74399
  };
73873
74400
  const settings = buildClaudishSettingsOverlay(statusLine, proxyAuthMode);
73874
- writeFileSync22(tempPath, JSON.stringify(settings, null, 2), "utf-8");
74401
+ writeFileSync21(tempPath, JSON.stringify(settings, null, 2), "utf-8");
73875
74402
  return { path: tempPath, statusLine, tokenFilePath };
73876
74403
  }
73877
74404
  function buildClaudishSettingsOverlay(statusLine, proxyAuthMode) {
@@ -73896,7 +74423,7 @@ function mergeUserSettingsIfPresent(config, tempSettingsPath, statusLine, proxyA
73896
74423
  if (proxyAuthMode && !("forceLoginMethod" in userSettings)) {
73897
74424
  userSettings.forceLoginMethod = "console";
73898
74425
  }
73899
- writeFileSync22(tempSettingsPath, JSON.stringify(userSettings, null, 2), "utf-8");
74426
+ writeFileSync21(tempSettingsPath, JSON.stringify(userSettings, null, 2), "utf-8");
73900
74427
  } catch {
73901
74428
  if (!config.quiet) {
73902
74429
  console.warn(`[claudish] Warning: could not merge user settings: ${userSettingsValue}`);
@@ -74410,7 +74937,7 @@ var init_claude_runner = __esm(() => {
74410
74937
  });
74411
74938
 
74412
74939
  // src/diag-output.ts
74413
- import { createWriteStream as createWriteStream3, mkdirSync as mkdirSync22, unlinkSync as unlinkSync10, writeFileSync as writeFileSync23 } from "fs";
74940
+ import { createWriteStream as createWriteStream3, mkdirSync as mkdirSync22, unlinkSync as unlinkSync10, writeFileSync as writeFileSync22 } from "fs";
74414
74941
  import { homedir as homedir38 } from "os";
74415
74942
  import { join as join45 } from "path";
74416
74943
  function getClaudishDir() {
@@ -74430,7 +74957,7 @@ class LogFileDiagOutput {
74430
74957
  constructor() {
74431
74958
  this.logPath = getDiagLogPath();
74432
74959
  try {
74433
- writeFileSync23(this.logPath, `--- claudish diag session ${new Date().toISOString()} ---
74960
+ writeFileSync22(this.logPath, `--- claudish diag session ${new Date().toISOString()} ---
74434
74961
  `);
74435
74962
  } catch {}
74436
74963
  this.stream = createWriteStream3(this.logPath, { flags: "a" });
@@ -74775,7 +75302,7 @@ function resolveMainModelFact(model) {
74775
75302
  const definitionName = resolution.catalogName === "gemini" ? "google" : resolution.catalogName;
74776
75303
  const definition = definitionName ? getProviderByName(definitionName) : undefined;
74777
75304
  const format = definition ? pinnedFormatForTransport(definition.transport, resolution.modelName) : null;
74778
- const providerName = resolution.category === "unknown" ? "unresolved (routed on the first request)" : definition?.displayName || resolution.providerName;
75305
+ const providerName = resolution.category === "unknown" || resolution.category === "auto-route" ? "unresolved (routed on the first request)" : definition?.displayName || resolution.providerName;
74779
75306
  return {
74780
75307
  model,
74781
75308
  providerName,
@@ -77119,9 +77646,22 @@ async function applyConfigOverride() {
77119
77646
  setConfigFileOverride(plan.path);
77120
77647
  process.env.CLAUDISH_CONFIG = plan.path;
77121
77648
  }
77649
+ async function applyDefaultProviderFlag() {
77650
+ await Promise.resolve();
77651
+ const plan = planDefaultProviderFlag(process.argv.slice(2));
77652
+ if (plan.kind === "none")
77653
+ return;
77654
+ if (plan.kind === "error") {
77655
+ console.error(`[claudish] ${plan.message}`);
77656
+ process.exit(1);
77657
+ }
77658
+ process.argv = [...process.argv.slice(0, 2), ...plan.argv];
77659
+ process.env.CLAUDISH_DEFAULT_PROVIDER = plan.value;
77660
+ }
77122
77661
  await traceSpan("startup:config-override", () => applyConfigOverride());
77123
77662
  await traceSpan("startup:op-env-flags", () => applyOpEnvironment());
77124
77663
  await traceSpan("startup:op-import-flag", () => applyOpImport());
77664
+ await traceSpan("startup:default-provider-flag", () => applyDefaultProviderFlag());
77125
77665
  var isMcpMode = process.argv.includes("--mcp");
77126
77666
  function handlePromptExit(err) {
77127
77667
  if (err instanceof PickerCancelled) {