claudish 7.23.0 → 7.25.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 +1300 -714
  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.25.0";
655
655
 
656
656
  // src/logger.ts
657
657
  var exports_logger = {};
@@ -27006,6 +27006,9 @@ function loadConfig() {
27006
27006
  if (config2.customEndpoints !== undefined) {
27007
27007
  merged.customEndpoints = config2.customEndpoints;
27008
27008
  }
27009
+ if (config2.behavior !== undefined) {
27010
+ merged.behavior = config2.behavior;
27011
+ }
27009
27012
  return merged;
27010
27013
  } catch (error46) {
27011
27014
  console.error(`Warning: Failed to load config, using defaults: ${error46}`);
@@ -30418,11 +30421,550 @@ var init_routing_hints = __esm(() => {
30418
30421
  };
30419
30422
  });
30420
30423
 
30424
+ // src/providers/all-models-cache.ts
30425
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
30426
+ import { homedir as homedir13 } from "os";
30427
+ import { dirname as dirname4, join as join13 } from "path";
30428
+ function readAllModelsCache(path = ALL_MODELS_CACHE_PATH) {
30429
+ if (!existsSync12(path))
30430
+ return null;
30431
+ let raw;
30432
+ try {
30433
+ raw = JSON.parse(readFileSync9(path, "utf-8"));
30434
+ } catch {
30435
+ return null;
30436
+ }
30437
+ if (!raw || typeof raw !== "object")
30438
+ return null;
30439
+ const data = raw;
30440
+ const lastUpdated = typeof data.lastUpdated === "string" ? data.lastUpdated : new Date(0).toISOString();
30441
+ const models = Array.isArray(data.models) ? data.models : [];
30442
+ const entries = Array.isArray(data.entries) ? data.entries : [];
30443
+ return {
30444
+ version: 2,
30445
+ lastUpdated,
30446
+ entries,
30447
+ models
30448
+ };
30449
+ }
30450
+ function writeAllModelsCache(data, path = ALL_MODELS_CACHE_PATH) {
30451
+ const existing = readAllModelsCache(path);
30452
+ const merged = {
30453
+ version: 2,
30454
+ lastUpdated: data.lastUpdated ?? new Date().toISOString(),
30455
+ entries: data.entries ?? existing?.entries ?? [],
30456
+ models: data.models ?? existing?.models ?? []
30457
+ };
30458
+ mkdirSync5(dirname4(path), { recursive: true });
30459
+ writeFileSync6(path, JSON.stringify(merged), "utf-8");
30460
+ }
30461
+ var ALL_MODELS_CACHE_PATH;
30462
+ var init_all_models_cache = __esm(() => {
30463
+ ALL_MODELS_CACHE_PATH = join13(homedir13(), ".claudish", "all-models.json");
30464
+ });
30465
+
30466
+ // src/adapters/model-catalog.ts
30467
+ function lookupModel(modelId, cachePath) {
30468
+ const entry = findCacheEntry(modelId, cachePath);
30469
+ if (!entry || entry.contextWindow === undefined)
30470
+ return;
30471
+ return {
30472
+ modelId: entry.modelId,
30473
+ contextWindow: entry.contextWindow,
30474
+ supportsVision: entry.supportsVision
30475
+ };
30476
+ }
30477
+ function lookupModelForProvider(modelId, provider, cachePath) {
30478
+ const entry = findCacheEntry(modelId, cachePath);
30479
+ if (!entry)
30480
+ return;
30481
+ return entry.aggregators?.find((a) => a.provider === provider)?.contextWindow ?? entry.contextWindow;
30482
+ }
30483
+ function resolveSubscriptionRouting(modelId, provider, cachePath) {
30484
+ const entry = findCacheEntry(modelId, cachePath);
30485
+ if (!entry)
30486
+ return { kind: "unknown" };
30487
+ if (entry.subscriptionPlans?.includes(provider)) {
30488
+ const agg = entry.aggregators?.find((a) => a.provider === provider);
30489
+ return agg?.externalId ? { kind: "serves", externalId: agg.externalId } : { kind: "unknown" };
30490
+ }
30491
+ return isSubscriptionPlan(provider, cachePath) ? { kind: "not-served" } : { kind: "unknown" };
30492
+ }
30493
+ function isSubscriptionPlan(provider, cachePath) {
30494
+ const cache = readAllModelsCache(cachePath);
30495
+ if (!cache)
30496
+ return false;
30497
+ return cache.entries.some((e) => e.subscriptionPlans?.includes(provider));
30498
+ }
30499
+ function findCacheEntry(modelId, cachePath) {
30500
+ if (modelId.includes("@")) {
30501
+ throw new Error(`model-catalog lookup received provider-routed ID "${modelId}" \u2014 callers must strip the "@" prefix before calling`);
30502
+ }
30503
+ const cache = readAllModelsCache(cachePath);
30504
+ if (!cache || cache.entries.length === 0)
30505
+ return;
30506
+ const lower = modelId.toLowerCase();
30507
+ const unprefixed = lower.includes("/") ? lower.substring(lower.lastIndexOf("/") + 1) : lower;
30508
+ for (const entry of cache.entries) {
30509
+ const entryId = entry.modelId.toLowerCase();
30510
+ const exactMatch = entryId === unprefixed || entryId === lower;
30511
+ const aliasMatch = entry.aliases?.some((a) => a.toLowerCase() === unprefixed || a.toLowerCase() === lower);
30512
+ if (exactMatch || aliasMatch) {
30513
+ return entry;
30514
+ }
30515
+ }
30516
+ return;
30517
+ }
30518
+ var init_model_catalog = __esm(() => {
30519
+ init_all_models_cache();
30520
+ });
30521
+
30522
+ // src/providers/auto-route.ts
30523
+ var PROVIDER_TO_PREFIX, DISPLAY_NAMES;
30524
+ var init_auto_route = __esm(() => {
30525
+ init_provider_definitions();
30526
+ PROVIDER_TO_PREFIX = (() => {
30527
+ const map3 = {};
30528
+ for (const def of getAllProviders()) {
30529
+ if (def.shortestPrefix) {
30530
+ map3[def.name] = def.shortestPrefix;
30531
+ }
30532
+ }
30533
+ return map3;
30534
+ })();
30535
+ DISPLAY_NAMES = (() => {
30536
+ const map3 = {};
30537
+ for (const def of getAllProviders()) {
30538
+ map3[def.name] = def.displayName;
30539
+ }
30540
+ return map3;
30541
+ })();
30542
+ });
30543
+
30544
+ // src/providers/default-routing-rules.ts
30545
+ function validateRoutingRulesAgainstProviders(rules) {
30546
+ const unknown3 = [];
30547
+ for (const ruleKey of Object.keys(rules)) {
30548
+ const entries = rules[ruleKey] ?? [];
30549
+ for (const entry of entries) {
30550
+ const atIdx = entry.indexOf("@");
30551
+ const providerRaw = atIdx === -1 ? entry : entry.slice(0, atIdx);
30552
+ const canonical = PROVIDER_SHORTCUTS[providerRaw.toLowerCase()] ?? providerRaw.toLowerCase();
30553
+ if (!getProviderByName(canonical)) {
30554
+ unknown3.push({ rule: ruleKey, entry, provider: canonical });
30555
+ }
30556
+ }
30557
+ }
30558
+ if (unknown3.length > 0) {
30559
+ const lines = unknown3.map((u) => ` rule "${u.rule}" \u2192 entry "${u.entry}" \u2192 unknown provider "${u.provider}"`);
30560
+ throw new Error(`[claudish] DEFAULT_ROUTING_RULES references unknown providers:
30561
+ ${lines.join(`
30562
+ `)}`);
30563
+ }
30564
+ }
30565
+ function validateDefaultRoutingRules() {
30566
+ validateRoutingRulesAgainstProviders(DEFAULT_ROUTING_RULES);
30567
+ }
30568
+ var DEFAULT_ROUTING_RULES;
30569
+ var init_default_routing_rules = __esm(() => {
30570
+ init_model_parser();
30571
+ init_provider_definitions();
30572
+ DEFAULT_ROUTING_RULES = {
30573
+ "claude-*": ["native-anthropic", "openrouter"],
30574
+ "gpt-*": ["openai-codex", "openai", "openrouter"],
30575
+ "o1-*": ["openai-codex", "openai", "openrouter"],
30576
+ "o3-*": ["openai-codex", "openai", "openrouter"],
30577
+ "gemini-*": ["gemini-codeassist", "google", "openrouter"],
30578
+ "grok-*": ["x-ai", "openrouter"],
30579
+ "kimi-*": ["kimi-coding", "kimi", "openrouter"],
30580
+ "k3*": ["kimi-coding", "kimi", "openrouter"],
30581
+ "minimax-*": ["minimax-coding", "minimax", "openrouter"],
30582
+ "glm-*": ["glm-coding", "glm", "openrouter"],
30583
+ "z-ai-*": ["z-ai", "openrouter"],
30584
+ "deepseek-*": ["deepseek", "openrouter"],
30585
+ fugu: ["sakana-subscription", "sakana"],
30586
+ "fugu-*": ["sakana-subscription", "sakana"],
30587
+ "*-zen": ["opencode-zen"],
30588
+ "*": ["openrouter"]
30589
+ };
30590
+ validateDefaultRoutingRules();
30591
+ });
30592
+
30593
+ // src/providers/catalog-resolvers/openrouter.ts
30594
+ class OpenRouterCatalogResolver {
30595
+ provider = "openrouter";
30596
+ resolveSync(userInput) {
30597
+ const entries = this._getEntries();
30598
+ if (userInput.includes("/")) {
30599
+ if (entries) {
30600
+ for (const entry of entries) {
30601
+ for (const src of Object.values(entry.sources)) {
30602
+ if (src.externalId === userInput)
30603
+ return userInput;
30604
+ }
30605
+ }
30606
+ }
30607
+ return userInput;
30608
+ }
30609
+ if (entries) {
30610
+ const byModelId = entries.find((e) => e.modelId === userInput);
30611
+ if (byModelId) {
30612
+ const orId = this._getOpenRouterExternalId(byModelId);
30613
+ if (orId)
30614
+ return orId;
30615
+ }
30616
+ const byAlias = entries.find((e) => e.aliases.includes(userInput));
30617
+ if (byAlias) {
30618
+ const orId = this._getOpenRouterExternalId(byAlias);
30619
+ if (orId)
30620
+ return orId;
30621
+ }
30622
+ for (const entry of entries) {
30623
+ for (const src of Object.values(entry.sources)) {
30624
+ if (src.externalId === userInput) {
30625
+ const orId = this._getOpenRouterExternalId(entry);
30626
+ if (orId)
30627
+ return orId;
30628
+ }
30629
+ }
30630
+ }
30631
+ const suffix = `/${userInput}`;
30632
+ for (const entry of entries) {
30633
+ const orId = this._getOpenRouterExternalId(entry);
30634
+ if (orId?.endsWith(suffix))
30635
+ return orId;
30636
+ }
30637
+ const lowerSuffix = `/${userInput.toLowerCase()}`;
30638
+ for (const entry of entries) {
30639
+ const orId = this._getOpenRouterExternalId(entry);
30640
+ if (orId?.toLowerCase().endsWith(lowerSuffix))
30641
+ return orId;
30642
+ }
30643
+ }
30644
+ return null;
30645
+ }
30646
+ async warmCache() {
30647
+ if (!_warmPromise) {
30648
+ _warmPromise = this._fetchAndCache();
30649
+ }
30650
+ await _warmPromise;
30651
+ }
30652
+ isCacheWarm() {
30653
+ return _memCache !== null && _memCache.length > 0;
30654
+ }
30655
+ async ensureReady(timeoutMs) {
30656
+ if (this.isCacheWarm())
30657
+ return;
30658
+ if (!_warmPromise) {
30659
+ _warmPromise = this._fetchAndCache();
30660
+ }
30661
+ await Promise.race([
30662
+ _warmPromise,
30663
+ new Promise((resolve) => setTimeout(resolve, timeoutMs))
30664
+ ]);
30665
+ }
30666
+ async refreshCatalog(timeoutMs) {
30667
+ let response;
30668
+ try {
30669
+ response = await fetch(FIREBASE_CATALOG_URL, {
30670
+ signal: AbortSignal.timeout(timeoutMs)
30671
+ });
30672
+ } catch (err) {
30673
+ const name = err?.name;
30674
+ const reason = name === "TimeoutError" || name === "AbortError" ? "timeout" : "network";
30675
+ return { kind: "fetch_failed", reason };
30676
+ }
30677
+ if (!response.ok) {
30678
+ return { kind: "fetch_failed", reason: "http_error" };
30679
+ }
30680
+ let data;
30681
+ try {
30682
+ data = await response.json();
30683
+ } catch {
30684
+ return { kind: "fetch_failed", reason: "network" };
30685
+ }
30686
+ if (!Array.isArray(data.models) || data.models.length === 0) {
30687
+ return { kind: "fetch_failed", reason: "empty" };
30688
+ }
30689
+ const backwardCompatModels = [];
30690
+ for (const entry of data.models) {
30691
+ const orSource = entry.sources["openrouter-api"];
30692
+ if (orSource?.externalId) {
30693
+ backwardCompatModels.push({ id: orSource.externalId });
30694
+ }
30695
+ }
30696
+ _memCache = data.models;
30697
+ writeAllModelsCache({
30698
+ entries: data.models,
30699
+ models: backwardCompatModels
30700
+ });
30701
+ _warmPromise = Promise.resolve();
30702
+ return { kind: "refreshed", modelCount: data.models.length };
30703
+ }
30704
+ _getOpenRouterExternalId(entry) {
30705
+ const orSource = entry.sources["openrouter-api"];
30706
+ if (orSource?.externalId)
30707
+ return orSource.externalId;
30708
+ for (const src of Object.values(entry.sources)) {
30709
+ if (src.externalId.includes("/"))
30710
+ return src.externalId;
30711
+ }
30712
+ return null;
30713
+ }
30714
+ _getEntries() {
30715
+ if (_memCache)
30716
+ return _memCache;
30717
+ const cache = readAllModelsCache();
30718
+ if (!cache)
30719
+ return null;
30720
+ if (cache.entries.length > 0) {
30721
+ _memCache = cache.entries;
30722
+ return _memCache;
30723
+ }
30724
+ if (cache.models.length > 0) {
30725
+ _memCache = cache.models.map((m) => ({
30726
+ modelId: m.id.includes("/") ? m.id.split("/").slice(1).join("/") : m.id,
30727
+ aliases: [],
30728
+ sources: { "openrouter-api": { externalId: m.id } }
30729
+ }));
30730
+ return _memCache;
30731
+ }
30732
+ return null;
30733
+ }
30734
+ async _fetchAndCache() {
30735
+ await this.refreshCatalog(8000);
30736
+ }
30737
+ }
30738
+ var FIREBASE_CATALOG_URL, _memCache = null, _warmPromise = null;
30739
+ var init_openrouter = __esm(() => {
30740
+ init_all_models_cache();
30741
+ 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";
30742
+ });
30743
+
30744
+ // src/providers/model-catalog-resolver.ts
30745
+ function registerResolver(resolver) {
30746
+ RESOLVER_REGISTRY.set(resolver.provider, resolver);
30747
+ }
30748
+ function getResolver(provider) {
30749
+ return RESOLVER_REGISTRY.get(provider) ?? null;
30750
+ }
30751
+ function resolveModelNameSync(userInput, targetProvider) {
30752
+ if (targetProvider !== "openrouter" && userInput.includes("/")) {
30753
+ return { resolvedId: userInput, wasResolved: false, sourceLabel: "passthrough" };
30754
+ }
30755
+ const resolver = getResolver(targetProvider);
30756
+ if (!resolver) {
30757
+ return { resolvedId: userInput, wasResolved: false, sourceLabel: "passthrough" };
30758
+ }
30759
+ const resolved = resolver.resolveSync(userInput);
30760
+ if (!resolved || resolved === userInput) {
30761
+ return { resolvedId: userInput, wasResolved: false, sourceLabel: "passthrough" };
30762
+ }
30763
+ return {
30764
+ resolvedId: resolved,
30765
+ wasResolved: true,
30766
+ sourceLabel: `${targetProvider} catalog`
30767
+ };
30768
+ }
30769
+ function logResolution(userInput, result, quiet = false) {
30770
+ if (result.wasResolved && !quiet) {
30771
+ process.stderr.write(`[Model] Resolved "${userInput}" \u2192 "${result.resolvedId}" (${result.sourceLabel})
30772
+ `);
30773
+ }
30774
+ }
30775
+ async function ensureCatalogReady(provider, timeoutMs = 5000) {
30776
+ const resolver = getResolver(provider);
30777
+ if (!resolver || resolver.isCacheWarm())
30778
+ return;
30779
+ await resolver.ensureReady(timeoutMs);
30780
+ }
30781
+ async function warmAllCatalogs(providers) {
30782
+ const targets = providers ? [...RESOLVER_REGISTRY.entries()].filter(([k]) => providers.includes(k)) : [...RESOLVER_REGISTRY.entries()];
30783
+ await Promise.allSettled(targets.map(([, r]) => r.warmCache()));
30784
+ }
30785
+ var RESOLVER_REGISTRY;
30786
+ var init_model_catalog_resolver = __esm(() => {
30787
+ init_openrouter();
30788
+ RESOLVER_REGISTRY = new Map;
30789
+ [
30790
+ new OpenRouterCatalogResolver
30791
+ ].forEach(registerResolver);
30792
+ });
30793
+
30794
+ // src/providers/routing-rules.ts
30795
+ function mergeRoutingRules(defaults, global_, local) {
30796
+ return { ...defaults, ...global_, ...local };
30797
+ }
30798
+ function loadRoutingRules() {
30799
+ const local = loadLocalConfig()?.routing ?? {};
30800
+ const global_ = loadConfig().routing ?? {};
30801
+ validateRoutingRules(local);
30802
+ validateRoutingRules(global_);
30803
+ return mergeRoutingRules(DEFAULT_ROUTING_RULES, global_, local);
30804
+ }
30805
+ function validateRoutingRules(rules) {
30806
+ const seenLower = new Map;
30807
+ for (const key of Object.keys(rules)) {
30808
+ if (key !== "*" && (key.match(/\*/g) || []).length > 1) {
30809
+ console.error(`[claudish] Warning: routing pattern "${key}" has multiple wildcards \u2014 only single * is supported. This pattern may not match as expected.`);
30810
+ }
30811
+ const lower = key.toLowerCase();
30812
+ const prior = seenLower.get(lower);
30813
+ if (prior !== undefined && prior !== key) {
30814
+ 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.`);
30815
+ } else {
30816
+ seenLower.set(lower, key);
30817
+ }
30818
+ }
30819
+ }
30820
+ function matchRoutingRule(modelName, rules) {
30821
+ const lowered = modelName.toLowerCase();
30822
+ for (const [key, entries] of Object.entries(rules)) {
30823
+ if (!key.includes("*") && key.toLowerCase() === lowered)
30824
+ return entries;
30825
+ }
30826
+ const globKeys = Object.keys(rules).filter((k) => k !== "*" && k.includes("*")).sort((a, b) => b.length - a.length);
30827
+ for (const pattern of globKeys) {
30828
+ if (globMatch(pattern, modelName))
30829
+ return rules[pattern];
30830
+ }
30831
+ if (rules["*"] !== undefined)
30832
+ return rules["*"];
30833
+ return null;
30834
+ }
30835
+ function buildRoutingChain(entries, originalModelName, cachePath) {
30836
+ const routes = [];
30837
+ for (const entry of entries) {
30838
+ const atIdx = entry.indexOf("@");
30839
+ let providerRaw;
30840
+ let modelName;
30841
+ if (atIdx !== -1) {
30842
+ providerRaw = entry.slice(0, atIdx);
30843
+ modelName = entry.slice(atIdx + 1);
30844
+ } else {
30845
+ providerRaw = entry;
30846
+ modelName = originalModelName;
30847
+ }
30848
+ const provider = PROVIDER_SHORTCUTS[providerRaw.toLowerCase()] ?? providerRaw.toLowerCase();
30849
+ if (atIdx === -1) {
30850
+ const routing = resolveSubscriptionRouting(modelName, provider, cachePath);
30851
+ if (routing.kind === "not-served")
30852
+ continue;
30853
+ if (routing.kind === "serves")
30854
+ modelName = routing.externalId;
30855
+ }
30856
+ let modelSpec;
30857
+ if (provider === "openrouter") {
30858
+ const resolution = resolveModelNameSync(modelName, "openrouter");
30859
+ modelSpec = resolution.resolvedId;
30860
+ } else {
30861
+ const prefix = PROVIDER_TO_PREFIX[provider] ?? provider;
30862
+ modelSpec = `${prefix}@${modelName}`;
30863
+ }
30864
+ const displayName = DISPLAY_NAMES[provider] ?? provider;
30865
+ routes.push({ provider, modelSpec, displayName });
30866
+ }
30867
+ return routes;
30868
+ }
30869
+ function globMatch(pattern, value) {
30870
+ const star = pattern.indexOf("*");
30871
+ const p = pattern.toLowerCase();
30872
+ const v = value.toLowerCase();
30873
+ if (star === -1)
30874
+ return p === v;
30875
+ const prefix = p.slice(0, star);
30876
+ const suffix = p.slice(star + 1);
30877
+ return v.startsWith(prefix) && v.endsWith(suffix) && v.length >= prefix.length + suffix.length;
30878
+ }
30879
+ async function hasCredentialsForProvider(provider) {
30880
+ return credentials.isAvailable(provider);
30881
+ }
30882
+ async function routeExplicit(modelSpec, model, provider, cachePath) {
30883
+ if (!await hasCredentialsForProvider(provider)) {
30884
+ return {
30885
+ kind: "no-route",
30886
+ reason: `No credentials configured for "${provider}".`,
30887
+ hint: buildCredentialHint(model, [provider]) ?? undefined
30888
+ };
30889
+ }
30890
+ const built = buildRoutingChain([modelSpec], model, cachePath)[0];
30891
+ if (!built) {
30892
+ return {
30893
+ kind: "no-route",
30894
+ reason: `Could not build a route for "${modelSpec}".`
30895
+ };
30896
+ }
30897
+ return { kind: "ok", primary: built, fallbacks: [] };
30898
+ }
30899
+ async function routeBare(model, nativeProvider, rules, defaultProvider, cachePath) {
30900
+ const matched = matchRoutingRule(model, rules) ?? [];
30901
+ const entries = [...matched];
30902
+ if (defaultProvider && defaultProvider.length > 0) {
30903
+ const canonicalDefault = PROVIDER_SHORTCUTS[defaultProvider.toLowerCase()] ?? defaultProvider.toLowerCase();
30904
+ const alreadyPresent = entries.some((e) => {
30905
+ const atIdx = e.indexOf("@");
30906
+ const providerRaw = atIdx === -1 ? e : e.slice(0, atIdx);
30907
+ const canonical = PROVIDER_SHORTCUTS[providerRaw.toLowerCase()] ?? providerRaw.toLowerCase();
30908
+ return canonical === canonicalDefault;
30909
+ });
30910
+ if (!alreadyPresent)
30911
+ entries.push(defaultProvider);
30912
+ }
30913
+ if (entries.length === 0) {
30914
+ return {
30915
+ kind: "no-route",
30916
+ reason: `No routing rule matched "${model}".`,
30917
+ hint: buildCredentialHint(model, [nativeProvider]) ?? undefined
30918
+ };
30919
+ }
30920
+ const candidates = buildRoutingChain(entries, model, cachePath);
30921
+ const credentialed = [];
30922
+ const skipped = [];
30923
+ const checks4 = await Promise.all(candidates.map((candidate) => hasCredentialsForProvider(candidate.provider)));
30924
+ candidates.forEach((candidate, i) => {
30925
+ if (checks4[i]) {
30926
+ credentialed.push(candidate);
30927
+ } else {
30928
+ skipped.push(candidate.provider);
30929
+ }
30930
+ });
30931
+ if (credentialed.length === 0) {
30932
+ return {
30933
+ kind: "no-route",
30934
+ reason: skipped.length > 0 ? `No credentialed providers in chain for "${model}" (tried: ${skipped.join(", ")}).` : `No providers available for "${model}".`,
30935
+ hint: buildCredentialHint(model, skipped) ?? undefined
30936
+ };
30937
+ }
30938
+ const [primary, ...fallbacks] = credentialed;
30939
+ return { kind: "ok", primary, fallbacks };
30940
+ }
30941
+ async function route(modelSpec, rulesOverride, defaultProviderOverride, cachePath) {
30942
+ const parsed = parseModelSpec(modelSpec);
30943
+ if (parsed.isExplicitProvider) {
30944
+ return routeExplicit(modelSpec, parsed.model, parsed.provider, cachePath);
30945
+ }
30946
+ const rules = rulesOverride ?? loadRoutingRules();
30947
+ const defaultProvider = defaultProviderOverride !== undefined ? defaultProviderOverride : rulesOverride !== undefined ? undefined : loadConfig().defaultProvider;
30948
+ return routeBare(parsed.model, parsed.provider, rules, defaultProvider, cachePath);
30949
+ }
30950
+ var init_routing_rules = __esm(() => {
30951
+ init_model_catalog();
30952
+ init_authority();
30953
+ init_profile_config();
30954
+ init_auto_route();
30955
+ init_default_routing_rules();
30956
+ init_model_catalog_resolver();
30957
+ init_model_parser();
30958
+ init_model_parser();
30959
+ init_routing_hints();
30960
+ });
30961
+
30421
30962
  // src/providers/provider-resolver.ts
30422
30963
  var exports_provider_resolver = {};
30423
30964
  __export(exports_provider_resolver, {
30424
30965
  validateApiKeysForModels: () => validateApiKeysForModels,
30425
30966
  resolveModelProvider: () => resolveModelProvider,
30967
+ rescueRoutableResolutions: () => rescueRoutableResolutions,
30426
30968
  requiresOpenRouterKey: () => requiresOpenRouterKey,
30427
30969
  isLocalModel: () => isLocalModel,
30428
30970
  getMissingKeysError: () => getMissingKeysError,
@@ -30594,8 +31136,20 @@ async function validateApiKeysForModels(models) {
30594
31136
  return;
30595
31137
  r.apiKeyAvailable = await credentials.isAvailable(r.catalogName);
30596
31138
  }));
31139
+ await rescueRoutableResolutions(resolutions);
30597
31140
  return resolutions;
30598
31141
  }
31142
+ async function rescueRoutableResolutions(resolutions, router = route) {
31143
+ await Promise.all(resolutions.map(async (r) => {
31144
+ if (!r.requiredApiKeyEnvVar || r.apiKeyAvailable)
31145
+ return;
31146
+ try {
31147
+ const plan = await router(r.fullModelId);
31148
+ if (plan.kind === "ok")
31149
+ r.apiKeyAvailable = true;
31150
+ } catch {}
31151
+ }));
31152
+ }
30599
31153
  function getMissingKeyResolutions(resolutions) {
30600
31154
  return resolutions.filter((r) => r.requiredApiKeyEnvVar && !r.apiKeyAvailable);
30601
31155
  }
@@ -30709,6 +31263,7 @@ var init_provider_resolver = __esm(() => {
30709
31263
  init_remote_provider_registry();
30710
31264
  init_onepassword();
30711
31265
  init_routing_hints();
31266
+ init_routing_rules();
30712
31267
  API_KEY_INFO = new Proxy({}, {
30713
31268
  get(_target, prop) {
30714
31269
  return getApiKeyInfoForProvider(prop);
@@ -30990,9 +31545,9 @@ var init_signal_watcher = __esm(() => {
30990
31545
  // src/channel/session-manager.ts
30991
31546
  import { spawn } from "child_process";
30992
31547
  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";
31548
+ import { createWriteStream, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
31549
+ import { homedir as homedir14 } from "os";
31550
+ import { join as join14 } from "path";
30996
31551
 
30997
31552
  class SessionManager {
30998
31553
  sessions = new Map;
@@ -31012,10 +31567,10 @@ class SessionManager {
31012
31567
  const sessionId = randomUUID2().slice(0, 8);
31013
31568
  const timeout = Math.min(opts.timeoutSeconds ?? DEFAULT_TIMEOUT, MAX_TIMEOUT);
31014
31569
  const startedAt = new Date().toISOString();
31015
- const sessionDir = join13(homedir13(), ".claudish", "sessions", sessionId);
31016
- mkdirSync5(sessionDir, { recursive: true });
31570
+ const sessionDir = join14(homedir14(), ".claudish", "sessions", sessionId);
31571
+ mkdirSync6(sessionDir, { recursive: true });
31017
31572
  if (opts.prompt) {
31018
- writeFileSync6(join13(sessionDir, "prompt.md"), opts.prompt, "utf-8");
31573
+ writeFileSync7(join14(sessionDir, "prompt.md"), opts.prompt, "utf-8");
31019
31574
  }
31020
31575
  const args = ["--model", opts.model, "-y", "--stdin", "--quiet", ...opts.claudishFlags ?? []];
31021
31576
  const proc = spawn("claudish", args, {
@@ -31042,7 +31597,7 @@ class SessionManager {
31042
31597
  });
31043
31598
  }
31044
31599
  });
31045
- const outputLogStream = createWriteStream(join13(sessionDir, "output.log"));
31600
+ const outputLogStream = createWriteStream(join14(sessionDir, "output.log"));
31046
31601
  const entry = {
31047
31602
  info: {
31048
31603
  sessionId,
@@ -31089,9 +31644,9 @@ class SessionManager {
31089
31644
  watcher.processExited(code);
31090
31645
  outputLogStream.end();
31091
31646
  if (entry.stderr) {
31092
- writeFileSync6(join13(sessionDir, "stderr.log"), entry.stderr, "utf-8");
31647
+ writeFileSync7(join14(sessionDir, "stderr.log"), entry.stderr, "utf-8");
31093
31648
  }
31094
- writeFileSync6(join13(sessionDir, "meta.json"), JSON.stringify(entry.info, null, 2), "utf-8");
31649
+ writeFileSync7(join14(sessionDir, "meta.json"), JSON.stringify(entry.info, null, 2), "utf-8");
31095
31650
  this.cleanupSigint();
31096
31651
  });
31097
31652
  proc.on("error", (err) => {
@@ -31264,9 +31819,9 @@ var init_cache_ttl = __esm(() => {
31264
31819
  });
31265
31820
 
31266
31821
  // 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";
31822
+ import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
31823
+ import { homedir as homedir15 } from "os";
31824
+ import { join as join15 } from "path";
31270
31825
  function groupRecommendedModels(entries) {
31271
31826
  const byId = new Map;
31272
31827
  for (const entry of entries) {
@@ -31362,9 +31917,9 @@ async function getRecommendedModels(opts = {}) {
31362
31917
  if (!forceRefresh && _cachedRecommendedModels) {
31363
31918
  return _cachedRecommendedModels;
31364
31919
  }
31365
- if (!forceRefresh && existsSync12(RECOMMENDED_MODELS_CACHE_PATH)) {
31920
+ if (!forceRefresh && existsSync13(RECOMMENDED_MODELS_CACHE_PATH)) {
31366
31921
  try {
31367
- const cacheData = JSON.parse(readFileSync9(RECOMMENDED_MODELS_CACHE_PATH, "utf-8"));
31922
+ const cacheData = JSON.parse(readFileSync10(RECOMMENDED_MODELS_CACHE_PATH, "utf-8"));
31368
31923
  if (cacheData.models && cacheData.models.length > 0 && isFreshEnough(cacheData)) {
31369
31924
  _cachedRecommendedModels = cacheData;
31370
31925
  return cacheData;
@@ -31380,9 +31935,9 @@ async function getRecommendedModels(opts = {}) {
31380
31935
  if (data.models && data.models.length > 0) {
31381
31936
  _cachedRecommendedModels = data;
31382
31937
  try {
31383
- const cacheDir = join14(homedir14(), ".claudish");
31384
- mkdirSync6(cacheDir, { recursive: true });
31385
- writeFileSync7(RECOMMENDED_MODELS_CACHE_PATH, JSON.stringify(data), "utf-8");
31938
+ const cacheDir = join15(homedir15(), ".claudish");
31939
+ mkdirSync7(cacheDir, { recursive: true });
31940
+ writeFileSync8(RECOMMENDED_MODELS_CACHE_PATH, JSON.stringify(data), "utf-8");
31386
31941
  } catch {}
31387
31942
  return data;
31388
31943
  }
@@ -31393,9 +31948,9 @@ async function getRecommendedModels(opts = {}) {
31393
31948
  function getRecommendedModelsSync() {
31394
31949
  if (_cachedRecommendedModels)
31395
31950
  return _cachedRecommendedModels;
31396
- if (existsSync12(RECOMMENDED_MODELS_CACHE_PATH)) {
31951
+ if (existsSync13(RECOMMENDED_MODELS_CACHE_PATH)) {
31397
31952
  try {
31398
- const cacheData = JSON.parse(readFileSync9(RECOMMENDED_MODELS_CACHE_PATH, "utf-8"));
31953
+ const cacheData = JSON.parse(readFileSync10(RECOMMENDED_MODELS_CACHE_PATH, "utf-8"));
31399
31954
  if (cacheData.models && cacheData.models.length > 0 && isFreshEnough(cacheData)) {
31400
31955
  _cachedRecommendedModels = cacheData;
31401
31956
  return cacheData;
@@ -31519,7 +32074,7 @@ var _cachedModelInfo = null, _cachedModelIds = null, _cachedRecommendedModels =
31519
32074
  var init_model_loader = __esm(() => {
31520
32075
  init_cache_ttl();
31521
32076
  FIREBASE_RECOMMENDED_URL = `${FIREBASE_BASE_URL}?catalog=recommended`;
31522
- RECOMMENDED_MODELS_CACHE_PATH = join14(homedir14(), ".claudish", "recommended-models-cache.json");
32077
+ RECOMMENDED_MODELS_CACHE_PATH = join15(homedir15(), ".claudish", "recommended-models-cache.json");
31523
32078
  FIREBASE_SLUG_TO_PROVIDER_NAME = {
31524
32079
  openai: "openai",
31525
32080
  google: "google",
@@ -31986,10 +32541,10 @@ var init_request = __esm(() => {
31986
32541
  return this.#matchResult;
31987
32542
  }
31988
32543
  get matchedRoutes() {
31989
- return this.#matchResult[0].map(([[, route]]) => route);
32544
+ return this.#matchResult[0].map(([[, route2]]) => route2);
31990
32545
  }
31991
32546
  get routePath() {
31992
- return this.#matchResult[0].map(([[, route]]) => route)[this.routeIndex].path;
32547
+ return this.#matchResult[0].map(([[, route2]]) => route2)[this.routeIndex].path;
31993
32548
  }
31994
32549
  };
31995
32550
  });
@@ -32637,7 +33192,7 @@ function buildMatcherFromPreprocessedRoutes(routes) {
32637
33192
  if (routes.length === 0) {
32638
33193
  return nullMatcher;
32639
33194
  }
32640
- const routesWithStaticPathFlag = routes.map((route) => [!/\*|\/:/.test(route[0]), ...route]).sort(([isStaticA, pathA], [isStaticB, pathB]) => isStaticA ? 1 : isStaticB ? -1 : pathA.length - pathB.length);
33195
+ const routesWithStaticPathFlag = routes.map((route2) => [!/\*|\/:/.test(route2[0]), ...route2]).sort(([isStaticA, pathA], [isStaticB, pathB]) => isStaticA ? 1 : isStaticB ? -1 : pathA.length - pathB.length);
32641
33196
  const staticMap = /* @__PURE__ */ Object.create(null);
32642
33197
  for (let i = 0, j = -1, len = routesWithStaticPathFlag.length;i < len; i++) {
32643
33198
  const [pathErrorCheckOnly, path, handlers] = routesWithStaticPathFlag[i];
@@ -33283,104 +33838,6 @@ var init_remote_provider_types = __esm(() => {
33283
33838
  };
33284
33839
  });
33285
33840
 
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
33841
  // src/adapters/tool-name-utils.ts
33385
33842
  function hashToolName(name) {
33386
33843
  let h1 = 3735928559;
@@ -36336,6 +36793,555 @@ ${text}`;
36336
36793
  };
36337
36794
  });
