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.
- package/dist/index.js +1300 -714
- 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.
|
|
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
|
|
30994
|
-
import { homedir as
|
|
30995
|
-
import { join as
|
|
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 =
|
|
31016
|
-
|
|
31570
|
+
const sessionDir = join14(homedir14(), ".claudish", "sessions", sessionId);
|
|
31571
|
+
mkdirSync6(sessionDir, { recursive: true });
|
|
31017
31572
|
if (opts.prompt) {
|
|
31018
|
-
|
|
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(
|
|
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
|
-
|
|
31647
|
+
writeFileSync7(join14(sessionDir, "stderr.log"), entry.stderr, "utf-8");
|
|
31093
31648
|
}
|
|
31094
|
-
|
|
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
|
|
31268
|
-
import { homedir as
|
|
31269
|
-
import { join as
|
|
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 &&
|
|
31920
|
+
if (!forceRefresh && existsSync13(RECOMMENDED_MODELS_CACHE_PATH)) {
|
|
31366
31921
|
try {
|
|
31367
|
-
const cacheData = JSON.parse(
|
|
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 =
|
|
31384
|
-
|
|
31385
|
-
|
|
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 (
|
|
31951
|
+
if (existsSync13(RECOMMENDED_MODELS_CACHE_PATH)) {
|
|
31397
31952
|
try {
|
|
31398
|
-
const cacheData = JSON.parse(
|
|
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 =
|
|
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(([[,
|
|
32544
|
+
return this.#matchResult[0].map(([[, route2]]) => route2);
|
|
31990
32545
|
}
|
|
31991
32546
|
get routePath() {
|
|
31992
|
-
return this.#matchResult[0].map(([[,
|
|
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((
|
|
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((
|
|
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((
|
|
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
|
-
|
|
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((
|
|
38126
|
-
timer = setTimeout(() =>
|
|
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
|
-
|
|
39013
|
-
|
|
39014
|
-
|
|
39015
|
-
|
|
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((
|
|
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((
|
|
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
|
|
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
|
|
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
|
|
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 (${
|
|
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 =
|
|
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((
|
|
43011
|
+
return new Promise((resolve3, reject) => {
|
|
42158
43012
|
const queuedRequest = {
|
|
42159
43013
|
fetchFn,
|
|
42160
|
-
resolve:
|
|
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((
|
|
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((
|
|
43868
|
+
return new Promise((resolve3, reject) => {
|
|
43254
43869
|
const queuedRequest = {
|
|
43255
43870
|
fetchFn,
|
|
43256
|
-
resolve:
|
|
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((
|
|
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((
|
|
44268
|
+
return new Promise((resolve3, reject) => {
|
|
43654
44269
|
const queuedRequest = {
|
|
43655
44270
|
fetchFn,
|
|
43656
|
-
resolve:
|
|
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((
|
|
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
|
|
45070
|
+
import { join as join20, resolve as resolve3 } from "path";
|
|
44447
45071
|
function validateSessionPath(sessionPath) {
|
|
44448
|
-
const resolved =
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
|
48298
|
+
let resolve4;
|
|
47675
48299
|
let reject;
|
|
47676
48300
|
const promise3 = new Promise((res, rej) => {
|
|
47677
|
-
|
|
48301
|
+
resolve4 = res;
|
|
47678
48302
|
reject = rej;
|
|
47679
48303
|
});
|
|
47680
|
-
return { promise: promise3, resolve:
|
|
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:
|
|
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(() =>
|
|
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((
|
|
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
|
-
|
|
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
|
|
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
|
|
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((
|
|
62677
|
-
resolveQuit =
|
|
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
|
|
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
|
-
|
|
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((
|
|
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((
|
|
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
|
-
|
|
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((
|
|
71649
|
-
renderer.once("destroy", () =>
|
|
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
|
-
|
|
72095
|
-
|
|
72096
|
-
|
|
72097
|
-
|
|
72098
|
-
|
|
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((
|
|
72768
|
+
const exitCode = await new Promise((resolve4) => {
|
|
72183
72769
|
proc.on("exit", (code) => {
|
|
72184
72770
|
setClaudeCodeRunning(false);
|
|
72185
|
-
|
|
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((
|
|
72851
|
+
const exitCode = await new Promise((resolve4) => {
|
|
72266
72852
|
proc.on("exit", (code) => {
|
|
72267
|
-
|
|
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
|
-
|
|
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((
|
|
73243
|
+
client = await new Promise((resolve4, reject) => {
|
|
72658
73244
|
const s = netConnect(sockPath);
|
|
72659
|
-
s.once("connect", () =>
|
|
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((
|
|
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 = () =>
|
|
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((
|
|
72770
|
-
proc.on("exit", () =>
|
|
72771
|
-
proc.on("error", () =>
|
|
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
|
|
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:
|
|
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(() => (
|
|
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((
|
|
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
|
-
|
|
73690
|
+
resolve5(ans.trim().toLowerCase());
|
|
73105
73691
|
});
|
|
73106
73692
|
});
|
|
73107
73693
|
const declined = answer === "n" || answer === "no";
|