claudish 7.57.0 → 7.59.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 +128 -7
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -729,7 +729,7 @@ var init_onepassword_config = __esm(() => {
729
729
  });
730
730
 
731
731
  // src/version.ts
732
- var VERSION = "7.57.0";
732
+ var VERSION = "7.59.0";
733
733
 
734
734
  // src/logger.ts
735
735
  var exports_logger = {};
@@ -41714,6 +41714,33 @@ var init_token_tracker = __esm(() => {
41714
41714
  init_remote_provider_types();
41715
41715
  });
41716
41716
 
41717
+ // src/handlers/shared/upstream-error-capture.ts
41718
+ import { appendFileSync as appendFileSync4 } from "fs";
41719
+ function captureUpstreamError(record4) {
41720
+ const path = process.env[UPSTREAM_ERROR_LOG_ENV];
41721
+ if (!path)
41722
+ return false;
41723
+ try {
41724
+ const raw = record4.body ?? "";
41725
+ const truncated = raw.length > MAX_CAPTURED_BODY_BYTES;
41726
+ const line = JSON.stringify({
41727
+ at: record4.at ?? new Date().toISOString(),
41728
+ provider: record4.provider,
41729
+ model: record4.model,
41730
+ status: record4.status,
41731
+ body: truncated ? raw.slice(0, MAX_CAPTURED_BODY_BYTES) : raw,
41732
+ ...truncated ? { truncated: true, original_bytes: raw.length } : {}
41733
+ });
41734
+ appendFileSync4(path, `${line}
41735
+ `);
41736
+ return true;
41737
+ } catch {
41738
+ return false;
41739
+ }
41740
+ }
41741
+ var UPSTREAM_ERROR_LOG_ENV = "CLAUDISH_UPSTREAM_ERROR_LOG", MAX_CAPTURED_BODY_BYTES = 2048;
41742
+ var init_upstream_error_capture = () => {};
41743
+
41717
41744
  // src/handlers/composed-handler.ts
