claudish 14.2.0 → 14.4.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 +264 -25
- package/package.json +5 -5
- package/skills/claudish-usage/SKILL.md +6 -2
package/dist/index.js
CHANGED
|
@@ -575,7 +575,7 @@ var init_onepassword_config = __esm(() => {
|
|
|
575
575
|
});
|
|
576
576
|
|
|
577
577
|
// src/version.ts
|
|
578
|
-
var VERSION = "14.
|
|
578
|
+
var VERSION = "14.4.0";
|
|
579
579
|
|
|
580
580
|
// src/redact.ts
|
|
581
581
|
function isPlaceholder(value) {
|
|
@@ -40453,6 +40453,20 @@ var init_claude_sse_sink = __esm(() => {
|
|
|
40453
40453
|
init_redact();
|
|
40454
40454
|
});
|
|
40455
40455
|
|
|
40456
|
+
// src/in-band-api-error.ts
|
|
40457
|
+
function formatInBandApiError(code, message) {
|
|
40458
|
+
return `
|
|
40459
|
+
|
|
40460
|
+
[API Error: ${code} ${message}]`;
|
|
40461
|
+
}
|
|
40462
|
+
function parseInBandApiError(answer, opts = {}) {
|
|
40463
|
+
const trimmed = answer.trim();
|
|
40464
|
+
const m = /^\[API Error: (\S+)([\s\S]*)\]$/.exec(trimmed) ?? (opts.trailing ? /\n\n\[API Error: (\S+)([^\n\]]*)\]$/.exec(trimmed) : null);
|
|
40465
|
+
if (!m)
|
|
40466
|
+
return null;
|
|
40467
|
+
return { code: m[1], text: `${m[1]}${m[2]}` };
|
|
40468
|
+
}
|
|
40469
|
+
|
|
40456
40470
|
// src/handlers/shared/stream-parsers/responses/parallel-wrapper.ts
|
|
40457
40471
|
function stripFunctionsPrefix(name) {
|
|
40458
40472
|
return name.startsWith("functions.") ? name.slice("functions.".length) : name;
|
|
@@ -41161,9 +41175,7 @@ function sum(values) {
|
|
|
41161
41175
|
function apiErrorText(code, message, opts) {
|
|
41162
41176
|
const isCtxOverflow = code === "context_length_exceeded" || /context (length|window)|exceeds? the context|too long|maximum context/i.test(message);
|
|
41163
41177
|
if (!isCtxOverflow)
|
|
41164
|
-
return
|
|
41165
|
-
|
|
41166
|
-
[API Error: ${code} ${message}]`;
|
|
41178
|
+
return formatInBandApiError(code, message);
|
|
41167
41179
|
const cap = opts.contextWindow && opts.contextWindow > 0 ? ` ~${Math.round(opts.contextWindow / 1000)}K tokens` : "";
|
|
41168
41180
|
return `
|
|
41169
41181
|
|
|
@@ -57687,6 +57699,10 @@ function findCatalogEntry(entries, model) {
|
|
|
57687
57699
|
return loweredId;
|
|
57688
57700
|
return entries.find((entry) => entry.aliases.some((alias) => alias.toLowerCase() === lowered));
|
|
57689
57701
|
}
|
|
57702
|
+
function catalogModelId(model, cachePath) {
|
|
57703
|
+
const entries = cachePath ? readAllModelsCache(cachePath)?.entries : getCatalogEntries();
|
|
57704
|
+
return entries ? findCatalogEntry(entries, model)?.modelId : undefined;
|
|
57705
|
+
}
|
|
57690
57706
|
function gatherFromConnections(entry, candidates, excluded, unmappedRoutes, cachePath) {
|
|
57691
57707
|
for (const connection of entry.aggregators ?? []) {
|
|
57692
57708
|
if (connection.routeStatus !== "mapped")
|
|
@@ -59655,7 +59671,9 @@ function toSlotRow(id, snap, acct, now = Date.now(), upstreamRecords = []) {
|
|
|
59655
59671
|
idle_seconds: idle,
|
|
59656
59672
|
activity: terminal ? null : snap.turnEndRecordMissing && snap.activity ? `${snap.activity}: turn_end_record_missing` : snap.agentCountMismatch && snap.activity ? `${snap.activity}: agent_count_mismatch` : snap.activity,
|
|
59657
59673
|
pane: snap.paneId || null,
|
|
59658
|
-
recent: snap.recent
|
|
59674
|
+
recent: snap.recent,
|
|
59675
|
+
agent_requested: id.agentRequested ?? null,
|
|
59676
|
+
agent: snap.agent ?? null
|
|
59659
59677
|
};
|
|
59660
59678
|
}
|
|
59661
59679
|
var init_slot_row = __esm(() => {
|
|
@@ -80167,6 +80185,18 @@ function reservedMessage(flag) {
|
|
|
80167
80185
|
return `${flag} would start a different claudish mode inside the pane`;
|
|
80168
80186
|
return null;
|
|
80169
80187
|
}
|
|
80188
|
+
function requestedAgentOf(flags) {
|
|
80189
|
+
let agent = null;
|
|
80190
|
+
for (let i = 0;i < flags.length; i++) {
|
|
80191
|
+
const [name, inline] = splitFlag(flags[i]);
|
|
80192
|
+
if (name !== "--agent")
|
|
80193
|
+
continue;
|
|
80194
|
+
const value = inline ?? flags[i + 1];
|
|
80195
|
+
if (value && !value.startsWith("-"))
|
|
80196
|
+
agent = value;
|
|
80197
|
+
}
|
|
80198
|
+
return agent;
|
|
80199
|
+
}
|
|
80170
80200
|
function flagsRemoveRead(flags) {
|
|
80171
80201
|
for (let i = 0;i < flags.length; i++) {
|
|
80172
80202
|
const [name, inline] = splitFlag(flags[i]);
|
|
@@ -82466,6 +82496,7 @@ function initialFollowerState(resolvePath = safeRealpath) {
|
|
|
82466
82496
|
mainAssistantIds: [],
|
|
82467
82497
|
mainAssistantSeen: new Set,
|
|
82468
82498
|
session: { stopHooksSeen: false, provenHookless: false },
|
|
82499
|
+
agentSetting: null,
|
|
82469
82500
|
records: 0,
|
|
82470
82501
|
resolvePath
|
|
82471
82502
|
};
|
|
@@ -82790,6 +82821,8 @@ function applyRecord(state, record, offset) {
|
|
|
82790
82821
|
if (r.isSidechain)
|
|
82791
82822
|
return state;
|
|
82792
82823
|
noteMainAssistant(state, r);
|
|
82824
|
+
if (r.type === "agent-setting" && typeof r.agentSetting === "string" && r.agentSetting)
|
|
82825
|
+
state.agentSetting = r.agentSetting;
|
|
82793
82826
|
if (r.type === "system" && r.subtype === "stop_hook_summary") {
|
|
82794
82827
|
state.session.stopHooksSeen = true;
|
|
82795
82828
|
state.session.provenHookless = false;
|
|
@@ -83072,7 +83105,8 @@ class TranscriptFollower {
|
|
|
83072
83105
|
usage: { tokensIn, tokensOut },
|
|
83073
83106
|
toolCalls: this.state.toolUseIds.size,
|
|
83074
83107
|
lastAppendAt: this.lastAppend,
|
|
83075
|
-
session: { ...this.state.session }
|
|
83108
|
+
session: { ...this.state.session },
|
|
83109
|
+
agentSetting: this.state.agentSetting
|
|
83076
83110
|
};
|
|
83077
83111
|
}
|
|
83078
83112
|
}
|
|
@@ -85146,7 +85180,8 @@ ${screenTailOf(this.screen)}`
|
|
|
85146
85180
|
shape: this.shape,
|
|
85147
85181
|
turnEndRecordMissing: !terminal && this.phase === "RUNNING" && this.activity === "finishing" && this.turnEndMissing,
|
|
85148
85182
|
agentCountMismatch: !terminal && this.phase === "RUNNING" && this.agentMismatch !== null,
|
|
85149
|
-
recent: cur && cur.recent.length > 0 ? cur.recent : null
|
|
85183
|
+
recent: cur && cur.recent.length > 0 ? cur.recent : null,
|
|
85184
|
+
agent: view.agentSetting
|
|
85150
85185
|
};
|
|
85151
85186
|
}
|
|
85152
85187
|
phaseActivity() {
|
|
@@ -86381,14 +86416,16 @@ function neverSpawnedRow(e, m) {
|
|
|
86381
86416
|
idle_seconds: null,
|
|
86382
86417
|
activity: null,
|
|
86383
86418
|
pane: null,
|
|
86384
|
-
recent: null
|
|
86419
|
+
recent: null,
|
|
86420
|
+
agent_requested: e.agentRequested,
|
|
86421
|
+
agent: null
|
|
86385
86422
|
};
|
|
86386
86423
|
}
|
|
86387
86424
|
function liveSlotRow(run, e) {
|
|
86388
86425
|
if (!e.session)
|
|
86389
86426
|
return neverSpawnedRow(e, run.status.models[e.id]);
|
|
86390
86427
|
const snap = e.session.snapshot();
|
|
86391
|
-
return toSlotRow({ slot: e.id, model: e.model, spawnModel: e.spawnModel }, snap, refreshAccounting(e, snap), Date.now(), upstreamRecordsFor(snap, upstreamLogPath(run.path, e.id)));
|
|
86428
|
+
return toSlotRow({ slot: e.id, model: e.model, spawnModel: e.spawnModel, agentRequested: e.agentRequested }, snap, refreshAccounting(e, snap), Date.now(), upstreamRecordsFor(snap, upstreamLogPath(run.path, e.id)));
|
|
86392
86429
|
}
|
|
86393
86430
|
function outcomeOf(states) {
|
|
86394
86431
|
const ok = states.filter((s) => s === "COMPLETED").length;
|
|
@@ -86445,7 +86482,9 @@ function teamRunRowFromDisk(path, status) {
|
|
|
86445
86482
|
idle_seconds: null,
|
|
86446
86483
|
activity: null,
|
|
86447
86484
|
pane: m?.pane ?? null,
|
|
86448
|
-
recent: null
|
|
86485
|
+
recent: null,
|
|
86486
|
+
agent_requested: m?.agentRequested ?? null,
|
|
86487
|
+
agent: m?.agent ?? null
|
|
86449
86488
|
};
|
|
86450
86489
|
});
|
|
86451
86490
|
const settled = slots.length > 0 && slots.every((s) => isTerminalState(s.state));
|
|
@@ -86597,7 +86636,9 @@ function classifyRunOutput(input) {
|
|
|
86597
86636
|
return v;
|
|
86598
86637
|
}
|
|
86599
86638
|
function classifyTurnSignals(input) {
|
|
86600
|
-
const {
|
|
86639
|
+
const { stopReason, promptRead } = input;
|
|
86640
|
+
const inBand = input.apiError ? null : parseInBandApiError(input.answer, { trailing: promptRead?.complete === false });
|
|
86641
|
+
const apiError = input.apiError ?? (inBand ? { status: null, text: inBand.text } : null);
|
|
86601
86642
|
if (apiError) {
|
|
86602
86643
|
const status = apiError.status === null ? "" : ` (status ${apiError.status})`;
|
|
86603
86644
|
return {
|
|
@@ -86824,6 +86865,7 @@ async function startModelsIn(flight, sessionPath, opts) {
|
|
|
86824
86865
|
const flagCheck = checkChildFlags(claudeFlags);
|
|
86825
86866
|
if (!flagCheck.ok)
|
|
86826
86867
|
throw new Error(`invalid_args: ${flagCheck.message}`);
|
|
86868
|
+
const agentRequested = requestedAgentOf(claudeFlags);
|
|
86827
86869
|
const path = resolve5(sessionPath);
|
|
86828
86870
|
const manifest = JSON.parse(readFileSync34(join52(path, "manifest.json"), "utf-8"));
|
|
86829
86871
|
const statusPath = join52(path, "status.json");
|
|
@@ -86940,6 +86982,7 @@ async function startModelsIn(flight, sessionPath, opts) {
|
|
|
86940
86982
|
lastActivityAt: snap.lastActivityAt,
|
|
86941
86983
|
turnsCompleted: snap.turnsCompleted,
|
|
86942
86984
|
claudeCodeVersion: snap.claudeCodeVersion,
|
|
86985
|
+
agent: snap.agent ?? null,
|
|
86943
86986
|
anomalies: notableAnomalies(snap),
|
|
86944
86987
|
...accountingFields(refreshAccounting(e, snap))
|
|
86945
86988
|
};
|
|
@@ -87111,6 +87154,7 @@ async function startModelsIn(flight, sessionPath, opts) {
|
|
|
87111
87154
|
id,
|
|
87112
87155
|
model: entry.model,
|
|
87113
87156
|
spawnModel,
|
|
87157
|
+
agentRequested,
|
|
87114
87158
|
tokenFile,
|
|
87115
87159
|
session: null,
|
|
87116
87160
|
acct: emptyAccounting(provider),
|
|
@@ -87129,6 +87173,8 @@ async function startModelsIn(flight, sessionPath, opts) {
|
|
|
87129
87173
|
model: entry.model,
|
|
87130
87174
|
spawnModel,
|
|
87131
87175
|
provider,
|
|
87176
|
+
agentRequested,
|
|
87177
|
+
agent: null,
|
|
87132
87178
|
sessionUuid: uuid,
|
|
87133
87179
|
transcriptPath,
|
|
87134
87180
|
pane: null,
|
|
@@ -87668,6 +87714,8 @@ function sessionRowOf(info, upstreamRecords = []) {
|
|
|
87668
87714
|
activity: terminal ? null : info.activity,
|
|
87669
87715
|
pane: info.pane,
|
|
87670
87716
|
recent: info.recent,
|
|
87717
|
+
agent_requested: info.agentRequested ?? null,
|
|
87718
|
+
agent: info.agent ?? null,
|
|
87671
87719
|
session_id: info.sessionId,
|
|
87672
87720
|
started_at: info.startedAt,
|
|
87673
87721
|
completed_at: info.completedAt,
|
|
@@ -87705,6 +87753,8 @@ function toMetaRecord(info, cwd) {
|
|
|
87705
87753
|
captureSource: info.captureSource,
|
|
87706
87754
|
turnSource: info.turnSource,
|
|
87707
87755
|
timeoutSeconds: info.timeoutSeconds,
|
|
87756
|
+
agentRequested: info.agentRequested ?? null,
|
|
87757
|
+
agent: info.agent ?? null,
|
|
87708
87758
|
cwd
|
|
87709
87759
|
};
|
|
87710
87760
|
}
|
|
@@ -87998,6 +88048,7 @@ class SessionManager {
|
|
|
87998
88048
|
const sessionId = opts.sessionId ?? randomUUID8().slice(0, 8);
|
|
87999
88049
|
const claudeSessionId = randomUUID8();
|
|
88000
88050
|
const timeout = normaliseTimeoutSeconds(opts.timeoutSeconds);
|
|
88051
|
+
const agentRequested = requestedAgentOf(opts.claudishFlags ?? []);
|
|
88001
88052
|
const startedAt = new Date().toISOString();
|
|
88002
88053
|
const sessionDir = opts.sessionDir ?? join54(this.sessionsDir, sessionId);
|
|
88003
88054
|
mkdirSync23(sessionDir, { recursive: true });
|
|
@@ -88011,7 +88062,8 @@ class SessionManager {
|
|
|
88011
88062
|
model: opts.model,
|
|
88012
88063
|
timeoutSeconds: timeout,
|
|
88013
88064
|
claudeSessionId,
|
|
88014
|
-
...opts.setupFrom ? { setupFrom: opts.setupFrom } : {}
|
|
88065
|
+
...opts.setupFrom ? { setupFrom: opts.setupFrom } : {},
|
|
88066
|
+
...agentRequested ? { agentRequested } : {}
|
|
88015
88067
|
});
|
|
88016
88068
|
if (inheritedLayer) {
|
|
88017
88069
|
writeFileSync22(join54(sessionDir, INHERITED_SETTINGS), `${JSON.stringify(inheritedLayer, null, 2)}
|
|
@@ -88044,6 +88096,8 @@ class SessionManager {
|
|
|
88044
88096
|
idleSeconds: null,
|
|
88045
88097
|
activity: null,
|
|
88046
88098
|
recent: null,
|
|
88099
|
+
agentRequested,
|
|
88100
|
+
agent: null,
|
|
88047
88101
|
reason: null,
|
|
88048
88102
|
detail: null,
|
|
88049
88103
|
pendingInputs: opts.prompt ? 1 : 0,
|
|
@@ -88299,6 +88353,7 @@ class SessionManager {
|
|
|
88299
88353
|
info.idleSeconds = snap.idleSeconds;
|
|
88300
88354
|
info.activity = snap.activity;
|
|
88301
88355
|
info.recent = snap.recent;
|
|
88356
|
+
info.agent = snap.agent ?? info.agent ?? null;
|
|
88302
88357
|
info.reason = snap.reason;
|
|
88303
88358
|
info.detail = snap.detail;
|
|
88304
88359
|
info.pendingInputs = snap.pendingInputs;
|
|
@@ -88823,6 +88878,8 @@ function diskInfo(d) {
|
|
|
88823
88878
|
idleSeconds: null,
|
|
88824
88879
|
activity: null,
|
|
88825
88880
|
recent: null,
|
|
88881
|
+
agentRequested: metaString(meta?.agentRequested) ?? metaString(spawnRecord?.agentRequested),
|
|
88882
|
+
agent: metaString(meta?.agent),
|
|
88826
88883
|
reason,
|
|
88827
88884
|
detail: meta === null ? NO_TERMINAL_RECORD : metaString(meta.detail),
|
|
88828
88885
|
pendingInputs: 0,
|
|
@@ -89654,15 +89711,15 @@ function stripUnrequestedProvider(spec, required, isCatalogModel = isCanonicalCa
|
|
|
89654
89711
|
return spec;
|
|
89655
89712
|
return spec.slice(spec.indexOf("@") + 1);
|
|
89656
89713
|
}
|
|
89657
|
-
function userRequiredProvidersArg(value, specs) {
|
|
89714
|
+
function userRequiredProvidersArg(value, specs, name = "user_required_provider") {
|
|
89658
89715
|
if (value === undefined || value === null)
|
|
89659
89716
|
return new Set;
|
|
89660
89717
|
if (!Array.isArray(value) || value.some((v) => typeof v !== "string")) {
|
|
89661
|
-
throw new Error(`Invalid '
|
|
89718
|
+
throw new Error(`Invalid '${name}': expected an array of model specs.`);
|
|
89662
89719
|
}
|
|
89663
89720
|
for (const spec of value) {
|
|
89664
89721
|
if (!specs.includes(spec)) {
|
|
89665
|
-
throw new Error(`Invalid '
|
|
89722
|
+
throw new Error(`Invalid '${name}': ${JSON.stringify(spec)} is not one of the ` + `models (${specs.join(", ")}). Entries must match a 'models' or 'judges' entry exactly.`);
|
|
89666
89723
|
}
|
|
89667
89724
|
}
|
|
89668
89725
|
return new Set(value);
|
|
@@ -89690,6 +89747,132 @@ function strippedNote(stripped) {
|
|
|
89690
89747
|
return;
|
|
89691
89748
|
return `Provider removed so claudish routing decides: ${stripped.map((x) => `${x.from} -> ${x.to}`).join(", ")}. ` + "Pass 'user_required_provider' only when the user named the provider.";
|
|
89692
89749
|
}
|
|
89750
|
+
function liveModelChoiceFacts(opts = {}) {
|
|
89751
|
+
const recommended = readLastRecommendedModels()?.models ?? null;
|
|
89752
|
+
if (!recommended || recommended.length === 0) {
|
|
89753
|
+
log("[mcp] model check skipped: no recommended models list on disk");
|
|
89754
|
+
}
|
|
89755
|
+
let rules;
|
|
89756
|
+
return {
|
|
89757
|
+
recommended: recommended && recommended.length > 0 ? recommended : null,
|
|
89758
|
+
userRule: (model) => {
|
|
89759
|
+
rules ??= loadRoutingRules();
|
|
89760
|
+
return matchRoutingRuleKey(model, rules) !== null;
|
|
89761
|
+
},
|
|
89762
|
+
inCatalog: (model) => !gatherRouteCandidates(model).catalogMiss,
|
|
89763
|
+
namespaceClaim: (model) => gatherRouteCandidates(model).candidates.length > 0,
|
|
89764
|
+
canonicalId: (model) => catalogModelId(model),
|
|
89765
|
+
catalogMissAllowed: (model) => {
|
|
89766
|
+
if (opts.projectRulesReadable === false)
|
|
89767
|
+
return true;
|
|
89768
|
+
if (!gatherRouteCandidates(model).catalogReadable)
|
|
89769
|
+
return true;
|
|
89770
|
+
const fallback = effectiveDefaultProvider();
|
|
89771
|
+
return fallback !== "" && fallback !== DEFAULT_FALLBACK_PROVIDER;
|
|
89772
|
+
}
|
|
89773
|
+
};
|
|
89774
|
+
}
|
|
89775
|
+
function familyOf(id) {
|
|
89776
|
+
return /^[a-z]+/i.exec(id)?.[0].toLowerCase() ?? id.toLowerCase();
|
|
89777
|
+
}
|
|
89778
|
+
function bareNameVerdict(spec, recommendedIds, facts) {
|
|
89779
|
+
if (parseModelChain(spec).length !== 1 || nativeRouteFor(spec) !== null)
|
|
89780
|
+
return;
|
|
89781
|
+
const parsed = parseModelSpec(spec);
|
|
89782
|
+
if (parsed.isExplicitProvider || parsed.isLegacySyntax)
|
|
89783
|
+
return;
|
|
89784
|
+
const model = parsed.model;
|
|
89785
|
+
if (recommendedIds.has(model) || facts.userRule(model))
|
|
89786
|
+
return;
|
|
89787
|
+
const canonical = facts.canonicalId?.(model);
|
|
89788
|
+
if (canonical !== undefined && recommendedIds.has(canonical))
|
|
89789
|
+
return;
|
|
89790
|
+
if (facts.inCatalog(model))
|
|
89791
|
+
return { kind: "not_recommended", model };
|
|
89792
|
+
if (facts.namespaceClaim(model) || facts.catalogMissAllowed?.(model))
|
|
89793
|
+
return;
|
|
89794
|
+
return { kind: "unknown_model", model };
|
|
89795
|
+
}
|
|
89796
|
+
function modelChoiceProblems(pairs, required, facts = liveModelChoiceFacts()) {
|
|
89797
|
+
const recommended = facts.recommended;
|
|
89798
|
+
if (!recommended)
|
|
89799
|
+
return [];
|
|
89800
|
+
const ids = new Set(recommended.map((m) => m.id));
|
|
89801
|
+
const problems = [];
|
|
89802
|
+
for (const { given, spec } of pairs) {
|
|
89803
|
+
if (required.has(given))
|
|
89804
|
+
continue;
|
|
89805
|
+
const verdict = bareNameVerdict(spec, ids, facts);
|
|
89806
|
+
if (!verdict)
|
|
89807
|
+
continue;
|
|
89808
|
+
const { kind, model } = verdict;
|
|
89809
|
+
const family = familyOf(model);
|
|
89810
|
+
const matching = [
|
|
89811
|
+
...new Set(recommended.filter((m) => familyOf(m.id) === family || m.provider.toLowerCase() === model.toLowerCase()).sort(compareByReleaseDateDesc).map((m) => m.id))
|
|
89812
|
+
];
|
|
89813
|
+
problems.push({ spec: given, kind, matching });
|
|
89814
|
+
}
|
|
89815
|
+
if (problems.length > 0) {
|
|
89816
|
+
log(`[mcp] model check: ${problems.map((p) => `${p.spec} ${p.kind}`).join(", ")}`);
|
|
89817
|
+
}
|
|
89818
|
+
return problems;
|
|
89819
|
+
}
|
|
89820
|
+
function recommendedChoices(recommended) {
|
|
89821
|
+
const byId = new Map;
|
|
89822
|
+
for (const m of recommended) {
|
|
89823
|
+
const row = byId.get(m.id);
|
|
89824
|
+
if (row) {
|
|
89825
|
+
row.tiers.push(m.recommendationTier);
|
|
89826
|
+
continue;
|
|
89827
|
+
}
|
|
89828
|
+
byId.set(m.id, {
|
|
89829
|
+
id: m.id,
|
|
89830
|
+
vendor: m.provider,
|
|
89831
|
+
tiers: [m.recommendationTier],
|
|
89832
|
+
...m.releaseDate ? { released: m.releaseDate } : {}
|
|
89833
|
+
});
|
|
89834
|
+
}
|
|
89835
|
+
return [...byId.values()];
|
|
89836
|
+
}
|
|
89837
|
+
function recommendedSearchMatches(query, recommended) {
|
|
89838
|
+
const words = query.toLowerCase().split(/[\s,/]+/).filter((w) => w.length >= 2);
|
|
89839
|
+
if (words.length === 0)
|
|
89840
|
+
return [];
|
|
89841
|
+
const hits = recommended.filter((m) => {
|
|
89842
|
+
const id = m.id.toLowerCase();
|
|
89843
|
+
return words.some((w) => id.includes(w) || familyOf(m.id) === familyOf(w) || m.provider.toLowerCase() === w);
|
|
89844
|
+
});
|
|
89845
|
+
return recommendedChoices([...hits].sort(compareByReleaseDateDesc));
|
|
89846
|
+
}
|
|
89847
|
+
function modelChoiceResult(problems, recommended, how) {
|
|
89848
|
+
const unknown = problems.filter((p) => p.kind === "unknown_model");
|
|
89849
|
+
const old = problems.filter((p) => p.kind === "not_recommended");
|
|
89850
|
+
const lines = [
|
|
89851
|
+
`${unknown.length > 0 ? "unknown_model" : "not_recommended"}: nothing was started.`
|
|
89852
|
+
];
|
|
89853
|
+
if (unknown.length > 0) {
|
|
89854
|
+
lines.push(`Not a model id: ${unknown.map((p) => JSON.stringify(p.spec)).join(", ")}. ` + "Pick the recommended model that best fits the task (for a family name, the newest " + "of the matching ids) and call again with its id.");
|
|
89855
|
+
}
|
|
89856
|
+
if (old.length > 0) {
|
|
89857
|
+
lines.push(`Not in the recommended list: ${old.map((p) => JSON.stringify(p.spec)).join(", ")}. ` + "Re-evaluate the choice against the recommended models below and call again with one " + `of them. If the user named this exact model, call again ${how}.`);
|
|
89858
|
+
}
|
|
89859
|
+
const matching = Object.fromEntries(problems.map((p) => [p.spec, p.matching]));
|
|
89860
|
+
lines.push(`Matching recommended ids: ${JSON.stringify(matching, null, 2)}`);
|
|
89861
|
+
const efforts = effortClarification([...new Set(problems.flatMap((p) => p.matching))]);
|
|
89862
|
+
if (efforts) {
|
|
89863
|
+
lines.push("Recommended effort for those ids, by task type (pass your task's level with the model " + `you pick): ${JSON.stringify(efforts, null, 2)}`);
|
|
89864
|
+
}
|
|
89865
|
+
lines.push(`Recommended models: ${JSON.stringify(recommendedChoices(recommended), null, 2)}`);
|
|
89866
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
89867
|
+
`) }], isError: true };
|
|
89868
|
+
}
|
|
89869
|
+
function modelChoiceStop(pairs, required, how, opts = {}) {
|
|
89870
|
+
const facts = liveModelChoiceFacts(opts);
|
|
89871
|
+
const problems = modelChoiceProblems(pairs, required, facts);
|
|
89872
|
+
if (problems.length === 0 || !facts.recommended)
|
|
89873
|
+
return;
|
|
89874
|
+
return modelChoiceResult(problems, facts.recommended, how);
|
|
89875
|
+
}
|
|
89693
89876
|
function buildChildClaudeFlags(agent, claudeFlags) {
|
|
89694
89877
|
const extra = typeof claudeFlags === "string" ? claudeFlags.split(/\s+/).filter(Boolean) : [];
|
|
89695
89878
|
const named = typeof agent === "string" ? agent.trim() : "";
|
|
@@ -89806,6 +89989,12 @@ function optionalString(raw, name) {
|
|
|
89806
89989
|
throw new ContractErrorException("invalid_args", `'${name}' must be a non-empty string`);
|
|
89807
89990
|
return raw;
|
|
89808
89991
|
}
|
|
89992
|
+
function requiredString(raw, name) {
|
|
89993
|
+
const value = optionalString(raw, name);
|
|
89994
|
+
if (value === undefined)
|
|
89995
|
+
throw new ContractErrorException("invalid_args", `'${name}' is required`);
|
|
89996
|
+
return value;
|
|
89997
|
+
}
|
|
89809
89998
|
function optionalBoolean(raw, name) {
|
|
89810
89999
|
if (raw === undefined || raw === null)
|
|
89811
90000
|
return false;
|
|
@@ -89914,6 +90103,7 @@ ${block.join(`
|
|
|
89914
90103
|
type: "boolean",
|
|
89915
90104
|
description: "True only when the USER named the provider ('use OpenRouter for it'): the " + "'provider@model' spec is then sent as given."
|
|
89916
90105
|
},
|
|
90106
|
+
user_required_model: { type: "boolean", description: USER_REQUIRED_MODEL_DESCRIPTION },
|
|
89917
90107
|
prompt: { type: "string", description: "The prompt to send to the model" },
|
|
89918
90108
|
system_prompt: { type: "string", description: "Optional system prompt" },
|
|
89919
90109
|
effort: {
|
|
@@ -89929,6 +90119,9 @@ ${block.join(`
|
|
|
89929
90119
|
handler: async (args, ctx) => {
|
|
89930
90120
|
try {
|
|
89931
90121
|
const { specs, stripped } = strippedProviders([args.model], new Set(args.user_required_provider === true ? [args.model] : []));
|
|
90122
|
+
const choice = modelChoiceStop([{ given: args.model, spec: specs[0] }], new Set(args.user_required_model === true ? [args.model] : []), "with user_required_model: true");
|
|
90123
|
+
if (choice)
|
|
90124
|
+
return choice;
|
|
89932
90125
|
const recommended = args.effort === undefined ? effortClarification([args.model]) : undefined;
|
|
89933
90126
|
if (recommended) {
|
|
89934
90127
|
return needsEffortResult(recommended, `with effort: "<level>" (effort: "${KEEP_EFFORT}" keeps Claude Code's own)`);
|
|
@@ -90026,6 +90219,26 @@ ${block.join(`
|
|
|
90026
90219
|
return compareByReleaseDateDesc(orderingKey(a.model), orderingKey(b.model));
|
|
90027
90220
|
}).slice(0, maxResults);
|
|
90028
90221
|
const catalogMatches = searchCatalogModels(query, Math.max(maxResults, 5));
|
|
90222
|
+
const recommendedHits = recommendedSearchMatches(query, readLastRecommendedModels()?.models ?? []);
|
|
90223
|
+
const renderRecommendedSection = () => {
|
|
90224
|
+
if (recommendedHits.length === 0)
|
|
90225
|
+
return "";
|
|
90226
|
+
let s = `## Recommended models (pass one of these)
|
|
90227
|
+
|
|
90228
|
+
`;
|
|
90229
|
+
s += `| Bare name | Vendor | Tiers | Released |
|
|
90230
|
+
`;
|
|
90231
|
+
s += `|-----------|--------|-------|----------|
|
|
90232
|
+
`;
|
|
90233
|
+
for (const m of recommendedHits) {
|
|
90234
|
+
s += `| ${m.id} | ${m.vendor} | ${m.tiers.join(", ")} | ${m.released ?? "-"} |
|
|
90235
|
+
`;
|
|
90236
|
+
}
|
|
90237
|
+
return `${s}
|
|
90238
|
+
`;
|
|
90239
|
+
};
|
|
90240
|
+
const useWith = () => recommendedHits.length > 0 ? `
|
|
90241
|
+
Use with: run_prompt(model="${recommendedHits[0].id}", prompt="your prompt")` : "\nNo recommended model matches this query: call `list_models` for the recommended set.";
|
|
90029
90242
|
const renderCatalogSection = () => {
|
|
90030
90243
|
if (catalogMatches.length === 0)
|
|
90031
90244
|
return "";
|
|
@@ -90042,12 +90255,17 @@ ${block.join(`
|
|
|
90042
90255
|
s += `| ${m.modelId} | ${m.matchedAlias ?? "-"} | ${plans} |
|
|
90043
90256
|
`;
|
|
90044
90257
|
}
|
|
90045
|
-
s += `
|
|
90046
|
-
Pass the **bare name**. Routing puts a subscription ahead of the metered API and ` + "rewrites the model to that plan's wire id for you. An aggregator-qualified id " + "(`moonshotai/...`, `accounts/fireworks/...`) pins that aggregator and bills per token.\n";
|
|
90258
|
+
s += "\nOnly the recommended models above start without `user_required_model`. " + "Pass the **bare name**. Routing puts a subscription ahead of the metered API and " + "rewrites the model to that plan's wire id for you. An aggregator-qualified id " + "(`moonshotai/...`, `accounts/fireworks/...`) pins that aggregator and bills per token.\n";
|
|
90047
90259
|
return s;
|
|
90048
90260
|
};
|
|
90049
90261
|
if (results.length === 0) {
|
|
90050
90262
|
const catalog = renderCatalogSection();
|
|
90263
|
+
if (recommendedHits.length > 0) {
|
|
90264
|
+
const text = `No OpenRouter listing matches "${query}".
|
|
90265
|
+
|
|
90266
|
+
${renderRecommendedSection()}${catalog}${useWith()}`;
|
|
90267
|
+
return { content: [{ type: "text", text }] };
|
|
90268
|
+
}
|
|
90051
90269
|
const text = catalog ? `No OpenRouter listing matches "${query}", but claudish's catalog knows these:
|
|
90052
90270
|
${catalog}` : `No models found matching "${query}".
|
|
90053
90271
|
|
|
@@ -90057,6 +90275,7 @@ ${catalog}` : `No models found matching "${query}".
|
|
|
90057
90275
|
let output = `# Search Results for "${query}"
|
|
90058
90276
|
|
|
90059
90277
|
`;
|
|
90278
|
+
output += renderRecommendedSection();
|
|
90060
90279
|
output += `## OpenRouter listing
|
|
90061
90280
|
|
|
90062
90281
|
`;
|
|
@@ -90075,9 +90294,7 @@ ${catalog}` : `No models found matching "${query}".
|
|
|
90075
90294
|
`;
|
|
90076
90295
|
}
|
|
90077
90296
|
output += renderCatalogSection();
|
|
90078
|
-
|
|
90079
|
-
output += `
|
|
90080
|
-
Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
|
|
90297
|
+
output += useWith();
|
|
90081
90298
|
return { content: [{ type: "text", text: output }] };
|
|
90082
90299
|
}
|
|
90083
90300
|
});
|
|
@@ -90261,6 +90478,11 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
|
|
|
90261
90478
|
items: { type: "string" },
|
|
90262
90479
|
description: "The 'models' or 'judges' entries whose provider the USER named ('run Kimi on " + "OpenRouter'), exactly as written there. Every other 'provider@' on a catalog " + "model is removed so claudish routing picks the provider from the user's setup; " + "pass bare names unless the user asked for a provider."
|
|
90263
90480
|
},
|
|
90481
|
+
user_required_model: {
|
|
90482
|
+
type: "array",
|
|
90483
|
+
items: { type: "string" },
|
|
90484
|
+
description: "The 'models' or 'judges' entries the USER named as exact models, exactly as " + "written there: they run even when not in the recommended list. Never list a " + "model you chose yourself."
|
|
90485
|
+
},
|
|
90264
90486
|
input_file: {
|
|
90265
90487
|
type: "string",
|
|
90266
90488
|
description: "PREFERRED. Path to a file holding the task prompt, relative to the working " + "directory. Use this rather than `input` for anything longer than a sentence: " + "a prompt passed inline is echoed verbatim in the caller's terminal, where a " + "200-line review brief buries every other argument and makes the call " + "unreadable. Write the brief to the session directory first (input.md is the " + "conventional name) and point here."
|
|
@@ -90345,7 +90567,15 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
|
|
|
90345
90567
|
const existing = join56(resolved, "input.md");
|
|
90346
90568
|
return existsSync37(existing) ? readFileSync37(existing, "utf-8") : undefined;
|
|
90347
90569
|
};
|
|
90348
|
-
|
|
90570
|
+
const runsModels = mode === "run" || mode === "run-and-judge";
|
|
90571
|
+
const runsJudges = mode === "judge" || mode === "run-and-judge";
|
|
90572
|
+
const choice = modelChoiceStop([
|
|
90573
|
+
...runsModels && givenModels && models ? givenModels.map((given, i) => ({ given, spec: models[i] })) : [],
|
|
90574
|
+
...runsJudges && givenJudges && judges ? givenJudges.map((given, i) => ({ given, spec: judges[i] })) : []
|
|
90575
|
+
], userRequiredProvidersArg(args.user_required_model, [...givenModels ?? [], ...givenJudges ?? []], "user_required_model"), 'with user_required_model: ["<that entry>"]');
|
|
90576
|
+
if (choice)
|
|
90577
|
+
return choice;
|
|
90578
|
+
if (runsModels && givenModels && args.efforts === undefined) {
|
|
90349
90579
|
const recommended = effortClarification(givenModels);
|
|
90350
90580
|
if (recommended) {
|
|
90351
90581
|
return needsEffortResult(recommended, `with efforts: {"<model>": "<level>"} (efforts: {} keeps Claude Code's own for every model)`);
|
|
@@ -90634,6 +90864,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
|
|
|
90634
90864
|
type: "boolean",
|
|
90635
90865
|
description: "True only when the USER named the provider: the 'provider@model' spec is then " + "used as given."
|
|
90636
90866
|
},
|
|
90867
|
+
user_required_model: { type: "boolean", description: USER_REQUIRED_MODEL_DESCRIPTION },
|
|
90637
90868
|
prompt: {
|
|
90638
90869
|
type: "string",
|
|
90639
90870
|
description: "Initial prompt to send. If omitted, send later via send_input."
|
|
@@ -90681,6 +90912,11 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
|
|
|
90681
90912
|
await assertMagmuxAvailable();
|
|
90682
90913
|
const { specs, stripped } = strippedProviders([args.model], new Set(args.user_required_provider === true ? [args.model] : []));
|
|
90683
90914
|
const requestedModel = specs[0];
|
|
90915
|
+
const choice = modelChoiceStop([{ given: args.model, spec: requestedModel }], new Set(args.user_required_model === true ? [args.model] : []), "with user_required_model: true", {
|
|
90916
|
+
projectRulesReadable: typeof args.work_dir !== "string" || resolve8(args.work_dir) === process.cwd()
|
|
90917
|
+
});
|
|
90918
|
+
if (choice)
|
|
90919
|
+
return choice;
|
|
90684
90920
|
const recommended = args.effort === undefined ? effortClarification([args.model]) : undefined;
|
|
90685
90921
|
if (recommended) {
|
|
90686
90922
|
return needsEffortResult(recommended, `with effort: "<level>" (effort: "${KEEP_EFFORT}" keeps Claude Code's own)`);
|
|
@@ -90748,7 +90984,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
|
|
|
90748
90984
|
},
|
|
90749
90985
|
group: "session",
|
|
90750
90986
|
handler: async (args) => {
|
|
90751
|
-
const result = sessionManager.sendInput(args.session_id, args.text);
|
|
90987
|
+
const result = sessionManager.sendInput(requiredString(args.session_id, "session_id"), requiredString(args.text, "text"));
|
|
90752
90988
|
return {
|
|
90753
90989
|
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
90754
90990
|
};
|
|
@@ -90771,7 +91007,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
|
|
|
90771
91007
|
group: "session",
|
|
90772
91008
|
handler: async (args) => {
|
|
90773
91009
|
try {
|
|
90774
|
-
const output = sessionManager.getOutput(args.session_id, args.tail_lines);
|
|
91010
|
+
const output = sessionManager.getOutput(requiredString(args.session_id, "session_id"), args.tail_lines);
|
|
90775
91011
|
return {
|
|
90776
91012
|
content: [{ type: "text", text: JSON.stringify(output) }]
|
|
90777
91013
|
};
|
|
@@ -90852,7 +91088,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
|
|
|
90852
91088
|
group: "session",
|
|
90853
91089
|
handler: async (args) => {
|
|
90854
91090
|
try {
|
|
90855
|
-
const diagnostics = sessionManager.getDiagnostics(args.session_id, args.event_limit);
|
|
91091
|
+
const diagnostics = sessionManager.getDiagnostics(requiredString(args.session_id, "session_id"), args.event_limit);
|
|
90856
91092
|
return {
|
|
90857
91093
|
content: [{ type: "text", text: JSON.stringify(diagnostics) }]
|
|
90858
91094
|
};
|
|
@@ -91075,7 +91311,7 @@ Each session is an interactive Claude Code running the model in its own headless
|
|
|
91075
91311
|
5. FAILED or TIMEOUT \u2014 or COMPLETED with an empty or surprising answer \u2014 call get_diagnostics. It returns the child's final screen, the upstream error bodies, the recent state records, the resolved model chain and the paths to the full records. It needs no re-run and no debug flag.
|
|
91076
91312
|
6. capture_session reads a session's current screen; resize_session sets its terminal size (any time, mid-turn included); cancel_session stops it. Nothing stops a session for being idle: a child inside a long build is silent and working.
|
|
91077
91313
|
|
|
91078
|
-
The session_id from create_session is the key for every session tool.`, proxyInstance = null, proxyStarting = null, SCORE_TIE_EPSILON = 0.000001, NEXT_STEP, KEEP_EFFORT = "default", sanitize2, TOOL_MODES;
|
|
91314
|
+
The session_id from create_session is the key for every session tool.`, proxyInstance = null, proxyStarting = null, SCORE_TIE_EPSILON = 0.000001, NEXT_STEP, KEEP_EFFORT = "default", USER_REQUIRED_MODEL_DESCRIPTION, sanitize2, TOOL_MODES;
|
|
91079
91315
|
var init_mcp_server = __esm(() => {
|
|
91080
91316
|
init_server2();
|
|
91081
91317
|
init_stdio2();
|
|
@@ -91107,6 +91343,8 @@ var init_mcp_server = __esm(() => {
|
|
|
91107
91343
|
init_preflight_chain();
|
|
91108
91344
|
init_probe_live();
|
|
91109
91345
|
init_provider_definitions();
|
|
91346
|
+
init_route_candidates();
|
|
91347
|
+
init_routing_rules();
|
|
91110
91348
|
init_proxy_server();
|
|
91111
91349
|
init_recommended_listing();
|
|
91112
91350
|
init_redact();
|
|
@@ -91133,6 +91371,7 @@ var init_mcp_server = __esm(() => {
|
|
|
91133
91371
|
empty_output: "retry once; if it repeats, drop the model",
|
|
91134
91372
|
shape_mismatch: "the answer (assistant text after the task was read) did not match. Every assistant " + "message of the turn was read from the transcript, so nothing was lost in transit \u2014 " + "the model did not produce it. Re-prompt with the required format restated; do NOT " + "count this slot as a vote"
|
|
91135
91373
|
};
|
|
91374
|
+
USER_REQUIRED_MODEL_DESCRIPTION = "True only when the USER named this exact model: it then runs even when it is not in the " + "recommended list. Never set it for a model you chose yourself.";
|
|
91136
91375
|
sanitize2 = sanitizeForReport;
|
|
91137
91376
|
TOOL_MODES = ["all", "low-level", "agentic", "session", "debug"];
|
|
91138
91377
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudish",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.4.0",
|
|
4
4
|
"description": "Run Claude Code with any model - OpenRouter, Ollama, LM Studio & local models",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -71,10 +71,10 @@
|
|
|
71
71
|
"ai"
|
|
72
72
|
],
|
|
73
73
|
"optionalDependencies": {
|
|
74
|
-
"@claudish/magmux-darwin-arm64": "14.
|
|
75
|
-
"@claudish/magmux-darwin-x64": "14.
|
|
76
|
-
"@claudish/magmux-linux-arm64": "14.
|
|
77
|
-
"@claudish/magmux-linux-x64": "14.
|
|
74
|
+
"@claudish/magmux-darwin-arm64": "14.4.0",
|
|
75
|
+
"@claudish/magmux-darwin-x64": "14.4.0",
|
|
76
|
+
"@claudish/magmux-linux-arm64": "14.4.0",
|
|
77
|
+
"@claudish/magmux-linux-x64": "14.4.0"
|
|
78
78
|
},
|
|
79
79
|
"author": "Jack Rudenko <i@madappgang.com>",
|
|
80
80
|
"license": "MIT",
|
|
@@ -58,9 +58,13 @@ subscriptions first, their preferences and manual overrides. Do not choose it yo
|
|
|
58
58
|
`create_session` / `run_prompt`: `true`).
|
|
59
59
|
3. **User named exact models:** pass them as given. No `preflight` or `search_models`
|
|
60
60
|
call is needed to check them: claudish routes and checks them. You still read
|
|
61
|
-
`list_models` once for their efforts (next section).
|
|
61
|
+
`list_models` once for their efforts (next section). A named model outside the
|
|
62
|
+
recommended list answers `not_recommended`; list it in `user_required_model` (`team`:
|
|
63
|
+
the entries; `create_session` / `run_prompt`: `true`) and call again.
|
|
62
64
|
4. **User named only a family or vendor** ("a GPT model", "minimax", "latest Grok"): call
|
|
63
|
-
`list_models` and take the newest model of that family from it, as a bare name.
|
|
65
|
+
`list_models` and take the newest model of that family from it, as a bare name. A
|
|
66
|
+
family name (`gpt`) or a model you recall (`gpt-5`) answers `unknown_model` /
|
|
67
|
+
`not_recommended` with the recommended ids: pick from them and call again.
|
|
64
68
|
|
|
65
69
|
### 🎚️ Reasoning Effort per Model
|
|
66
70
|
|