36338
36795
 
36796
+ // ../../node_modules/.bun/zod@4.1.13/node_modules/zod/index.js
36797
+ var init_zod = __esm(() => {
36798
+ init_external2();
36799
+ init_external2();
36800
+ });
36801
+
36802
+ // src/behavior/config.ts
36803
+ function parseBehaviorConfig(raw2) {
36804
+ if (raw2 === undefined || raw2 === null)
36805
+ return {};
36806
+ const result = BehaviorConfigSchema.safeParse(raw2);
36807
+ if (!result.success) {
36808
+ logStderr(`[behavior] Ignoring invalid "behavior" config: ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`);
36809
+ return {};
36810
+ }
36811
+ return result.data;
36812
+ }
36813
+ function resolveSeverity(ruleId, defaultSeverity, config2) {
36814
+ const rules = config2.rules;
36815
+ if (!rules)
36816
+ return defaultSeverity;
36817
+ const exact = rules[ruleId];
36818
+ if (exact)
36819
+ return exact;
36820
+ let best = null;
36821
+ for (const [pattern, severity] of Object.entries(rules)) {
36822
+ if (!pattern.includes("*"))
36823
+ continue;
36824
+ if (!globMatches(pattern, ruleId))
36825
+ continue;
36826
+ const len = pattern.replace(/\*/g, "").length;
36827
+ if (!best || len > best.len)
36828
+ best = { len, severity };
36829
+ }
36830
+ return best ? best.severity : defaultSeverity;
36831
+ }
36832
+ function globMatches(pattern, value) {
36833
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
36834
+ return new RegExp(`^${escaped}$`).test(value);
36835
+ }
36836
+ var SeveritySchema, BehaviorConfigSchema;
36837
+ var init_config = __esm(() => {
36838
+ init_zod();
36839
+ init_logger();
36840
+ SeveritySchema = exports_external.enum(["off", "warn", "fix"]);
36841
+ BehaviorConfigSchema = exports_external.object({
36842
+ preset: exports_external.string().optional(),
36843
+ rules: exports_external.record(exports_external.string(), SeveritySchema).optional(),
36844
+ hooks: exports_external.array(exports_external.string()).optional(),
36845
+ observer: exports_external.object({
36846
+ enabled: exports_external.boolean().optional(),
36847
+ mode: exports_external.enum(["off", "suggest", "enforce"]).optional(),
36848
+ model: exports_external.string().optional(),
36849
+ timeoutMs: exports_external.number().int().positive().optional()
36850
+ }).optional()
36851
+ });
36852
+ });
36853
+
36854
+ // src/behavior/harness.ts
36855
+ function textOf(value) {
36856
+ if (!value)
36857
+ return "";
36858
+ if (typeof value === "string")
36859
+ return value;
36860
+ if (Array.isArray(value)) {
36861
+ let out = "";
36862
+ for (const part of value) {
36863
+ if (typeof part === "string")
36864
+ out += part;
36865
+ else if (typeof part?.text === "string")
36866
+ out += part.text;
36867
+ else if (typeof part?.content === "string")
36868
+ out += part.content;
36869
+ }
36870
+ return out;
36871
+ }
36872
+ if (typeof value?.text === "string")
36873
+ return value.text;
36874
+ return "";
36875
+ }
36876
+ function matchPlanPath(text) {
36877
+ if (!PLAN_MODE_HINT.test(text))
36878
+ return;
36879
+ for (const re of PLAN_PATH_PATTERNS) {
36880
+ const m = re.exec(text);
36881
+ if (m?.[1])
36882
+ return m[1];
36883
+ }
36884
+ return;
36885
+ }
36886
+ function detectHarnessFacts(claudeRequest) {
36887
+ const facts = { planModeActive: false };
36888
+ let planPath = matchPlanPath(textOf(claudeRequest?.system));
36889
+ if (!planPath && Array.isArray(claudeRequest?.messages)) {
36890
+ const messages = claudeRequest.messages;
36891
+ for (let i = messages.length - 1;i >= 0; i--) {
36892
+ planPath = matchPlanPath(textOf(messages[i]?.content));
36893
+ if (planPath)
36894
+ break;
36895
+ }
36896
+ }
36897
+ if (planPath) {
36898
+ facts.planModeActive = true;
36899
+ facts.planFilePath = planPath;
36900
+ const slash = planPath.lastIndexOf("/");
36901
+ if (slash > 0)
36902
+ facts.planDir = planPath.slice(0, slash);
36903
+ }
36904
+ return facts;
36905
+ }
36906
+ var PLAN_PATH_PATTERNS, PLAN_MODE_HINT;
36907
+ var init_harness = __esm(() => {
36908
+ PLAN_PATH_PATTERNS = [
36909
+ /You should create your plan at\s+(\S+?\.md)/,
36910
+ /A plan file already exists at\s+(\S+?\.md)/,
36911
+ /Read-only except plan file\s*\(([^)]+\.md)\)/
36912
+ ];
36913
+ PLAN_MODE_HINT = /plan file|create your plan at|Plan mode is active|Plan mode still active/i;
36914
+ });
36915
+
36916
+ // src/behavior/engine.ts
36917
+ class BehaviorSession {
36918
+ active;
36919
+ modelId;
36920
+ providerName;
36921
+ facts = { planModeActive: false };
36922
+ bufferedTools = new Set;
36923
+ constructor(active, modelId, providerName) {
36924
+ this.active = active;
36925
+ this.modelId = modelId;
36926
+ this.providerName = providerName;
36927
+ }
36928
+ armBuffering() {
36929
+ const armed = new Set;
36930
+ for (const { rule, severity } of this.active) {
36931
+ if (severity !== "fix")
36932
+ continue;
36933
+ if (rule.armed && !rule.armed(this.facts))
36934
+ continue;
36935
+ for (const t of rule.interceptsTools ?? [])
36936
+ armed.add(t);
36937
+ }
36938
+ this.bufferedTools = armed;
36939
+ }
36940
+ get harness() {
36941
+ return this.facts;
36942
+ }
36943
+ get isNoop() {
36944
+ return this.active.length === 0;
36945
+ }
36946
+ applyRequest(claudeRequest, claudeTools, tools, messages) {
36947
+ if (this.active.length === 0)
36948
+ return;
36949
+ this.facts = detectHarnessFacts(claudeRequest);
36950
+ this.armBuffering();
36951
+ const ctx = {
36952
+ modelId: this.modelId,
36953
+ providerName: this.providerName,
36954
+ isNativeAnthropic: false,
36955
+ claudeRequest,
36956
+ claudeTools,
36957
+ tools,
36958
+ messages,
36959
+ harness: this.facts
36960
+ };
36961
+ for (const { rule, severity } of this.active) {
36962
+ if (!rule.onRequest)
36963
+ continue;
36964
+ let actions = [];
36965
+ try {
36966
+ actions = rule.onRequest(ctx) ?? [];
36967
+ } catch (err) {
36968
+ log(`[behavior] rule ${rule.id} onRequest threw: ${err}`);
36969
+ continue;
36970
+ }
36971
+ for (const action of actions)
36972
+ this.applyAction(rule.id, severity, action, ctx);
36973
+ }
36974
+ }
36975
+ interceptsTool(toolName) {
36976
+ return this.bufferedTools.has(toolName);
36977
+ }
36978
+ repairToolCall(toolName, rawArgs) {
36979
+ if (!this.bufferedTools.has(toolName))
36980
+ return null;
36981
+ let args = {};
36982
+ try {
36983
+ const parsed = JSON.parse(rawArgs || "{}");
36984
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
36985
+ args = parsed;
36986
+ } catch {
36987
+ return null;
36988
+ }
36989
+ let changed = false;
36990
+ for (const { rule, severity } of this.active) {
36991
+ if (!rule.onToolCall)
36992
+ continue;
36993
+ if (!(rule.interceptsTools ?? []).includes(toolName))
36994
+ continue;
36995
+ let actions = [];
36996
+ try {
36997
+ actions = rule.onToolCall({
36998
+ modelId: this.modelId,
36999
+ toolName,
37000
+ args,
37001
+ rawArgs,
37002
+ harness: this.facts
37003
+ }) ?? [];
37004
+ } catch (err) {
37005
+ log(`[behavior] rule ${rule.id} onToolCall threw: ${err}`);
37006
+ continue;
37007
+ }
37008
+ for (const action of actions) {
37009
+ if (action.type === "warn") {
37010
+ log(`[behavior] ${rule.id} (warn): ${action.message}`);
37011
+ continue;
37012
+ }
37013
+ if (action.type !== "repairToolArgs")
37014
+ continue;
37015
+ if (severity !== "fix") {
37016
+ log(`[behavior] ${rule.id} (warn-only, not applied): ${action.reason}`);
37017
+ continue;
37018
+ }
37019
+ args = action.args;
37020
+ changed = true;
37021
+ log(`[behavior] ${rule.id} repaired ${toolName}: ${action.reason}`);
37022
+ }
37023
+ }
37024
+ return changed ? JSON.stringify(args) : null;
37025
+ }
37026
+ applyAction(ruleId, severity, action, ctx) {
37027
+ if (action.type === "warn") {
37028
+ log(`[behavior] ${ruleId} (warn): ${action.message}`);
37029
+ return;
37030
+ }
37031
+ if (severity !== "fix") {
37032
+ log(`[behavior] ${ruleId} (warn-only, not applied): ${action.type}`);
37033
+ return;
37034
+ }
37035
+ switch (action.type) {
37036
+ case "injectSystemNote": {
37037
+ const req = ctx.claudeRequest;
37038
+ if (typeof req.system === "string") {
37039
+ req.system = `${req.system}
37040
+
37041
+ ${action.text}`;
37042
+ } else if (Array.isArray(req.system)) {
37043
+ req.system.push({ type: "text", text: action.text });
37044
+ } else {
37045
+ req.system = action.text;
37046
+ }
37047
+ log(`[behavior] ${ruleId} injected system note (${action.text.length} chars)`);
37048
+ break;
37049
+ }
37050
+ case "rewriteToolDescription": {
37051
+ let hits = 0;
37052
+ for (const t of ctx.claudeTools) {
37053
+ if (t?.name !== action.tool)
37054
+ continue;
37055
+ t.description = `${t.description ?? ""}${action.append}`;
37056
+ hits++;
37057
+ }
37058
+ for (const t of ctx.tools) {
37059
+ const fn = t?.function ?? t;
37060
+ if (fn?.name !== action.tool)
37061
+ continue;
37062
+ fn.description = `${fn.description ?? ""}${action.append}`;
37063
+ hits++;
37064
+ }
37065
+ log(`[behavior] ${ruleId} rewrote description of ${action.tool} (${hits} site(s))`);
37066
+ break;
37067
+ }
37068
+ case "repairToolArgs":
37069
+ log(`[behavior] ${ruleId} returned repairToolArgs from onRequest \u2014 ignored`);
37070
+ break;
37071
+ }
37072
+ }
37073
+ }
37074
+
37075
+ class BehaviorEngine {
37076
+ config;
37077
+ rules;
37078
+ constructor(config2, rules) {
37079
+ this.config = config2;
37080
+ this.rules = rules;
37081
+ }
37082
+ startSession(params) {
37083
+ const active = [];
37084
+ if (!params.isNativeAnthropic) {
37085
+ for (const rule of this.rules) {
37086
+ const severity = resolveSeverity(rule.id, rule.defaultSeverity, this.config);
37087
+ if (severity === "off")
37088
+ continue;
37089
+ let applies = false;
37090
+ try {
37091
+ applies = rule.appliesTo(params);
37092
+ } catch (err) {
37093
+ log(`[behavior] rule ${rule.id} appliesTo threw: ${err}`);
37094
+ continue;
37095
+ }
37096
+ if (applies)
37097
+ active.push({ rule, severity });
37098
+ }
37099
+ }
37100
+ if (active.length > 0) {
37101
+ log(`[behavior] ${active.length} rule(s) active for ${params.modelId}: ` + active.map((a) => `${a.rule.id}=${a.severity}`).join(", "));
37102
+ }
37103
+ return new BehaviorSession(active, params.modelId, params.providerName);
37104
+ }
37105
+ }
37106
+ var init_engine = __esm(() => {
37107
+ init_logger();
37108
+ init_config();
37109
+ init_harness();
37110
+ });
37111
+
37112
+ // src/behavior/rules/plan-mode.ts
37113
+ function directoryOf(filePath) {
37114
+ const slash = filePath.lastIndexOf("/");
37115
+ return slash > 0 ? filePath.slice(0, slash) : undefined;
37116
+ }
37117
+ var WRITE_TOOLS, planFilePathRule, PLAN_MODE_RULES;
37118
+ var init_plan_mode = __esm(() => {
37119
+ WRITE_TOOLS = ["Write", "Edit", "NotebookEdit"];
37120
+ planFilePathRule = {
37121
+ id: "plan-mode/plan-file-path",
37122
+ description: "Keep plan-mode writes on the plan file Claude Code assigned, and name that " + "path in the ExitPlanMode description.",
37123
+ defaultSeverity: "fix",
37124
+ interceptsTools: WRITE_TOOLS,
37125
+ appliesTo: ({ isNativeAnthropic }) => !isNativeAnthropic,
37126
+ armed: (facts) => facts.planModeActive === true,
37127
+ onRequest(ctx) {
37128
+ const { planModeActive, planFilePath } = ctx.harness;
37129
+ if (!planModeActive || !planFilePath)
37130
+ return [];
37131
+ const hasExitPlanMode = ctx.claudeTools.some((t) => t?.name === "ExitPlanMode") || ctx.tools.some((t) => (t?.function ?? t)?.name === "ExitPlanMode");
37132
+ if (!hasExitPlanMode)
37133
+ return [];
37134
+ return [
37135
+ {
37136
+ type: "rewriteToolDescription",
37137
+ tool: "ExitPlanMode",
37138
+ append: `
37139
+
37140
+ ## Plan file for THIS session
37141
+ Your plan MUST be written to exactly this path:
37142
+ ${planFilePath}
37143
+ Do not invent a different filename, and do not derive one from the task. Claude Code reads only that exact path; a plan written anywhere else is invisible to it and the approval will show "No plan found".`
37144
+ }
37145
+ ];
37146
+ },
37147
+ onToolCall(ctx) {
37148
+ const { planFilePath, planDir } = ctx.harness;
37149
+ if (!planFilePath || !planDir)
37150
+ return [];
37151
+ const filePath = ctx.args.file_path;
37152
+ if (typeof filePath !== "string" || filePath === planFilePath)
37153
+ return [];
37154
+ if (directoryOf(filePath) !== planDir)
37155
+ return [];
37156
+ return [
37157
+ {
37158
+ type: "repairToolArgs",
37159
+ args: { ...ctx.args, file_path: planFilePath },
37160
+ reason: `redirected ${ctx.toolName} from ${filePath} to the session's assigned ` + `plan file ${planFilePath}`
37161
+ }
37162
+ ];
37163
+ }
37164
+ };
37165
+ PLAN_MODE_RULES = [planFilePathRule];
37166
+ });
37167
+
37168
+ // src/behavior/hooks.ts
37169
+ import { isAbsolute, resolve } from "path";
37170
+ function isBehaviorRule(value) {
37171
+ return !!value && typeof value === "object" && typeof value.id === "string" && value.id.length > 0 && typeof value.appliesTo === "function" && (value.onRequest === undefined || typeof value.onRequest === "function") && (value.onToolCall === undefined || typeof value.onToolCall === "function");
37172
+ }
37173
+ function collectRules(mod) {
37174
+ const found = [];
37175
+ const consider = (v) => {
37176
+ if (Array.isArray(v))
37177
+ v.forEach(consider);
37178
+ else if (isBehaviorRule(v))
37179
+ found.push(v);
37180
+ };
37181
+ consider(mod?.default);
37182
+ consider(mod?.rules);
37183
+ for (const [key, value] of Object.entries(mod ?? {})) {
37184
+ if (key === "default" || key === "rules")
37185
+ continue;
37186
+ consider(value);
37187
+ }
37188
+ return [...new Set(found)];
37189
+ }
37190
+ function shortName(path) {
37191
+ const base = path.split("/").pop() ?? path;
37192
+ return base.replace(/\.[cm]?[jt]s$/, "");
37193
+ }
37194
+ async function loadHookRules(paths, cwd = process.cwd()) {
37195
+ if (!paths?.length)
37196
+ return [];
37197
+ const loaded = [];
37198
+ const seen = new Set;
37199
+ for (const raw2 of paths) {
37200
+ const abs = isAbsolute(raw2) ? raw2 : resolve(cwd, raw2);
37201
+ const rules = await importHook(abs, raw2);
37202
+ for (const rule of rules)
37203
+ namespaceInto(rule, abs, seen, loaded);
37204
+ }
37205
+ if (loaded.length > 0) {
37206
+ logStderr(`[behavior] Loaded ${loaded.length} hook rule(s): ${loaded.map((r) => r.id).join(", ")}`);
37207
+ }
37208
+ return loaded;
37209
+ }
37210
+ async function importHook(abs, raw2) {
37211
+ let mod;
37212
+ try {
37213
+ mod = await import(abs);
37214
+ } catch (err) {
37215
+ logStderr(`[behavior] Skipping hook ${raw2}: ${err instanceof Error ? err.message : err}`);
37216
+ return [];
37217
+ }
37218
+ const rules = collectRules(mod);
37219
+ if (rules.length === 0) {
37220
+ logStderr(`[behavior] Hook ${raw2} exported no valid BehaviorRule \u2014 skipped`);
37221
+ }
37222
+ return rules;
37223
+ }
37224
+ function namespaceInto(rule, abs, seen, out) {
37225
+ const namespaced = `hook:${shortName(abs)}/${rule.id}`;
37226
+ if (seen.has(namespaced)) {
37227
+ logStderr(`[behavior] Duplicate hook rule ${namespaced} \u2014 keeping the first`);
37228
+ return;
37229
+ }
37230
+ seen.add(namespaced);
37231
+ out.push({
37232
+ ...rule,
37233
+ id: namespaced,
37234
+ defaultSeverity: rule.defaultSeverity ?? "warn"
37235
+ });
37236
+ }
37237
+ var init_hooks = __esm(() => {
37238
+ init_logger();
37239
+ });
37240
+
37241
+ // src/behavior/observer/digest.ts
37242
+ var PATH_KEYS;
37243
+ var init_digest = __esm(() => {
37244
+ PATH_KEYS = new Set(["file_path", "path", "notebook_path", "filePath"]);
37245
+ });
37246
+
37247
+ // src/providers/ollama-discovery.ts
37248
+ function ollamaBaseUrl() {
37249
+ return process.env.OLLAMA_HOST || process.env.OLLAMA_BASE_URL || "http://localhost:11434";
37250
+ }
37251
+ async function fetchOllamaModels(options = {}) {
37252
+ const { enrichCapabilities = true } = options;
37253
+ const host = ollamaBaseUrl();
37254
+ try {
37255
+ const response = await fetch(`${host}/api/tags`, {
37256
+ signal: AbortSignal.timeout(3000)
37257
+ });
37258
+ if (!response.ok)
37259
+ return [];
37260
+ const data = await response.json();
37261
+ const models = data.models || [];
37262
+ const enriched = await Promise.all(models.map(async (m) => {
37263
+ let capabilities = [];
37264
+ if (enrichCapabilities) {
37265
+ try {
37266
+ const showResponse = await fetch(`${host}/api/show`, {
37267
+ method: "POST",
37268
+ headers: { "Content-Type": "application/json" },
37269
+ body: JSON.stringify({ name: m.name }),
37270
+ signal: AbortSignal.timeout(2000)
37271
+ });
37272
+ if (showResponse.ok) {
37273
+ const showData = await showResponse.json();
37274
+ capabilities = showData.capabilities || [];
37275
+ }
37276
+ } catch {}
37277
+ }
37278
+ const nameLower = String(m.name).toLowerCase();
37279
+ const supportsTools = capabilities.includes("tools");
37280
+ const isEmbeddingModel = capabilities.includes("embedding") || nameLower.includes("embed");
37281
+ const sizeInfo = m.details?.parameter_size || "unknown size";
37282
+ const toolsIndicator = supportsTools ? "\u2713 tools" : "\u2717 no tools";
37283
+ return {
37284
+ id: `ollama/${m.name}`,
37285
+ name: m.name,
37286
+ description: `Local Ollama model (${sizeInfo}, ${toolsIndicator})`,
37287
+ provider: "ollama",
37288
+ pricing: { prompt: "0", completion: "0" },
37289
+ isLocal: true,
37290
+ supportsTools,
37291
+ isEmbeddingModel,
37292
+ capabilities,
37293
+ details: m.details,
37294
+ size: m.size
37295
+ };
37296
+ }));
37297
+ return enriched.filter((m) => !m.isEmbeddingModel);
37298
+ } catch {
37299
+ return [];
37300
+ }
37301
+ }
37302
+
37303
+ // src/behavior/observer/client.ts
37304
+ var init_client = __esm(() => {
37305
+ init_logger();
37306
+ });
37307
+
37308
+ // src/behavior/observer/corpus.ts
37309
+ var WRITE_TOOLS2;
37310
+ var init_corpus = __esm(() => {
37311
+ WRITE_TOOLS2 = new Set(["Write", "Edit", "NotebookEdit"]);
37312
+ });
37313
+
37314
+ // src/behavior/index.ts
37315
+ function createBehaviorEngine(rawConfig, extraRules = []) {
37316
+ return new BehaviorEngine(parseBehaviorConfig(rawConfig), [...BUILTIN_RULES, ...extraRules]);
37317
+ }
37318
+ function getBehaviorEngine() {
37319
+ if (!sharedEngine) {
37320
+ sharedEngine = createBehaviorEngine(loadConfig().behavior, hookRules);
37321
+ }
37322
+ return sharedEngine;
37323
+ }
37324
+ function registerHookRules(rules) {
37325
+ hookRules = [...hookRules, ...rules];
37326
+ sharedEngine = null;
37327
+ }
37328
+ var BUILTIN_RULES, sharedEngine = null, hookRules;
37329
+ var init_behavior = __esm(() => {
37330
+ init_profile_config();
37331
+ init_config();
37332
+ init_engine();
37333
+ init_plan_mode();
37334
+ init_engine();
37335
+ init_config();
37336
+ init_harness();
37337
+ init_hooks();
37338
+ init_digest();
37339
+ init_client();
37340
+ init_corpus();
37341
+ BUILTIN_RULES = [...PLAN_MODE_RULES];
37342
+ hookRules = [];
37343
+ });
37344
+
36339
37345
  // src/middleware/manager.ts