41718
41745
  function extractAuthHeaders(c) {
41719
41746
  const headers = c.req.header();
@@ -42113,6 +42140,12 @@ class ComposedHandler {
42113
42140
  } else {
42114
42141
  const errorText = await response.text();
42115
42142
  log(`[${this.provider.displayName}] Error: ${errorText}`);
42143
+ captureUpstreamError({
42144
+ provider: this.provider.displayName,
42145
+ model: this.bareModelName,
42146
+ status: response.status,
42147
+ body: errorText
42148
+ });
42116
42149
  const transportTerminal = this.provider.classifyTerminalError?.(response.status, errorText);
42117
42150
  const hint = getRecoveryHint(response.status, errorText, this.provider.displayName, transportTerminal);
42118
42151
  let parsedErrorBody;
@@ -42615,6 +42648,7 @@ var init_composed_handler = __esm(() => {
42615
42648
  init_openai_responses_sse();
42616
42649
  init_openai_sse();
42617
42650
  init_token_tracker();
42651
+ init_upstream_error_capture();
42618
42652
  STREAM_RETRY_DELAYS_MS = [3000, 15000, 30000];
42619
42653
  });
42620
42654
 
@@ -45015,6 +45049,36 @@ var init_default_routing_rules = __esm(() => {
45015
45049
  validateDefaultRoutingRules();
45016
45050
  });
45017
45051
 
45052
+ // src/providers/model-availability.ts
45053
+ function rosterHas(ids, wireId) {
45054
+ const needle = wireId.trim().toLowerCase();
45055
+ return ids.some((id) => id.trim().toLowerCase() === needle);
45056
+ }
45057
+ async function providerServesModel(provider, wireId) {
45058
+ const def = getProviderByName(provider);
45059
+ if (def?.modelDiscovery) {
45060
+ const models = await discoverProviderModels(provider);
45061
+ if (models.length > 0) {
45062
+ return rosterHas(models.map((m) => m.id), wireId) ? "serves" : "not-served";
45063
+ }
45064
+ getDiscoveryFailure(provider);
45065
+ return "unknown";
45066
+ }
45067
+ const entries = getCatalogEntries();
45068
+ if (!entries)
45069
+ return "unknown";
45070
+ const needle = wireId.trim().toLowerCase();
45071
+ const row = entries.find((e) => e.modelId.toLowerCase() === needle || e.aliases.some((a) => a.toLowerCase() === needle) || (e.aggregators ?? []).some((a) => a.externalId?.toLowerCase() === needle));
45072
+ if (!row)
45073
+ return "unknown";
45074
+ return (row.aggregators ?? []).some((a) => a.provider === provider) ? "serves" : "unknown";
45075
+ }
45076
+ var init_model_availability = __esm(() => {
45077
+ init_catalog_client();
45078
+ init_model_discovery();
45079
+ init_provider_definitions();
45080
+ });
45081
+
45018
45082
  // src/providers/routing-rules.ts
45019
45083
  function mergeRoutingRules(defaults, global_, local) {
45020
45084
  return { ...defaults, ...global_, ...local };
@@ -45123,6 +45187,13 @@ async function routeExplicit(modelSpec, model, provider, cachePath) {
45123
45187
  reason: `Could not build a route for "${modelSpec}".`
45124
45188
  };
45125
45189
  }
45190
+ if (await providerServesModel(built.provider, wireIdOf(built)) === "not-served") {
45191
+ return {
45192
+ kind: "no-route",
45193
+ reason: `${built.displayName} does not serve "${model}".`,
45194
+ hint: `Check the model id, or use a bare \`${model}\` to let claudish pick a provider ` + "that carries it."
45195
+ };
45196
+ }
45126
45197
  return { kind: "ok", primary: built, fallbacks: [] };
45127
45198
  }
45128
45199
  async function routeBare(model, nativeProvider, rules, defaultProvider, cachePath) {
@@ -45164,9 +45235,37 @@ async function routeBare(model, nativeProvider, rules, defaultProvider, cachePat
45164
45235
  hint: buildCredentialHint(model, skipped) ?? undefined
45165
45236
  };
45166
45237
  }
45167
- const [primary, ...fallbacks] = credentialed;
45238
+ const availability = await Promise.all(credentialed.map((candidate) => providerServesModel(candidate.provider, wireIdOf(candidate))));
45239
+ const serving = [];
45240
+ const notServing = [];
45241
+ credentialed.forEach((candidate, i) => {
45242
+ if (availability[i] === "not-served") {
45243
+ notServing.push(candidate.provider);
45244
+ } else {
45245
+ serving.push(candidate);
45246
+ }
45247
+ });
45248
+ if (serving.length === 0) {
45249
+ return {
45250
+ kind: "no-route",
45251
+ reason: `No provider serves "${model}" (checked: ${notServing.join(", ")}).`,
45252
+ hint: buildCredentialHint(model, notServing) ?? undefined
45253
+ };
45254
+ }
45255
+ if (notServing.length > 0) {
45256
+ log(`[routing] ${model}: skipped ${notServing.join(", ")} \u2014 does not serve this model`);
45257
+ const droppedSubscription = notServing.filter((p) => isSubscriptionProvider(p));
45258
+ if (droppedSubscription.length > 0 && !isSubscriptionProvider(serving[0].provider)) {
45259
+ logStderr(`[claudish] ${droppedSubscription.join(", ")} does not serve ${model} \u2014 ` + `using ${serving[0].displayName}, which bills per token.`);
45260
+ }
45261
+ }
45262
+ const [primary, ...fallbacks] = serving;
45168
45263
  return { kind: "ok", primary, fallbacks };
45169
45264
  }
45265
+ function wireIdOf(route) {
45266
+ const at = route.modelSpec.indexOf("@");
45267
+ return at === -1 ? route.modelSpec : route.modelSpec.slice(at + 1);
45268
+ }
45170
45269
  function normalizeGlmSlug(model) {
45171
45270
  return model.replace(/^glm-(\d+)-(\d+)(-.*)?$/i, (_m, major, minor, suffix) => `glm-${major}.${minor}${suffix ?? ""}`);
45172
45271
  }
