claudish 7.23.0 → 7.24.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 +604 -572
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -651,7 +651,7 @@ var init_onepassword_config = __esm(() => {
651
651
  });
652
652
 
653
653
  // src/version.ts
654
- var VERSION = "7.23.0";
654
+ var VERSION = "7.24.0";
655
655
 
656
656
  // src/logger.ts
657
657
  var exports_logger = {};
@@ -30418,11 +30418,550 @@ var init_routing_hints = __esm(() => {
30418
30418
  };
30419
30419
  });
30420
30420
 
30421
+ // src/providers/all-models-cache.ts
30422
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
30423
+ import { homedir as homedir13 } from "os";
30424
+ import { dirname as dirname4, join as join13 } from "path";
30425
+ function readAllModelsCache(path = ALL_MODELS_CACHE_PATH) {
30426
+ if (!existsSync12(path))
30427
+ return null;
30428
+ let raw;
30429
+ try {
30430
+ raw = JSON.parse(readFileSync9(path, "utf-8"));
30431
+ } catch {
30432
+ return null;
30433
+ }
30434
+ if (!raw || typeof raw !== "object")
30435
+ return null;
30436
+ const data = raw;
30437
+ const lastUpdated = typeof data.lastUpdated === "string" ? data.lastUpdated : new Date(0).toISOString();
30438
+ const models = Array.isArray(data.models) ? data.models : [];
30439
+ const entries = Array.isArray(data.entries) ? data.entries : [];
30440
+ return {
30441
+ version: 2,
30442
+ lastUpdated,
30443
+ entries,
30444
+ models
30445
+ };
30446
+ }
30447
+ function writeAllModelsCache(data, path = ALL_MODELS_CACHE_PATH) {
30448
+ const existing = readAllModelsCache(path);
30449
+ const merged = {
30450
+ version: 2,
30451
+ lastUpdated: data.lastUpdated ?? new Date().toISOString(),
30452
+ entries: data.entries ?? existing?.entries ?? [],
30453
+ models: data.models ?? existing?.models ?? []
30454
+ };
30455
+ mkdirSync5(dirname4(path), { recursive: true });
30456
+ writeFileSync6(path, JSON.stringify(merged), "utf-8");
30457
+ }
30458
+ var ALL_MODELS_CACHE_PATH;
30459
+ var init_all_models_cache = __esm(() => {
30460
+ ALL_MODELS_CACHE_PATH = join13(homedir13(), ".claudish", "all-models.json");
30461
+ });
30462
+
30463
+ // src/adapters/model-catalog.ts
30464
+ function lookupModel(modelId, cachePath) {
30465
+ const entry = findCacheEntry(modelId, cachePath);
30466
+ if (!entry || entry.contextWindow === undefined)
30467
+ return;
30468
+ return {
30469
+ modelId: entry.modelId,
30470
+ contextWindow: entry.contextWindow,
30471
+ supportsVision: entry.supportsVision
30472
+ };
30473
+ }
30474
+ function lookupModelForProvider(modelId, provider, cachePath) {
30475
+ const entry = findCacheEntry(modelId, cachePath);
30476
+ if (!entry)
30477
+ return;
30478
+ return entry.aggregators?.find((a) => a.provider === provider)?.contextWindow ?? entry.contextWindow;
30479
+ }
30480
+ function resolveSubscriptionRouting(modelId, provider, cachePath) {
30481
+ const entry = findCacheEntry(modelId, cachePath);
30482
+ if (!entry)
30483
+ return { kind: "unknown" };
30484
+ if (entry.subscriptionPlans?.includes(provider)) {
30485
+ const agg = entry.aggregators?.find((a) => a.provider === provider);
30486
+ return agg?.externalId ? { kind: "serves", externalId: agg.externalId } : { kind: "unknown" };
30487
+ }
30488
+ return isSubscriptionPlan(provider, cachePath) ? { kind: "not-served" } : { kind: "unknown" };
30489
+ }
30490
+ function isSubscriptionPlan(provider, cachePath) {
30491
+ const cache = readAllModelsCache(cachePath);
30492
+ if (!cache)
30493
+ return false;
30494
+ return cache.entries.some((e) => e.subscriptionPlans?.includes(provider));
30495
+ }
30496
+ function findCacheEntry(modelId, cachePath) {
30497
+ if (modelId.includes("@")) {
30498
+ throw new Error(`model-catalog lookup received provider-routed ID "${modelId}" \u2014 callers must strip the "@" prefix before calling`);
30499
+ }
30500
+ const cache = readAllModelsCache(cachePath);
30501
+ if (!cache || cache.entries.length === 0)
30502
+ return;
30503
+ const lower = modelId.toLowerCase();
30504
+ const unprefixed = lower.includes("/") ? lower.substring(lower.lastIndexOf("/") + 1) : lower;
30505
+ for (const entry of cache.entries) {
30506
+ const entryId = entry.modelId.toLowerCase();
30507
+ const exactMatch = entryId === unprefixed || entryId === lower;
30508
+ const aliasMatch = entry.aliases?.some((a) => a.toLowerCase() === unprefixed || a.toLowerCase() === lower);
30509
+ if (exactMatch || aliasMatch) {
30510
+ return entry;
30511
+ }
30512
+ }
30513
+ return;
30514
+ }
30515
+ var init_model_catalog = __esm(() => {
30516
+ init_all_models_cache();
30517
+ });
30518
+
30519
+ // src/providers/auto-route.ts
30520
+ var PROVIDER_TO_PREFIX, DISPLAY_NAMES;
30521
+ var init_auto_route = __esm(() => {
30522
+ init_provider_definitions();
30523
+ PROVIDER_TO_PREFIX = (() => {
30524
+ const map3 = {};
30525
+ for (const def of getAllProviders()) {
30526
+ if (def.shortestPrefix) {
30527
+ map3[def.name] = def.shortestPrefix;
30528
+ }
30529
+ }
30530
+ return map3;
30531
+ })();
30532
+ DISPLAY_NAMES = (() => {
30533
+ const map3 = {};
30534
+ for (const def of getAllProviders()) {
30535
+ map3[def.name] = def.displayName;
30536
+ }
30537
+ return map3;
30538
+ })();
30539
+ });
30540
+
30541
+ // src/providers/default-routing-rules.ts
30542
+ function validateRoutingRulesAgainstProviders(rules) {
30543
+ const unknown3 = [];
30544
+ for (const ruleKey of Object.keys(rules)) {
30545
+ const entries = rules[ruleKey] ?? [];
30546
+ for (const entry of entries) {
30547
+ const atIdx = entry.indexOf("@");
30548
+ const providerRaw = atIdx === -1 ? entry : entry.slice(0, atIdx);
30549
+ const canonical = PROVIDER_SHORTCUTS[providerRaw.toLowerCase()] ?? providerRaw.toLowerCase();
30550
+ if (!getProviderByName(canonical)) {
30551
+ unknown3.push({ rule: ruleKey, entry, provider: canonical });
30552
+ }
30553
+ }
30554
+ }
30555
+ if (unknown3.length > 0) {
30556
+ const lines = unknown3.map((u) => ` rule "${u.rule}" \u2192 entry "${u.entry}" \u2192 unknown provider "${u.provider}"`);
30557
+ throw new Error(`[claudish] DEFAULT_ROUTING_RULES references unknown providers:
30558
+ ${lines.join(`
30559
+ `)}`);
30560
+ }
30561
+ }
30562
+ function validateDefaultRoutingRules() {
30563
+ validateRoutingRulesAgainstProviders(DEFAULT_ROUTING_RULES);
30564
+ }
30565
+ var DEFAULT_ROUTING_RULES;
30566
+ var init_default_routing_rules = __esm(() => {
30567
+ init_model_parser();
30568
+ init_provider_definitions();
30569
+ DEFAULT_ROUTING_RULES = {
30570
+ "claude-*": ["native-anthropic", "openrouter"],
30571
+ "gpt-*": ["openai-codex", "openai", "openrouter"],
30572
+ "o1-*": ["openai-codex", "openai", "openrouter"],
30573
+ "o3-*": ["openai-codex", "openai", "openrouter"],
30574
+ "gemini-*": ["gemini-codeassist", "google", "openrouter"],
30575
+ "grok-*": ["x-ai", "openrouter"],
30576
+ "kimi-*": ["kimi-coding", "kimi", "openrouter"],
30577
+ "k3*": ["kimi-coding", "kimi", "openrouter"],
30578
+ "minimax-*": ["minimax-coding", "minimax", "openrouter"],
30579
+ "glm-*": ["glm-coding", "glm", "openrouter"],
30580
+ "z-ai-*": ["z-ai", "openrouter"],
30581
+ "deepseek-*": ["deepseek", "openrouter"],
30582
+ fugu: ["sakana-subscription", "sakana"],
30583
+ "fugu-*": ["sakana-subscription", "sakana"],
30584
+ "*-zen": ["opencode-zen"],
30585
+ "*": ["openrouter"]
30586
+ };
30587
+ validateDefaultRoutingRules();
30588
+ });
30589
+
30590
+ // src/providers/catalog-resolvers/openrouter.ts
30591
+ class OpenRouterCatalogResolver {
30592
+ provider = "openrouter";
30593
+ resolveSync(userInput) {
30594
+ const entries = this._getEntries();
30595
+ if (userInput.includes("/")) {
30596
+ if (entries) {
30597
+ for (const entry of entries) {
30598
+ for (const src of Object.values(entry.sources)) {
30599
+ if (src.externalId === userInput)
30600
+ return userInput;
30601
+ }
30602
+ }
30603
+ }
30604
+ return userInput;
30605
+ }
30606
+ if (entries) {
30607
+ const byModelId = entries.find((e) => e.modelId === userInput);
30608
+ if (byModelId) {
30609
+ const orId = this._getOpenRouterExternalId(byModelId);
30610
+ if (orId)
30611
+ return orId;
30612
+ }
30613
+ const byAlias = entries.find((e) => e.aliases.includes(userInput));
30614
+ if (byAlias) {
30615
+ const orId = this._getOpenRouterExternalId(byAlias);
30616
+ if (orId)
30617
+ return orId;
30618
+ }
30619
+ for (const entry of entries) {
30620
+ for (const src of Object.values(entry.sources)) {
30621
+ if (src.externalId === userInput) {
30622
+ const orId = this._getOpenRouterExternalId(entry);
30623
+ if (orId)
30624
+ return orId;
30625
+ }
30626
+ }
30627
+ }
30628
+ const suffix = `/${userInput}`;
30629
+ for (const entry of entries) {
30630
+ const orId = this._getOpenRouterExternalId(entry);
30631
+ if (orId?.endsWith(suffix))
30632
+ return orId;
30633
+ }
30634
+ const lowerSuffix = `/${userInput.toLowerCase()}`;
30635
+ for (const entry of entries) {
30636
+ const orId = this._getOpenRouterExternalId(entry);
30637
+ if (orId?.toLowerCase().endsWith(lowerSuffix))
30638
+ return orId;
30639
+ }
30640
+ }
30641
+ return null;
30642
+ }
30643
+ async warmCache() {
30644
+ if (!_warmPromise) {
30645
+ _warmPromise = this._fetchAndCache();
30646
+ }
30647
+ await _warmPromise;
30648
+ }
30649
+ isCacheWarm() {
30650
+ return _memCache !== null && _memCache.length > 0;
30651
+ }
30652
+ async ensureReady(timeoutMs) {
30653
+ if (this.isCacheWarm())
30654
+ return;
30655
+ if (!_warmPromise) {
30656
+ _warmPromise = this._fetchAndCache();
30657
+ }
30658
+ await Promise.race([
30659
+ _warmPromise,
30660
+ new Promise((resolve) => setTimeout(resolve, timeoutMs))
30661
+ ]);
30662
+ }
30663
+ async refreshCatalog(timeoutMs) {
30664
+ let response;
30665
+ try {
30666
+ response = await fetch(FIREBASE_CATALOG_URL, {
30667
+ signal: AbortSignal.timeout(timeoutMs)
30668
+ });
30669
+ } catch (err) {
30670
+ const name = err?.name;
30671
+ const reason = name === "TimeoutError" || name === "AbortError" ? "timeout" : "network";
30672
+ return { kind: "fetch_failed", reason };
30673
+ }
30674
+ if (!response.ok) {
30675
+ return { kind: "fetch_failed", reason: "http_error" };
30676
+ }
30677
+ let data;
30678
+ try {
30679
+ data = await response.json();
30680
+ } catch {
30681
+ return { kind: "fetch_failed", reason: "network" };
30682
+ }
30683
+ if (!Array.isArray(data.models) || data.models.length === 0) {
30684
+ return { kind: "fetch_failed", reason: "empty" };
30685
+ }
30686
+ const backwardCompatModels = [];
30687
+ for (const entry of data.models) {
30688
+ const orSource = entry.sources["openrouter-api"];
30689
+ if (orSource?.externalId) {
30690
+ backwardCompatModels.push({ id: orSource.externalId });
30691
+ }
30692
+ }
30693
+ _memCache = data.models;
30694
+ writeAllModelsCache({
30695
+ entries: data.models,
30696
+ models: backwardCompatModels
30697
+ });
30698
+ _warmPromise = Promise.resolve();
30699
+ return { kind: "refreshed", modelCount: data.models.length };
30700
+ }
30701
+ _getOpenRouterExternalId(entry) {
30702
+ const orSource = entry.sources["openrouter-api"];
30703
+ if (orSource?.externalId)
30704
+ return orSource.externalId;
30705
+ for (const src of Object.values(entry.sources)) {
30706
+ if (src.externalId.includes("/"))
30707
+ return src.externalId;
30708
+ }
30709
+ return null;
30710
+ }
30711
+ _getEntries() {
30712
+ if (_memCache)
30713
+ return _memCache;
30714
+ const cache = readAllModelsCache();
30715
+ if (!cache)
30716
+ return null;
30717
+ if (cache.entries.length > 0) {
30718
+ _memCache = cache.entries;
30719
+ return _memCache;
30720
+ }
30721
+ if (cache.models.length > 0) {
30722
+ _memCache = cache.models.map((m) => ({
30723
+ modelId: m.id.includes("/") ? m.id.split("/").slice(1).join("/") : m.id,
30724
+ aliases: [],
30725
+ sources: { "openrouter-api": { externalId: m.id } }
30726
+ }));
30727
+ return _memCache;
30728
+ }
30729
+ return null;
30730
+ }
30731
+ async _fetchAndCache() {
30732
+ await this.refreshCatalog(8000);
30733
+ }
30734
+ }
30735
+ var FIREBASE_CATALOG_URL, _memCache = null, _warmPromise = null;
30736
+ var init_openrouter = __esm(() => {
30737
+ init_all_models_cache();
30738
+ FIREBASE_CATALOG_URL = process.env.CLAUDISH_CATALOG_URL ?? process.env.FIREBASE_CATALOG_URL ?? "https://us-central1-claudish-6da10.cloudfunctions.net/queryModels?status=active&catalog=slim&limit=1000";
30739
+ });
30740
+
30741
+ // src/providers/model-catalog-resolver.ts
30742
+ function registerResolver(resolver) {
30743
+ RESOLVER_REGISTRY.set(resolver.provider, resolver);
30744
+ }
30745
+ function getResolver(provider) {
30746
+ return RESOLVER_REGISTRY.get(provider) ?? null;
30747
+ }
30748
+ function resolveModelNameSync(userInput, targetProvider) {
30749
+ if (targetProvider !== "openrouter" && userInput.includes("/")) {
30750
+ return { resolvedId: userInput, wasResolved: false, sourceLabel: "passthrough" };
30751
+ }
30752
+ const resolver = getResolver(targetProvider);
30753
+ if (!resolver) {
30754
+ return { resolvedId: userInput, wasResolved: false, sourceLabel: "passthrough" };
30755
+ }
30756
+ const resolved = resolver.resolveSync(userInput);
30757
+ if (!resolved || resolved === userInput) {
30758
+ return { resolvedId: userInput, wasResolved: false, sourceLabel: "passthrough" };
30759
+ }
30760
+ return {
30761
+ resolvedId: resolved,
30762
+ wasResolved: true,
30763
+ sourceLabel: `${targetProvider} catalog`
30764
+ };
30765
+ }
30766
+ function logResolution(userInput, result, quiet = false) {
30767
+ if (result.wasResolved && !quiet) {
30768
+ process.stderr.write(`[Model] Resolved "${userInput}" \u2192 "${result.resolvedId}" (${result.sourceLabel})
30769
+ `);
30770
+ }
30771
+ }
30772
+ async function ensureCatalogReady(provider, timeoutMs = 5000) {
30773
+ const resolver = getResolver(provider);
30774
+ if (!resolver || resolver.isCacheWarm())
30775
+ return;
30776
+ await resolver.ensureReady(timeoutMs);
30777
+ }
30778
+ async function warmAllCatalogs(providers) {
30779
+ const targets = providers ? [...RESOLVER_REGISTRY.entries()].filter(([k]) => providers.includes(k)) : [...RESOLVER_REGISTRY.entries()];
30780
+ await Promise.allSettled(targets.map(([, r]) => r.warmCache()));
30781
+ }
30782
+ var RESOLVER_REGISTRY;
30783
+ var init_model_catalog_resolver = __esm(() => {
30784
+ init_openrouter();
30785
+ RESOLVER_REGISTRY = new Map;
30786
+ [
30787
+ new OpenRouterCatalogResolver
30788
+ ].forEach(registerResolver);
30789
+ });
30790
+
30791
+ // src/providers/routing-rules.ts
30792
+ function mergeRoutingRules(defaults, global_, local) {
30793
+ return { ...defaults, ...global_, ...local };
30794
+ }
30795
+ function loadRoutingRules() {
30796
+ const local = loadLocalConfig()?.routing ?? {};
30797
+ const global_ = loadConfig().routing ?? {};
30798
+ validateRoutingRules(local);
30799
+ validateRoutingRules(global_);
30800
+ return mergeRoutingRules(DEFAULT_ROUTING_RULES, global_, local);
30801
+ }
30802
+ function validateRoutingRules(rules) {
30803
+ const seenLower = new Map;
30804
+ for (const key of Object.keys(rules)) {
30805
+ if (key !== "*" && (key.match(/\*/g) || []).length > 1) {
30806
+ console.error(`[claudish] Warning: routing pattern "${key}" has multiple wildcards \u2014 only single * is supported. This pattern may not match as expected.`);
30807
+ }
30808
+ const lower = key.toLowerCase();
30809
+ const prior = seenLower.get(lower);
30810
+ if (prior !== undefined && prior !== key) {
30811
+ 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.`);
30812
+ } else {
30813
+ seenLower.set(lower, key);
30814
+ }
30815
+ }
30816
+ }
30817
+ function matchRoutingRule(modelName, rules) {
30818
+ const lowered = modelName.toLowerCase();
30819
+ for (const [key, entries] of Object.entries(rules)) {
30820
+ if (!key.includes("*") && key.toLowerCase() === lowered)
30821
+ return entries;
30822
+ }
30823
+ const globKeys = Object.keys(rules).filter((k) => k !== "*" && k.includes("*")).sort((a, b) => b.length - a.length);
30824
+ for (const pattern of globKeys) {
30825
+ if (globMatch(pattern, modelName))
30826
+ return rules[pattern];
30827
+ }
30828
+ if (rules["*"] !== undefined)
30829
+ return rules["*"];
30830
+ return null;
30831
+ }
30832
+ function buildRoutingChain(entries, originalModelName, cachePath) {
30833
+ const routes = [];
30834
+ for (const entry of entries) {
30835
+ const atIdx = entry.indexOf("@");
30836
+ let providerRaw;
30837
+ let modelName;
30838
+ if (atIdx !== -1) {
30839
+ providerRaw = entry.slice(0, atIdx);
30840
+ modelName = entry.slice(atIdx + 1);
30841
+ } else {
30842
+ providerRaw = entry;
30843
+ modelName = originalModelName;
30844
+ }
30845
+ const provider = PROVIDER_SHORTCUTS[providerRaw.toLowerCase()] ?? providerRaw.toLowerCase();
30846
+ if (atIdx === -1) {
30847
+ const routing = resolveSubscriptionRouting(modelName, provider, cachePath);
30848
+ if (routing.kind === "not-served")
30849
+ continue;
30850
+ if (routing.kind === "serves")
30851
+ modelName = routing.externalId;
30852
+ }
30853
+ let modelSpec;
30854
+ if (provider === "openrouter") {
30855
+ const resolution = resolveModelNameSync(modelName, "openrouter");
30856
+ modelSpec = resolution.resolvedId;
30857
+ } else {
30858
+ const prefix = PROVIDER_TO_PREFIX[provider] ?? provider;
30859
+ modelSpec = `${prefix}@${modelName}`;
30860
+ }
30861
+ const displayName = DISPLAY_NAMES[provider] ?? provider;
30862
+ routes.push({ provider, modelSpec, displayName });
30863
+ }
30864
+ return routes;
30865
+ }
30866
+ function globMatch(pattern, value) {
30867
+ const star = pattern.indexOf("*");
30868
+ const p = pattern.toLowerCase();
30869
+ const v = value.toLowerCase();
30870
+ if (star === -1)
30871
+ return p === v;
30872
+ const prefix = p.slice(0, star);
30873
+ const suffix = p.slice(star + 1);
30874
+ return v.startsWith(prefix) && v.endsWith(suffix) && v.length >= prefix.length + suffix.length;
30875
+ }
30876
+ async function hasCredentialsForProvider(provider) {
30877
+ return credentials.isAvailable(provider);
30878
+ }
30879
+ async function routeExplicit(modelSpec, model, provider, cachePath) {
30880
+ if (!await hasCredentialsForProvider(provider)) {
30881
+ return {
30882
+ kind: "no-route",
30883
+ reason: `No credentials configured for "${provider}".`,
30884
+ hint: buildCredentialHint(model, [provider]) ?? undefined
30885
+ };
30886
+ }
30887
+ const built = buildRoutingChain([modelSpec], model, cachePath)[0];
30888
+ if (!built) {
30889
+ return {
30890
+ kind: "no-route",
30891
+ reason: `Could not build a route for "${modelSpec}".`
30892
+ };
30893
+ }
30894
+ return { kind: "ok", primary: built, fallbacks: [] };
30895
+ }
30896
+ async function routeBare(model, nativeProvider, rules, defaultProvider, cachePath) {
30897
+ const matched = matchRoutingRule(model, rules) ?? [];
30898
+ const entries = [...matched];
30899
+ if (defaultProvider && defaultProvider.length > 0) {
30900
+ const canonicalDefault = PROVIDER_SHORTCUTS[defaultProvider.toLowerCase()] ?? defaultProvider.toLowerCase();
30901
+ const alreadyPresent = entries.some((e) => {
30902
+ const atIdx = e.indexOf("@");
30903
+ const providerRaw = atIdx === -1 ? e : e.slice(0, atIdx);
30904
+ const canonical = PROVIDER_SHORTCUTS[providerRaw.toLowerCase()] ?? providerRaw.toLowerCase();
30905
+ return canonical === canonicalDefault;
30906
+ });
30907
+ if (!alreadyPresent)
30908
+ entries.push(defaultProvider);
30909
+ }
30910
+ if (entries.length === 0) {
30911
+ return {
30912
+ kind: "no-route",
30913
+ reason: `No routing rule matched "${model}".`,
30914
+ hint: buildCredentialHint(model, [nativeProvider]) ?? undefined
30915
+ };
30916
+ }
30917
+ const candidates = buildRoutingChain(entries, model, cachePath);
30918
+ const credentialed = [];
30919
+ const skipped = [];
30920
+ const checks4 = await Promise.all(candidates.map((candidate) => hasCredentialsForProvider(candidate.provider)));
30921
+ candidates.forEach((candidate, i) => {
30922
+ if (checks4[i]) {
30923
+ credentialed.push(candidate);
30924
+ } else {
30925
+ skipped.push(candidate.provider);
30926
+ }
30927
+ });
30928
+ if (credentialed.length === 0) {
30929
+ return {
30930
+ kind: "no-route",
30931
+ reason: skipped.length > 0 ? `No credentialed providers in chain for "${model}" (tried: ${skipped.join(", ")}).` : `No providers available for "${model}".`,
30932
+ hint: buildCredentialHint(model, skipped) ?? undefined
30933
+ };
30934
+ }
30935
+ const [primary, ...fallbacks] = credentialed;
30936
+ return { kind: "ok", primary, fallbacks };
30937
+ }
30938
+ async function route(modelSpec, rulesOverride, defaultProviderOverride, cachePath) {
30939
+ const parsed = parseModelSpec(modelSpec);
30940
+ if (parsed.isExplicitProvider) {
30941
+ return routeExplicit(modelSpec, parsed.model, parsed.provider, cachePath);
30942
+ }
30943
+ const rules = rulesOverride ?? loadRoutingRules();
30944
+ const defaultProvider = defaultProviderOverride !== undefined ? defaultProviderOverride : rulesOverride !== undefined ? undefined : loadConfig().defaultProvider;
30945
+ return routeBare(parsed.model, parsed.provider, rules, defaultProvider, cachePath);
30946
+ }
30947
+ var init_routing_rules = __esm(() => {
30948
+ init_model_catalog();
30949
+ init_authority();
30950
+ init_profile_config();
30951
+ init_auto_route();
30952
+ init_default_routing_rules();
30953
+ init_model_catalog_resolver();
30954
+ init_model_parser();
30955
+ init_model_parser();
30956
+ init_routing_hints();
30957
+ });
30958
+
30421
30959
  // src/providers/provider-resolver.ts
30422
30960
  var exports_provider_resolver = {};
30423
30961
  __export(exports_provider_resolver, {
30424
30962
  validateApiKeysForModels: () => validateApiKeysForModels,
30425
30963
  resolveModelProvider: () => resolveModelProvider,
30964
+ rescueRoutableResolutions: () => rescueRoutableResolutions,
30426
30965
  requiresOpenRouterKey: () => requiresOpenRouterKey,
30427
30966
  isLocalModel: () => isLocalModel,
30428
30967
  getMissingKeysError: () => getMissingKeysError,
@@ -30594,8 +31133,20 @@ async function validateApiKeysForModels(models) {
30594
31133
  return;
30595
31134
  r.apiKeyAvailable = await credentials.isAvailable(r.catalogName);
30596
31135
  }));
31136
+ await rescueRoutableResolutions(resolutions);
30597
31137
  return resolutions;
30598
31138
  }
31139
+ async function rescueRoutableResolutions(resolutions, router = route) {
31140
+ await Promise.all(resolutions.map(async (r) => {
31141
+ if (!r.requiredApiKeyEnvVar || r.apiKeyAvailable)
31142
+ return;
31143
+ try {
31144
+ const plan = await router(r.fullModelId);
31145
+ if (plan.kind === "ok")
31146
+ r.apiKeyAvailable = true;
31147
+ } catch {}
31148
+ }));
31149
+ }
30599
31150
  function getMissingKeyResolutions(resolutions) {
30600
31151
  return resolutions.filter((r) => r.requiredApiKeyEnvVar && !r.apiKeyAvailable);
30601
31152
  }
@@ -30709,6 +31260,7 @@ var init_provider_resolver = __esm(() => {
30709
31260
  init_remote_provider_registry();
30710
31261
  init_onepassword();
30711
31262
  init_routing_hints();
31263
+ init_routing_rules();
30712
31264
  API_KEY_INFO = new Proxy({}, {
30713
31265
  get(_target, prop) {
30714
31266
  return getApiKeyInfoForProvider(prop);
@@ -30990,9 +31542,9 @@ var init_signal_watcher = __esm(() => {
30990
31542
  // src/channel/session-manager.ts
30991
31543
  import { spawn } from "child_process";
30992
31544
  import { randomUUID as randomUUID2 } from "crypto";
30993
- import { createWriteStream, mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
30994
- import { homedir as homedir13 } from "os";
30995
- import { join as join13 } from "path";
31545
+ import { createWriteStream, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
31546
+ import { homedir as homedir14 } from "os";
31547
+ import { join as join14 } from "path";
30996
31548
 
30997
31549
  class SessionManager {
30998
31550
  sessions = new Map;
@@ -31012,10 +31564,10 @@ class SessionManager {
31012
31564
  const sessionId = randomUUID2().slice(0, 8);
31013
31565
  const timeout = Math.min(opts.timeoutSeconds ?? DEFAULT_TIMEOUT, MAX_TIMEOUT);
31014
31566
  const startedAt = new Date().toISOString();
31015
- const sessionDir = join13(homedir13(), ".claudish", "sessions", sessionId);
31016
- mkdirSync5(sessionDir, { recursive: true });
31567
+ const sessionDir = join14(homedir14(), ".claudish", "sessions", sessionId);
31568
+ mkdirSync6(sessionDir, { recursive: true });
31017
31569
  if (opts.prompt) {
31018
- writeFileSync6(join13(sessionDir, "prompt.md"), opts.prompt, "utf-8");
31570
+ writeFileSync7(join14(sessionDir, "prompt.md"), opts.prompt, "utf-8");
31019
31571
  }
31020
31572
  const args = ["--model", opts.model, "-y", "--stdin", "--quiet", ...opts.claudishFlags ?? []];
31021
31573
  const proc = spawn("claudish", args, {
@@ -31042,7 +31594,7 @@ class SessionManager {
31042
31594
  });
31043
31595
  }
31044
31596
  });
31045
- const outputLogStream = createWriteStream(join13(sessionDir, "output.log"));
31597
+ const outputLogStream = createWriteStream(join14(sessionDir, "output.log"));
31046
31598
  const entry = {
31047
31599
  info: {
31048
31600
  sessionId,
@@ -31089,9 +31641,9 @@ class SessionManager {
31089
31641
  watcher.processExited(code);
31090
31642
  outputLogStream.end();
31091
31643
  if (entry.stderr) {
31092
- writeFileSync6(join13(sessionDir, "stderr.log"), entry.stderr, "utf-8");
31644
+ writeFileSync7(join14(sessionDir, "stderr.log"), entry.stderr, "utf-8");
31093
31645
  }
31094
- writeFileSync6(join13(sessionDir, "meta.json"), JSON.stringify(entry.info, null, 2), "utf-8");
31646
+ writeFileSync7(join14(sessionDir, "meta.json"), JSON.stringify(entry.info, null, 2), "utf-8");
31095
31647
  this.cleanupSigint();
31096
31648
  });
31097
31649
  proc.on("error", (err) => {
@@ -31264,9 +31816,9 @@ var init_cache_ttl = __esm(() => {
31264
31816
  });
31265
31817
 
31266
31818
  // src/model-loader.ts
31267
- import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
31268
- import { homedir as homedir14 } from "os";
31269
- import { join as join14 } from "path";
31819
+ import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
31820
+ import { homedir as homedir15 } from "os";
31821
+ import { join as join15 } from "path";
31270
31822
  function groupRecommendedModels(entries) {
31271
31823
  const byId = new Map;
31272
31824
  for (const entry of entries) {
@@ -31362,9 +31914,9 @@ async function getRecommendedModels(opts = {}) {
31362
31914
  if (!forceRefresh && _cachedRecommendedModels) {
31363
31915
  return _cachedRecommendedModels;
31364
31916
  }
31365
- if (!forceRefresh && existsSync12(RECOMMENDED_MODELS_CACHE_PATH)) {
31917
+ if (!forceRefresh && existsSync13(RECOMMENDED_MODELS_CACHE_PATH)) {
31366
31918
  try {
31367
- const cacheData = JSON.parse(readFileSync9(RECOMMENDED_MODELS_CACHE_PATH, "utf-8"));
31919
+ const cacheData = JSON.parse(readFileSync10(RECOMMENDED_MODELS_CACHE_PATH, "utf-8"));
31368
31920
  if (cacheData.models && cacheData.models.length > 0 && isFreshEnough(cacheData)) {
31369
31921
  _cachedRecommendedModels = cacheData;
31370
31922
  return cacheData;
@@ -31380,9 +31932,9 @@ async function getRecommendedModels(opts = {}) {
31380
31932
  if (data.models && data.models.length > 0) {
31381
31933
  _cachedRecommendedModels = data;
31382
31934
  try {
31383
- const cacheDir = join14(homedir14(), ".claudish");
31384
- mkdirSync6(cacheDir, { recursive: true });
31385
- writeFileSync7(RECOMMENDED_MODELS_CACHE_PATH, JSON.stringify(data), "utf-8");
31935
+ const cacheDir = join15(homedir15(), ".claudish");
31936
+ mkdirSync7(cacheDir, { recursive: true });
31937
+ writeFileSync8(RECOMMENDED_MODELS_CACHE_PATH, JSON.stringify(data), "utf-8");
31386
31938
  } catch {}
31387
31939
  return data;
31388
31940
  }
@@ -31393,9 +31945,9 @@ async function getRecommendedModels(opts = {}) {
31393
31945
  function getRecommendedModelsSync() {
31394
31946
  if (_cachedRecommendedModels)
31395
31947
  return _cachedRecommendedModels;
31396
- if (existsSync12(RECOMMENDED_MODELS_CACHE_PATH)) {
31948
+ if (existsSync13(RECOMMENDED_MODELS_CACHE_PATH)) {
31397
31949
  try {
31398
- const cacheData = JSON.parse(readFileSync9(RECOMMENDED_MODELS_CACHE_PATH, "utf-8"));
31950
+ const cacheData = JSON.parse(readFileSync10(RECOMMENDED_MODELS_CACHE_PATH, "utf-8"));
31399
31951
  if (cacheData.models && cacheData.models.length > 0 && isFreshEnough(cacheData)) {
31400
31952
  _cachedRecommendedModels = cacheData;
31401
31953
  return cacheData;
@@ -31519,7 +32071,7 @@ var _cachedModelInfo = null, _cachedModelIds = null, _cachedRecommendedModels =
31519
32071
  var init_model_loader = __esm(() => {
31520
32072
  init_cache_ttl();
31521
32073
  FIREBASE_RECOMMENDED_URL = `${FIREBASE_BASE_URL}?catalog=recommended`;
31522
- RECOMMENDED_MODELS_CACHE_PATH = join14(homedir14(), ".claudish", "recommended-models-cache.json");
32074
+ RECOMMENDED_MODELS_CACHE_PATH = join15(homedir15(), ".claudish", "recommended-models-cache.json");
31523
32075
  FIREBASE_SLUG_TO_PROVIDER_NAME = {
31524
32076
  openai: "openai",
31525
32077
  google: "google",
@@ -31986,10 +32538,10 @@ var init_request = __esm(() => {
31986
32538
  return this.#matchResult;
31987
32539
  }
31988
32540
  get matchedRoutes() {
31989
- return this.#matchResult[0].map(([[, route]]) => route);
32541
+ return this.#matchResult[0].map(([[, route2]]) => route2);
31990
32542
  }
31991
32543
  get routePath() {
31992
- return this.#matchResult[0].map(([[, route]]) => route)[this.routeIndex].path;
32544
+ return this.#matchResult[0].map(([[, route2]]) => route2)[this.routeIndex].path;
31993
32545
  }
31994
32546
  };
31995
32547
  });
@@ -32637,7 +33189,7 @@ function buildMatcherFromPreprocessedRoutes(routes) {
32637
33189
  if (routes.length === 0) {
32638
33190
  return nullMatcher;
32639
33191
  }
32640
- const routesWithStaticPathFlag = routes.map((route) => [!/\*|\/:/.test(route[0]), ...route]).sort(([isStaticA, pathA], [isStaticB, pathB]) => isStaticA ? 1 : isStaticB ? -1 : pathA.length - pathB.length);
33192
+ const routesWithStaticPathFlag = routes.map((route2) => [!/\*|\/:/.test(route2[0]), ...route2]).sort(([isStaticA, pathA], [isStaticB, pathB]) => isStaticA ? 1 : isStaticB ? -1 : pathA.length - pathB.length);
32641
33193
  const staticMap = /* @__PURE__ */ Object.create(null);
32642
33194
  for (let i = 0, j = -1, len = routesWithStaticPathFlag.length;i < len; i++) {
32643
33195
  const [pathErrorCheckOnly, path, handlers] = routesWithStaticPathFlag[i];
@@ -33283,104 +33835,6 @@ var init_remote_provider_types = __esm(() => {
33283
33835
  };
33284
33836
  });
33285
33837
 
33286
- // src/providers/all-models-cache.ts
33287
- import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
33288
- import { homedir as homedir15 } from "os";
33289
- import { dirname as dirname4, join as join15 } from "path";
33290
- function readAllModelsCache(path = ALL_MODELS_CACHE_PATH) {
33291
- if (!existsSync13(path))
33292
- return null;
33293
- let raw2;
33294
- try {
33295
- raw2 = JSON.parse(readFileSync10(path, "utf-8"));
33296
- } catch {
33297
- return null;
33298
- }
33299
- if (!raw2 || typeof raw2 !== "object")
33300
- return null;
33301
- const data = raw2;
33302
- const lastUpdated = typeof data.lastUpdated === "string" ? data.lastUpdated : new Date(0).toISOString();
33303
- const models = Array.isArray(data.models) ? data.models : [];
33304
- const entries = Array.isArray(data.entries) ? data.entries : [];
33305
- return {
33306
- version: 2,
33307
- lastUpdated,
33308
- entries,
33309
- models
33310
- };
33311
- }
33312
- function writeAllModelsCache(data, path = ALL_MODELS_CACHE_PATH) {
33313
- const existing = readAllModelsCache(path);
33314
- const merged = {
33315
- version: 2,
33316
- lastUpdated: data.lastUpdated ?? new Date().toISOString(),
33317
- entries: data.entries ?? existing?.entries ?? [],
33318
- models: data.models ?? existing?.models ?? []
33319
- };
33320
- mkdirSync7(dirname4(path), { recursive: true });
33321
- writeFileSync8(path, JSON.stringify(merged), "utf-8");
33322
- }
33323
- var ALL_MODELS_CACHE_PATH;
33324
- var init_all_models_cache = __esm(() => {
33325
- ALL_MODELS_CACHE_PATH = join15(homedir15(), ".claudish", "all-models.json");
33326
- });
33327
-
33328
- // src/adapters/model-catalog.ts
33329
- function lookupModel(modelId, cachePath) {
33330
- const entry = findCacheEntry(modelId, cachePath);
33331
- if (!entry || entry.contextWindow === undefined)
33332
- return;
33333
- return {
33334
- modelId: entry.modelId,
33335
- contextWindow: entry.contextWindow,
33336
- supportsVision: entry.supportsVision
33337
- };
33338
- }
33339
- function lookupModelForProvider(modelId, provider, cachePath) {
33340
- const entry = findCacheEntry(modelId, cachePath);
33341
- if (!entry)
33342
- return;
33343
- return entry.aggregators?.find((a) => a.provider === provider)?.contextWindow ?? entry.contextWindow;
33344
- }
33345
- function resolveSubscriptionRouting(modelId, provider, cachePath) {
33346
- const entry = findCacheEntry(modelId, cachePath);
33347
- if (!entry)
33348
- return { kind: "unknown" };
33349
- if (entry.subscriptionPlans?.includes(provider)) {
33350
- const agg = entry.aggregators?.find((a) => a.provider === provider);
33351
- return agg?.externalId ? { kind: "serves", externalId: agg.externalId } : { kind: "unknown" };
33352
- }
33353
- return isSubscriptionPlan(provider, cachePath) ? { kind: "not-served" } : { kind: "unknown" };
33354
- }
33355
- function isSubscriptionPlan(provider, cachePath) {
33356
- const cache = readAllModelsCache(cachePath);
33357
- if (!cache)
33358
- return false;
33359
- return cache.entries.some((e) => e.subscriptionPlans?.includes(provider));
33360
- }
33361
- function findCacheEntry(modelId, cachePath) {
33362
- if (modelId.includes("@")) {
33363
- throw new Error(`model-catalog lookup received provider-routed ID "${modelId}" \u2014 callers must strip the "@" prefix before calling`);
33364
- }
33365
- const cache = readAllModelsCache(cachePath);
33366
- if (!cache || cache.entries.length === 0)
33367
- return;
33368
- const lower = modelId.toLowerCase();
33369
- const unprefixed = lower.includes("/") ? lower.substring(lower.lastIndexOf("/") + 1) : lower;
33370
- for (const entry of cache.entries) {
33371
- const entryId = entry.modelId.toLowerCase();
33372
- const exactMatch = entryId === unprefixed || entryId === lower;
33373
- const aliasMatch = entry.aliases?.some((a) => a.toLowerCase() === unprefixed || a.toLowerCase() === lower);
33374
- if (exactMatch || aliasMatch) {
33375
- return entry;
33376
- }
33377
- }
33378
- return;
33379
- }
33380
- var init_model_catalog = __esm(() => {
33381
- init_all_models_cache();
33382
- });
33383
-
33384
33838
  // src/adapters/tool-name-utils.ts
33385
33839
  function hashToolName(name) {
33386
33840
  let h1 = 3735928559;
@@ -40151,207 +40605,6 @@ var init_fallback_handler = __esm(() => {
40151
40605
  init_composed_handler();
40152
40606
  });
40153
40607
 
40154
- // src/providers/catalog-resolvers/openrouter.ts
40155
- class OpenRouterCatalogResolver {
40156
- provider = "openrouter";
40157
- resolveSync(userInput) {
40158
- const entries = this._getEntries();
40159
- if (userInput.includes("/")) {
40160
- if (entries) {
40161
- for (const entry of entries) {
40162
- for (const src of Object.values(entry.sources)) {
40163
- if (src.externalId === userInput)
40164
- return userInput;
40165
- }
40166
- }
40167
- }
40168
- return userInput;
40169
- }
40170
- if (entries) {
40171
- const byModelId = entries.find((e) => e.modelId === userInput);
40172
- if (byModelId) {
40173
- const orId = this._getOpenRouterExternalId(byModelId);
40174
- if (orId)
40175
- return orId;
40176
- }
40177
- const byAlias = entries.find((e) => e.aliases.includes(userInput));
40178
- if (byAlias) {
40179
- const orId = this._getOpenRouterExternalId(byAlias);
40180
- if (orId)
40181
- return orId;
40182
- }
40183
- for (const entry of entries) {
40184
- for (const src of Object.values(entry.sources)) {
40185
- if (src.externalId === userInput) {
40186
- const orId = this._getOpenRouterExternalId(entry);
40187
- if (orId)
40188
- return orId;
40189
- }
40190
- }
40191
- }
40192
- const suffix = `/${userInput}`;
40193
- for (const entry of entries) {
40194
- const orId = this._getOpenRouterExternalId(entry);
40195
- if (orId?.endsWith(suffix))
40196
- return orId;
40197
- }
40198
- const lowerSuffix = `/${userInput.toLowerCase()}`;
40199
- for (const entry of entries) {
40200
- const orId = this._getOpenRouterExternalId(entry);
40201
- if (orId?.toLowerCase().endsWith(lowerSuffix))
40202
- return orId;
40203
- }
40204
- }
40205
- return null;
40206
- }
40207
- async warmCache() {
40208
- if (!_warmPromise) {
40209
- _warmPromise = this._fetchAndCache();
40210
- }
40211
- await _warmPromise;
40212
- }
40213
- isCacheWarm() {
40214
- return _memCache !== null && _memCache.length > 0;
40215
- }
40216
- async ensureReady(timeoutMs) {
40217
- if (this.isCacheWarm())
40218
- return;
40219
- if (!_warmPromise) {
40220
- _warmPromise = this._fetchAndCache();
40221
- }
40222
- await Promise.race([
40223
- _warmPromise,
40224
- new Promise((resolve) => setTimeout(resolve, timeoutMs))
40225
- ]);
40226
- }
40227
- async refreshCatalog(timeoutMs) {
40228
- let response;
40229
- try {
40230
- response = await fetch(FIREBASE_CATALOG_URL, {
40231
- signal: AbortSignal.timeout(timeoutMs)
40232
- });
40233
- } catch (err) {
40234
- const name = err?.name;
40235
- const reason = name === "TimeoutError" || name === "AbortError" ? "timeout" : "network";
40236
- return { kind: "fetch_failed", reason };
40237
- }
40238
- if (!response.ok) {
40239
- return { kind: "fetch_failed", reason: "http_error" };
40240
- }
40241
- let data;
40242
- try {
40243
- data = await response.json();
40244
- } catch {
40245
- return { kind: "fetch_failed", reason: "network" };
40246
- }
40247
- if (!Array.isArray(data.models) || data.models.length === 0) {
40248
- return { kind: "fetch_failed", reason: "empty" };
40249
- }
40250
- const backwardCompatModels = [];
40251
- for (const entry of data.models) {
40252
- const orSource = entry.sources["openrouter-api"];
40253
- if (orSource?.externalId) {
40254
- backwardCompatModels.push({ id: orSource.externalId });
40255
- }
40256
- }
40257
- _memCache = data.models;
40258
- writeAllModelsCache({
40259
- entries: data.models,
40260
- models: backwardCompatModels
40261
- });
40262
- _warmPromise = Promise.resolve();
40263
- return { kind: "refreshed", modelCount: data.models.length };
40264
- }
40265
- _getOpenRouterExternalId(entry) {
40266
- const orSource = entry.sources["openrouter-api"];
40267
- if (orSource?.externalId)
40268
- return orSource.externalId;
40269
- for (const src of Object.values(entry.sources)) {
40270
- if (src.externalId.includes("/"))
40271
- return src.externalId;
40272
- }
40273
- return null;
40274
- }
40275
- _getEntries() {
40276
- if (_memCache)
40277
- return _memCache;
40278
- const cache2 = readAllModelsCache();
40279
- if (!cache2)
40280
- return null;
40281
- if (cache2.entries.length > 0) {
40282
- _memCache = cache2.entries;
40283
- return _memCache;
40284
- }
40285
- if (cache2.models.length > 0) {
40286
- _memCache = cache2.models.map((m) => ({
40287
- modelId: m.id.includes("/") ? m.id.split("/").slice(1).join("/") : m.id,
40288
- aliases: [],
40289
- sources: { "openrouter-api": { externalId: m.id } }
40290
- }));
40291
- return _memCache;
40292
- }
40293
- return null;
40294
- }
40295
- async _fetchAndCache() {
40296
- await this.refreshCatalog(8000);
40297
- }
40298
- }
40299
- var FIREBASE_CATALOG_URL, _memCache = null, _warmPromise = null;
40300
- var init_openrouter = __esm(() => {
40301
- init_all_models_cache();
40302
- FIREBASE_CATALOG_URL = process.env.CLAUDISH_CATALOG_URL ?? process.env.FIREBASE_CATALOG_URL ?? "https://us-central1-claudish-6da10.cloudfunctions.net/queryModels?status=active&catalog=slim&limit=1000";
40303
- });
40304
-
40305
- // src/providers/model-catalog-resolver.ts
40306
- function registerResolver(resolver) {
40307
- RESOLVER_REGISTRY.set(resolver.provider, resolver);
40308
- }
40309
- function getResolver(provider) {
40310
- return RESOLVER_REGISTRY.get(provider) ?? null;
40311
- }
40312
- function resolveModelNameSync(userInput, targetProvider) {
40313
- if (targetProvider !== "openrouter" && userInput.includes("/")) {
40314
- return { resolvedId: userInput, wasResolved: false, sourceLabel: "passthrough" };
40315
- }
40316
- const resolver = getResolver(targetProvider);
40317
- if (!resolver) {
40318
- return { resolvedId: userInput, wasResolved: false, sourceLabel: "passthrough" };
40319
- }
40320
- const resolved = resolver.resolveSync(userInput);
40321
- if (!resolved || resolved === userInput) {
40322
- return { resolvedId: userInput, wasResolved: false, sourceLabel: "passthrough" };
40323
- }
40324
- return {
40325
- resolvedId: resolved,
40326
- wasResolved: true,
40327
- sourceLabel: `${targetProvider} catalog`
40328
- };
40329
- }
40330
- function logResolution(userInput, result, quiet = false) {
40331
- if (result.wasResolved && !quiet) {
40332
- process.stderr.write(`[Model] Resolved "${userInput}" \u2192 "${result.resolvedId}" (${result.sourceLabel})
40333
- `);
40334
- }
40335
- }
40336
- async function ensureCatalogReady(provider, timeoutMs = 5000) {
40337
- const resolver = getResolver(provider);
40338
- if (!resolver || resolver.isCacheWarm())
40339
- return;
40340
- await resolver.ensureReady(timeoutMs);
40341
- }
40342
- async function warmAllCatalogs(providers) {
40343
- const targets = providers ? [...RESOLVER_REGISTRY.entries()].filter(([k]) => providers.includes(k)) : [...RESOLVER_REGISTRY.entries()];
40344
- await Promise.allSettled(targets.map(([, r]) => r.warmCache()));
40345
- }
40346
- var RESOLVER_REGISTRY;
40347
- var init_model_catalog_resolver = __esm(() => {
40348
- init_openrouter();
40349
- RESOLVER_REGISTRY = new Map;
40350
- [
40351
- new OpenRouterCatalogResolver
40352
- ].forEach(registerResolver);
40353
- });
40354
-
40355
40608
  // src/handlers/native-handler-advisor.ts
40356
40609
  import { appendFileSync as appendFileSync3 } from "fs";
40357
40610
  function loadAdvisorSwapConfig(cliModels, cliCollector) {
@@ -42958,245 +43211,6 @@ var init_provider_profiles = __esm(() => {
42958
43211
  };
42959
43212
  });
42960
43213
 
42961
- // src/providers/auto-route.ts
42962
- var PROVIDER_TO_PREFIX, DISPLAY_NAMES;
42963
- var init_auto_route = __esm(() => {
42964
- init_provider_definitions();
42965
- PROVIDER_TO_PREFIX = (() => {
42966
- const map3 = {};
42967
- for (const def of getAllProviders()) {
42968
- if (def.shortestPrefix) {
42969
- map3[def.name] = def.shortestPrefix;
42970
- }
42971
- }
42972
- return map3;
42973
- })();
42974
- DISPLAY_NAMES = (() => {
42975
- const map3 = {};
42976
- for (const def of getAllProviders()) {
42977
- map3[def.name] = def.displayName;
42978
- }
42979
- return map3;
42980
- })();
42981
- });
42982
-
42983
- // src/providers/default-routing-rules.ts
42984
- function validateRoutingRulesAgainstProviders(rules) {
42985
- const unknown3 = [];
42986
- for (const ruleKey of Object.keys(rules)) {
42987
- const entries = rules[ruleKey] ?? [];
42988
- for (const entry of entries) {
42989
- const atIdx = entry.indexOf("@");
42990
- const providerRaw = atIdx === -1 ? entry : entry.slice(0, atIdx);
42991
- const canonical = PROVIDER_SHORTCUTS[providerRaw.toLowerCase()] ?? providerRaw.toLowerCase();
42992
- if (!getProviderByName(canonical)) {
42993
- unknown3.push({ rule: ruleKey, entry, provider: canonical });
42994
- }
42995
- }
42996
- }
42997
- if (unknown3.length > 0) {
42998
- const lines = unknown3.map((u) => ` rule "${u.rule}" \u2192 entry "${u.entry}" \u2192 unknown provider "${u.provider}"`);
42999
- throw new Error(`[claudish] DEFAULT_ROUTING_RULES references unknown providers:
43000
- ${lines.join(`
43001
- `)}`);
43002
- }
43003
- }
43004
- function validateDefaultRoutingRules() {
43005
- validateRoutingRulesAgainstProviders(DEFAULT_ROUTING_RULES);
43006
- }
43007
- var DEFAULT_ROUTING_RULES;
43008
- var init_default_routing_rules = __esm(() => {
43009
- init_model_parser();
43010
- init_provider_definitions();
43011
- DEFAULT_ROUTING_RULES = {
43012
- "claude-*": ["native-anthropic", "openrouter"],
43013
- "gpt-*": ["openai-codex", "openai", "openrouter"],
43014
- "o1-*": ["openai-codex", "openai", "openrouter"],
43015
- "o3-*": ["openai-codex", "openai", "openrouter"],
43016
- "gemini-*": ["gemini-codeassist", "google", "openrouter"],
43017
- "grok-*": ["x-ai", "openrouter"],
43018
- "kimi-*": ["kimi-coding", "kimi", "openrouter"],
43019
- "k3*": ["kimi-coding", "kimi", "openrouter"],
43020
- "minimax-*": ["minimax-coding", "minimax", "openrouter"],
43021
- "glm-*": ["glm-coding", "glm", "openrouter"],
43022
- "z-ai-*": ["z-ai", "openrouter"],
43023
- "deepseek-*": ["deepseek", "openrouter"],
43024
- fugu: ["sakana-subscription", "sakana"],
43025
- "fugu-*": ["sakana-subscription", "sakana"],
43026
- "*-zen": ["opencode-zen"],
43027
- "*": ["openrouter"]
43028
- };
43029
- validateDefaultRoutingRules();
43030
- });
43031
-
43032
- // src/providers/routing-rules.ts
43033
- function mergeRoutingRules(defaults, global_, local) {
43034
- return { ...defaults, ...global_, ...local };
43035
- }
43036
- function loadRoutingRules() {
43037
- const local = loadLocalConfig()?.routing ?? {};
43038
- const global_ = loadConfig().routing ?? {};
43039
- validateRoutingRules(local);
43040
- validateRoutingRules(global_);
43041
- return mergeRoutingRules(DEFAULT_ROUTING_RULES, global_, local);
43042
- }
43043
- function validateRoutingRules(rules) {
43044
- const seenLower = new Map;
43045
- for (const key of Object.keys(rules)) {
43046
- if (key !== "*" && (key.match(/\*/g) || []).length > 1) {
43047
- console.error(`[claudish] Warning: routing pattern "${key}" has multiple wildcards \u2014 only single * is supported. This pattern may not match as expected.`);
43048
- }
43049
- const lower = key.toLowerCase();
43050
- const prior = seenLower.get(lower);
43051
- if (prior !== undefined && prior !== key) {
43052
- 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.`);
43053
- } else {
43054
- seenLower.set(lower, key);
43055
- }
43056
- }
43057
- }
43058
- function matchRoutingRule(modelName, rules) {
43059
- const lowered = modelName.toLowerCase();
43060
- for (const [key, entries] of Object.entries(rules)) {
43061
- if (!key.includes("*") && key.toLowerCase() === lowered)
43062
- return entries;
43063
- }
43064
- const globKeys = Object.keys(rules).filter((k) => k !== "*" && k.includes("*")).sort((a, b) => b.length - a.length);
43065
- for (const pattern of globKeys) {
43066
- if (globMatch(pattern, modelName))
43067
- return rules[pattern];
43068
- }
43069
- if (rules["*"] !== undefined)
43070
- return rules["*"];
43071
- return null;
43072
- }
43073
- function buildRoutingChain(entries, originalModelName, cachePath) {
43074
- const routes = [];
43075
- for (const entry of entries) {
43076
- const atIdx = entry.indexOf("@");
43077
- let providerRaw;
43078
- let modelName;
43079
- if (atIdx !== -1) {
43080
- providerRaw = entry.slice(0, atIdx);
43081
- modelName = entry.slice(atIdx + 1);
43082
- } else {
43083
- providerRaw = entry;
43084
- modelName = originalModelName;
43085
- }
43086
- const provider = PROVIDER_SHORTCUTS[providerRaw.toLowerCase()] ?? providerRaw.toLowerCase();
43087
- if (atIdx === -1) {
43088
- const routing = resolveSubscriptionRouting(modelName, provider, cachePath);
43089
- if (routing.kind === "not-served")
43090
- continue;
43091
- if (routing.kind === "serves")
43092
- modelName = routing.externalId;
43093
- }
43094
- let modelSpec;
43095
- if (provider === "openrouter") {
43096
- const resolution = resolveModelNameSync(modelName, "openrouter");
43097
- modelSpec = resolution.resolvedId;
43098
- } else {
43099
- const prefix = PROVIDER_TO_PREFIX[provider] ?? provider;
43100
- modelSpec = `${prefix}@${modelName}`;
43101
- }
43102
- const displayName = DISPLAY_NAMES[provider] ?? provider;
43103
- routes.push({ provider, modelSpec, displayName });
43104
- }
43105
- return routes;
43106
- }
43107
- function globMatch(pattern, value) {
43108
- const star = pattern.indexOf("*");
43109
- const p = pattern.toLowerCase();
43110
- const v = value.toLowerCase();
43111
- if (star === -1)
43112
- return p === v;
43113
- const prefix = p.slice(0, star);
43114
- const suffix = p.slice(star + 1);
43115
- return v.startsWith(prefix) && v.endsWith(suffix) && v.length >= prefix.length + suffix.length;
43116
- }
43117
- async function hasCredentialsForProvider(provider) {
43118
- return credentials.isAvailable(provider);
43119
- }
43120
- async function routeExplicit(modelSpec, model, provider, cachePath) {
43121
- if (!await hasCredentialsForProvider(provider)) {
43122
- return {
43123
- kind: "no-route",
43124
- reason: `No credentials configured for "${provider}".`,
43125
- hint: buildCredentialHint(model, [provider]) ?? undefined
43126
- };
43127
- }
43128
- const built = buildRoutingChain([modelSpec], model, cachePath)[0];
43129
- if (!built) {
43130
- return {
43131
- kind: "no-route",
43132
- reason: `Could not build a route for "${modelSpec}".`
43133
- };
43134
- }
43135
- return { kind: "ok", primary: built, fallbacks: [] };
43136
- }
43137
- async function routeBare(model, nativeProvider, rules, defaultProvider, cachePath) {
43138
- const matched = matchRoutingRule(model, rules) ?? [];
43139
- const entries = [...matched];
43140
- if (defaultProvider && defaultProvider.length > 0) {
43141
- const canonicalDefault = PROVIDER_SHORTCUTS[defaultProvider.toLowerCase()] ?? defaultProvider.toLowerCase();
43142
- const alreadyPresent = entries.some((e) => {
43143
- const atIdx = e.indexOf("@");
43144
- const providerRaw = atIdx === -1 ? e : e.slice(0, atIdx);
43145
- const canonical = PROVIDER_SHORTCUTS[providerRaw.toLowerCase()] ?? providerRaw.toLowerCase();
43146
- return canonical === canonicalDefault;
43147
- });
43148
- if (!alreadyPresent)
43149
- entries.push(defaultProvider);
43150
- }
43151
- if (entries.length === 0) {
43152
- return {
43153
- kind: "no-route",
43154
- reason: `No routing rule matched "${model}".`,
43155
- hint: buildCredentialHint(model, [nativeProvider]) ?? undefined
43156
- };
43157
- }
43158
- const candidates = buildRoutingChain(entries, model, cachePath);
43159
- const credentialed = [];
43160
- const skipped = [];
43161
- const checks4 = await Promise.all(candidates.map((candidate) => hasCredentialsForProvider(candidate.provider)));
43162
- candidates.forEach((candidate, i) => {
43163
- if (checks4[i]) {
43164
- credentialed.push(candidate);
43165
- } else {
43166
- skipped.push(candidate.provider);
43167
- }
43168
- });
43169
- if (credentialed.length === 0) {
43170
- return {
43171
- kind: "no-route",
43172
- reason: skipped.length > 0 ? `No credentialed providers in chain for "${model}" (tried: ${skipped.join(", ")}).` : `No providers available for "${model}".`,
43173
- hint: buildCredentialHint(model, skipped) ?? undefined
43174
- };
43175
- }
43176
- const [primary, ...fallbacks] = credentialed;
43177
- return { kind: "ok", primary, fallbacks };
43178
- }
43179
- async function route(modelSpec, rulesOverride, defaultProviderOverride, cachePath) {
43180
- const parsed = parseModelSpec(modelSpec);
43181
- if (parsed.isExplicitProvider) {
43182
- return routeExplicit(modelSpec, parsed.model, parsed.provider, cachePath);
43183
- }
43184
- const rules = rulesOverride ?? loadRoutingRules();
43185
- const defaultProvider = defaultProviderOverride !== undefined ? defaultProviderOverride : rulesOverride !== undefined ? undefined : loadConfig().defaultProvider;
43186
- return routeBare(parsed.model, parsed.provider, rules, defaultProvider, cachePath);
43187
- }
43188
- var init_routing_rules = __esm(() => {
43189
- init_model_catalog();
43190
- init_authority();
43191
- init_profile_config();
43192
- init_auto_route();
43193
- init_default_routing_rules();
43194
- init_model_catalog_resolver();
43195
- init_model_parser();
43196
- init_model_parser();
43197
- init_routing_hints();
43198
- });
43199
-
43200
43214
  // src/handlers/shared/local-queue.ts
