claudish 9.5.0 → 9.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +307 -60
  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.0";
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();
@@ -21589,6 +21686,31 @@ function formatRawSseLogPayload(dataStr) {
21589
21686
  return dataStr;
21590
21687
  return `${dataStr.substring(0, SSE_LOG_MAX_CHARS)} ${SSE_LOG_TRUNCATION_MARKER} original_chars=${dataStr.length}`;
21591
21688
  }
21689
+ function describeInStreamError(chunk) {
21690
+ if (!chunk || typeof chunk !== "object")
21691
+ return;
21692
+ const error = chunk.error;
21693
+ if (!error)
21694
+ return;
21695
+ if (typeof error === "string")
21696
+ return error;
21697
+ if (typeof error !== "object")
21698
+ return String(error);
21699
+ const e = error;
21700
+ const metadata = e.metadata ?? {};
21701
+ const parts = [];
21702
+ const provider = chunk.provider;
21703
+ if (typeof provider === "string" && provider)
21704
+ parts.push(`[${provider}]`);
21705
+ const code = e.code ?? metadata.provider_code;
21706
+ const type = e.type ?? metadata.error_type;
21707
+ const label = [code, type].filter((v) => v !== undefined && v !== null && v !== "").join(" ");
21708
+ if (label)
21709
+ parts.push(label);
21710
+ const message = e.message ?? metadata.raw;
21711
+ parts.push(typeof message === "string" && message.trim() ? message : JSON.stringify(error).slice(0, 500));
21712
+ return parts.join(" ");
21713
+ }
21592
21714
  function validateToolArguments(toolName, argsStr, toolSchemas, textContent) {
21593
21715
  const result = validateAndRepairToolCall(toolName, argsStr, toolSchemas, textContent);
21594
21716
  if (result.repaired) {
@@ -21865,6 +21987,12 @@ data: ${JSON.stringify(d)}
21865
21987
  state.usage = chunk.usage;
21866
21988
  log(`[Streaming] Usage data received: prompt=${chunk.usage.prompt_tokens}, completion=${chunk.usage.completion_tokens}, total=${chunk.usage.total_tokens}`);
21867
21989
  }
21990
+ const inStreamError = describeInStreamError(chunk);
21991
+ if (inStreamError) {
21992
+ log(`[Streaming] Upstream error inside a 200 stream: ${inStreamError}`);
21993
+ await finalize("error", inStreamError);
21994
+ return;
21995
+ }
21868
21996
  const delta = chunk.choices?.[0]?.delta;
21869
21997
  const finishReason = chunk.choices?.[0]?.finish_reason;
21870
21998
  if (finishReason)
@@ -23370,11 +23498,18 @@ var init_qwen_model_dialect = __esm(() => {
23370
23498
  log(`[QwenModelDialect] effort ${effort} -> enable_thinking: false for ${this.modelId}`);
23371
23499
  } else {
23372
23500
  request.enable_thinking = true;
23373
- const budget = this.effortToThinkingTokenBudget(effort);
23374
- if (budget !== undefined) {
23375
- request.thinking_budget = budget;
23501
+ const requested = this.effortToThinkingTokenBudget(effort);
23502
+ const ceiling = outputCeilingOf(originalRequest, request);
23503
+ const budget = clampThinkingBudget(requested, ceiling);
23504
+ if (budget === "no-room") {
23505
+ delete request.thinking_budget;
23506
+ log(`[QwenModelDialect] effort ${effort} -> enable_thinking: true, no thinking_budget (${requested} does not fit under ceiling ${ceiling}) for ${this.modelId}`);
23507
+ } else {
23508
+ if (budget !== undefined) {
23509
+ request.thinking_budget = budget;
23510
+ }
23511
+ 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
23512
  }
23377
- log(`[QwenModelDialect] effort ${effort} -> enable_thinking: true, thinking_budget: ${budget ?? "(model max)"} for ${this.modelId}`);
23378
23513
  }
23379
23514
  if (originalRequest.thinking)
23380
23515
  delete request.thinking;
@@ -37624,6 +37759,12 @@ var init_auto_route = __esm(() => {
37624
37759
  });
37625
37760
 
37626
37761
  // src/providers/catalog-client.ts
37762
+ function catalogUrl() {
37763
+ return process.env.CLAUDISH_CATALOG_URL ?? process.env.FIREBASE_CATALOG_URL ?? DEFAULT_CATALOG_URL;
37764
+ }
37765
+ function plansUrl() {
37766
+ return process.env.CLAUDISH_PLANS_URL ?? derivePlansUrl(catalogUrl());
37767
+ }
37627
37768
  function derivePlansUrl(catalogUrl) {
37628
37769
  try {
37629
37770
  const url = new URL(catalogUrl);
@@ -37772,49 +37913,97 @@ function catalogWarmDisabledFor(value) {
37772
37913
  function catalogWarmDisabled() {
37773
37914
  return catalogWarmDisabledFor(process.env.CLAUDISH_DISABLE_CATALOG_WARM);
37774
37915
  }
37775
- async function refreshCatalog(timeoutMs) {
37776
- if (catalogWarmDisabled()) {
37777
- return { kind: "fetch_failed", reason: "disabled" };
37778
- }
37779
- const plansPromise = fetchSubscriptionPlans(timeoutMs);
37916
+ function buildCatalogPageUrl(baseUrl, offset, limit, revision) {
37917
+ const url = new URL(baseUrl);
37918
+ url.searchParams.set("offset", String(offset));
37919
+ url.searchParams.set("limit", String(limit));
37920
+ if (revision)
37921
+ url.searchParams.set("revision", revision);
37922
+ return url.toString();
37923
+ }
37924
+ async function fetchCatalogPage(url, timeoutMs) {
37780
37925
  let response;
37781
37926
  try {
37782
- response = await fetch(FIREBASE_CATALOG_URL, { signal: AbortSignal.timeout(timeoutMs) });
37927
+ response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
37783
37928
  } catch (err) {
37784
37929
  const name = err?.name;
37785
37930
  const reason = name === "TimeoutError" || name === "AbortError" ? "timeout" : "network";
37786
- return { kind: "fetch_failed", reason };
37931
+ return { ok: false, reason };
37787
37932
  }
37788
37933
  if (!response.ok)
37789
- return { kind: "fetch_failed", reason: "http_error" };
37790
- let data;
37934
+ return { ok: false, reason: "http_error" };
37935
+ let page;
37791
37936
  try {
37792
- data = await response.json();
37937
+ page = await response.json();
37793
37938
  } catch {
37794
- return { kind: "fetch_failed", reason: "network" };
37939
+ return { ok: false, reason: "network" };
37795
37940
  }
37796
- if (!Array.isArray(data.models) || data.models.length === 0) {
37797
- return { kind: "fetch_failed", reason: "empty" };
37941
+ const revision = response.headers.get(CATALOG_REVISION_HEADER) ?? undefined;
37942
+ return { ok: true, page, revision };
37943
+ }
37944
+ async function refreshCatalog(timeoutMs, options = {}) {
37945
+ if (catalogWarmDisabled()) {
37946
+ return { kind: "fetch_failed", reason: "disabled" };
37947
+ }
37948
+ const entries = [];
37949
+ let revision;
37950
+ let pages = 0;
37951
+ let offset = 0;
37952
+ for (;; ) {
37953
+ const url = buildCatalogPageUrl(catalogUrl(), offset, CATALOG_PAGE_LIMIT, revision);
37954
+ const result = await fetchCatalogPage(url, timeoutMs);
37955
+ if (!result.ok) {
37956
+ return {
37957
+ kind: "fetch_failed",
37958
+ reason: pages === 0 ? result.reason : "incomplete"
37959
+ };
37960
+ }
37961
+ const { page } = result;
37962
+ if (!Array.isArray(page.models))
37963
+ return { kind: "fetch_failed", reason: "empty" };
37964
+ if (pages === 0) {
37965
+ revision = result.revision;
37966
+ if (page.models.length === 0)
37967
+ return { kind: "fetch_failed", reason: "empty" };
37968
+ } else if (revision && result.revision && result.revision !== revision) {
37969
+ return { kind: "fetch_failed", reason: "revision_mismatch" };
37970
+ }
37971
+ entries.push(...page.models);
37972
+ pages++;
37973
+ if (page.hasMore !== true)
37974
+ break;
37975
+ if (page.models.length === 0)
37976
+ return { kind: "fetch_failed", reason: "incomplete" };
37977
+ offset += page.models.length;
37978
+ if (pages >= MAX_CATALOG_PAGES) {
37979
+ return { kind: "fetch_failed", reason: "incomplete" };
37980
+ }
37798
37981
  }
37982
+ if (entries.length === 0)
37983
+ return { kind: "fetch_failed", reason: "empty" };
37984
+ const plans = await fetchSubscriptionPlans(timeoutMs, revision);
37799
37985
  const backwardCompatModels = [];
37800
- for (const entry of data.models) {
37986
+ for (const entry of entries) {
37801
37987
  const id = externalIdFor(entry, "openrouter");
37802
37988
  if (id)
37803
37989
  backwardCompatModels.push({ id });
37804
37990
  }
37805
- _memCache = data.models;
37806
- const plans = await plansPromise;
37991
+ _memCache = entries;
37807
37992
  writeAllModelsCache({
37808
- entries: data.models,
37993
+ entries,
37809
37994
  models: backwardCompatModels,
37810
- ...plans !== undefined ? { plans } : {}
37811
- });
37995
+ ...plans !== undefined ? { plans } : {},
37996
+ ...revision !== undefined ? { catalogRevision: revision } : {}
37997
+ }, options.cachePath);
37812
37998
  _warmPromise = Promise.resolve();
37813
- return { kind: "refreshed", modelCount: data.models.length };
37999
+ return { kind: "refreshed", modelCount: entries.length, catalogRevision: revision, pages };
37814
38000
  }
37815
- async function fetchSubscriptionPlans(timeoutMs) {
38001
+ async function fetchSubscriptionPlans(timeoutMs, revision) {
37816
38002
  try {
37817
- const response = await fetch(FIREBASE_PLANS_URL, {
38003
+ const url = new URL(plansUrl());
38004
+ if (revision)
38005
+ url.searchParams.set("revision", revision);
38006
+ const response = await fetch(url.toString(), {
37818
38007
  signal: AbortSignal.timeout(timeoutMs)
37819
38008
  });
37820
38009
  if (!response.ok)
@@ -37846,11 +38035,9 @@ async function ensureCatalogReady(timeoutMs = 5000) {
37846
38035
  new Promise((resolve) => setTimeout(resolve, timeoutMs))
37847
38036
  ]);
37848
38037
  }
37849
- var FIREBASE_CATALOG_URL, FIREBASE_PLANS_URL, _memCache = null, _catalogEntriesForTest, _warmPromise = null;
38038
+ 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
38039
  var init_catalog_client = __esm(() => {
37851
38040
  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
38041
  });
37855
38042
 
37856
38043
  // src/config-schema.ts
@@ -41049,6 +41236,18 @@ async function shutdownAllTeamRuns() {
41049
41236
  return;
41050
41237
  })));
41051
41238
  }
41239
+ function snippetHeadAndTail(text) {
41240
+ if (text.length <= SNIPPET_LIMIT)
41241
+ return text;
41242
+ const head = text.slice(0, SNIPPET_HEAD);
41243
+ const tail = text.slice(-(SNIPPET_LIMIT - SNIPPET_HEAD));
41244
+ const omitted = text.length - head.length - tail.length;
41245
+ return `${head}
41246
+
41247
+ \u2026 [${omitted} bytes omitted] \u2026
41248
+
41249
+ ${tail}`;
41250
+ }
41052
41251
  function classifyRunOutput(opts) {
41053
41252
  const {
41054
41253
  outputSize,
@@ -41419,7 +41618,7 @@ async function startModels(sessionPath, opts = {}) {
41419
41618
  reason,
41420
41619
  detail,
41421
41620
  stderrSnippet: stderr ? redactSecrets(stderr).slice(-2000) : undefined,
41422
- stdoutSnippet: stdoutTail ? redactSecrets(stdoutTail).slice(-2000) : undefined,
41621
+ stdoutSnippet: stdoutTail ? snippetHeadAndTail(redactSecrets(stdoutTail)) : undefined,
41423
41622
  errorLogPath,
41424
41623
  upstreamErrorLogPath: existsSync20(upstreamErrorLogPath) ? upstreamErrorLogPath : undefined,
41425
41624
  workDir: sessionPath
@@ -41696,7 +41895,7 @@ function formatVerdict(verdict, sessionPath) {
41696
41895
  }
41697
41896
  return output;
41698
41897
  }
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;
41898
+ 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
41899
  var init_team_orchestrator = __esm(() => {
41701
41900
  init_prehydrate();
41702
41901
  init_stream_json_reducer();
@@ -67784,6 +67983,46 @@ function resolveAdvisorModelArg(config, cwd = process.cwd()) {
67784
67983
  source: "claudish"
67785
67984
  };
67786
67985
  }
67986
+ function isSuppressibleChildStderrLine(line) {
67987
+ return CHILD_STDERR_NOISE.some((re) => re.test(line));
67988
+ }
67989
+ function relayChildStderr(stream) {
67990
+ let buffered = "";
67991
+ const emit = (line) => {
67992
+ if (isSuppressibleChildStderrLine(line)) {
67993
+ log(`[Suppressed] claude-code-stderr: ${line.trimEnd()}`);
67994
+ return;
67995
+ }
67996
+ process.stderr.write(`${line}
67997
+ `);
67998
+ };
67999
+ stream.setEncoding("utf8");
68000
+ stream.on("data", (chunk) => {
68001
+ buffered += chunk;
68002
+ const lines = buffered.split(`
68003
+ `);
68004
+ buffered = lines.pop() ?? "";
68005
+ for (const line of lines)
68006
+ emit(line);
68007
+ });
68008
+ const flush = () => {
68009
+ if (buffered.length === 0)
68010
+ return;
68011
+ const tail = buffered;
68012
+ buffered = "";
68013
+ if (isSuppressibleChildStderrLine(tail)) {
68014
+ log(`[Suppressed] claude-code-stderr: ${tail}`);
68015
+ } else {
68016
+ process.stderr.write(tail);
68017
+ }
68018
+ };
68019
+ stream.on("end", flush);
68020
+ stream.on("close", flush);
68021
+ stream.on("error", (err) => {
68022
+ flush();
68023
+ logStderr(`child stderr relay ended: ${err}`);
68024
+ });
68025
+ }
67787
68026
  async function runClaudeWithProxy(config, proxyUrl, onCleanup) {
67788
68027
  const hasProfileMappings = config.modelOpus || config.modelSonnet || config.modelHaiku || config.modelSubagent;
67789
68028
  const advisorNativeSession = isAdvisorNativeSession(config);
@@ -67858,6 +68097,9 @@ async function runClaudeWithProxy(config, proxyUrl, onCleanup) {
67858
68097
  }
67859
68098
  let hidAnthropicApiKey = false;
67860
68099
  delete env.CLAUDECODE;
68100
+ if (!config.interactive && env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC === undefined) {
68101
+ env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1";
68102
+ }
67861
68103
  if (config.monitor || advisorNativeSession) {
67862
68104
  delete env.ANTHROPIC_API_KEY;
67863
68105
  delete env.ANTHROPIC_AUTH_TOKEN;
@@ -67955,12 +68197,16 @@ Or set CLAUDE_PATH to your custom installation:`);
67955
68197
  } else if (config.interactive && !process.stdout.isTTY && !process.stdin.isTTY) {
67956
68198
  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
68199
  }
67958
- const stdio = ttyFd !== undefined ? [0, ttyFd, ttyFd] : "inherit";
68200
+ const filterChildStderr = !config.interactive && ttyFd === undefined;
68201
+ const stdio = ttyFd !== undefined ? [0, ttyFd, ttyFd] : filterChildStderr ? ["inherit", "inherit", "pipe"] : "inherit";
67959
68202
  const proc = spawn5(spawnCommand, claudeArgs, {
67960
68203
  env,
67961
68204
  stdio,
67962
68205
  shell: needsShell
67963
68206
  });
68207
+ if (filterChildStderr && proc.stderr) {
68208
+ relayChildStderr(proc.stderr);
68209
+ }
67964
68210
  if (config.interactive) {
67965
68211
  restoreTerminal = beginTerminalIsolation((entry) => {
67966
68212
  logStderr(`[Suppressed] ${entry.source}: ${entry.text.trimEnd()}`);
@@ -68098,7 +68344,7 @@ var restoreTerminal = null, macosKeychainAnthropicResult, defaultKeychainAnthrop
68098
68344
  macosKeychainAnthropicResult = false;
68099
68345
  }
68100
68346
  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;
68347
+ }, 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
68348
  var init_claude_runner = __esm(() => {
68103
68349
  init_model_catalog();
68104
68350
  init_config2();
@@ -68112,6 +68358,7 @@ var init_claude_runner = __esm(() => {
68112
68358
  init_terminal_isolation();
68113
68359
  init_theme_mode();
68114
68360
  STALE_TOKEN_FILE_MS = 7 * 24 * 60 * 60 * 1000;
68361
+ CHILD_STDERR_NOISE = [/^\[claude-code:unrecognized_model\]/];
68115
68362
  SIGNAL_EXIT_NUMBERS = {
68116
68363
  SIGHUP: 1,
68117
68364
  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.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",
@@ -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.0",
64
+ "@claudish/magmux-darwin-x64": "9.6.0",
65
+ "@claudish/magmux-linux-arm64": "9.6.0",
66
+ "@claudish/magmux-linux-x64": "9.6.0"
67
67
  },
68
68
  "author": "Jack Rudenko <i@madappgang.com>",
69
69
  "license": "MIT",