36340
37346
  class MiddlewareManager {
36341
37347
  middlewares = [];
@@ -36658,7 +37664,7 @@ class OpenAIProviderTransport {
36658
37664
  delayMs = 500 * (attempt + 1);
36659
37665
  }
36660
37666
  log(`[${this.displayName}] 429 rate limited, retry ${attempt + 1}/${maxRetries} in ${(delayMs / 1000).toFixed(1)}s`);
36661
- await new Promise((resolve) => setTimeout(resolve, delayMs));
37667
+ await new Promise((resolve2) => setTimeout(resolve2, delayMs));
36662
37668
  continue;
36663
37669
  }
36664
37670
  return response;
@@ -37377,11 +38383,11 @@ async function runConsentPrompt(ctx) {
37377
38383
  Does NOT send: prompts, paths, API keys, or credentials.
37378
38384
  Disable anytime: claudish telemetry off
37379
38385
  `);
37380
- const answer = await new Promise((resolve) => {
38386
+ const answer = await new Promise((resolve2) => {
37381
38387
  const rl = createInterface({ input: process.stdin, output: process.stderr });
37382
38388
  rl.question("Send anonymous error report? [y/N] ", (ans) => {
37383
38389
  rl.close();
37384
- resolve(ans.trim().toLowerCase());
38390
+ resolve2(ans.trim().toLowerCase());
37385
38391
  });
37386
38392
  });
37387
38393
  const accepted = answer === "y" || answer === "yes";
@@ -38122,8 +39128,8 @@ async function sniffResponsesStreamHead(response, opts = {}) {
38122
39128
  return { kind: "clean", response: replayResponse() };
38123
39129
  }
38124
39130
  let timer;
38125
- const timeout = new Promise((resolve) => {
38126
- timer = setTimeout(() => resolve("timeout"), remaining);
39131
+ const timeout = new Promise((resolve2) => {
39132
+ timer = setTimeout(() => resolve2("timeout"), remaining);
38127
39133
  });
38128
39134
  let result;
38129
39135
  try {
@@ -38852,6 +39858,7 @@ function createResponsesStreamHandler(c, response, opts) {
38852
39858
  let lastActivity = Date.now();
38853
39859
  let pingInterval = null;
38854
39860
  let isClosed = false;
39861
+ const streamMetadata = new Map;
38855
39862
  const functionCalls = new Map;
38856
39863
  const openToolBlocks = new Set;
38857
39864
  const stream = new ReadableStream({
@@ -38883,6 +39890,13 @@ data: ${JSON.stringify(data)}
38883
39890
  };
38884
39891
  const closeTools = () => {
38885
39892
  for (const fnCall of openToolBlocks) {
39893
+ if (fnCall.buffered && fnCall.arguments) {
39894
+ send("content_block_delta", {
39895
+ type: "content_block_delta",
39896
+ index: fnCall.index,
39897
+ delta: { type: "input_json_delta", partial_json: fnCall.arguments }
39898
+ });
39899
+ }
38886
39900
  send("content_block_stop", { type: "content_block_stop", index: fnCall.index });
38887
39901
  }
38888
39902
  openToolBlocks.clear();
@@ -38929,6 +39943,14 @@ data: ${JSON.stringify(data)}
38929
39943
  }
38930
39944
  try {
38931
39945
  const event = JSON.parse(data);
39946
+ if (opts.middlewareManager) {
39947
+ await opts.middlewareManager.afterStreamChunk({
39948
+ modelId: opts.modelName,
39949
+ chunk: event,
39950
+ delta: event,
39951
+ metadata: streamMetadata
39952
+ });
39953
+ }
38932
39954
  if (getLogLevel() === "debug" && event.type) {
38933
39955
  log(`[ResponsesSSE] Event: ${event.type}`);
38934
39956
  }
@@ -38960,7 +39982,8 @@ data: ${JSON.stringify(data)}
38960
39982
  name: fnName,
38961
39983
  arguments: "",
38962
39984
  index: curIdx++,
38963
- claudeId: callId
39985
+ claudeId: callId,
39986
+ buffered: opts.shouldBufferTool?.(fnName) === true
38964
39987
  };
38965
39988
  functionCalls.set(openaiCallId, fnCallData);
38966
39989
  if (itemId && itemId !== openaiCallId) {
@@ -39009,11 +40032,13 @@ data: ${JSON.stringify(data)}
39009
40032
  const fnCall = functionCalls.get(callId);
39010
40033
  if (fnCall) {
39011
40034
  fnCall.arguments += event.delta || "";
39012
- send("content_block_delta", {
39013
- type: "content_block_delta",
39014
- index: fnCall.index,
39015
- delta: { type: "input_json_delta", partial_json: event.delta || "" }
39016
- });
40035
+ if (!fnCall.buffered) {
40036
+ send("content_block_delta", {
40037
+ type: "content_block_delta",
40038
+ index: fnCall.index,
40039
+ delta: { type: "input_json_delta", partial_json: event.delta || "" }
40040
+ });
40041
+ }
39017
40042
  }
39018
40043
  } else if (event.type === "response.output_item.done") {
39019
40044
  if (event.item?.type === "reasoning" && event.item.encrypted_content) {
@@ -39028,6 +40053,23 @@ data: ${JSON.stringify(data)}
39028
40053
  const callId = event.item.call_id || event.item.id;
39029
40054
  const fnCall = functionCalls.get(callId) || functionCalls.get(event.item.id);
39030
40055
  if (fnCall && openToolBlocks.has(fnCall)) {
40056
+ if (fnCall.buffered) {
40057
+ let finalArgs = fnCall.arguments;
40058
+ try {
40059
+ const repaired = opts.onToolCall?.(fnCall.name, finalArgs);
40060
+ if (typeof repaired === "string" && repaired !== finalArgs) {
40061
+ log(`[ResponsesSSE] tool call repaired: ${fnCall.name}`);
40062
+ finalArgs = repaired;
40063
+ }
40064
+ } catch (err) {
40065
+ log(`[ResponsesSSE] onToolCall threw for ${fnCall.name}: ${err}`);
40066
+ }
40067
+ send("content_block_delta", {
40068
+ type: "content_block_delta",
40069
+ index: fnCall.index,
40070
+ delta: { type: "input_json_delta", partial_json: finalArgs }
40071
+ });
40072
+ }
39031
40073
  send("content_block_stop", { type: "content_block_stop", index: fnCall.index });
39032
40074
  openToolBlocks.delete(fnCall);
39033
40075
  }
@@ -39119,6 +40161,9 @@ data: ${JSON.stringify(data)}
39119
40161
  isClosed = true;
39120
40162
  if (opts.onTokenUpdate)
39121
40163
  opts.onTokenUpdate(inputTokens, outputTokens);
40164
+ if (opts.middlewareManager) {
40165
+ await opts.middlewareManager.afterStreamComplete(opts.modelName, streamMetadata);
40166
+ }
39122
40167
  safeClose();
39123
40168
  } catch (error46) {
39124
40169
  if (pingInterval) {
@@ -39355,6 +40400,7 @@ class ComposedHandler {
39355
40400
  explicitAdapter;
39356
40401
  modelAdapter;
39357
40402
  middlewareManager;
40403
+ behaviorEngine;
39358
40404
  tokenTracker;
39359
40405
  targetModel;
39360
40406
  bareModelName;
@@ -39381,6 +40427,7 @@ class ComposedHandler {
39381
40427
  this.middlewareManager.register(new GeminiThoughtSignatureMiddleware);
39382
40428
  }
39383
40429
  this.middlewareManager.initialize().catch((err) => log(`[ComposedHandler:${this.bareModelName}] Middleware init error: ${err}`));
40430
+ this.behaviorEngine = getBehaviorEngine();
39384
40431
  this.tokenTracker = new TokenTracker(port, {
39385
40432
  contextWindow: this.getModelContextWindow(),
39386
40433
  providerName: provider.name,
@@ -39493,6 +40540,22 @@ class ComposedHandler {
39493
40540
  log(`[${this.provider.displayName}] Tools: ${toolNames}`);
39494
40541
  }
39495
40542
  }
40543
+ await this.middlewareManager.beforeRequest({
40544
+ modelId: this.bareModelName,
40545
+ messages,
40546
+ tools,
40547
+ stream: true,
40548
+ claudeRequest,
40549
+ claudeTools: claudeRequest.tools ?? []
40550
+ });
40551
+ const behaviorSession = this.behaviorEngine.startSession({
40552
+ modelId: this.bareModelName,
40553
+ providerName: this.provider.name,
40554
+ isNativeAnthropic: /^claude[-.]/i.test(this.bareModelName) || this.provider.name === "anthropic"
40555
+ });
40556
+ if (!behaviorSession.isNoop) {
40557
+ behaviorSession.applyRequest(claudeRequest, claudeRequest.tools ?? [], tools, messages);
40558
+ }
39496
40559
  let requestPayload = adapter.buildPayload(claudeRequest, messages, tools);
39497
40560
  const extraFields = this.provider.getExtraPayloadFields?.();
39498
40561
  if (extraFields) {
@@ -39539,12 +40602,6 @@ class ComposedHandler {
39539
40602
  if (this.provider.transformPayload) {
39540
40603
  requestPayload = this.provider.transformPayload(requestPayload);
39541
40604
  }
39542
- await this.middlewareManager.beforeRequest({
39543
- modelId: this.bareModelName,
39544
- messages,
39545
- tools,
39546
- stream: true
39547
- });
39548
40605
  const endpoint = this.provider.getEndpoint(this.targetModel);
39549
40606
  const headers = await this.provider.getHeaders();
39550
40607
  headers["Content-Type"] = "application/json";
@@ -39826,7 +40883,7 @@ class ComposedHandler {
39826
40883
  };
39827
40884
  return this.handleStream(c, response, adapter, claudeRequest, toolNameMap, onStreamComplete, (code, message) => {
39828
40885
  streamApiError = { code, message };
39829
- });
40886
+ }, behaviorSession);
39830
40887
  }
39831
40888
  async settleResponsesStreamHead(initial, reissue) {
39832
40889
  let response = initial;
@@ -39845,7 +40902,7 @@ class ComposedHandler {
39845
40902
  };
39846
40903
  }
39847
40904
  log(`[${this.provider.displayName}] in-stream ${verdict.code} before any output \u2014 ` + `retry ${attempt + 1}/${STREAM_RETRY_DELAYS_MS.length} in ${delayMs / 1000}s`);
39848
- await new Promise((resolve) => setTimeout(resolve, delayMs));
40905
+ await new Promise((resolve2) => setTimeout(resolve2, delayMs));
39849
40906
  let next;
39850
40907
  try {
39851
40908
  next = await reissue();
@@ -39874,7 +40931,7 @@ class ComposedHandler {
39874
40931
  resolveStreamFormat() {
39875
40932
  return this.provider.overrideStreamFormat?.() ?? this.explicitAdapter?.getStreamFormat() ?? this.modelAdapter?.getStreamFormat() ?? this.getAdapter().getStreamFormat();
39876
40933
  }
39877
- handleStream(c, response, adapter, claudeRequest, toolNameMap, onComplete, onApiError) {
40934
+ handleStream(c, response, adapter, claudeRequest, toolNameMap, onComplete, onApiError, behaviorSession) {
39878
40935
  let pendingOnComplete = onComplete;
39879
40936
  const onTokenUpdate = (input, output) => {
39880
40937
  const strategy = this.options.tokenStrategy || "standard";
@@ -39911,7 +40968,10 @@ class ComposedHandler {
39911
40968
  toolNameMap: adapter.getToolNameMap(),
39912
40969
  contextWindow: lookupModelForProvider(this.bareModelName, this.provider.name),
39913
40970
  onApiError,
39914
- priorInputTokens
40971
+ priorInputTokens,
40972
+ middlewareManager: this.middlewareManager,
40973
+ shouldBufferTool: (name) => behaviorSession?.interceptsTool(name) ?? false,
40974
+ onToolCall: (name, argsJson) => behaviorSession?.repairToolCall(name, argsJson) ?? null
39915
40975
  });
39916
40976
  case "anthropic-sse":
39917
40977
  return createAnthropicPassthroughStream(c, response, {
@@ -40007,6 +41067,7 @@ var STREAM_RETRY_DELAYS_MS;
40007
41067
  var init_composed_handler = __esm(() => {
40008
41068
  init_dialect_manager();
40009
41069
  init_logger();
41070
+ init_behavior();
40010
41071
  init_middleware();
40011
41072
  init_openai();
40012
41073
  init_vision_proxy();
@@ -40151,207 +41212,6 @@ var init_fallback_handler = __esm(() => {
40151
41212
  init_composed_handler();
40152
41213
  });
40153
41214
 
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
41215
  // src/handlers/native-handler-advisor.ts
40356
41216
  import { appendFileSync as appendFileSync3 } from "fs";
40357
41217
  function loadAdvisorSwapConfig(cliModels, cliCollector) {
@@ -41005,12 +41865,6 @@ var init_api_key_map = __esm(() => {
41005
41865
  };
41006
41866
  });
41007
41867
 
41008
- // ../../node_modules/.bun/zod@4.1.13/node_modules/zod/index.js
41009
- var init_zod = __esm(() => {
41010
- init_external2();
41011
- init_external2();
41012
- });
41013
-
41014
41868
  // src/adapters/anthropic-api-format.ts
41015
41869
  var AnthropicAPIFormat;
41016
41870
  var init_anthropic_api_format = __esm(() => {
@@ -41288,7 +42142,7 @@ class AnthropicProviderTransport {
41288
42142
  delayMs = 500 * (attempt + 1);
41289
42143
  }
41290
42144
  log(`[${this.displayName}] 429 rate limited, retry ${attempt + 1}/${maxRetries} in ${(delayMs / 1000).toFixed(1)}s`);
41291
- await new Promise((resolve) => setTimeout(resolve, delayMs));
42145
+ await new Promise((resolve2) => setTimeout(resolve2, delayMs));
41292
42146
  continue;
41293
42147
  }
41294
42148
  return response;
@@ -41469,14 +42323,14 @@ async function discoverViaOllama(baseUrl, cacheKey) {
41469
42323
  let connectionError;
41470
42324
  let loadedRaw = [];
41471
42325
  try {
41472
- loadedRaw = await fetchOllamaModels(`${baseUrl}/api/ps`);
42326
+ loadedRaw = await fetchOllamaModels2(`${baseUrl}/api/ps`);
41473
42327
  } catch (e) {
41474
42328
  connectionError = classifyFetchError(e, `${baseUrl}/api/ps`);
41475
42329
  }
41476
42330
  let allRaw = loadedRaw;
41477
42331
  if (allRaw.length === 0) {
41478
42332
  try {
41479
- allRaw = await fetchOllamaModels(`${baseUrl}/api/tags`);
42333
+ allRaw = await fetchOllamaModels2(`${baseUrl}/api/tags`);
41480
42334
  } catch (e) {
41481
42335
  connectionError ??= classifyFetchError(e, `${baseUrl}/api/tags`);
41482
42336
  }
@@ -41587,7 +42441,7 @@ function extractLMStudioModels(body) {
41587
42441
  }
41588
42442
  return out;
41589
42443
  }
41590
- async function fetchOllamaModels(url2) {
42444
+ async function fetchOllamaModels2(url2) {
41591
42445
  const response = await fetch(url2, {
41592
42446
  method: "GET",
41593
42447
  signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
@@ -42019,7 +42873,7 @@ var init_ollama_api_format = __esm(() => {
42019
42873
  // src/providers/api-key-provenance.ts
42020
42874
  import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
42021
42875
  import { homedir as homedir18 } from "os";
42022
- import { join as join18, resolve } from "path";
42876
+ import { join as join18, resolve as resolve2 } from "path";
42023
42877
  function activeConfigPath() {
42024
42878
  return activeGlobalConfigFile(join18(homedir18(), ".claudish", "config.json"));
42025
42879
  }
@@ -42039,7 +42893,7 @@ function resolveApiKeyProvenance(envVar, aliases) {
42039
42893
  const allVars = [envVar, ...aliases || []];
42040
42894
  const dotenvValue = readDotenvKey(allVars);
42041
42895
  layers.push({
42042
- source: `.env (${resolve(".env")})`,
42896
+ source: `.env (${resolve2(".env")})`,
42043
42897
  maskedValue: maskKey(dotenvValue),
42044
42898
  isActive: false
42045
42899
  });
@@ -42097,7 +42951,7 @@ function formatProvenanceLog(p) {
42097
42951
  }
42098
42952
  function readDotenvKey(envVars) {
42099
42953
  try {
42100
- const dotenvPath = resolve(".env");
42954
+ const dotenvPath = resolve2(".env");
42101
42955
  if (!existsSync15(dotenvPath))
42102
42956
  return null;
42103
42957
  const parsed = import_dotenv.parse(readFileSync12(dotenvPath, "utf-8"));
@@ -42154,10 +43008,10 @@ class GeminiRequestQueue {
42154
43008
  log(`[GeminiQueue] Queue full (${this.queue.length}/${this.maxQueueSize}), rejecting request`);
42155
43009
  throw new Error("Gemini request queue full. Please retry later.");
42156
43010
  }
42157
- return new Promise((resolve2, reject) => {
43011
+ return new Promise((resolve3, reject) => {
42158
43012
  const queuedRequest = {
42159
43013
  fetchFn,
42160
- resolve: resolve2,
43014
+ resolve: resolve3,
42161
43015
  reject
42162
43016
  };
42163
43017
  this.queue.push(queuedRequest);
@@ -42213,7 +43067,7 @@ class GeminiRequestQueue {
42213
43067
  if (timeSinceLastRequest < delayMs) {
42214
43068
  const waitMs = delayMs - timeSinceLastRequest;
42215
43069
  log(`[GeminiQueue] Waiting ${waitMs}ms before next request`);
42216
- await new Promise((resolve2) => setTimeout(resolve2, waitMs));
43070
+ await new Promise((resolve3) => setTimeout(resolve3, waitMs));
42217
43071
  }
42218
43072
  }
42219
43073
  handleRateLimitResponse(errorText) {
@@ -42958,245 +43812,6 @@ var init_provider_profiles = __esm(() => {
42958
43812
  };
42959
43813
  });
42960
43814
 
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
43815
  // src/handlers/shared/local-queue.ts
43201
43816
  class LocalModelQueue {
43202
43817
  static instance = null;
@@ -43250,10 +43865,10 @@ class LocalModelQueue {
43250
43865
  }
43251
43866
  throw new Error(`Local model queue full (${this.queue.length}/${this.maxQueueSize}). GPU is overloaded. Please wait for current requests to complete.`);
43252
43867
  }
43253
- return new Promise((resolve2, reject) => {
43868
+ return new Promise((resolve3, reject) => {
43254
43869
  const queuedRequest = {
43255
43870
  fetchFn,
43256
- resolve: resolve2,
43871
+ resolve: resolve3,
43257
43872
  reject,
43258
43873
  providerId
43259
43874
  };
@@ -43348,7 +43963,7 @@ class LocalModelQueue {
43348
43963
  return parsed;
43349
43964
  }
43350
43965
  delay(ms) {
43351
- return new Promise((resolve2) => setTimeout(resolve2, ms));
43966
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
43352
43967
  }
43353
43968
  getStats() {
43354
43969
  return {
@@ -43650,10 +44265,10 @@ class OpenRouterRequestQueue {
43650
44265
  }
43651
44266
  throw new Error(`OpenRouter request queue full (${this.queue.length}/${this.maxQueueSize}). The API is rate-limited. Please wait and try again.`);
43652
44267
  }
43653
- return new Promise((resolve2, reject) => {
44268
+ return new Promise((resolve3, reject) => {
43654
44269
  const queuedRequest = {
43655
44270
  fetchFn,
43656
- resolve: resolve2,
44271
+ resolve: resolve3,
43657
44272
  reject
43658
44273
  };
43659
44274
  this.queue.push(queuedRequest);
@@ -43721,7 +44336,7 @@ class OpenRouterRequestQueue {
43721
44336
  if (getLogLevel() === "debug") {
43722
44337
  log(`[OpenRouterQueue] Waiting ${waitMs}ms before next request`);
43723
44338
  }
43724
- await new Promise((resolve2) => setTimeout(resolve2, waitMs));
44339
+ await new Promise((resolve3) => setTimeout(resolve3, waitMs));
43725
44340
  }
43726
44341
  }
43727
44342
  calculateDelay() {
@@ -43995,6 +44610,13 @@ async function createProxyServer(port, _openrouterApiKey, model, monitorMode = f
43995
44610
  } catch (err) {
43996
44611
  log(`[Proxy] customEndpoints load skipped: ${err instanceof Error ? err.message : String(err)}`);
43997
44612
  }
44613
+ try {
44614
+ const hookRules2 = await loadHookRules(parseBehaviorConfig(loadConfig().behavior).hooks);
44615
+ if (hookRules2.length > 0)
44616
+ registerHookRules(hookRules2);
44617
+ } catch (err) {
44618
+ log(`[Proxy] behavior hooks load skipped: ${err instanceof Error ? err.message : String(err)}`);
44619
+ }
43998
44620
  const nativeHandler = new NativeHandler(anthropicApiKey, options.advisorModels, options.advisorCollector);
43999
44621
  const openRouterHandlers = new Map;
44000
44622
  const localProviderHandlers = new Map;
@@ -44401,6 +45023,8 @@ var init_proxy_server = __esm(() => {
44401
45023
  init_model_loader();
44402
45024
  init_profile_config();
44403
45025
  init_api_key_map();
45026
+ init_behavior();
45027
+ init_hooks();
44404
45028
  init_custom_endpoints_loader();
44405
45029
  init_model_catalog_resolver();
44406
45030
  init_model_parser();
@@ -44443,9 +45067,9 @@ import {
44443
45067
  readdirSync as readdirSync2,
44444
45068
  writeFileSync as writeFileSync11
44445
45069
  } from "fs";
44446
- import { join as join20, resolve as resolve2 } from "path";
45070
+ import { join as join20, resolve as resolve3 } from "path";
44447
45071
  function validateSessionPath(sessionPath) {
44448
- const resolved = resolve2(sessionPath);
45072
+ const resolved = resolve3(sessionPath);
44449
45073
  const cwd = process.cwd();
44450
45074
  if (!resolved.startsWith(`${cwd}/`) && resolved !== cwd) {
44451
45075
  throw new Error(`Session path must be within current directory: ${sessionPath}`);
@@ -44557,7 +45181,7 @@ async function runModels(sessionPath, opts = {}) {
44557
45181
  });
44558
45182
  proc.stdin?.write(inputContent);
44559
45183
  proc.stdin?.end();
44560
- const completionPromise = new Promise((resolve3) => {
45184
+ const completionPromise = new Promise((resolve4) => {
44561
45185
  let exitCode = null;
44562
45186
  let resolved = false;
44563
45187
  const finish = () => {
@@ -44565,7 +45189,7 @@ async function runModels(sessionPath, opts = {}) {
44565
45189
  return;
44566
45190
  if (statusCache.models[anonId].state === "TIMEOUT") {
44567
45191
  resolved = true;
44568
- resolve3();
45192
+ resolve4();
44569
45193
  return;
44570
45194
  }
44571
45195
  resolved = true;
@@ -44585,14 +45209,14 @@ async function runModels(sessionPath, opts = {}) {
44585
45209
  } : undefined
44586
45210
  });
44587
45211
  opts.onStatusChange?.(anonId, statusCache.models[anonId]);
44588
- resolve3();
45212
+ resolve4();
44589
45213
  };
44590
45214
  outputStream.on("close", finish);
44591
45215
  proc.on("exit", (code) => {
44592
45216
  const current = statusCache.models[anonId];
44593
45217
  if (current?.state === "TIMEOUT") {
44594
45218
  resolved = true;
44595
- resolve3();
45219
+ resolve4();
44596
45220
  return;
44597
45221
  }
44598
45222
  if (stderr) {
@@ -44610,7 +45234,7 @@ async function runModels(sessionPath, opts = {}) {
44610
45234
  let timeoutHandle = null;
44611
45235
  await Promise.race([
44612
45236
  Promise.all(completionPromises),
44613
- new Promise((resolve3) => {
45237
+ new Promise((resolve4) => {
44614
45238
  timeoutHandle = setTimeout(() => {
44615
45239
  for (const [id, proc] of processes) {
44616
45240
  const current = statusCache.models[id];
@@ -44624,7 +45248,7 @@ async function runModels(sessionPath, opts = {}) {
44624
45248
  opts.onStatusChange?.(id, statusCache.models[id]);
44625
45249
  }
44626
45250
  }
44627
- resolve3();
45251
+ resolve4();
44628
45252
  }, timeoutMs);
44629
45253
  })
44630
45254
  ]);
@@ -47671,13 +48295,13 @@ var PromisePolyfill;
47671
48295
  var init_promise_polyfill = __esm(() => {
47672
48296
  PromisePolyfill = class PromisePolyfill extends Promise {
47673
48297
  static withResolver() {
47674
- let resolve3;
48298
+ let resolve4;
47675
48299
  let reject;
47676
48300
  const promise3 = new Promise((res, rej) => {
47677
- resolve3 = res;
48301
+ resolve4 = res;
47678
48302
  reject = rej;
47679
48303
  });
47680
- return { promise: promise3, resolve: resolve3, reject };
48304
+ return { promise: promise3, resolve: resolve4, reject };
47681
48305
  }
47682
48306
  };
47683
48307
  });
@@ -47714,7 +48338,7 @@ function createPrompt(view) {
47714
48338
  output
47715
48339
  });
47716
48340
  const screen = new ScreenManager(rl);
47717
- const { promise: promise3, resolve: resolve3, reject } = PromisePolyfill.withResolver();
48341
+ const { promise: promise3, resolve: resolve4, reject } = PromisePolyfill.withResolver();
47718
48342
  const cancel = () => reject(new CancelPromptError);
47719
48343
  if (signal) {
47720
48344
  const abort = () => reject(new AbortPromptError({ cause: signal.reason }));
@@ -47741,7 +48365,7 @@ function createPrompt(view) {
47741
48365
  cycle(() => {
47742
48366
  try {
47743
48367
  const nextView = view(config3, (value) => {
47744
- setImmediate(() => resolve3(value));
48368
+ setImmediate(() => resolve4(value));
47745
48369
  });
47746
48370
  if (nextView === undefined) {
47747
48371
  const callerFilename = callSites[1]?.getFileName();
@@ -53582,7 +54206,7 @@ var require_lib2 = __commonJS((exports) => {
53582
54206
  return matches;
53583
54207
  };
53584
54208
  exports.analyse = analyse;
53585
- var detectFile = (filepath, opts = {}) => new Promise((resolve3, reject) => {
54209
+ var detectFile = (filepath, opts = {}) => new Promise((resolve4, reject) => {
53586
54210
  let fd;
53587
54211
  const fs = (0, node_1.default)();
53588
54212
  const handler = (err, buffer) => {
@@ -53592,7 +54216,7 @@ var require_lib2 = __commonJS((exports) => {
53592
54216
  if (err) {
53593
54217
  reject(err);
53594
54218
  } else if (buffer) {
53595
- resolve3((0, exports.detect)(buffer));
54219
+ resolve4((0, exports.detect)(buffer));
53596
54220
  } else {
53597
54221
  reject(new Error("No error and no buffer received"));
53598
54222
  }
@@ -58853,7 +59477,7 @@ __export(exports_config, {
58853
59477
  DEFAULT_PORT_RANGE: () => DEFAULT_PORT_RANGE
58854
59478
  });
58855
59479
  var DEFAULT_PORT_RANGE, ENV, OPENROUTER_API_URL2 = "https://openrouter.ai/api/v1/chat/completions", OPENROUTER_HEADERS;
58856
- var init_config = __esm(() => {
59480
+ var init_config2 = __esm(() => {
58857
59481
  DEFAULT_PORT_RANGE = { start: 3000, end: 9000 };
58858
59482
  ENV = {
58859
59483
  OPENROUTER_API_KEY: "OPENROUTER_API_KEY",
@@ -58871,6 +59495,7 @@ var init_config = __esm(() => {
58871
59495
  ANTHROPIC_DEFAULT_HAIKU_MODEL: "ANTHROPIC_DEFAULT_HAIKU_MODEL",
58872
59496
  CLAUDE_CODE_SUBAGENT_MODEL: "CLAUDE_CODE_SUBAGENT_MODEL",
58873
59497
  CLAUDE_CODE_AUTO_COMPACT_WINDOW: "CLAUDE_CODE_AUTO_COMPACT_WINDOW",
59498
+ CLAUDE_CODE_MAX_CONTEXT_TOKENS: "CLAUDE_CODE_MAX_CONTEXT_TOKENS",
58874
59499
  OLLAMA_BASE_URL: "OLLAMA_BASE_URL",
58875
59500
  OLLAMA_HOST: "OLLAMA_HOST",
58876
59501
  LMSTUDIO_BASE_URL: "LMSTUDIO_BASE_URL",
@@ -59147,62 +59772,6 @@ var init_model_discovery = __esm(() => {
59147
59772
  _cache2 = new Map;
59148
59773
  });
59149
59774
 
59150
- // src/providers/ollama-discovery.ts
59151
- function ollamaBaseUrl() {
59152
- return process.env.OLLAMA_HOST || process.env.OLLAMA_BASE_URL || "http://localhost:11434";
59153
- }
59154
- async function fetchOllamaModels2(options = {}) {
59155
- const { enrichCapabilities = true } = options;
59156
- const host = ollamaBaseUrl();
59157
- try {
59158
- const response = await fetch(`${host}/api/tags`, {
59159
- signal: AbortSignal.timeout(3000)
59160
- });
59161
- if (!response.ok)
59162
- return [];
59163
- const data = await response.json();
59164
- const models = data.models || [];
59165
- const enriched = await Promise.all(models.map(async (m) => {
59166
- let capabilities = [];
59167
- if (enrichCapabilities) {
59168
- try {
59169
- const showResponse = await fetch(`${host}/api/show`, {
59170
- method: "POST",
59171
- headers: { "Content-Type": "application/json" },
59172
- body: JSON.stringify({ name: m.name }),
59173
- signal: AbortSignal.timeout(2000)
59174
- });
59175
- if (showResponse.ok) {
59176
- const showData = await showResponse.json();
59177
- capabilities = showData.capabilities || [];
59178
- }
59179
- } catch {}
59180
- }
59181
- const nameLower = String(m.name).toLowerCase();
59182
- const supportsTools = capabilities.includes("tools");
59183
- const isEmbeddingModel = capabilities.includes("embedding") || nameLower.includes("embed");
59184
- const sizeInfo = m.details?.parameter_size || "unknown size";
59185
- const toolsIndicator = supportsTools ? "\u2713 tools" : "\u2717 no tools";
59186
- return {
59187
- id: `ollama/${m.name}`,
59188
- name: m.name,
59189
- description: `Local Ollama model (${sizeInfo}, ${toolsIndicator})`,
59190
- provider: "ollama",
59191
- pricing: { prompt: "0", completion: "0" },
59192
- isLocal: true,
59193
- supportsTools,
59194
- isEmbeddingModel,
59195
- capabilities,
59196
- details: m.details,
59197
- size: m.size
59198
- };
59199
- }));
59200
- return enriched.filter((m) => !m.isEmbeddingModel);
59201
- } catch {
59202
- return [];
59203
- }
59204
- }
59205
-
59206
59775
  // src/model-selector.ts
59207
59776
  var exports_model_selector = {};
59208
59777
  __export(exports_model_selector, {
@@ -59734,7 +60303,7 @@ async function selectModelFromProvider(provider, tierName, recommendedModels, _f
59734
60303
  }
59735
60304
  }
59736
60305
  if (provider === "ollama") {
59737
- const ollamaModels = await fetchOllamaModels2({ enrichCapabilities: false });
60306
+ const ollamaModels = await fetchOllamaModels({ enrichCapabilities: false });
59738
60307
  const chatModels = ollamaModels.map((m) => ({
59739
60308
  id: m.name,
59740
60309
  name: m.name,
@@ -62673,8 +63242,8 @@ async function startProbeTui(initial) {
62673
63242
  });
62674
63243
  const store = new ProbeStore(initial);
62675
63244
  let resolveQuit;
62676
- const quitPromise = new Promise((resolve3) => {
62677
- resolveQuit = resolve3;
63245
+ const quitPromise = new Promise((resolve4) => {
63246
+ resolveQuit = resolve4;
62678
63247
  });
62679
63248
  let quit = false;
62680
63249
  const onQuit = () => {
@@ -63285,7 +63854,7 @@ Local providers`);
63285
63854
  console.log(` ${"\u2500".repeat(70)}`);
