@blockrun/llm 3.23.2 → 3.24.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.
package/README.md CHANGED
@@ -444,10 +444,13 @@ picked:
444
444
  **Search and routing.** xAI search (`x_search` / `web_search` tools, or
445
445
  `search` / `searchParameters` not set to `mode: 'off'`) runs on `xai/*` models
446
446
  only. When a routed call asks for search, the candidate chain is narrowed to
447
- its `xai/*` models in the router's order; when the route has none, `route()` /
448
- `smartChat()` / `smartChatCompletion()` throw before anything is sent or paid.
449
- For search, calling an `xai/*` model directly (e.g. `xai/grok-4.3`) is the
450
- dependable path.
447
+ its `xai/*` models in the router's order. The router seldom ranks xAI, so when
448
+ the route has none, the call falls back to the catalog's search-capable xAI
449
+ models (those `/v1/models` tags `search`): cheapest first on `eco` / `auto`,
450
+ most capable first on `premium`. `routing.reasoning` says when that happened.
451
+ Only a catalog with no `xai/*` chat model at all makes `route()` /
452
+ `smartChat()` / `smartChatCompletion()` throw, before anything is sent or paid.
453
+ Pin an `xai/*` model yourself when you want a specific one.
451
454
 
452
455
  ### TypeScript Types
453
456
 
