claudish 14.3.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 CHANGED
@@ -575,7 +575,7 @@ var init_onepassword_config = __esm(() => {
575
575
  });
576
576
 
577
577
  // src/version.ts
578
- var VERSION = "14.3.0";
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")
@@ -86620,7 +86636,9 @@ function classifyRunOutput(input) {
86620
86636
  return v;
86621
86637
  }
86622
86638
  function classifyTurnSignals(input) {
86623
- const { apiError, stopReason, promptRead } = input;
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);
86624
86642
  if (apiError) {
86625
86643
  const status = apiError.status === null ? "" : ` (status ${apiError.status})`;
86626
86644
  return {
@@ -89693,15 +89711,15 @@ function stripUnrequestedProvider(spec, required, isCatalogModel = isCanonicalCa
89693
89711
  return spec;
89694
89712
  return spec.slice(spec.indexOf("@") + 1);
89695
89713
  }
89696
- function userRequiredProvidersArg(value, specs) {
89714
+ function userRequiredProvidersArg(value, specs, name = "user_required_provider") {
89697
89715
  if (value === undefined || value === null)
89698
89716
  return new Set;
89699
89717
  if (!Array.isArray(value) || value.some((v) => typeof v !== "string")) {
89700
- throw new Error(`Invalid 'user_required_provider': expected an array of model specs.`);
89718
+ throw new Error(`Invalid '${name}': expected an array of model specs.`);
89701
89719
  }
89702
89720
  for (const spec of value) {
89703
89721
  if (!specs.includes(spec)) {
89704
- throw new Error(`Invalid 'user_required_provider': ${JSON.stringify(spec)} is not one of the ` + `models (${specs.join(", ")}). Entries must match a 'models' or 'judges' entry exactly.`);
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.`);
89705
89723
  }
89706
89724
  }
89707
89725
  return new Set(value);
@@ -89729,6 +89747,132 @@ function strippedNote(stripped) {
89729
89747
  return;
89730
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.";
89731
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
+ }
89732
89876
  function buildChildClaudeFlags(agent, claudeFlags) {
89733
89877
  const extra = typeof claudeFlags === "string" ? claudeFlags.split(/\s+/).filter(Boolean) : [];
89734
89878
  const named = typeof agent === "string" ? agent.trim() : "";
@@ -89845,6 +89989,12 @@ function optionalString(raw, name) {
89845
89989
  throw new ContractErrorException("invalid_args", `'${name}' must be a non-empty string`);
89846
89990
  return raw;
89847
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
+ }
89848
89998
  function optionalBoolean(raw, name) {
89849
89999
  if (raw === undefined || raw === null)
89850
90000
  return false;
@@ -89953,6 +90103,7 @@ ${block.join(`
89953
90103
  type: "boolean",
89954
90104
  description: "True only when the USER named the provider ('use OpenRouter for it'): the " + "'provider@model' spec is then sent as given."
89955
90105
  },
90106
+ user_required_model: { type: "boolean", description: USER_REQUIRED_MODEL_DESCRIPTION },
89956
90107
  prompt: { type: "string", description: "The prompt to send to the model" },
89957
90108
  system_prompt: { type: "string", description: "Optional system prompt" },
89958
90109
  effort: {
@@ -89968,6 +90119,9 @@ ${block.join(`
89968
90119
  handler: async (args, ctx) => {
89969
90120
  try {
89970
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;
89971
90125
  const recommended = args.effort === undefined ? effortClarification([args.model]) : undefined;
89972
90126
  if (recommended) {
89973
90127
  return needsEffortResult(recommended, `with effort: "<level>" (effort: "${KEEP_EFFORT}" keeps Claude Code's own)`);
@@ -90065,6 +90219,26 @@ ${block.join(`
90065
90219
  return compareByReleaseDateDesc(orderingKey(a.model), orderingKey(b.model));
90066
90220
  }).slice(0, maxResults);
90067
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.";
90068
90242
  const renderCatalogSection = () => {
90069
90243
  if (catalogMatches.length === 0)
90070
90244
  return "";
@@ -90081,12 +90255,17 @@ ${block.join(`
90081
90255
  s += `| ${m.modelId} | ${m.matchedAlias ?? "-"} | ${plans} |
90082
90256
  `;
90083
90257
  }
90084
- s += `
90085
- 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";
90086
90259
  return s;
90087
90260
  };
90088
90261
  if (results.length === 0) {
90089
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
+ }
90090
90269
  const text = catalog ? `No OpenRouter listing matches "${query}", but claudish's catalog knows these:
90091
90270
  ${catalog}` : `No models found matching "${query}".
90092
90271
 
@@ -90096,6 +90275,7 @@ ${catalog}` : `No models found matching "${query}".
90096
90275
  let output = `# Search Results for "${query}"
90097
90276
 
90098
90277
  `;
90278
+ output += renderRecommendedSection();
90099
90279
  output += `## OpenRouter listing
90100
90280
 
90101
90281
  `;
@@ -90114,9 +90294,7 @@ ${catalog}` : `No models found matching "${query}".
90114
90294
  `;
90115
90295
  }
90116
90296
  output += renderCatalogSection();
90117
- const suggested = catalogMatches[0]?.modelId ?? results[0].model.id;
90118
- output += `
90119
- Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
90297
+ output += useWith();
90120
90298
  return { content: [{ type: "text", text: output }] };
90121
90299
  }
90122
90300
  });
@@ -90300,6 +90478,11 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
90300
90478
  items: { type: "string" },
90301
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."
90302
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
+ },
90303
90486
  input_file: {
90304
90487
  type: "string",
90305
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."
@@ -90384,7 +90567,15 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
90384
90567
  const existing = join56(resolved, "input.md");
90385
90568
  return existsSync37(existing) ? readFileSync37(existing, "utf-8") : undefined;
90386
90569
  };
90387
- if ((mode === "run" || mode === "run-and-judge") && givenModels && args.efforts === undefined) {
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) {
90388
90579
  const recommended = effortClarification(givenModels);
90389
90580
  if (recommended) {
90390
90581
  return needsEffortResult(recommended, `with efforts: {"<model>": "<level>"} (efforts: {} keeps Claude Code's own for every model)`);
@@ -90673,6 +90864,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
90673
90864
  type: "boolean",
90674
90865
  description: "True only when the USER named the provider: the 'provider@model' spec is then " + "used as given."
90675
90866
  },
90867
+ user_required_model: { type: "boolean", description: USER_REQUIRED_MODEL_DESCRIPTION },
90676
90868
  prompt: {
90677
90869
  type: "string",
90678
90870
  description: "Initial prompt to send. If omitted, send later via send_input."
@@ -90720,6 +90912,11 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
90720
90912
  await assertMagmuxAvailable();
90721
90913
  const { specs, stripped } = strippedProviders([args.model], new Set(args.user_required_provider === true ? [args.model] : []));
90722
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;
90723
90920
  const recommended = args.effort === undefined ? effortClarification([args.model]) : undefined;
90724
90921
  if (recommended) {
90725
90922
  return needsEffortResult(recommended, `with effort: "<level>" (effort: "${KEEP_EFFORT}" keeps Claude Code's own)`);
@@ -90787,7 +90984,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
90787
90984
  },
90788
90985
  group: "session",
90789
90986
  handler: async (args) => {
90790
- const result = sessionManager.sendInput(args.session_id, args.text);
90987
+ const result = sessionManager.sendInput(requiredString(args.session_id, "session_id"), requiredString(args.text, "text"));
90791
90988
  return {
90792
90989
  content: [{ type: "text", text: JSON.stringify(result) }]
90793
90990
  };
@@ -90810,7 +91007,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
90810
91007
  group: "session",
90811
91008
  handler: async (args) => {
90812
91009
  try {
90813
- const output = sessionManager.getOutput(args.session_id, args.tail_lines);
91010
+ const output = sessionManager.getOutput(requiredString(args.session_id, "session_id"), args.tail_lines);
90814
91011
  return {
90815
91012
  content: [{ type: "text", text: JSON.stringify(output) }]
90816
91013
  };
@@ -90891,7 +91088,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
90891
91088
  group: "session",
90892
91089
  handler: async (args) => {
90893
91090
  try {
90894
- const diagnostics = sessionManager.getDiagnostics(args.session_id, args.event_limit);
91091
+ const diagnostics = sessionManager.getDiagnostics(requiredString(args.session_id, "session_id"), args.event_limit);
90895
91092
  return {
90896
91093
  content: [{ type: "text", text: JSON.stringify(diagnostics) }]
90897
91094
  };
@@ -91114,7 +91311,7 @@ Each session is an interactive Claude Code running the model in its own headless
91114
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.
91115
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.
91116
91313
 
91117
- 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;
91118
91315
  var init_mcp_server = __esm(() => {
91119
91316
  init_server2();
91120
91317
  init_stdio2();
@@ -91146,6 +91343,8 @@ var init_mcp_server = __esm(() => {
91146
91343
  init_preflight_chain();
91147
91344
  init_probe_live();
91148
91345
  init_provider_definitions();
91346
+ init_route_candidates();
91347
+ init_routing_rules();
91149
91348
  init_proxy_server();
91150
91349
  init_recommended_listing();
91151
91350
  init_redact();
@@ -91172,6 +91371,7 @@ var init_mcp_server = __esm(() => {
91172
91371
  empty_output: "retry once; if it repeats, drop the model",
91173
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"
91174
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.";
91175
91375
  sanitize2 = sanitizeForReport;
91176
91376
  TOOL_MODES = ["all", "low-level", "agentic", "session", "debug"];
91177
91377
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "14.3.0",
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.3.0",
75
- "@claudish/magmux-darwin-x64": "14.3.0",
76
- "@claudish/magmux-linux-arm64": "14.3.0",
77
- "@claudish/magmux-linux-x64": "14.3.0"
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