63286
63855
  let ollamaLine = " Ollama: not running";
63287
63856
  try {
63288
- const ollamaModels = await fetchOllamaModels2();
63857
+ const ollamaModels = await fetchOllamaModels();
63289
63858
  if (ollamaModels.length > 0) {
63290
63859
  const toolCount = ollamaModels.filter((m) => m.supportsTools).length;
63291
63860
  ollamaLine = ` Ollama: ${ollamaModels.length} models installed (${toolCount} with tools) \u2014 use: claudish --model ollama@<name>`;
@@ -64343,7 +64912,7 @@ function printAvailableModels() {
64343
64912
  }
64344
64913
  var __filename3, __dirname3;
64345
64914
  var init_cli = __esm(() => {
64346
- init_config();
64915
+ init_config2();
64347
64916
  init_model_loader();
64348
64917
  init_model_selector();
64349
64918
  init_probe_results_printer();
@@ -64459,7 +65028,7 @@ async function fetchLatestVersionOrThrow(options = {}) {
64459
65028
  } catch (error46) {
64460
65029
  lastError = error46 instanceof Error && error46.name === "AbortError" ? new Error(`request timed out after ${timeoutMs}ms`) : error46 instanceof Error ? error46 : new Error(String(error46));
64461
65030
  if (attempt < retries) {
64462
- await new Promise((resolve3) => setTimeout(resolve3, 300 * (attempt + 1)));
65031
+ await new Promise((resolve4) => setTimeout(resolve4, 300 * (attempt + 1)));
64463
65032
  }
64464
65033
  } finally {
64465
65034
  clearTimeout(timeout);
@@ -70192,14 +70761,14 @@ function App({ requestLogin } = {}) {
70192
70761
  return resolveSdkAuth({
70193
70762
  interactive: true,
70194
70763
  configAccount: readOnepasswordAccount(),
70195
- onNeedsPicker: (accounts) => new Promise((resolve3) => {
70764
+ onNeedsPicker: (accounts) => new Promise((resolve4) => {
70196
70765
  setOpAccounts(accounts);
70197
70766
  setOpAccountCursor(0);
70198
70767
  opPickerResolver.current = (url2) => {
70199
70768
  if (url2?.trim())
70200
70769
  saveOnepasswordAccount(url2.trim(), "global");
70201
70770
  opPickerResolver.current = null;
70202
- resolve3(url2);
70771
+ resolve4(url2);
70203
70772
  };
70204
70773
  setMode("pick_op_account");
70205
70774
  })
@@ -71645,8 +72214,8 @@ async function startConfigTui() {
71645
72214
  const renderer = await createCliRenderer2({
71646
72215
  exitOnCtrlC: false
71647
72216
  });
71648
- await new Promise((resolve3) => {
71649
- renderer.once("destroy", () => resolve3());
72217
+ await new Promise((resolve4) => {
72218
+ renderer.once("destroy", () => resolve4());
71650
72219
  createRoot2(renderer).render(/* @__PURE__ */ jsxDEV17(App, {
71651
72220
  requestLogin
71652
72221
  }, undefined, false, undefined, this));
@@ -71758,6 +72327,7 @@ var init_terminal_isolation = __esm(() => {
71758
72327
  var exports_claude_runner = {};
71759
72328
  __export(exports_claude_runner, {
71760
72329
  runClaudeWithProxy: () => runClaudeWithProxy,
72330
+ resolveContextWindowEnv: () => resolveContextWindowEnv,
71761
72331
  managedSettingsForcesClaudeAi: () => managedSettingsForcesClaudeAi,
71762
72332
  isProxyAuthMode: () => isProxyAuthMode,
71763
72333
  computeMainThreadContextWindow: () => computeMainThreadContextWindow,
@@ -72021,6 +72591,27 @@ async function computeMainThreadContextWindow(config3, cachePath) {
72021
72591
  }
72022
72592
  return Number.isFinite(min) ? min : 0;
72023
72593
  }
72594
+ function resolveContextWindowEnv(realWindow, processEnv = process.env) {
72595
+ const vars = {};
72596
+ if (!(realWindow > 0))
72597
+ return { vars };
72598
+ if (!processEnv[ENV.CLAUDE_CODE_MAX_CONTEXT_TOKENS]) {
72599
+ vars[ENV.CLAUDE_CODE_MAX_CONTEXT_TOKENS] = String(realWindow);
72600
+ }
72601
+ if (processEnv[ENV.CLAUDE_CODE_AUTO_COMPACT_WINDOW])
72602
+ return { vars };
72603
+ if (realWindow >= MIN_AUTO_COMPACT_WINDOW) {
72604
+ vars[ENV.CLAUDE_CODE_AUTO_COMPACT_WINDOW] = String(realWindow);
72605
+ return {
72606
+ vars,
72607
+ notice: `[claudish] Auto-compact window: ${realWindow.toLocaleString()} tokens ` + "(Claude Code compacts before the backend's real limit)"
72608
+ };
72609
+ }
72610
+ return {
72611
+ vars,
72612
+ 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."
72613
+ };
72614
+ }
72024
72615
  async function runClaudeWithProxy(config3, proxyUrl, onCleanup) {
72025
72616
  const hasProfileMappings = config3.modelOpus || config3.modelSonnet || config3.modelHaiku || config3.modelSubagent;
72026
72617
  const modelId = config3.model || (hasProfileMappings || config3.monitor ? undefined : "unknown");
@@ -72091,16 +72682,11 @@ async function runClaudeWithProxy(config3, proxyUrl, onCleanup) {
72091
72682
  } else {
72092
72683
  env.ANTHROPIC_API_KEY = "sk-ant-api03-placeholder-not-used-proxy-handles-auth-with-openrouter-key-xxxxxxxxxxxxxxxxxxxxx";
72093
72684
  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
- }
72685
+ const realWindow = await computeMainThreadContextWindow(config3);
72686
+ const contextEnv = resolveContextWindowEnv(realWindow, process.env);
72687
+ Object.assign(env, contextEnv.vars);
72688
+ if (contextEnv.notice && !config3.quiet) {
72689
+ console.error(contextEnv.notice);
72104
72690
  }
72105
72691
  }
72106
72692
  }
@@ -72179,10 +72765,10 @@ Or set CLAUDE_PATH to your custom installation:`);
72179
72765
  });
72180
72766
  }
72181
72767
  setupSignalHandlers(proc, tempSettingsPath, config3.quiet, onCleanup);
72182
- const exitCode = await new Promise((resolve3) => {
72768
+ const exitCode = await new Promise((resolve4) => {
72183
72769
  proc.on("exit", (code) => {
72184
72770
  setClaudeCodeRunning(false);
72185
- resolve3(code ?? 1);
72771
+ resolve4(code ?? 1);
72186
72772
  });
72187
72773
  });
72188
72774
  releaseTerminalIsolation();
@@ -72262,9 +72848,9 @@ async function findClaudeBinary() {
72262
72848
  proc.stdout?.on("data", (data) => {
72263
72849
  output += data.toString();
72264
72850
  });
72265
- const exitCode = await new Promise((resolve3) => {
72851
+ const exitCode = await new Promise((resolve4) => {
72266
72852
  proc.on("exit", (code) => {
72267
- resolve3(code ?? 1);
72853
+ resolve4(code ?? 1);
72268
72854
  });
72269
72855
  });
72270
72856
  if (exitCode === 0 && output.trim()) {
@@ -72287,7 +72873,7 @@ async function checkClaudeInstalled() {
72287
72873
  var restoreTerminal = null, MIN_AUTO_COMPACT_WINDOW = 200000;
72288
72874
  var init_claude_runner = __esm(() => {
72289
72875
  init_model_catalog();
72290
- init_config();
72876
+ init_config2();
72291
72877
  init_logger();
72292
72878
  init_profile_config();
72293
72879
  init_model_discovery();
@@ -72654,9 +73240,9 @@ async function subscribeToMagmux(sockPath, onEvent) {
72654
73240
  for (let attempt = 0;attempt < 40; attempt++) {
72655
73241
  if (existsSync24(sockPath)) {
72656
73242
  try {
72657
- client = await new Promise((resolve3, reject) => {
73243
+ client = await new Promise((resolve4, reject) => {
72658
73244
  const s = netConnect(sockPath);
72659
- s.once("connect", () => resolve3(s));
73245
+ s.once("connect", () => resolve4(s));
72660
73246
  s.once("error", reject);
72661
73247
  });
72662
73248
  break;
@@ -72667,7 +73253,7 @@ async function subscribeToMagmux(sockPath, onEvent) {
72667
73253
  if (!client) {
72668
73254
  return { results: null, client: null };
72669
73255
  }
72670
- return await new Promise((resolve3) => {
73256
+ return await new Promise((resolve4) => {
72671
73257
  let buf = "";
72672
73258
  let finalResults = null;
72673
73259
  client.on("data", (chunk) => {
@@ -72690,7 +73276,7 @@ async function subscribeToMagmux(sockPath, onEvent) {
72690
73276
  } catch {}
72691
73277
  }
72692
73278
  });
72693
- const done = () => resolve3({ results: finalResults, client });
73279
+ const done = () => resolve4({ results: finalResults, client });
72694
73280
  client.once("end", done);
72695
73281
  client.once("close", done);
72696
73282
  client.once("error", done);
@@ -72766,9 +73352,9 @@ async function runWithGrid(sessionPath, models, input, opts) {
72766
73352
  });
72767
73353
  const sockPath = `/tmp/magmux-${proc.pid}.sock`;
72768
73354
  const subscription = subscribeToMagmux(sockPath);
72769
- const procExit = new Promise((resolve3) => {
72770
- proc.on("exit", () => resolve3());
72771
- proc.on("error", () => resolve3());
73355
+ const procExit = new Promise((resolve4) => {
73356
+ proc.on("exit", () => resolve4());
73357
+ proc.on("error", () => resolve4());
72772
73358
  });
72773
73359
  const [{ results }] = await Promise.all([subscription, procExit]);
72774
73360
  const status = buildTeamStatus(manifest, startedAt, results?.panes ?? null);
@@ -72797,7 +73383,7 @@ init_op_source();
72797
73383
  init_startup_trace();
72798
73384
  var import_dotenv3 = __toESM(require_main(), 1);
72799
73385
  import { existsSync as existsSync25, readFileSync as readFileSync23 } from "fs";
72800
- import { join as join28, resolve as resolve3 } from "path";
73386
+ import { join as join28, resolve as resolve4 } from "path";
72801
73387
  import_dotenv3.config({ quiet: true });
72802
73388
  function classifyStartupKind() {
72803
73389
  const argv = process.argv.slice(2);
@@ -72895,7 +73481,7 @@ async function applyOpImport() {
72895
73481
  async function applyConfigOverride() {
72896
73482
  const { planConfigOverride: planConfigOverride2, setConfigFileOverride: setConfigFileOverride2 } = await Promise.resolve().then(() => exports_config_override);
72897
73483
  const plan = planConfigOverride2(process.argv.slice(2), process.env, {
72898
- resolve: resolve3,
73484
+ resolve: resolve4,
72899
73485
  exists: existsSync25
72900
73486
  });
72901
73487
  if (plan.kind === "none")
@@ -73004,7 +73590,7 @@ async function runCli() {
73004
73590
  const endImports = beginSpan("startup:cli-imports");
73005
73591
  const { checkClaudeInstalled: checkClaudeInstalled2, runClaudeWithProxy: runClaudeWithProxy2 } = await Promise.resolve().then(() => (init_claude_runner(), exports_claude_runner));
73006
73592
  const { parseArgs: parseArgs2, getVersion: getVersion4 } = await Promise.resolve().then(() => (init_cli(), exports_cli));
73007
- const { DEFAULT_PORT_RANGE: DEFAULT_PORT_RANGE2 } = await Promise.resolve().then(() => (init_config(), exports_config));
73593
+ const { DEFAULT_PORT_RANGE: DEFAULT_PORT_RANGE2 } = await Promise.resolve().then(() => (init_config2(), exports_config));
73008
73594
  const { selectModel: selectModel2, promptForApiKey: promptForApiKey2 } = await Promise.resolve().then(() => (init_model_selector(), exports_model_selector));
73009
73595
  const {
73010
73596
  resolveModelProvider: resolveModelProvider2,
@@ -73097,11 +73683,11 @@ Team Status`);
73097
73683
  You can disable it anytime with: --no-auto-approve
73098
73684
 
73099
73685
  `);
73100
- const answer = await new Promise((resolve4) => {
73686
+ const answer = await new Promise((resolve5) => {
73101
73687
  const rl = createInterface2({ input: process.stdin, output: process.stderr });
73102
73688
  rl.question("Enable auto-approve? [Y/n] ", (ans) => {
73103
73689
  rl.close();
73104
- resolve4(ans.trim().toLowerCase());
73690
+ resolve5(ans.trim().toLowerCase());
73105
73691
  });
73106
73692
  });
73107
73693
  const declined = answer === "n" || answer === "no";