package/dist/index.cjs CHANGED
@@ -571,7 +571,7 @@ function withCost(body, response, bookedUsd, scheme) {
571
571
  // src/client.ts
572
572
  var import_accounts2 = require("viem/accounts");
573
573
 
574
- // node_modules/.pnpm/@blockrun+router-core@https+++codeload.github.com+BlockRunAI+router-core+tar.gz+814fd3b6bc788_xq5fkcckbb2p7dtp6dodey4yri/node_modules/@blockrun/router-core/dist/index.js
574
+ // node_modules/.pnpm/@blockrun+router-core@https+++codeload.github.com+BlockRunAI+router-core+tar.gz+27125a3dfe319_a6zjl5xcggrab43wsmb4mvkqym/node_modules/@blockrun/router-core/dist/index.js
575
575
  function scoreTokenCount(estimatedTokens, thresholds) {
576
576
  if (estimatedTokens < thresholds.simple) {
577
577
  return { name: "tokenCount", score: -1, signal: `short (${estimatedTokens} tokens)` };
@@ -1044,6 +1044,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
1044
1044
  supportsTools: true,
1045
1045
  supportsVision: true
1046
1046
  },
1047
+ "anthropic/claude-fable-5.1": {
1048
+ contextWindow: 1e6,
1049
+ maxOutputTokens: 128e3,
1050
+ supportsTools: true,
1051
+ supportsVision: true
1052
+ },
1047
1053
  "anthropic/claude-haiku-4.5": {
1048
1054
  // override: The public catalog's `categories` omit "vision" for this Anthropic model even though the gateway accepts image input for it (the prior hand-maintained snapshot had it, and Anthropic's model card lists it). Without this the vision filter would silently drop it — reported against the catalog; remove once the categories carry vision.
1049
1055
  contextWindow: 2e5,
@@ -1075,6 +1081,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
1075
1081
  supportsTools: true,
1076
1082
  supportsVision: true
1077
1083
  },
1084
+ "anthropic/claude-opus-5.5": {
1085
+ contextWindow: 1e6,
1086
+ maxOutputTokens: 128e3,
1087
+ supportsTools: true,
1088
+ supportsVision: true
1089
+ },
1078
1090
  "anthropic/claude-sonnet-4.5": {
1079
1091
  contextWindow: 2e5,
1080
1092
  maxOutputTokens: 64e3,
@@ -1094,6 +1106,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
1094
1106
  supportsTools: true,
1095
1107
  supportsVision: true
1096
1108
  },
1109
+ "anthropic/claude-sonnet-5.5": {
1110
+ contextWindow: 1e6,
1111
+ maxOutputTokens: 128e3,
1112
+ supportsTools: true,
1113
+ supportsVision: true
1114
+ },
1097
1115
  "cohere/north-mini-code": {
1098
1116
  // supportsTools: not probed — fails closed
1099
1117
  contextWindow: 256e3,
@@ -1113,6 +1131,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
1113
1131
  supportsTools: true,
1114
1132
  supportsVision: false
1115
1133
  },
1134
+ "deepseek/deepseek-v4-flash-vision-exp": {
1135
+ contextWindow: 1048576,
1136
+ maxOutputTokens: 65536,
1137
+ supportsTools: false,
1138
+ supportsVision: true
1139
+ },
1116
1140
  "deepseek/deepseek-v4-pro": {
1117
1141
  contextWindow: 1048576,
1118
1142
  maxOutputTokens: 65536,
@@ -1173,6 +1197,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
1173
1197
  supportsTools: true,
1174
1198
  supportsVision: true
1175
1199
  },
1200
+ "google/gemini-3.8-flash": {
1201
+ contextWindow: 1048576,
1202
+ maxOutputTokens: 65536,
1203
+ supportsTools: true,
1204
+ supportsVision: true
1205
+ },
1176
1206
  "minimax/minimax-m2.7": {
1177
1207
  contextWindow: 204800,
1178
1208
  maxOutputTokens: 16384,
@@ -1198,7 +1228,7 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
1198
1228
  supportsTools: false,
1199
1229
  supportsVision: true
1200
1230
  },
1201
- "nvidia/nemotron-3-nano-30b": {
1231
+ "nvidia/muse-glimmer-30b": {
1202
1232
  // supportsTools: not probed — fails closed
1203
1233
  contextWindow: 131072,
1204
1234
  maxOutputTokens: 16384,
@@ -1206,10 +1236,11 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
1206
1236
  supportsVision: false
1207
1237
  },
1208
1238
  "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning": {
1239
+ // override: The catalog tags this model "vision", but a correctly sized probe does not hold up (ClawRouter, 2026-08-31): a 64x64 solid-red PNG was named correctly 1 of 4 times on Base, and on Solana the image was silently dropped and a text model answered "white". An HTTP 200 with a confident wrong answer gives the caller nothing to branch on, so image turns must not be routed here. It is ecoTiers.SIMPLE.fallback[0] since nemotron-3-nano-30b was delisted (2026-09-08); remove once a probe of this size comes back right on both chains.
1209
1240
  contextWindow: 256e3,
1210
1241
  maxOutputTokens: 16384,
1211
1242
  supportsTools: false,
1212
- supportsVision: true
1243
+ supportsVision: false
1213
1244
  },
1214
1245
  "nvidia/nemotron-3-ultra-550b": {
1215
1246
  // supportsTools: not probed — fails closed
@@ -1267,6 +1298,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
1267
1298
  supportsTools: true,
1268
1299
  supportsVision: false
1269
1300
  },
1301
+ "openai/gpt-5.1": {
1302
+ contextWindow: 4e5,
1303
+ maxOutputTokens: 128e3,
1304
+ supportsTools: true,
1305
+ supportsVision: true
1306
+ },
1270
1307
  "openai/gpt-5.2": {
1271
1308
  contextWindow: 4e5,
1272
1309
  maxOutputTokens: 128e3,
@@ -1361,6 +1398,24 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
1361
1398
  supportsTools: true,
1362
1399
  supportsVision: true
1363
1400
  },
1401
+ "openai/gpt-6-astra": {
1402
+ contextWindow: 105e4,
1403
+ maxOutputTokens: 128e3,
1404
+ supportsTools: true,
1405
+ supportsVision: true
1406
+ },
1407
+ "openai/gpt-6-luna": {
1408
+ contextWindow: 105e4,
1409
+ maxOutputTokens: 128e3,
1410
+ supportsTools: true,
1411
+ supportsVision: true
1412
+ },
1413
+ "openai/gpt-6-sol": {
1414
+ contextWindow: 105e4,
1415
+ maxOutputTokens: 128e3,
1416
+ supportsTools: true,
1417
+ supportsVision: true
1418
+ },
1364
1419
  "openai/o1": {
1365
1420
  contextWindow: 2e5,
1366
1421
  maxOutputTokens: 1e5,
@@ -1385,6 +1440,13 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
1385
1440
  supportsTools: true,
1386
1441
  supportsVision: false
1387
1442
  },
1443
+ "poolside/laguna-s-2.1": {
1444
+ // supportsTools: not probed — fails closed
1445
+ contextWindow: 1048576,
1446
+ maxOutputTokens: 131072,
1447
+ supportsTools: false,
1448
+ supportsVision: false
1449
+ },
1388
1450
  "poolside/laguna-xs-2.1": {
1389
1451
  // supportsTools: not probed — fails closed
1390
1452
  contextWindow: 131072,
@@ -1410,10 +1472,24 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
1410
1472
  supportsTools: true,
1411
1473
  supportsVision: false
1412
1474
  },
1413
- "tencent/hy3": {
1414
- contextWindow: 262144,
1415
- maxOutputTokens: 128e3,
1416
- supportsTools: true,
1475
+ "qwen/qwen3.8-flash": {
1476
+ contextWindow: 1e6,
1477
+ maxOutputTokens: 131072,
1478
+ supportsTools: false,
1479
+ supportsVision: true
1480
+ },
1481
+ "qwen/qwen3.8-max": {
1482
+ // supportsTools: not probed — fails closed
1483
+ contextWindow: 1e6,
1484
+ maxOutputTokens: 131072,
1485
+ supportsTools: false,
1486
+ supportsVision: false
1487
+ },
1488
+ "tencent/hy4-preview": {
1489
+ // supportsTools: not probed — fails closed
1490
+ contextWindow: 1048576,
1491
+ maxOutputTokens: 64e3,
1492
+ supportsTools: false,
1417
1493
  supportsVision: false
1418
1494
  },
1419
1495
  "xai/grok-4.3": {
@@ -1428,12 +1504,30 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
1428
1504
  supportsTools: true,
1429
1505
  supportsVision: true
1430
1506
  },
1507
+ "xai/grok-4.6": {
1508
+ contextWindow: 5e5,
1509
+ maxOutputTokens: 16384,
1510
+ supportsTools: true,
1511
+ supportsVision: true
1512
+ },
1513
+ "xai/grok-4.7": {
1514
+ contextWindow: 5e5,
1515
+ maxOutputTokens: 16384,
1516
+ supportsTools: true,
1517
+ supportsVision: true
1518
+ },
1431
1519
  "xai/grok-build-0.1": {
1432
1520
  contextWindow: 256e3,
1433
1521
  maxOutputTokens: 16384,
1434
1522
  supportsTools: true,
1435
1523
  supportsVision: false
1436
1524
  },
1525
+ "xiaomi/mimo-v2.5": {
1526
+ contextWindow: 1048576,
1527
+ maxOutputTokens: 131072,
1528
+ supportsTools: true,
1529
+ supportsVision: true
1530
+ },
1437
1531
  "xiaomi/mimo-v2.5-pro": {
1438
1532
  contextWindow: 1048576,
1439
1533
  maxOutputTokens: 131072,
@@ -2676,6 +2770,19 @@ function evidenceCandidates(task) {
2676
2770
  }
2677
2771
  return [];
2678
2772
  }
2773
+ var VERIFIED_TOOL_FALLBACKS = [
2774
+ "anthropic/claude-fable-5.1",
2775
+ "anthropic/claude-opus-5.5",
2776
+ "anthropic/claude-sonnet-5.5",
2777
+ "google/gemini-3.8-flash",
2778
+ "openai/gpt-5.1",
2779
+ "openai/gpt-6-astra",
2780
+ "openai/gpt-6-luna",
2781
+ "openai/gpt-6-sol",
2782
+ "xai/grok-4.6",
2783
+ "xai/grok-4.7",
2784
+ "xiaomi/mimo-v2.5"
2785
+ ];
2679
2786
  function isEligible(modelId, features, maxOutputTokens, options) {
2680
2787
  const model = options.modelCapabilities?.[modelId] ?? DEFAULT_MODEL_CAPABILITIES[modelId];
2681
2788
  if (!model) return true;
@@ -2722,8 +2829,17 @@ var PortfolioStrategy = class {
2722
2829
  const tierConfig = tierConfigs[targetTier];
2723
2830
  const configuredCandidates = tierConfig ? getFallbackChain(targetTier, tierConfigs) : [];
2724
2831
  const unavailable = new Set(options.unavailableModels ?? []);
2832
+ const verifiedToolFallbacks = features.needsTools ? VERIFIED_TOOL_FALLBACKS.filter((model2) => options.modelPricing.has(model2)) : [];
2833
+ if (options.routingProfile !== "premium") {
2834
+ const fallbackCost = (model2) => estimatedCost(model2, options, features.estimatedInputTokens, maxOutputTokens);
2835
+ verifiedToolFallbacks.sort((a, b) => fallbackCost(a) - fallbackCost(b));
2836
+ }
2725
2837
  const chain = [
2726
- .../* @__PURE__ */ new Set([...configuredCandidates, ...evidenceCandidates(features.taskType)])
2838
+ .../* @__PURE__ */ new Set([
2839
+ ...configuredCandidates,
2840
+ ...evidenceCandidates(features.taskType),
2841
+ ...verifiedToolFallbacks
2842
+ ])
2727
2843
  ].filter(
2728
2844
  (model2) => typeof model2 === "string" && model2.length > 0 && !unavailable.has(model2)
2729
2845
  );
@@ -4026,8 +4142,13 @@ var DEFAULT_ROUTING_CONFIG = {
4026
4142
  primary: "nvidia/nemotron-3.5-lightning",
4027
4143
  // FREE — NVIDIA free tier flagship, 1M ctx
4028
4144
  fallback: [
4029
- "nvidia/nemotron-3-nano-30b",
4030
- // FREE — fastest free model (~121 tok/s)
4145
+ // Was nvidia/nemotron-3-nano-30b until blockrun delisted it on 2026-09-08
4146
+ // (NVIDIA deprovisioned it for the account — a structured per-account
4147
+ // 404). nano-omni is blockrun's own redirect target for that id, so this
4148
+ // follows the same rule as the head: the router and the gateway name the
4149
+ // same model.
4150
+ "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
4151
+ // FREE — nano-30b's successor, 256K ctx
4031
4152
  // The free head keeps rotting with NVIDIA's hosting (deepseek-v4-flash
4032
4153
  // 410 2026-08-12, seed-oss-36b 410 2026-08-03, gpt-oss-120b/20b 400
4033
4154
  // 2026-08-21, and on 2026-08-30 FOUR of the five visible free models at
@@ -4359,6 +4480,24 @@ function requestsSearch(options) {
4359
4480
  if (options.searchParameters !== void 0) return options.searchParameters.mode !== "off";
4360
4481
  return options.search === true;
4361
4482
  }
4483
+ var searchModelsByCatalog = /* @__PURE__ */ new WeakMap();
4484
+ function registerSearchModels(pricing, models) {
4485
+ const xaiChat = models.filter(
4486
+ (m) => m.id.startsWith("xai/") && m.available !== false && Array.isArray(m.categories) && m.categories.includes("chat") && pricing.has(m.id)
4487
+ );
4488
+ const tagged = xaiChat.filter((m) => m.categories.includes("search"));
4489
+ searchModelsByCatalog.set(pricing, (tagged.length > 0 ? tagged : xaiChat).map((m) => m.id));
4490
+ }
4491
+ function searchFallbackChain(modelPricing, routingProfile) {
4492
+ const pool = searchModelsByCatalog.get(modelPricing) ?? [...modelPricing.keys()].filter((id) => id.startsWith("xai/"));
4493
+ const price = (id) => {
4494
+ const p = modelPricing.get(id);
4495
+ return p ? p.inputPrice + p.outputPrice : Number.POSITIVE_INFINITY;
4496
+ };
4497
+ const sorted = [...pool].sort((a, b) => price(a) - price(b) || b.localeCompare(a, void 0, { numeric: true }));
4498
+ if (routingProfile !== "premium") return sorted;
4499
+ return [...pool].sort((a, b) => price(b) - price(a) || b.localeCompare(a, void 0, { numeric: true }));
4500
+ }
4362
4501
  function routingProfileForModel(model) {
4363
4502
  return AUTO_ROUTING_PROFILES[model.toLowerCase()];
4364
4503
  }
@@ -4406,11 +4545,16 @@ function routeWithCatalog(prompt, systemPrompt, maxOutputTokens, modelPricing, o
4406
4545
  const resolved = !id.startsWith("free/") ? id : modelPricing.has(`nvidia/${id.slice(5)}`) ? `nvidia/${id.slice(5)}` : null;
4407
4546
  if (resolved && !callable.includes(resolved)) callable.push(resolved);
4408
4547
  }
4548
+ let searchFallback = false;
4409
4549
  if (requestsSearch(options)) {
4410
- const searchable = callable.filter((id) => id.startsWith("xai/"));
4550
+ let searchable = callable.filter((id) => id.startsWith("xai/"));
4551
+ if (searchable.length === 0) {
4552
+ searchable = searchFallbackChain(modelPricing, options.routingProfile);
4553
+ searchFallback = true;
4554
+ }
4411
4555
  if (searchable.length === 0) {
4412
4556
  throw new Error(
4413
- `Search (x_search / web_search tools or searchParameters) runs on xai/* models only, and the ${options.routingProfile ?? "auto"} route has no xai/* candidate for this request. Call an xai/* model directly (e.g. xai/grok-4.3) or drop the search options.`
4557
+ `Search (x_search / web_search tools or searchParameters) runs on xai/* models only, and the catalog lists no available xai/* chat model. Call an xai/* model directly or drop the search options.`
4414
4558
  );
4415
4559
  }
4416
4560
  callable.splice(0, callable.length, ...searchable);
@@ -4447,7 +4591,7 @@ function routeWithCatalog(prompt, systemPrompt, maxOutputTokens, modelPricing, o
4447
4591
  costEstimate,
4448
4592
  savings,
4449
4593
  model,
4450
- reasoning: model === decision.model ? decision.reasoning : `${decision.reasoning} | catalog fallback: ${model}`,
4594
+ reasoning: searchFallback ? `${decision.reasoning} | search runs on xai/* only; the route had none, so the ${options.routingProfile === "premium" ? "most capable" : "cheapest"} search-capable xAI model: ${model}` : model === decision.model ? decision.reasoning : `${decision.reasoning} | catalog fallback: ${model}`,
4451
4595
  candidates: availableCandidates,
4452
4596
  candidateScores: decision.candidateScores?.filter(
4453
4597
  (score) => availableCandidates.includes(score.model)
@@ -4833,7 +4977,7 @@ function getCostSummary() {
4833
4977
  }
4834
4978
 
4835
4979
  // src/version.ts
4836
- var SDK_VERSION = "3.23.2";
4980
+ var SDK_VERSION = "3.24.1";
4837
4981
  var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
4838
4982
 
4839
4983
  // src/client.ts
@@ -5104,6 +5248,7 @@ var LLMClient = class _LLMClient {
5104
5248
  pricing.set(model.id, { inputPrice, outputPrice });
5105
5249
  }
5106
5250
  }
5251
+ registerSearchModels(pricing, models);
5107
5252
  return pricing;
5108
5253
  }
5109
5254
  /**
@@ -12567,6 +12712,7 @@ var SolanaLLMClient = class {
12567
12712
  ...(model.billingMode ?? raw.billing_mode) === "flat" && flatPrice > 0 ? { flatPrice } : {}
12568
12713
  });
12569
12714
  }
12715
+ registerSearchModels(pricing, models);
12570
12716
  return pricing;
12571
12717
  })();
12572
12718
  try {
package/dist/index.d.cts CHANGED
@@ -2953,8 +2953,9 @@ interface SolanaBatchOptions {
2953
2953
  * re-read, in addition to the one-line log the SDK writes to stderr for each.
2954
2954
  * A callback that throws, or returns a promise that rejects, is logged and
2955
2955
  * ignored; it never affects the payment, and its promise is not awaited.
2956
+ * Any other return value is ignored, so `(e) => events.push(e)` is fine.
2956
2957
  */
2957
- onEvent?: (event: SolanaBatchEvent) => void | PromiseLike<void>;
2958
+ onEvent?: (event: SolanaBatchEvent) => unknown;
2958
2959
  }
2959
2960
  /** Default {@link SolanaBatchOptions.rateLimit}. */
2960
2961
  declare const DEFAULT_BATCH_RATE_LIMIT: {
package/dist/index.d.ts CHANGED
@@ -2953,8 +2953,9 @@ interface SolanaBatchOptions {
2953
2953
  * re-read, in addition to the one-line log the SDK writes to stderr for each.
2954
2954
  * A callback that throws, or returns a promise that rejects, is logged and
2955
2955
  * ignored; it never affects the payment, and its promise is not awaited.
2956
+ * Any other return value is ignored, so `(e) => events.push(e)` is fine.
2956
2957
  */
2957
- onEvent?: (event: SolanaBatchEvent) => void | PromiseLike<void>;
2958
+ onEvent?: (event: SolanaBatchEvent) => unknown;
2958
2959
  }
2959
2960
  /** Default {@link SolanaBatchOptions.rateLimit}. */
2960
2961
  declare const DEFAULT_BATCH_RATE_LIMIT: {
package/dist/index.js CHANGED
@@ -440,7 +440,7 @@ function withCost(body, response, bookedUsd, scheme) {
440
440
  // src/client.ts
441
441
  import { privateKeyToAccount } from "viem/accounts";
442
442
 
443
- // node_modules/.pnpm/@blockrun+router-core@https+++codeload.github.com+BlockRunAI+router-core+tar.gz+814fd3b6bc788_xq5fkcckbb2p7dtp6dodey4yri/node_modules/@blockrun/router-core/dist/index.js
443
+ // node_modules/.pnpm/@blockrun+router-core@https+++codeload.github.com+BlockRunAI+router-core+tar.gz+27125a3dfe319_a6zjl5xcggrab43wsmb4mvkqym/node_modules/@blockrun/router-core/dist/index.js
444
444
  function scoreTokenCount(estimatedTokens, thresholds) {
445
445
  if (estimatedTokens < thresholds.simple) {
446
446
  return { name: "tokenCount", score: -1, signal: `short (${estimatedTokens} tokens)` };
@@ -913,6 +913,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
913
913
  supportsTools: true,
914
914
  supportsVision: true
915
915
  },
916
+ "anthropic/claude-fable-5.1": {
917
+ contextWindow: 1e6,
918
+ maxOutputTokens: 128e3,
919
+ supportsTools: true,
920
+ supportsVision: true
921
+ },
916
922
  "anthropic/claude-haiku-4.5": {
917
923
  // override: The public catalog's `categories` omit "vision" for this Anthropic model even though the gateway accepts image input for it (the prior hand-maintained snapshot had it, and Anthropic's model card lists it). Without this the vision filter would silently drop it — reported against the catalog; remove once the categories carry vision.
918
924
  contextWindow: 2e5,
@@ -944,6 +950,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
944
950
  supportsTools: true,
945
951
  supportsVision: true
946
952
  },
953
+ "anthropic/claude-opus-5.5": {
954
+ contextWindow: 1e6,
955
+ maxOutputTokens: 128e3,
956
+ supportsTools: true,
957
+ supportsVision: true
958
+ },
947
959
  "anthropic/claude-sonnet-4.5": {
948
960
  contextWindow: 2e5,
949
961
  maxOutputTokens: 64e3,
@@ -963,6 +975,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
963
975
  supportsTools: true,
964
976
  supportsVision: true
965
977
  },
978
+ "anthropic/claude-sonnet-5.5": {
979
+ contextWindow: 1e6,
980
+ maxOutputTokens: 128e3,
981
+ supportsTools: true,
982
+ supportsVision: true
983
+ },
966
984
  "cohere/north-mini-code": {
967
985
  // supportsTools: not probed — fails closed
968
986
  contextWindow: 256e3,
@@ -982,6 +1000,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
982
1000
  supportsTools: true,
983
1001
  supportsVision: false
984
1002
  },
1003
+ "deepseek/deepseek-v4-flash-vision-exp": {
1004
+ contextWindow: 1048576,
1005
+ maxOutputTokens: 65536,
1006
+ supportsTools: false,
1007
+ supportsVision: true
1008
+ },
985
1009
  "deepseek/deepseek-v4-pro": {
986
1010
  contextWindow: 1048576,
987
1011
  maxOutputTokens: 65536,
@@ -1042,6 +1066,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
1042
1066
  supportsTools: true,
1043
1067
  supportsVision: true
1044
1068
  },
1069
+ "google/gemini-3.8-flash": {
1070
+ contextWindow: 1048576,
1071
+ maxOutputTokens: 65536,
1072
+ supportsTools: true,
1073
+ supportsVision: true
1074
+ },
1045
1075
  "minimax/minimax-m2.7": {
1046
1076
  contextWindow: 204800,
1047
1077
  maxOutputTokens: 16384,
@@ -1067,7 +1097,7 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
1067
1097
  supportsTools: false,
1068
1098
  supportsVision: true
1069
1099
  },
1070
- "nvidia/nemotron-3-nano-30b": {
1100
+ "nvidia/muse-glimmer-30b": {
1071
1101
  // supportsTools: not probed — fails closed
1072
1102
  contextWindow: 131072,
1073
1103
  maxOutputTokens: 16384,
@@ -1075,10 +1105,11 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
1075
1105
  supportsVision: false
1076
1106
  },
1077
1107
  "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning": {
1108
+ // override: The catalog tags this model "vision", but a correctly sized probe does not hold up (ClawRouter, 2026-08-31): a 64x64 solid-red PNG was named correctly 1 of 4 times on Base, and on Solana the image was silently dropped and a text model answered "white". An HTTP 200 with a confident wrong answer gives the caller nothing to branch on, so image turns must not be routed here. It is ecoTiers.SIMPLE.fallback[0] since nemotron-3-nano-30b was delisted (2026-09-08); remove once a probe of this size comes back right on both chains.
1078
1109
  contextWindow: 256e3,
1079
1110
  maxOutputTokens: 16384,
1080
1111
  supportsTools: false,
1081
- supportsVision: true
1112
+ supportsVision: false
1082
1113
  },
1083
1114
  "nvidia/nemotron-3-ultra-550b": {
1084
1115
  // supportsTools: not probed — fails closed
@@ -1136,6 +1167,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
1136
1167
  supportsTools: true,
1137
1168
  supportsVision: false
1138
1169
  },
1170
+ "openai/gpt-5.1": {
1171
+ contextWindow: 4e5,
1172
+ maxOutputTokens: 128e3,
1173
+ supportsTools: true,
1174
+ supportsVision: true
1175
+ },
1139
1176
  "openai/gpt-5.2": {
1140
1177
  contextWindow: 4e5,
1141
1178
  maxOutputTokens: 128e3,
@@ -1230,6 +1267,24 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
1230
1267
  supportsTools: true,
1231
1268
  supportsVision: true
1232
1269
  },
1270
+ "openai/gpt-6-astra": {
1271
+ contextWindow: 105e4,
1272
+ maxOutputTokens: 128e3,
1273
+ supportsTools: true,
1274
+ supportsVision: true
1275
+ },
1276
+ "openai/gpt-6-luna": {
1277
+ contextWindow: 105e4,
1278
+ maxOutputTokens: 128e3,
1279
+ supportsTools: true,
1280
+ supportsVision: true
1281
+ },
1282
+ "openai/gpt-6-sol": {
1283
+ contextWindow: 105e4,
1284
+ maxOutputTokens: 128e3,
1285
+ supportsTools: true,
1286
+ supportsVision: true
1287
+ },
1233
1288
  "openai/o1": {
1234
1289
  contextWindow: 2e5,
1235
1290
  maxOutputTokens: 1e5,
@@ -1254,6 +1309,13 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
1254
1309
  supportsTools: true,
1255
1310
  supportsVision: false
1256
1311
  },
1312
+ "poolside/laguna-s-2.1": {
1313
+ // supportsTools: not probed — fails closed
1314
+ contextWindow: 1048576,
1315
+ maxOutputTokens: 131072,
1316
+ supportsTools: false,
1317
+ supportsVision: false
1318
+ },
1257
1319
  "poolside/laguna-xs-2.1": {
1258
1320
  // supportsTools: not probed — fails closed
1259
1321
  contextWindow: 131072,
@@ -1279,10 +1341,24 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
1279
1341
  supportsTools: true,
1280
1342
  supportsVision: false
1281
1343
  },
1282
- "tencent/hy3": {
1283
- contextWindow: 262144,
1284
- maxOutputTokens: 128e3,
1285
- supportsTools: true,
1344
+ "qwen/qwen3.8-flash": {
1345
+ contextWindow: 1e6,
1346
+ maxOutputTokens: 131072,
1347
+ supportsTools: false,
1348
+ supportsVision: true
1349
+ },
1350
+ "qwen/qwen3.8-max": {
1351
+ // supportsTools: not probed — fails closed
1352
+ contextWindow: 1e6,
1353
+ maxOutputTokens: 131072,
1354
+ supportsTools: false,
1355
+ supportsVision: false
1356
+ },
1357
+ "tencent/hy4-preview": {
1358
+ // supportsTools: not probed — fails closed
1359
+ contextWindow: 1048576,
1360
+ maxOutputTokens: 64e3,
1361
+ supportsTools: false,
1286
1362
  supportsVision: false
1287
1363
  },
1288
1364
  "xai/grok-4.3": {
@@ -1297,12 +1373,30 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
1297
1373
  supportsTools: true,
1298
1374
  supportsVision: true
1299
1375
  },
1376
+ "xai/grok-4.6": {
1377
+ contextWindow: 5e5,
1378
+ maxOutputTokens: 16384,
1379
+ supportsTools: true,
1380
+ supportsVision: true
1381
+ },
1382
+ "xai/grok-4.7": {
1383
+ contextWindow: 5e5,
1384
+ maxOutputTokens: 16384,
1385
+ supportsTools: true,
1386
+ supportsVision: true
1387
+ },
1300
1388
  "xai/grok-build-0.1": {
1301
1389
  contextWindow: 256e3,
1302
1390
  maxOutputTokens: 16384,
1303
1391
  supportsTools: true,
1304
1392
  supportsVision: false
1305
1393
  },
1394
+ "xiaomi/mimo-v2.5": {
1395
+ contextWindow: 1048576,
1396
+ maxOutputTokens: 131072,
1397
+ supportsTools: true,
1398
+ supportsVision: true
1399
+ },
1306
1400
  "xiaomi/mimo-v2.5-pro": {
1307
1401
  contextWindow: 1048576,
1308
1402
  maxOutputTokens: 131072,
@@ -2545,6 +2639,19 @@ function evidenceCandidates(task) {
2545
2639
  }
2546
2640
  return [];
2547
2641
  }
2642
+ var VERIFIED_TOOL_FALLBACKS = [
2643
+ "anthropic/claude-fable-5.1",
2644
+ "anthropic/claude-opus-5.5",
2645
+ "anthropic/claude-sonnet-5.5",
2646
+ "google/gemini-3.8-flash",
2647
+ "openai/gpt-5.1",
2648
+ "openai/gpt-6-astra",
2649
+ "openai/gpt-6-luna",
2650
+ "openai/gpt-6-sol",
2651
+ "xai/grok-4.6",
2652
+ "xai/grok-4.7",
2653
+ "xiaomi/mimo-v2.5"
2654
+ ];
2548
2655
  function isEligible(modelId, features, maxOutputTokens, options) {
2549
2656
  const model = options.modelCapabilities?.[modelId] ?? DEFAULT_MODEL_CAPABILITIES[modelId];
2550
2657
  if (!model) return true;
@@ -2591,8 +2698,17 @@ var PortfolioStrategy = class {
2591
2698
  const tierConfig = tierConfigs[targetTier];
2592
2699
  const configuredCandidates = tierConfig ? getFallbackChain(targetTier, tierConfigs) : [];
2593
2700
  const unavailable = new Set(options.unavailableModels ?? []);
2701
+ const verifiedToolFallbacks = features.needsTools ? VERIFIED_TOOL_FALLBACKS.filter((model2) => options.modelPricing.has(model2)) : [];
2702
+ if (options.routingProfile !== "premium") {
2703
+ const fallbackCost = (model2) => estimatedCost(model2, options, features.estimatedInputTokens, maxOutputTokens);
2704
+ verifiedToolFallbacks.sort((a, b) => fallbackCost(a) - fallbackCost(b));
2705
+ }
2594
2706
  const chain = [
2595
- .../* @__PURE__ */ new Set([...configuredCandidates, ...evidenceCandidates(features.taskType)])
2707
+ .../* @__PURE__ */ new Set([
2708
+ ...configuredCandidates,
2709
+ ...evidenceCandidates(features.taskType),
2710
+ ...verifiedToolFallbacks
2711
+ ])
2596
2712
  ].filter(
2597
2713
  (model2) => typeof model2 === "string" && model2.length > 0 && !unavailable.has(model2)
2598
2714
  );
@@ -3895,8 +4011,13 @@ var DEFAULT_ROUTING_CONFIG = {
3895
4011
  primary: "nvidia/nemotron-3.5-lightning",
3896
4012
  // FREE — NVIDIA free tier flagship, 1M ctx
3897
4013
  fallback: [
3898
- "nvidia/nemotron-3-nano-30b",
3899
- // FREE — fastest free model (~121 tok/s)
4014
+ // Was nvidia/nemotron-3-nano-30b until blockrun delisted it on 2026-09-08
4015
+ // (NVIDIA deprovisioned it for the account — a structured per-account
4016
+ // 404). nano-omni is blockrun's own redirect target for that id, so this
4017
+ // follows the same rule as the head: the router and the gateway name the
4018
+ // same model.
4019
+ "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
4020
+ // FREE — nano-30b's successor, 256K ctx
3900
4021
  // The free head keeps rotting with NVIDIA's hosting (deepseek-v4-flash
3901
4022
  // 410 2026-08-12, seed-oss-36b 410 2026-08-03, gpt-oss-120b/20b 400
3902
4023
  // 2026-08-21, and on 2026-08-30 FOUR of the five visible free models at
@@ -4228,6 +4349,24 @@ function requestsSearch(options) {
4228
4349
  if (options.searchParameters !== void 0) return options.searchParameters.mode !== "off";
4229
4350
  return options.search === true;
4230
4351
  }
4352
+ var searchModelsByCatalog = /* @__PURE__ */ new WeakMap();
4353
+ function registerSearchModels(pricing, models) {
4354
+ const xaiChat = models.filter(
4355
+ (m) => m.id.startsWith("xai/") && m.available !== false && Array.isArray(m.categories) && m.categories.includes("chat") && pricing.has(m.id)
4356
+ );
4357
+ const tagged = xaiChat.filter((m) => m.categories.includes("search"));
4358
+ searchModelsByCatalog.set(pricing, (tagged.length > 0 ? tagged : xaiChat).map((m) => m.id));
4359
+ }
4360
+ function searchFallbackChain(modelPricing, routingProfile) {
4361
+ const pool = searchModelsByCatalog.get(modelPricing) ?? [...modelPricing.keys()].filter((id) => id.startsWith("xai/"));
4362
+ const price = (id) => {
4363
+ const p = modelPricing.get(id);
4364
+ return p ? p.inputPrice + p.outputPrice : Number.POSITIVE_INFINITY;
4365
+ };
4366
+ const sorted = [...pool].sort((a, b) => price(a) - price(b) || b.localeCompare(a, void 0, { numeric: true }));
4367
+ if (routingProfile !== "premium") return sorted;
4368
+ return [...pool].sort((a, b) => price(b) - price(a) || b.localeCompare(a, void 0, { numeric: true }));
4369
+ }
4231
4370
  function routingProfileForModel(model) {
4232
4371
  return AUTO_ROUTING_PROFILES[model.toLowerCase()];
4233
4372
  }
@@ -4275,11 +4414,16 @@ function routeWithCatalog(prompt, systemPrompt, maxOutputTokens, modelPricing, o
4275
4414
  const resolved = !id.startsWith("free/") ? id : modelPricing.has(`nvidia/${id.slice(5)}`) ? `nvidia/${id.slice(5)}` : null;
4276
4415
  if (resolved && !callable.includes(resolved)) callable.push(resolved);
4277
4416
  }
4417
+ let searchFallback = false;
4278
4418
  if (requestsSearch(options)) {
4279
- const searchable = callable.filter((id) => id.startsWith("xai/"));
4419
+ let searchable = callable.filter((id) => id.startsWith("xai/"));
4420
+ if (searchable.length === 0) {
4421
+ searchable = searchFallbackChain(modelPricing, options.routingProfile);
4422
+ searchFallback = true;
4423
+ }
4280
4424
  if (searchable.length === 0) {
4281
4425
  throw new Error(
4282
- `Search (x_search / web_search tools or searchParameters) runs on xai/* models only, and the ${options.routingProfile ?? "auto"} route has no xai/* candidate for this request. Call an xai/* model directly (e.g. xai/grok-4.3) or drop the search options.`
4426
+ `Search (x_search / web_search tools or searchParameters) runs on xai/* models only, and the catalog lists no available xai/* chat model. Call an xai/* model directly or drop the search options.`
4283
4427
  );
4284
4428
  }
4285
4429
  callable.splice(0, callable.length, ...searchable);
@@ -4316,7 +4460,7 @@ function routeWithCatalog(prompt, systemPrompt, maxOutputTokens, modelPricing, o
4316
4460
  costEstimate,
4317
4461
  savings,
4318
4462
  model,
4319
- reasoning: model === decision.model ? decision.reasoning : `${decision.reasoning} | catalog fallback: ${model}`,
4463
+ reasoning: searchFallback ? `${decision.reasoning} | search runs on xai/* only; the route had none, so the ${options.routingProfile === "premium" ? "most capable" : "cheapest"} search-capable xAI model: ${model}` : model === decision.model ? decision.reasoning : `${decision.reasoning} | catalog fallback: ${model}`,
4320
4464
  candidates: availableCandidates,
4321
4465
  candidateScores: decision.candidateScores?.filter(
4322
4466
  (score) => availableCandidates.includes(score.model)
@@ -4702,7 +4846,7 @@ function getCostSummary() {
4702
4846
  }
4703
4847
 
4704
4848
  // src/version.ts
4705
- var SDK_VERSION = "3.23.2";
4849
+ var SDK_VERSION = "3.24.1";
4706
4850
  var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
4707
4851
 
4708
4852
  // src/client.ts
@@ -4973,6 +5117,7 @@ var LLMClient = class _LLMClient {
4973
5117
  pricing.set(model.id, { inputPrice, outputPrice });
4974
5118
  }
4975
5119
  }
5120
+ registerSearchModels(pricing, models);
4976
5121
  return pricing;
4977
5122
  }
4978
5123
  /**
@@ -12436,6 +12581,7 @@ var SolanaLLMClient = class {
12436
12581
  ...(model.billingMode ?? raw.billing_mode) === "flat" && flatPrice > 0 ? { flatPrice } : {}
12437
12582
  });
12438
12583
  }
12584
+ registerSearchModels(pricing, models);
12439
12585
  return pricing;
12440
12586
  })();
12441
12587
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/llm",
3
- "version": "3.23.2",
3
+ "version": "3.24.1",
4
4
  "type": "module",
5
5
  "description": "TypeScript SDK for BlockRun - every frontier model behind one OpenAI-compatible client. Pay with a BlockRun API key or per-call in USDC. Smart routing picks the cheapest capable model. No rate limits.",
6
6
  "main": "dist/index.cjs",
@@ -58,7 +58,7 @@
58
58
  },
59
59
  "devDependencies": {
60
60
  "@blockrun/core": "^0.1.0",
61
- "@blockrun/router-core": "https://codeload.github.com/BlockRunAI/router-core/tar.gz/814fd3b6bc7880a84bbe030bd08c98e8670031e7",
61
+ "@blockrun/router-core": "https://codeload.github.com/BlockRunAI/router-core/tar.gz/27125a3dfe319a6f422a0c0f41bf1666b0dc61a9",
62
62
  "@eslint/js": "^10.0.1",
63
63
  "@solana/kit": "^5.5.1",
64
64
  "@types/node": "^26.4.1",