43201
43215
  class LocalModelQueue {
43202
43216
  static instance = null;
@@ -58871,6 +58885,7 @@ var init_config = __esm(() => {
58871
58885
  ANTHROPIC_DEFAULT_HAIKU_MODEL: "ANTHROPIC_DEFAULT_HAIKU_MODEL",
58872
58886
  CLAUDE_CODE_SUBAGENT_MODEL: "CLAUDE_CODE_SUBAGENT_MODEL",
58873
58887
  CLAUDE_CODE_AUTO_COMPACT_WINDOW: "CLAUDE_CODE_AUTO_COMPACT_WINDOW",
58888
+ CLAUDE_CODE_MAX_CONTEXT_TOKENS: "CLAUDE_CODE_MAX_CONTEXT_TOKENS",
58874
58889
  OLLAMA_BASE_URL: "OLLAMA_BASE_URL",
58875
58890
  OLLAMA_HOST: "OLLAMA_HOST",
58876
58891
  LMSTUDIO_BASE_URL: "LMSTUDIO_BASE_URL",
@@ -71758,6 +71773,7 @@ var init_terminal_isolation = __esm(() => {
71758
71773
  var exports_claude_runner = {};
71759
71774
  __export(exports_claude_runner, {
71760
71775
  runClaudeWithProxy: () => runClaudeWithProxy,
71776
+ resolveContextWindowEnv: () => resolveContextWindowEnv,
71761
71777
  managedSettingsForcesClaudeAi: () => managedSettingsForcesClaudeAi,
71762
71778
  isProxyAuthMode: () => isProxyAuthMode,
71763
71779
  computeMainThreadContextWindow: () => computeMainThreadContextWindow,
@@ -72021,6 +72037,27 @@ async function computeMainThreadContextWindow(config3, cachePath) {
72021
72037
  }
72022
72038
  return Number.isFinite(min) ? min : 0;
72023
72039
  }
72040
+ function resolveContextWindowEnv(realWindow, processEnv = process.env) {
72041
+ const vars = {};
72042
+ if (!(realWindow > 0))
72043
+ return { vars };
72044
+ if (!processEnv[ENV.CLAUDE_CODE_MAX_CONTEXT_TOKENS]) {
72045
+ vars[ENV.CLAUDE_CODE_MAX_CONTEXT_TOKENS] = String(realWindow);
72046
+ }
72047
+ if (processEnv[ENV.CLAUDE_CODE_AUTO_COMPACT_WINDOW])
72048
+ return { vars };
72049
+ if (realWindow >= MIN_AUTO_COMPACT_WINDOW) {
72050
+ vars[ENV.CLAUDE_CODE_AUTO_COMPACT_WINDOW] = String(realWindow);
72051
+ return {
72052
+ vars,
72053
+ notice: `[claudish] Auto-compact window: ${realWindow.toLocaleString()} tokens ` + "(Claude Code compacts before the backend's real limit)"
72054
+ };
72055
+ }
72056
+ return {
72057
+ vars,
72058
+ notice: `[claudish] Model's real context window (${realWindow.toLocaleString()}) is below ` + `Claude Code's ${MIN_AUTO_COMPACT_WINDOW.toLocaleString()}-token auto-compact floor \u2014 ` + "leaving CLAUDE_CODE_AUTO_COMPACT_WINDOW unset so native auto-compaction stays on."
72059
+ };
72060
+ }
72024
72061
  async function runClaudeWithProxy(config3, proxyUrl, onCleanup) {
72025
72062
  const hasProfileMappings = config3.modelOpus || config3.modelSonnet || config3.modelHaiku || config3.modelSubagent;
72026
72063
  const modelId = config3.model || (hasProfileMappings || config3.monitor ? undefined : "unknown");
@@ -72091,16 +72128,11 @@ async function runClaudeWithProxy(config3, proxyUrl, onCleanup) {
72091
72128
  } else {
72092
72129
  env.ANTHROPIC_API_KEY = "sk-ant-api03-placeholder-not-used-proxy-handles-auth-with-openrouter-key-xxxxxxxxxxxxxxxxxxxxx";
72093
72130
  env.ANTHROPIC_AUTH_TOKEN = "placeholder-token-not-used-proxy-handles-auth";
72094
- if (!process.env[ENV.CLAUDE_CODE_AUTO_COMPACT_WINDOW]) {
72095
- const autoCompactWindow = await computeMainThreadContextWindow(config3);
72096
- if (autoCompactWindow >= MIN_AUTO_COMPACT_WINDOW) {
72097
- env[ENV.CLAUDE_CODE_AUTO_COMPACT_WINDOW] = String(autoCompactWindow);
72098
- if (!config3.quiet) {
72099
- console.error(`[claudish] Auto-compact window: ${autoCompactWindow.toLocaleString()} tokens ` + "(Claude Code compacts before the backend's real limit)");
72100
- }
72101
- } else if (autoCompactWindow > 0 && !config3.quiet) {
72102
- console.error(`[claudish] Model's real context window (${autoCompactWindow.toLocaleString()}) is below ` + `Claude Code's ${MIN_AUTO_COMPACT_WINDOW.toLocaleString()}-token auto-compact floor \u2014 ` + "leaving CLAUDE_CODE_AUTO_COMPACT_WINDOW unset so native auto-compaction stays on.");
72103
- }
72131
+ const realWindow = await computeMainThreadContextWindow(config3);
72132
+ const contextEnv = resolveContextWindowEnv(realWindow, process.env);
72133
+ Object.assign(env, contextEnv.vars);
72134
+ if (contextEnv.notice && !config3.quiet) {
72135
+ console.error(contextEnv.notice);
72104
72136
  }
72105
72137
  }
72106
72138
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "7.23.0",
3
+ "version": "7.24.0",
4
4
  "description": "Run Claude Code with any model - OpenRouter, Ollama, LM Studio & local models",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -60,10 +60,10 @@
60
60
  "ai"
61
61
  ],
62
62
  "optionalDependencies": {
63
- "@claudish/magmux-darwin-arm64": "7.23.0",
64
- "@claudish/magmux-darwin-x64": "7.23.0",
65
- "@claudish/magmux-linux-arm64": "7.23.0",
66
- "@claudish/magmux-linux-x64": "7.23.0"
63
+ "@claudish/magmux-darwin-arm64": "7.24.0",
64
+ "@claudish/magmux-darwin-x64": "7.24.0",
65
+ "@claudish/magmux-linux-arm64": "7.24.0",
66
+ "@claudish/magmux-linux-x64": "7.24.0"
67
67
  },
68
68
  "author": "Jack Rudenko <i@madappgang.com>",
69
69
  "license": "MIT",