@@ -45182,10 +45281,13 @@ async function route(modelSpec, rulesOverride, defaultProviderOverride, cachePat
45182
45281
  var init_routing_rules = __esm(() => {
45183
45282
  init_model_catalog();
45184
45283
  init_authority();
45284
+ init_remote_provider_types();
45285
+ init_logger();
45185
45286
  init_profile_config();
45186
45287
  init_auto_route();
45187
45288
  init_catalog_client();
45188
45289
  init_default_routing_rules();
45290
+ init_model_availability();
45189
45291
  init_model_parser();
45190
45292
  init_model_parser();
45191
45293
  init_routing_hints();
@@ -45611,7 +45713,7 @@ var init_prehydrate = __esm(() => {
45611
45713
  });
45612
45714
 
45613
45715
  // src/channel/diagnostics.ts
45614
- import { appendFileSync as appendFileSync4 } from "fs";
45716
+ import { appendFileSync as appendFileSync5 } from "fs";
45615
45717
  function traceEnabled() {
45616
45718
  return process.env[TRACE_ENV] === "1";
45617
45719
  }
@@ -45622,7 +45724,7 @@ function emit(line) {
45622
45724
  const filePath = process.env[TRACE_FILE_ENV];
45623
45725
  if (filePath) {
45624
45726
  try {
45625
- appendFileSync4(filePath, formatted);
45727
+ appendFileSync5(filePath, formatted);
45626
45728
  } catch {}
45627
45729
  }
45628
45730
  }
@@ -49175,7 +49277,7 @@ var init_fallback_handler = __esm(() => {
49175
49277
  });
49176
49278
 
49177
49279
  // src/handlers/native-handler-advisor.ts
49178
- import { appendFileSync as appendFileSync5 } from "fs";
49280
+ import { appendFileSync as appendFileSync6 } from "fs";
49179
49281
  function loadAdvisorSwapConfig(cliModels, cliCollector) {
49180
49282
  return {
49181
49283
  enabled: process.env.CLAUDISH_SWAP_ADVISOR === "1" || (cliModels?.length ?? 0) > 0,
@@ -49230,7 +49332,7 @@ function logAdvisorEvent(cfg, event) {
49230
49332
  const line = `${JSON.stringify({ ts: new Date().toISOString(), ...event })}
49231
49333
  `;
49232
49334
  try {
49233
- appendFileSync5(cfg.logPath, line);
49335
+ appendFileSync6(cfg.logPath, line);
49234
49336
  } catch {}
49235
49337
  }
49236
49338
  function recordAdvisorEventsFromChunk(cfg, chunkText) {
@@ -52031,11 +52133,30 @@ ${plan.hint}` : `[Route] ${plan.reason}`;
52031
52133
  }));
52032
52134
  app.get("/health", (c) => c.json({ status: "ok" }));
52033
52135
  const servedSlotIds = options.servedSlotIds ?? [];
52136
+ const discoverableModelIds = (() => {
52137
+ const seen = new Set;
52138
+ try {
52139
+ const cfg = loadConfig();
52140
+ for (const name of Object.keys(cfg.routing ?? {})) {
52141
+ if (name === "*")
52142
+ continue;
52143
+ seen.add(name);
52144
+ }
52145
+ for (const ep of Object.values(cfg.customEndpoints ?? {})) {
52146
+ for (const m of ep.models ?? [])
52147
+ seen.add(m);
52148
+ }
52149
+ } catch (err) {
52150
+ log(`[Proxy] /v1/models discovery skipped: ${err instanceof Error ? err.message : err}`);
52151
+ }
52152
+ return [...seen];
52153
+ })();
52034
52154
  app.get("/v1/models", (c) => {
52155
+ const ids = servedSlotIds.length > 0 ? servedSlotIds : discoverableModelIds;
52035
52156
  return c.json({
52036
52157
  object: "list",
52037
52158
  has_more: false,
52038
- data: servedSlotIds.map((id) => ({
52159
+ data: ids.map((id) => ({
52039
52160
  id,
52040
52161
  object: "model",
52041
52162
  type: "model",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "7.57.0",
3
+ "version": "7.59.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": "7.57.0",
64
- "@claudish/magmux-darwin-x64": "7.57.0",
65
- "@claudish/magmux-linux-arm64": "7.57.0",
66
- "@claudish/magmux-linux-x64": "7.57.0"
63
+ "@claudish/magmux-darwin-arm64": "7.59.0",
64
+ "@claudish/magmux-darwin-x64": "7.59.0",
65
+ "@claudish/magmux-linux-arm64": "7.59.0",
66
+ "@claudish/magmux-linux-x64": "7.59.0"
67
67
  },
68
68
  "author": "Jack Rudenko <i@madappgang.com>",
69
69
  "license": "MIT",