claudish 9.5.0 → 9.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +436 -67
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -715,7 +715,7 @@ var init_onepassword_config = __esm(() => {
715
715
  });
716
716
 
717
717
  // src/version.ts
718
- var VERSION = "9.5.0";
718
+ var VERSION = "9.6.1";
719
719
 
720
720
  // src/logger.ts
721
721
  import { appendFile, existsSync as existsSync2, mkdirSync, readdirSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
@@ -815,14 +815,17 @@ function redactLogLine(message, timestamp) {
815
815
  return `[${timestamp}] ${message}
816
816
  `;
817
817
  }
818
+ function logFileStamp() {
819
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-").split("T").join("_").slice(0, -5);
820
+ return `${timestamp}_${process.pid}`;
821
+ }
818
822
  function initLogger(debugMode, level = "info", noLogs = false) {
819
823
  if (!noLogs) {
820
824
  const logsDir = join2(homedir2(), ".claudish", "logs");
821
825
  if (!existsSync2(logsDir)) {
822
826
  mkdirSync(logsDir, { recursive: true });
823
827
  }
824
- const timestamp = new Date().toISOString().replace(/[:.]/g, "-").split("T").join("_").slice(0, -5);
825
- alwaysOnLogPath = join2(logsDir, `claudish_${timestamp}.log`);
828
+ alwaysOnLogPath = join2(logsDir, `claudish_${logFileStamp()}.log`);
826
829
  writeFileSync2(alwaysOnLogPath, `Claudish Session Log - ${new Date().toISOString()}
827
830
  Mode: structural (content redacted)
828
831
  ${"=".repeat(60)}
@@ -837,8 +840,7 @@ ${"=".repeat(60)}
837
840
  if (!existsSync2(logsDir)) {
838
841
  mkdirSync(logsDir, { recursive: true });
839
842
  }
840
- const timestamp = new Date().toISOString().replace(/[:.]/g, "-").split("T").join("_").slice(0, -5);
841
- logFilePath = join2(logsDir, `claudish_${timestamp}.log`);
843
+ logFilePath = join2(logsDir, `claudish_${logFileStamp()}.log`);
842
844
  writeFileSync2(logFilePath, `Claudish Debug Log - ${new Date().toISOString()}
843
845
  Log Level: ${level}
844
846
  ${"=".repeat(80)}
@@ -18167,6 +18169,12 @@ var init_stdio2 = __esm(() => {
18167
18169
  import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
18168
18170
  import { homedir as homedir7 } from "os";
18169
18171
  import { dirname as dirname4, join as join7 } from "path";
18172
+ function reasoningStatusOf(entry) {
18173
+ if (entry.reasoningStatus === "known" || entry.reasoningStatus === "unknown") {
18174
+ return entry.reasoningStatus;
18175
+ }
18176
+ return entry.reasoning !== undefined ? "known" : "unknown";
18177
+ }
18170
18178
  function readAllModelsCache(path = ALL_MODELS_CACHE_PATH) {
18171
18179
  if (!existsSync5(path))
18172
18180
  return null;
@@ -18183,12 +18191,14 @@ function readAllModelsCache(path = ALL_MODELS_CACHE_PATH) {
18183
18191
  const models = Array.isArray(data.models) ? data.models : [];
18184
18192
  const entries = Array.isArray(data.entries) ? data.entries : [];
18185
18193
  const plans = Array.isArray(data.plans) ? data.plans : undefined;
18194
+ const catalogRevision = typeof data.catalogRevision === "string" ? data.catalogRevision : undefined;
18186
18195
  return {
18187
18196
  version: 2,
18188
18197
  lastUpdated,
18189
18198
  entries,
18190
18199
  models,
18191
- ...plans !== undefined ? { plans } : {}
18200
+ ...plans !== undefined ? { plans } : {},
18201
+ ...catalogRevision !== undefined ? { catalogRevision } : {}
18192
18202
  };
18193
18203
  }
18194
18204
  function writeAllModelsCache(data, path = ALL_MODELS_CACHE_PATH) {
@@ -18198,7 +18208,8 @@ function writeAllModelsCache(data, path = ALL_MODELS_CACHE_PATH) {
18198
18208
  lastUpdated: data.lastUpdated ?? new Date().toISOString(),
18199
18209
  entries: data.entries ?? existing?.entries ?? [],
18200
18210
  models: data.models ?? existing?.models ?? [],
18201
- ...data.plans !== undefined || existing?.plans !== undefined ? { plans: data.plans ?? existing?.plans ?? [] } : {}
18211
+ ...data.plans !== undefined || existing?.plans !== undefined ? { plans: data.plans ?? existing?.plans ?? [] } : {},
18212
+ ...data.catalogRevision !== undefined ? { catalogRevision: data.catalogRevision } : {}
18202
18213
  };
18203
18214
  mkdirSync5(dirname4(path), { recursive: true });
18204
18215
  writeFileSync5(path, JSON.stringify(merged), "utf-8");
@@ -18285,6 +18296,10 @@ function lookupModel(modelId, cachePath) {
18285
18296
  function lookupModelReasoning(modelId, cachePath) {
18286
18297
  return findCacheEntry(modelId, cachePath)?.reasoning;
18287
18298
  }
18299
+ function lookupModelReasoningStatus(modelId, cachePath) {
18300
+ const entry = findCacheEntry(modelId, cachePath);
18301
+ return entry ? reasoningStatusOf(entry) : undefined;
18302
+ }
18288
18303
  function lookupModelTokenParam(modelId, cachePath) {
18289
18304
  return findCacheEntry(modelId, cachePath)?.tokenParam;
18290
18305
  }
@@ -18398,6 +18413,26 @@ function findCacheEntry(modelId, cachePath) {
18398
18413
  return entry;
18399
18414
  }
18400
18415
  }
18416
+ const canonical = resolvePlanDescribedModelId(modelId, cache.plans);
18417
+ if (!canonical)
18418
+ return;
18419
+ const canonicalLower = canonical.toLowerCase();
18420
+ for (const entry of cache.entries) {
18421
+ if (entry.modelId.toLowerCase() === canonicalLower)
18422
+ return entry;
18423
+ if (entry.aliases?.some((a) => a.toLowerCase() === canonicalLower))
18424
+ return entry;
18425
+ }
18426
+ return;
18427
+ }
18428
+ function resolvePlanDescribedModelId(wireId, plans) {
18429
+ if (!plans)
18430
+ return;
18431
+ for (const plan of plans) {
18432
+ const described = plan.modelDescriptions?.[wireId];
18433
+ if (described?.status === "described" && described.modelId)
18434
+ return described.modelId;
18435
+ }
18401
18436
  return;
18402
18437
  }
18403
18438
  function classifyCatalogHit(entry, q) {
@@ -19737,6 +19772,29 @@ function matchesModelFamily(modelId, family) {
19737
19772
  function isEffortLevel(value) {
19738
19773
  return typeof value === "string" && EFFORT_ORDER.includes(value);
19739
19774
  }
19775
+ function outputCeilingOf(originalRequest, payload) {
19776
+ const candidates = [
19777
+ originalRequest?.max_tokens,
19778
+ payload?.max_tokens,
19779
+ payload?.max_completion_tokens,
19780
+ payload?.max_output_tokens
19781
+ ];
19782
+ for (const value of candidates) {
19783
+ if (typeof value === "number" && Number.isFinite(value) && value > 0)
19784
+ return value;
19785
+ }
19786
+ return;
19787
+ }
19788
+ function clampThinkingBudget(budget, ceiling) {
19789
+ if (budget === undefined)
19790
+ return;
19791
+ if (ceiling === undefined)
19792
+ return budget;
19793
+ const allowed = ceiling - ANSWER_TOKEN_RESERVE;
19794
+ if (allowed < MIN_THINKING_BUDGET)
19795
+ return "no-room";
19796
+ return Math.min(budget, allowed);
19797
+ }
19740
19798
 
19741
19799
  class BaseAPIFormat {
19742
19800
  modelId;
@@ -19826,6 +19884,11 @@ class BaseAPIFormat {
19826
19884
  }
19827
19885
  }
19828
19886
  applyAnthropicWireReasoning(request, originalRequest) {
19887
+ const status = this.lookupReasoningStatus();
19888
+ if (status !== "known") {
19889
+ log(`[${this.getName()}] ${this.modelId} reasoning control ${status === "unknown" ? "unknown" : "absent from the catalog"} -> no reasoning parameter emitted`);
19890
+ return request;
19891
+ }
19829
19892
  const reasoning = this.lookupReasoningCapability();
19830
19893
  if (reasoning?.supported === false) {
19831
19894
  request.thinking = { type: "disabled" };
@@ -19836,36 +19899,63 @@ class BaseAPIFormat {
19836
19899
  if (!effort)
19837
19900
  return request;
19838
19901
  if (effort === "none" || effort === "minimal") {
19902
+ if (reasoning?.mandatory) {
19903
+ return this.enableAnthropicEffort(request, effort, reasoning, "mandatory reasoning");
19904
+ }
19839
19905
  request.thinking = { type: "disabled" };
19840
19906
  log(`[${this.getName()}] effort ${effort} -> thinking.type: disabled for ${this.modelId}`);
19841
19907
  return request;
19842
19908
  }
19843
- if (reasoning && (reasoning.control === "budget" || reasoning.supportsBudgetTokens)) {
19844
- return this.enableAnthropicThinkingWithBudget(request, effort, "catalog: budget-controlled");
19909
+ const control = reasoning?.control;
19910
+ const advertisesEfforts = (reasoning?.efforts?.length ?? 0) > 0;
19911
+ if (control === "effort" || control === undefined && advertisesEfforts) {
19912
+ return this.enableAnthropicEffort(request, effort, reasoning, "catalog: effort-controlled");
19845
19913
  }
19846
- const advertised = reasoning?.efforts?.length ? reasoning : undefined;
19847
- if (advertised) {
19848
- const level = this.clampToAdvertisedEffort(effort, advertised);
19849
- request.thinking = { type: "enabled" };
19850
- if (level) {
19851
- request.output_config = { ...request.output_config ?? {}, effort: level };
19852
- }
19853
- log(`[${this.getName()}] effort ${effort} -> thinking: enabled, output_config.effort: ${level ?? "(none advertised)"} for ${this.modelId} (advertised: ${advertised.efforts?.join("/")})`);
19854
- return request;
19914
+ if (control === "budget" || control === undefined && reasoning?.supportsBudgetTokens) {
19915
+ return this.enableAnthropicBudget(request, effort, originalRequest, reasoning);
19855
19916
  }
19856
- if (reasoning) {
19857
- request.thinking = { type: "enabled" };
19858
- log(`[${this.getName()}] effort ${effort} -> thinking: enabled (no depth knob; catalog control=${reasoning.control ?? "unknown"}) for ${this.modelId}`);
19859
- return request;
19917
+ request.thinking = { type: "enabled" };
19918
+ this.stripAnthropicEffortField(request);
19919
+ log(`[${this.getName()}] effort ${effort} -> thinking: enabled (no depth knob; catalog control=${control ?? "unspecified"}) for ${this.modelId}`);
19920
+ return request;
19921
+ }
19922
+ enableAnthropicEffort(request, effort, reasoning, why) {
19923
+ const level = reasoning ? this.clampToAdvertisedEffort(effort, reasoning) : undefined;
19924
+ request.thinking = { type: "enabled" };
19925
+ if (level) {
19926
+ request.output_config = { ...request.output_config ?? {}, effort: level };
19860
19927
  }
19861
- return this.enableAnthropicThinkingWithBudget(request, effort, "no catalog entry");
19928
+ log(`[${this.getName()}] effort ${effort} -> thinking: enabled, output_config.effort: ${level ?? "(none advertised)"} for ${this.modelId} (${why}; advertised: ${reasoning?.efforts?.join("/") ?? "none"})`);
19929
+ return request;
19862
19930
  }
19863
- enableAnthropicThinkingWithBudget(request, effort, why) {
19864
- const budget = this.effortToThinkingTokenBudget(effort);
19931
+ enableAnthropicBudget(request, effort, originalRequest, reasoning) {
19932
+ const requested = this.effortToThinkingTokenBudget(effort);
19933
+ const ceiling = outputCeilingOf(originalRequest, request);
19934
+ const budget = clampThinkingBudget(requested, ceiling);
19935
+ if (budget === "no-room") {
19936
+ if (reasoning?.mandatory) {
19937
+ request.thinking = { type: "enabled" };
19938
+ } else {
19939
+ request.thinking = { type: "disabled" };
19940
+ }
19941
+ this.stripAnthropicEffortField(request);
19942
+ log(`[${this.getName()}] effort ${effort} -> budget ${requested ?? "(model max)"} does not fit under max_tokens ${ceiling}; sent thinking.type: ${request.thinking.type} for ${this.modelId}`);
19943
+ return request;
19944
+ }
19865
19945
  request.thinking = budget === undefined ? { type: "enabled" } : { type: "enabled", budget_tokens: budget };
19866
- log(`[${this.getName()}] effort ${effort} -> thinking: enabled, budget_tokens: ${budget ?? "(model max)"} for ${this.modelId} (${why})`);
19946
+ this.stripAnthropicEffortField(request);
19947
+ log(`[${this.getName()}] effort ${effort} -> thinking: enabled, budget_tokens: ${budget ?? "(model max)"}${budget !== undefined && budget !== requested ? ` (clamped from ${requested} under max_tokens ${ceiling})` : ""} for ${this.modelId} (catalog: budget-controlled)`);
19867
19948
  return request;
19868
19949
  }
19950
+ stripAnthropicEffortField(request) {
19951
+ if (!request?.output_config || typeof request.output_config !== "object")
19952
+ return;
19953
+ if (request.output_config.effort === undefined)
19954
+ return;
19955
+ delete request.output_config.effort;
19956
+ if (Object.keys(request.output_config).length === 0)
19957
+ delete request.output_config;
19958
+ }
19869
19959
  clampToAdvertisedEffort(requested, reasoning) {
19870
19960
  if (this.pinnedEffort)
19871
19961
  return this.pinnedEffort;
@@ -19894,6 +19984,13 @@ class BaseAPIFormat {
19894
19984
  return;
19895
19985
  }
19896
19986
  }
19987
+ lookupReasoningStatus() {
19988
+ try {
19989
+ return lookupModelReasoningStatus(this.modelId);
19990
+ } catch {
19991
+ return;
19992
+ }
19993
+ }
19897
19994
  effortToThinkingTokenBudget(effort) {
19898
19995
  switch (effort) {
19899
19996
  case "low":
@@ -20024,7 +20121,7 @@ class BaseAPIFormat {
20024
20121
  }
20025
20122
  }
20026
20123
  }
20027
- var OPTIONAL_SAMPLING_PARAMS, OPENAI_TOOL_NAME_LIMIT = 64, EFFORT_ORDER, EFFORT_LEVELS, NON_ANTHROPIC_REASONING_FIELDS, DefaultAPIFormat;
20124
+ var OPTIONAL_SAMPLING_PARAMS, OPENAI_TOOL_NAME_LIMIT = 64, EFFORT_ORDER, EFFORT_LEVELS, NON_ANTHROPIC_REASONING_FIELDS, MIN_THINKING_BUDGET = 1024, ANSWER_TOKEN_RESERVE = 1024, DefaultAPIFormat;
20028
20125
  var init_base_api_format = __esm(() => {
20029
20126
  init_remote_provider_types();
20030
20127
  init_logger();
@@ -21103,10 +21200,34 @@ function keepOnlyRealTools(extracted, knownToolNames, decodeToolName) {
21103
21200
  }
21104
21201
  return kept;
21105
21202
  }
21203
+ function unwrapToolCallTags(body) {
21204
+ if (!body.startsWith("<tool_call>"))
21205
+ return null;
21206
+ const blocks = [];
21207
+ let cursor = 0;
21208
+ while (cursor < body.length) {
21209
+ TOOL_CALL_OPEN_AT_CURSOR.lastIndex = cursor;
21210
+ if (!TOOL_CALL_OPEN_AT_CURSOR.exec(body))
21211
+ return null;
21212
+ cursor = TOOL_CALL_OPEN_AT_CURSOR.lastIndex;
21213
+ const close = body.indexOf(TOOL_CALL_CLOSE_TAG, cursor);
21214
+ const block = (close === -1 ? body.slice(cursor) : body.slice(cursor, close)).trim();
21215
+ if (!block.startsWith("<function="))
21216
+ return null;
21217
+ blocks.push(block);
21218
+ if (close === -1)
21219
+ break;
21220
+ cursor = close + TOOL_CALL_CLOSE_TAG.length;
21221
+ cursor += /^\s*/.exec(body.slice(cursor))?.[0].length ?? 0;
21222
+ }
21223
+ return blocks.length > 0 ? blocks.join(`
21224
+ `) : null;
21225
+ }
21106
21226
  function parseFunctionTagEnvelope(text) {
21107
- const body = text.trim();
21108
- if (body.length === 0)
21227
+ const trimmed = text.trim();
21228
+ if (trimmed.length === 0)
21109
21229
  return null;
21230
+ const body = unwrapToolCallTags(trimmed) ?? trimmed;
21110
21231
  if (!body.startsWith("<function="))
21111
21232
  return null;
21112
21233
  const calls = [];
@@ -21343,7 +21464,7 @@ function validateAndRepairToolCall(toolName, argsStr, toolSchemas, textContent)
21343
21464
  }
21344
21465
  return { valid: false, args, repaired: false, missingParams };
21345
21466
  }
21346
- var FUNCTION_TAG_SOURCE, FUNCTION_TAG_PRESENT, FUNCTION_TAG_AT_CURSOR, PARAMETER_TAG_AT_CURSOR;
21467
+ var FUNCTION_TAG_SOURCE, FUNCTION_TAG_PRESENT, FUNCTION_TAG_AT_CURSOR, PARAMETER_TAG_AT_CURSOR, TOOL_CALL_OPEN_AT_CURSOR, TOOL_CALL_CLOSE_TAG = "</tool_call>";
21347
21468
  var init_tool_call_recovery = __esm(() => {
21348
21469
  init_tool_name_utils();
21349
21470
  init_logger();
@@ -21352,6 +21473,7 @@ var init_tool_call_recovery = __esm(() => {
21352
21473
  FUNCTION_TAG_PRESENT = new RegExp(`<function=${TOOL_NAME_SOURCE}>`);
21353
21474
  FUNCTION_TAG_AT_CURSOR = new RegExp(`<function=(${TOOL_NAME_SOURCE})>`, "y");
21354
21475
  PARAMETER_TAG_AT_CURSOR = /<parameter=([^>\s]+)>/y;
21476
+ TOOL_CALL_OPEN_AT_CURSOR = /<tool_call>\s*/y;
21355
21477
  });
21356
21478
 
21357
21479
  // src/handlers/shared/web-search-detector.ts
@@ -21589,6 +21711,31 @@ function formatRawSseLogPayload(dataStr) {
21589
21711
  return dataStr;
21590
21712
  return `${dataStr.substring(0, SSE_LOG_MAX_CHARS)} ${SSE_LOG_TRUNCATION_MARKER} original_chars=${dataStr.length}`;
21591
21713
  }
21714
+ function describeInStreamError(chunk) {
21715
+ if (!chunk || typeof chunk !== "object")
21716
+ return;
21717
+ const error = chunk.error;
21718
+ if (!error)
21719
+ return;
21720
+ if (typeof error === "string")
21721
+ return error;
21722
+ if (typeof error !== "object")
21723
+ return String(error);
21724
+ const e = error;
21725
+ const metadata = e.metadata ?? {};
21726
+ const parts = [];
21727
+ const provider = chunk.provider;
21728
+ if (typeof provider === "string" && provider)
21729
+ parts.push(`[${provider}]`);
21730
+ const code = e.code ?? metadata.provider_code;
21731
+ const type = e.type ?? metadata.error_type;
21732
+ const label = [code, type].filter((v) => v !== undefined && v !== null && v !== "").join(" ");
21733
+ if (label)
21734
+ parts.push(label);
21735
+ const message = e.message ?? metadata.raw;
21736
+ parts.push(typeof message === "string" && message.trim() ? message : JSON.stringify(error).slice(0, 500));
21737
+ return parts.join(" ");
21738
+ }
21592
21739
  function validateToolArguments(toolName, argsStr, toolSchemas, textContent) {
21593
21740
  const result = validateAndRepairToolCall(toolName, argsStr, toolSchemas, textContent);
21594
21741
  if (result.repaired) {
@@ -21865,6 +22012,12 @@ data: ${JSON.stringify(d)}
21865
22012
  state.usage = chunk.usage;
21866
22013
  log(`[Streaming] Usage data received: prompt=${chunk.usage.prompt_tokens}, completion=${chunk.usage.completion_tokens}, total=${chunk.usage.total_tokens}`);
21867
22014
  }
22015
+ const inStreamError = describeInStreamError(chunk);
22016
+ if (inStreamError) {
22017
+ log(`[Streaming] Upstream error inside a 200 stream: ${inStreamError}`);
22018
+ await finalize("error", inStreamError);
22019
+ return;
22020
+ }
21868
22021
  const delta = chunk.choices?.[0]?.delta;
21869
22022
  const finishReason = chunk.choices?.[0]?.finish_reason;
21870
22023
  if (finishReason)
@@ -23370,11 +23523,18 @@ var init_qwen_model_dialect = __esm(() => {
23370
23523
  log(`[QwenModelDialect] effort ${effort} -> enable_thinking: false for ${this.modelId}`);
23371
23524
  } else {
23372
23525
  request.enable_thinking = true;
23373
- const budget = this.effortToThinkingTokenBudget(effort);
23374
- if (budget !== undefined) {
23375
- request.thinking_budget = budget;
23526
+ const requested = this.effortToThinkingTokenBudget(effort);
23527
+ const ceiling = outputCeilingOf(originalRequest, request);
23528
+ const budget = clampThinkingBudget(requested, ceiling);
23529
+ if (budget === "no-room") {
23530
+ delete request.thinking_budget;
23531
+ log(`[QwenModelDialect] effort ${effort} -> enable_thinking: true, no thinking_budget (${requested} does not fit under ceiling ${ceiling}) for ${this.modelId}`);
23532
+ } else {
23533
+ if (budget !== undefined) {
23534
+ request.thinking_budget = budget;
23535
+ }
23536
+ log(`[QwenModelDialect] effort ${effort} -> enable_thinking: true, thinking_budget: ${budget ?? "(model max)"}${budget !== undefined && budget !== requested ? ` (clamped from ${requested} under ceiling ${ceiling})` : ""} for ${this.modelId}`);
23376
23537
  }
23377
- log(`[QwenModelDialect] effort ${effort} -> enable_thinking: true, thinking_budget: ${budget ?? "(model max)"} for ${this.modelId}`);
23378
23538
  }
23379
23539
  if (originalRequest.thinking)
23380
23540
  delete request.thinking;
@@ -37624,6 +37784,12 @@ var init_auto_route = __esm(() => {
37624
37784
  });
37625
37785
 
37626
37786
  // src/providers/catalog-client.ts
37787
+ function catalogUrl() {
37788
+ return process.env.CLAUDISH_CATALOG_URL ?? process.env.FIREBASE_CATALOG_URL ?? DEFAULT_CATALOG_URL;
37789
+ }
37790
+ function plansUrl() {
37791
+ return process.env.CLAUDISH_PLANS_URL ?? derivePlansUrl(catalogUrl());
37792
+ }
37627
37793
  function derivePlansUrl(catalogUrl) {
37628
37794
  try {
37629
37795
  const url = new URL(catalogUrl);
@@ -37772,49 +37938,97 @@ function catalogWarmDisabledFor(value) {
37772
37938
  function catalogWarmDisabled() {
37773
37939
  return catalogWarmDisabledFor(process.env.CLAUDISH_DISABLE_CATALOG_WARM);
37774
37940
  }
37775
- async function refreshCatalog(timeoutMs) {
37776
- if (catalogWarmDisabled()) {
37777
- return { kind: "fetch_failed", reason: "disabled" };
37778
- }
37779
- const plansPromise = fetchSubscriptionPlans(timeoutMs);
37941
+ function buildCatalogPageUrl(baseUrl, offset, limit, revision) {
37942
+ const url = new URL(baseUrl);
37943
+ url.searchParams.set("offset", String(offset));
37944
+ url.searchParams.set("limit", String(limit));
37945
+ if (revision)
37946
+ url.searchParams.set("revision", revision);
37947
+ return url.toString();
37948
+ }
37949
+ async function fetchCatalogPage(url, timeoutMs) {
37780
37950
  let response;
37781
37951
  try {
37782
- response = await fetch(FIREBASE_CATALOG_URL, { signal: AbortSignal.timeout(timeoutMs) });
37952
+ response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
37783
37953
  } catch (err) {
37784
37954
  const name = err?.name;
37785
37955
  const reason = name === "TimeoutError" || name === "AbortError" ? "timeout" : "network";
37786
- return { kind: "fetch_failed", reason };
37956
+ return { ok: false, reason };
37787
37957
  }
37788
37958
  if (!response.ok)
37789
- return { kind: "fetch_failed", reason: "http_error" };
37790
- let data;
37959
+ return { ok: false, reason: "http_error" };
37960
+ let page;
37791
37961
  try {
37792
- data = await response.json();
37962
+ page = await response.json();
37793
37963
  } catch {
37794
- return { kind: "fetch_failed", reason: "network" };
37964
+ return { ok: false, reason: "network" };
37795
37965
  }
37796
- if (!Array.isArray(data.models) || data.models.length === 0) {
37797
- return { kind: "fetch_failed", reason: "empty" };
37966
+ const revision = response.headers.get(CATALOG_REVISION_HEADER) ?? undefined;
37967
+ return { ok: true, page, revision };
37968
+ }
37969
+ async function refreshCatalog(timeoutMs, options = {}) {
37970
+ if (catalogWarmDisabled()) {
37971
+ return { kind: "fetch_failed", reason: "disabled" };
37798
37972
  }
37973
+ const entries = [];
37974
+ let revision;
37975
+ let pages = 0;
37976
+ let offset = 0;
37977
+ for (;; ) {
37978
+ const url = buildCatalogPageUrl(catalogUrl(), offset, CATALOG_PAGE_LIMIT, revision);
37979
+ const result = await fetchCatalogPage(url, timeoutMs);
37980
+ if (!result.ok) {
37981
+ return {
37982
+ kind: "fetch_failed",
37983
+ reason: pages === 0 ? result.reason : "incomplete"
37984
+ };
37985
+ }
37986
+ const { page } = result;
37987
+ if (!Array.isArray(page.models))
37988
+ return { kind: "fetch_failed", reason: "empty" };
37989
+ if (pages === 0) {
37990
+ revision = result.revision;
37991
+ if (page.models.length === 0)
37992
+ return { kind: "fetch_failed", reason: "empty" };
37993
+ } else if (revision && result.revision && result.revision !== revision) {
37994
+ return { kind: "fetch_failed", reason: "revision_mismatch" };
37995
+ }
37996
+ entries.push(...page.models);
37997
+ pages++;
37998
+ if (page.hasMore !== true)
37999
+ break;
38000
+ if (page.models.length === 0)
38001
+ return { kind: "fetch_failed", reason: "incomplete" };
38002
+ offset += page.models.length;
38003
+ if (pages >= MAX_CATALOG_PAGES) {
38004
+ return { kind: "fetch_failed", reason: "incomplete" };
38005
+ }
38006
+ }
38007
+ if (entries.length === 0)
38008
+ return { kind: "fetch_failed", reason: "empty" };
38009
+ const plans = await fetchSubscriptionPlans(timeoutMs, revision);
37799
38010
  const backwardCompatModels = [];
37800
- for (const entry of data.models) {
38011
+ for (const entry of entries) {
37801
38012
  const id = externalIdFor(entry, "openrouter");
37802
38013
  if (id)
37803
38014
  backwardCompatModels.push({ id });
37804
38015
  }
37805
- _memCache = data.models;
37806
- const plans = await plansPromise;
38016
+ _memCache = entries;
37807
38017
  writeAllModelsCache({
37808
- entries: data.models,
38018
+ entries,
37809
38019
  models: backwardCompatModels,
37810
- ...plans !== undefined ? { plans } : {}
37811
- });
38020
+ ...plans !== undefined ? { plans } : {},
38021
+ ...revision !== undefined ? { catalogRevision: revision } : {}
38022
+ }, options.cachePath);
37812
38023
  _warmPromise = Promise.resolve();
37813
- return { kind: "refreshed", modelCount: data.models.length };
38024
+ return { kind: "refreshed", modelCount: entries.length, catalogRevision: revision, pages };
37814
38025
  }
37815
- async function fetchSubscriptionPlans(timeoutMs) {
38026
+ async function fetchSubscriptionPlans(timeoutMs, revision) {
37816
38027
  try {
37817
- const response = await fetch(FIREBASE_PLANS_URL, {
38028
+ const url = new URL(plansUrl());
38029
+ if (revision)
38030
+ url.searchParams.set("revision", revision);
38031
+ const response = await fetch(url.toString(), {
37818
38032
  signal: AbortSignal.timeout(timeoutMs)
37819
38033
  });
37820
38034
  if (!response.ok)
@@ -37846,11 +38060,9 @@ async function ensureCatalogReady(timeoutMs = 5000) {
37846
38060
  new Promise((resolve) => setTimeout(resolve, timeoutMs))
37847
38061
  ]);
37848
38062
  }
37849
- var FIREBASE_CATALOG_URL, FIREBASE_PLANS_URL, _memCache = null, _catalogEntriesForTest, _warmPromise = null;
38063
+ var DEFAULT_CATALOG_URL = "https://us-central1-claudish-6da10.cloudfunctions.net/queryModels?status=active&catalog=slim&limit=1000", _memCache = null, _catalogEntriesForTest, _warmPromise = null, CATALOG_REVISION_HEADER = "x-catalog-revision", MAX_CATALOG_PAGES = 40, CATALOG_PAGE_LIMIT = 1000;
37850
38064
  var init_catalog_client = __esm(() => {
37851
38065
  init_all_models_cache();
37852
- 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";
37853
- FIREBASE_PLANS_URL = process.env.CLAUDISH_PLANS_URL ?? derivePlansUrl(FIREBASE_CATALOG_URL);
37854
38066
  });
37855
38067
 
37856
38068
  // src/config-schema.ts
@@ -41049,6 +41261,18 @@ async function shutdownAllTeamRuns() {
41049
41261
  return;
41050
41262
  })));
41051
41263
  }
41264
+ function snippetHeadAndTail(text) {
41265
+ if (text.length <= SNIPPET_LIMIT)
41266
+ return text;
41267
+ const head = text.slice(0, SNIPPET_HEAD);
41268
+ const tail = text.slice(-(SNIPPET_LIMIT - SNIPPET_HEAD));
41269
+ const omitted = text.length - head.length - tail.length;
41270
+ return `${head}
41271
+
41272
+ \u2026 [${omitted} bytes omitted] \u2026
41273
+
41274
+ ${tail}`;
41275
+ }
41052
41276
  function classifyRunOutput(opts) {
41053
41277
  const {
41054
41278
  outputSize,
@@ -41419,7 +41643,7 @@ async function startModels(sessionPath, opts = {}) {
41419
41643
  reason,
41420
41644
  detail,
41421
41645
  stderrSnippet: stderr ? redactSecrets(stderr).slice(-2000) : undefined,
41422
- stdoutSnippet: stdoutTail ? redactSecrets(stdoutTail).slice(-2000) : undefined,
41646
+ stdoutSnippet: stdoutTail ? snippetHeadAndTail(redactSecrets(stdoutTail)) : undefined,
41423
41647
  errorLogPath,
41424
41648
  upstreamErrorLogPath: existsSync20(upstreamErrorLogPath) ? upstreamErrorLogPath : undefined,
41425
41649
  workDir: sessionPath
@@ -41696,7 +41920,7 @@ function formatVerdict(verdict, sessionPath) {
41696
41920
  }
41697
41921
  return output;
41698
41922
  }
41699
- var TEAM_CAPTURE_ENV_VAR = "CLAUDISH_TEAM_CAPTURE", liveTeamRuns, STDOUT_TAIL_LIMIT = 4000, API_ERROR_RE, BG_CEILING_RE, DEFAULT_MIN_OUTPUT_BYTES = 0, BENIGN_STDERR_PATTERNS;
41923
+ var TEAM_CAPTURE_ENV_VAR = "CLAUDISH_TEAM_CAPTURE", liveTeamRuns, STDOUT_TAIL_LIMIT = 4000, SNIPPET_LIMIT = 2000, SNIPPET_HEAD = 600, API_ERROR_RE, BG_CEILING_RE, DEFAULT_MIN_OUTPUT_BYTES = 0, BENIGN_STDERR_PATTERNS;
41700
41924
  var init_team_orchestrator = __esm(() => {
41701
41925
  init_prehydrate();
41702
41926
  init_stream_json_reducer();
@@ -57743,6 +57967,51 @@ var init_api_key_map = __esm(() => {
57743
57967
  };
57744
57968
  });
57745
57969
 
57970
+ // src/providers/provider-slug-resolve.ts
57971
+ function sharedPrefixLength(a, b) {
57972
+ let n = 0;
57973
+ while (n < a.length && n < b.length && a[n] === b[n])
57974
+ n++;
57975
+ return n;
57976
+ }
57977
+ function rankSuggestions(typed, providers) {
57978
+ const needle = typed.toLowerCase();
57979
+ const scored = [];
57980
+ for (const provider of providers) {
57981
+ const slug = provider.slug.toLowerCase();
57982
+ let score = 0;
57983
+ if (slug.includes(needle) || needle.includes(slug)) {
57984
+ score = 2;
57985
+ } else if (sharedPrefixLength(slug, needle) >= MIN_PREFIX_OVERLAP) {
57986
+ score = 1;
57987
+ }
57988
+ if (score > 0)
57989
+ scored.push({ provider, score });
57990
+ }
57991
+ scored.sort((a, b) => b.score - a.score || b.provider.count - a.provider.count);
57992
+ return scored.slice(0, SUGGESTION_LIMIT).map((s) => s.provider);
57993
+ }
57994
+ function resolveProviderSlug(typed, providers) {
57995
+ const routingOwner = reservedNamespaceOwner(typed) ?? null;
57996
+ if (providers.length === 0) {
57997
+ return { kind: "match", canonical: typed, suggestions: [], routingOwner };
57998
+ }
57999
+ const exact = providers.find((p) => p.slug.toLowerCase() === typed.toLowerCase());
58000
+ if (exact) {
58001
+ return { kind: "match", canonical: exact.slug, suggestions: [], routingOwner };
58002
+ }
58003
+ return {
58004
+ kind: "unknown",
58005
+ canonical: null,
58006
+ suggestions: rankSuggestions(typed, providers),
58007
+ routingOwner
58008
+ };
58009
+ }
58010
+ var SUGGESTION_LIMIT = 5, MIN_PREFIX_OVERLAP = 3;
58011
+ var init_provider_slug_resolve = __esm(() => {
58012
+ init_reserved_namespace();
58013
+ });
58014
+
57746
58015
  // src/branding.ts
57747
58016
  function paint(line) {
57748
58017
  const { RESET, BOLD, CYAN, BLUE, DIM } = cliAnsi();
@@ -58313,7 +58582,19 @@ Usage: claudish --models --provider <slug>`);
58313
58582
  const hasJsonFlag = args.includes("--json");
58314
58583
  const forceUpdate = config.forceUpdate || args.includes("--models-refresh");
58315
58584
  const providerIdx = args.indexOf("--provider");
58316
- const providerSlug = providerIdx !== -1 && providerIdx + 1 < args.length ? args[providerIdx + 1] : null;
58585
+ const inlineProvider = args.find((a) => a.startsWith("--provider="));
58586
+ let providerSlug = null;
58587
+ if (inlineProvider) {
58588
+ providerSlug = inlineProvider.slice("--provider=".length);
58589
+ } else if (providerIdx !== -1) {
58590
+ const next = args[providerIdx + 1];
58591
+ providerSlug = next && !next.startsWith("--") ? next : "";
58592
+ }
58593
+ if (providerSlug === "") {
58594
+ console.error("--provider needs a slug: claudish --models --provider <slug>");
58595
+ console.error("Run `claudish --providers` for the full list.");
58596
+ process.exit(1);
58597
+ }
58317
58598
  if (forceUpdate)
58318
58599
  clearAllModelCaches();
58319
58600
  if (query && providerSlug) {
@@ -58588,7 +58869,46 @@ Top ${response.total} models from Firebase (pool: ${response.poolSize} eligible)
58588
58869
  console.log("Top recommended: claudish --models-top");
58589
58870
  console.log("");
58590
58871
  }
58591
- async function printByProvider(providerSlug, jsonOutput) {
58872
+ function printUnknownProviderSlug(typedSlug, resolved, catalogSize, jsonOutput) {
58873
+ if (jsonOutput) {
58874
+ console.log(JSON.stringify({
58875
+ error: `"${typedSlug}" is not a provider slug in the model catalog`,
58876
+ provider: typedSlug,
58877
+ suggestions: resolved.suggestions.map((s) => s.slug),
58878
+ routingPrefixOwner: resolved.routingOwner,
58879
+ validSlugs: catalogSize
58880
+ }, null, 2));
58881
+ return;
58882
+ }
58883
+ console.error(`
58884
+ \u274C "${typedSlug}" is not a provider slug in the model catalog.`);
58885
+ if (resolved.suggestions.length > 0) {
58886
+ const list = resolved.suggestions.map((s) => `${s.slug} (${s.count} active model${s.count === 1 ? "" : "s"})`).join(", ");
58887
+ console.error(`
58888
+ Did you mean: ${list}`);
58889
+ }
58890
+ if (resolved.routingOwner) {
58891
+ console.error(`
58892
+ "${typedSlug}" IS a claudish routing prefix for the "${resolved.routingOwner}" provider \u2014` + `
58893
+ use it with --model: claudish --model ${typedSlug}@<model-id>` + `
58894
+ Routing prefixes and catalog vendor slugs are different vocabularies.`);
58895
+ }
58896
+ console.error(`
58897
+ claudish --providers lists all ${catalogSize} catalog slugs`);
58898
+ console.error(` claudish -s ${typedSlug}${" ".repeat(Math.max(1, 12 - typedSlug.length))}searches model ids instead
58899
+ `);
58900
+ }
58901
+ async function printByProvider(typedSlug, jsonOutput) {
58902
+ let catalogProviders = [];
58903
+ try {
58904
+ catalogProviders = await getProviderList();
58905
+ } catch {}
58906
+ const resolved = resolveProviderSlug(typedSlug, catalogProviders);
58907
+ if (resolved.kind === "unknown") {
58908
+ printUnknownProviderSlug(typedSlug, resolved, catalogProviders.length, jsonOutput);
58909
+ process.exit(1);
58910
+ }
58911
+ const providerSlug = resolved.canonical ?? typedSlug;
58592
58912
  let models;
58593
58913
  try {
58594
58914
  models = await getModelsByProvider(providerSlug, 200);
@@ -58603,8 +58923,8 @@ async function printByProvider(providerSlug, jsonOutput) {
58603
58923
  }
58604
58924
  if (models.length === 0) {
58605
58925
  console.log(`
58606
- No active models found for provider "${providerSlug}". Try \`claudish -s <query>\` to search the full catalog.
58607
- `);
58926
+ Provider "${providerSlug}" is in the catalog but has no active models right now.`);
58927
+ console.log("Try `claudish -s <query>` to search the full catalog.\n");
58608
58928
  return;
58609
58929
  }
58610
58930
  console.log(`
@@ -59685,6 +60005,7 @@ var init_cli = __esm(() => {
59685
60005
  init_probe_live();
59686
60006
  init_probe_runner();
59687
60007
  init_provider_definitions();
60008
+ init_provider_slug_resolve();
59688
60009
  init_routing_rules();
59689
60010
  init_ansi();
59690
60011
  init_provider_resolver();
@@ -67784,6 +68105,46 @@ function resolveAdvisorModelArg(config, cwd = process.cwd()) {
67784
68105
  source: "claudish"
67785
68106
  };
67786
68107
  }
68108
+ function isSuppressibleChildStderrLine(line) {
68109
+ return CHILD_STDERR_NOISE.some((re) => re.test(line));
68110
+ }
68111
+ function relayChildStderr(stream) {
68112
+ let buffered = "";
68113
+ const emit = (line) => {
68114
+ if (isSuppressibleChildStderrLine(line)) {
68115
+ log(`[Suppressed] claude-code-stderr: ${line.trimEnd()}`);
68116
+ return;
68117
+ }
68118
+ process.stderr.write(`${line}
68119
+ `);
68120
+ };
68121
+ stream.setEncoding("utf8");
68122
+ stream.on("data", (chunk) => {
68123
+ buffered += chunk;
68124
+ const lines = buffered.split(`
68125
+ `);
68126
+ buffered = lines.pop() ?? "";
68127
+ for (const line of lines)
68128
+ emit(line);
68129
+ });
68130
+ const flush = () => {
68131
+ if (buffered.length === 0)
68132
+ return;
68133
+ const tail = buffered;
68134
+ buffered = "";
68135
+ if (isSuppressibleChildStderrLine(tail)) {
68136
+ log(`[Suppressed] claude-code-stderr: ${tail}`);
68137
+ } else {
68138
+ process.stderr.write(tail);
68139
+ }
68140
+ };
68141
+ stream.on("end", flush);
68142
+ stream.on("close", flush);
68143
+ stream.on("error", (err) => {
68144
+ flush();
68145
+ logStderr(`child stderr relay ended: ${err}`);
68146
+ });
68147
+ }
67787
68148
  async function runClaudeWithProxy(config, proxyUrl, onCleanup) {
67788
68149
  const hasProfileMappings = config.modelOpus || config.modelSonnet || config.modelHaiku || config.modelSubagent;
67789
68150
  const advisorNativeSession = isAdvisorNativeSession(config);
@@ -67858,6 +68219,9 @@ async function runClaudeWithProxy(config, proxyUrl, onCleanup) {
67858
68219
  }
67859
68220
  let hidAnthropicApiKey = false;
67860
68221
  delete env.CLAUDECODE;
68222
+ if (!config.interactive && env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC === undefined) {
68223
+ env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1";
68224
+ }
67861
68225
  if (config.monitor || advisorNativeSession) {
67862
68226
  delete env.ANTHROPIC_API_KEY;
67863
68227
  delete env.ANTHROPIC_AUTH_TOKEN;
@@ -67955,12 +68319,16 @@ Or set CLAUDE_PATH to your custom installation:`);
67955
68319
  } else if (config.interactive && !process.stdout.isTTY && !process.stdin.isTTY) {
67956
68320
  console.error("[claudish] An interactive session was requested but no terminal is attached (stdin and stdout are both non-TTY). Pass a prompt argument, or use --stdin / -p for non-interactive mode.");
67957
68321
  }
67958
- const stdio = ttyFd !== undefined ? [0, ttyFd, ttyFd] : "inherit";
68322
+ const filterChildStderr = !config.interactive && ttyFd === undefined;
68323
+ const stdio = ttyFd !== undefined ? [0, ttyFd, ttyFd] : filterChildStderr ? ["inherit", "inherit", "pipe"] : "inherit";
67959
68324
  const proc = spawn5(spawnCommand, claudeArgs, {
67960
68325
  env,
67961
68326
  stdio,
67962
68327
  shell: needsShell
67963
68328
  });
68329
+ if (filterChildStderr && proc.stderr) {
68330
+ relayChildStderr(proc.stderr);
68331
+ }
67964
68332
  if (config.interactive) {
67965
68333
  restoreTerminal = beginTerminalIsolation((entry) => {
67966
68334
  logStderr(`[Suppressed] ${entry.source}: ${entry.text.trimEnd()}`);
@@ -68098,7 +68466,7 @@ var restoreTerminal = null, macosKeychainAnthropicResult, defaultKeychainAnthrop
68098
68466
  macosKeychainAnthropicResult = false;
68099
68467
  }
68100
68468
  return macosKeychainAnthropicResult;
68101
- }, CLAUDISH_PLACEHOLDER_API_KEY = "sk-ant-api03-placeholder-not-used-proxy-handles-auth-with-openrouter-key-xxxxxxxxxxxxxxxxxxxxx", CLAUDISH_PLACEHOLDER_AUTH_TOKEN = "placeholder-token-not-used-proxy-handles-auth", STALE_TOKEN_FILE_MS, MAX_TOKEN_FILES_SCANNED = 4000, CLAUDE_CODE_DEFAULT_MAX_CONTEXT = 200000, USER_STATUS_LINE_TIMEOUT_SECONDS = 3, MIN_AUTO_COMPACT_WINDOW = 200000, ADVISOR_TOOL_ENV_VAR = "CLAUDE_CODE_ENABLE_EXPERIMENTAL_ADVISOR_TOOL", CLAUDISH_CHILD_ADVISOR_MODEL = "sonnet", SIGNAL_EXIT_NUMBERS;
68469
+ }, CLAUDISH_PLACEHOLDER_API_KEY = "sk-ant-api03-placeholder-not-used-proxy-handles-auth-with-openrouter-key-xxxxxxxxxxxxxxxxxxxxx", CLAUDISH_PLACEHOLDER_AUTH_TOKEN = "placeholder-token-not-used-proxy-handles-auth", STALE_TOKEN_FILE_MS, MAX_TOKEN_FILES_SCANNED = 4000, CLAUDE_CODE_DEFAULT_MAX_CONTEXT = 200000, USER_STATUS_LINE_TIMEOUT_SECONDS = 3, MIN_AUTO_COMPACT_WINDOW = 200000, ADVISOR_TOOL_ENV_VAR = "CLAUDE_CODE_ENABLE_EXPERIMENTAL_ADVISOR_TOOL", CLAUDISH_CHILD_ADVISOR_MODEL = "sonnet", CHILD_STDERR_NOISE, SIGNAL_EXIT_NUMBERS;
68102
68470
  var init_claude_runner = __esm(() => {
68103
68471
  init_model_catalog();
68104
68472
  init_config2();
@@ -68112,6 +68480,7 @@ var init_claude_runner = __esm(() => {
68112
68480
  init_terminal_isolation();
68113
68481
  init_theme_mode();
68114
68482
  STALE_TOKEN_FILE_MS = 7 * 24 * 60 * 60 * 1000;
68483
+ CHILD_STDERR_NOISE = [/^\[claude-code:unrecognized_model\]/];
68115
68484
  SIGNAL_EXIT_NUMBERS = {
68116
68485
  SIGHUP: 1,
68117
68486
  SIGINT: 2,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "9.5.0",
3
+ "version": "9.6.1",
4
4
  "description": "Run Claude Code with any model - OpenRouter, Ollama, LM Studio & local models",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -60,10 +60,10 @@
60
60
  "ai"
61
61
  ],
62
62
  "optionalDependencies": {
63
- "@claudish/magmux-darwin-arm64": "9.5.0",
64
- "@claudish/magmux-darwin-x64": "9.5.0",
65
- "@claudish/magmux-linux-arm64": "9.5.0",
66
- "@claudish/magmux-linux-x64": "9.5.0"
63
+ "@claudish/magmux-darwin-arm64": "9.6.1",
64
+ "@claudish/magmux-darwin-x64": "9.6.1",
65
+ "@claudish/magmux-linux-arm64": "9.6.1",
66
+ "@claudish/magmux-linux-x64": "9.6.1"
67
67
  },
68
68
  "author": "Jack Rudenko <i@madappgang.com>",
69
69
  "license": "MIT",