@blockrun/llm 3.24.0 → 3.24.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +241 -52
- package/dist/index.js +241 -52
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -571,7 +571,69 @@ 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+
|
|
574
|
+
// node_modules/.pnpm/@blockrun+router-core@https+++codeload.github.com+BlockRunAI+router-core+tar.gz+65a92c85ea1f1_ulf6q3mq2pzpwvcnu73z4krxvy/node_modules/@blockrun/router-core/dist/index.js
|
|
575
|
+
var UNSEGMENTED_SCRIPT = /[-ۿ -ヿ㐀-䶿一-鿿가-豈--]/;
|
|
576
|
+
var WORD_CHAR = /[\p{L}\p{N}]/u;
|
|
577
|
+
var ASCII_PHRASE = /^[a-z0-9' -]+$/;
|
|
578
|
+
var SEGMENTED_WORD_CHAR = "\\p{Script=Latin}\\p{Script=Cyrillic}\\p{Script=Greek}\\p{N}";
|
|
579
|
+
var BOUNDARY_BEFORE = `(?<![${SEGMENTED_WORD_CHAR}])`;
|
|
580
|
+
var BOUNDARY_AFTER = `(?![${SEGMENTED_WORD_CHAR}])`;
|
|
581
|
+
var BOUNDED_LAST_WORD_LENGTH = 6;
|
|
582
|
+
var SHORT_SUFFIXES = "(?:s|ed|ing)?";
|
|
583
|
+
var SIBILANT_PLURAL = "es";
|
|
584
|
+
var MID_SUFFIXES = "(?:s|es|ed|d|ing|er|ers|ly|ment|ments|ation|ations|e|en|n|a|as|o|os|em|et|ung|ungen)?";
|
|
585
|
+
var DROPPED_E_SUFFIXES = "(?:ing|ed|es|er|ers|ation|ations)";
|
|
586
|
+
var DOUBLED_CONSONANT_SUFFIXES = "(?:ing|ed|er|ers)";
|
|
587
|
+
var matcherCache = /* @__PURE__ */ new Map();
|
|
588
|
+
function escapeRegExp(value) {
|
|
589
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
590
|
+
}
|
|
591
|
+
function keywordMatcher(keyword) {
|
|
592
|
+
const cached = matcherCache.get(keyword);
|
|
593
|
+
if (cached) return cached;
|
|
594
|
+
const kw = keyword.toLowerCase();
|
|
595
|
+
let matcher;
|
|
596
|
+
if (UNSEGMENTED_SCRIPT.test(kw) || kw.length === 0) {
|
|
597
|
+
matcher = { needle: kw, pattern: null };
|
|
598
|
+
} else {
|
|
599
|
+
const lead = WORD_CHAR.test(kw[0]) ? BOUNDARY_BEFORE : "";
|
|
600
|
+
const escaped = escapeRegExp(kw);
|
|
601
|
+
let body = escaped;
|
|
602
|
+
let trail = "";
|
|
603
|
+
let needle = kw;
|
|
604
|
+
const lastWord = kw.slice(kw.lastIndexOf(" ") + 1);
|
|
605
|
+
if (WORD_CHAR.test(kw[kw.length - 1]) && ASCII_PHRASE.test(kw) && lastWord.length <= BOUNDED_LAST_WORD_LENGTH) {
|
|
606
|
+
const forms = [];
|
|
607
|
+
if (/\d$/.test(kw)) {
|
|
608
|
+
forms.push(escaped);
|
|
609
|
+
} else if (lastWord.length <= 3) {
|
|
610
|
+
forms.push(`${escaped}${SHORT_SUFFIXES}`);
|
|
611
|
+
if (/(?:x|s|z|ch|sh)$/.test(kw)) forms.push(`${escaped}${SIBILANT_PLURAL}`);
|
|
612
|
+
} else {
|
|
613
|
+
forms.push(`${escaped}${MID_SUFFIXES}`);
|
|
614
|
+
if (kw.endsWith("e")) {
|
|
615
|
+
forms.push(`${escaped.slice(0, -1)}${DROPPED_E_SUFFIXES}`);
|
|
616
|
+
needle = kw.slice(0, -1);
|
|
617
|
+
} else if (kw.endsWith("y")) {
|
|
618
|
+
forms.push(`${escaped.slice(0, -1)}(?:ies|ied)`);
|
|
619
|
+
needle = kw.slice(0, -1);
|
|
620
|
+
} else if (/[aeiou][bdgmnprt]$/.test(kw)) {
|
|
621
|
+
forms.push(`${escaped}${kw[kw.length - 1]}${DOUBLED_CONSONANT_SUFFIXES}`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
body = `(?:${forms.join("|")})`;
|
|
625
|
+
trail = BOUNDARY_AFTER;
|
|
626
|
+
}
|
|
627
|
+
matcher = { needle, pattern: new RegExp(`${lead}${body}${trail}`, "u") };
|
|
628
|
+
}
|
|
629
|
+
matcherCache.set(keyword, matcher);
|
|
630
|
+
return matcher;
|
|
631
|
+
}
|
|
632
|
+
function matchesKeyword(text, keyword) {
|
|
633
|
+
const { needle, pattern } = keywordMatcher(keyword);
|
|
634
|
+
if (!text.includes(needle)) return false;
|
|
635
|
+
return pattern === null ? true : pattern.test(text);
|
|
636
|
+
}
|
|
575
637
|
function scoreTokenCount(estimatedTokens, thresholds) {
|
|
576
638
|
if (estimatedTokens < thresholds.simple) {
|
|
577
639
|
return { name: "tokenCount", score: -1, signal: `short (${estimatedTokens} tokens)` };
|
|
@@ -582,7 +644,7 @@ function scoreTokenCount(estimatedTokens, thresholds) {
|
|
|
582
644
|
return { name: "tokenCount", score: 0, signal: null };
|
|
583
645
|
}
|
|
584
646
|
function scoreKeywordMatch(text, keywords, name, signalLabel, thresholds, scores) {
|
|
585
|
-
const matches = keywords.filter((kw) => text
|
|
647
|
+
const matches = keywords.filter((kw) => matchesKeyword(text, kw));
|
|
586
648
|
if (matches.length >= thresholds.high) {
|
|
587
649
|
return {
|
|
588
650
|
name,
|
|
@@ -618,7 +680,7 @@ function scoreAgenticTask(text, keywords) {
|
|
|
618
680
|
let matchCount = 0;
|
|
619
681
|
const signals = [];
|
|
620
682
|
for (const keyword of keywords) {
|
|
621
|
-
if (text
|
|
683
|
+
if (matchesKeyword(text, keyword)) {
|
|
622
684
|
matchCount++;
|
|
623
685
|
if (signals.length < 3) {
|
|
624
686
|
signals.push(keyword);
|
|
@@ -765,9 +827,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
|
|
|
765
827
|
const w = weights[d.name] ?? 0;
|
|
766
828
|
weightedScore += d.score * w;
|
|
767
829
|
}
|
|
768
|
-
const reasoningMatches = config.reasoningKeywords.filter(
|
|
769
|
-
(kw) => userText.includes(kw.toLowerCase())
|
|
770
|
-
);
|
|
830
|
+
const reasoningMatches = config.reasoningKeywords.filter((kw) => matchesKeyword(userText, kw));
|
|
771
831
|
if (reasoningMatches.length >= 2) {
|
|
772
832
|
const confidence2 = calibrateConfidence(
|
|
773
833
|
Math.max(weightedScore, 0.3),
|
|
@@ -832,7 +892,7 @@ function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPric
|
|
|
832
892
|
const baselineInput = estimatedInputTokens / 1e6 * opusInputPrice;
|
|
833
893
|
const baselineOutput = maxOutputTokens / 1e6 * opusOutputPrice;
|
|
834
894
|
const baselineCost = baselineInput + baselineOutput;
|
|
835
|
-
const savings = routingProfile === "premium" ? 0 : baselineCost > 0 ? Math.max(0, (baselineCost - costEstimate) / baselineCost) : 0;
|
|
895
|
+
const savings = routingProfile === "premium" || pricing === void 0 ? 0 : baselineCost > 0 ? Math.max(0, (baselineCost - costEstimate) / baselineCost) : 0;
|
|
836
896
|
return {
|
|
837
897
|
model,
|
|
838
898
|
tier,
|
|
@@ -883,6 +943,21 @@ function filterCandidatesByCapacity(models, estimatedInputTokens, requestedOutpu
|
|
|
883
943
|
});
|
|
884
944
|
return filtered;
|
|
885
945
|
}
|
|
946
|
+
var CJK_CHARS = /[ -ヿ㐀-䶿一-鿿가-豈--]/g;
|
|
947
|
+
function estimateTokens(text) {
|
|
948
|
+
if (text.length === 0) return 0;
|
|
949
|
+
const cjk = text.match(CJK_CHARS)?.length ?? 0;
|
|
950
|
+
return cjk + Math.ceil((text.length - cjk) / 4);
|
|
951
|
+
}
|
|
952
|
+
function scanLimit(promptTruncationChars) {
|
|
953
|
+
return Math.max(1, Math.min(8e3, promptTruncationChars));
|
|
954
|
+
}
|
|
955
|
+
function sampleForScan(value, limit) {
|
|
956
|
+
if (value.length <= limit) return value;
|
|
957
|
+
const prefixLength = Math.ceil(limit / 2);
|
|
958
|
+
return `${value.slice(0, prefixLength)}
|
|
959
|
+
${value.slice(-(limit - prefixLength))}`;
|
|
960
|
+
}
|
|
886
961
|
function applyPromotions(tierConfigs, promotions, profile, now = /* @__PURE__ */ new Date()) {
|
|
887
962
|
if (!promotions || promotions.length === 0) return tierConfigs;
|
|
888
963
|
let result = tierConfigs;
|
|
@@ -925,16 +1000,10 @@ var RulesStrategy = class {
|
|
|
925
1000
|
route(prompt, systemPrompt, maxOutputTokens, options) {
|
|
926
1001
|
const { config, modelPricing } = options;
|
|
927
1002
|
const fullText = `${systemPrompt ?? ""} ${prompt}`;
|
|
928
|
-
const estimatedTokens =
|
|
929
|
-
const
|
|
930
|
-
const
|
|
931
|
-
|
|
932
|
-
const prefixLength = Math.ceil(scanLimit / 2);
|
|
933
|
-
return `${value.slice(0, prefixLength)}
|
|
934
|
-
${value.slice(-(scanLimit - prefixLength))}`;
|
|
935
|
-
};
|
|
936
|
-
const scannedPrompt = sample(prompt);
|
|
937
|
-
const scannedSystemPrompt = systemPrompt ? sample(systemPrompt) : void 0;
|
|
1003
|
+
const estimatedTokens = estimateTokens(fullText);
|
|
1004
|
+
const limit = scanLimit(config.classifier.promptTruncationChars);
|
|
1005
|
+
const scannedPrompt = sampleForScan(prompt, limit);
|
|
1006
|
+
const scannedSystemPrompt = systemPrompt ? sampleForScan(systemPrompt, limit) : void 0;
|
|
938
1007
|
const ruleResult = classifyByRules(
|
|
939
1008
|
scannedPrompt,
|
|
940
1009
|
scannedSystemPrompt,
|
|
@@ -988,7 +1057,7 @@ ${value.slice(-(scanLimit - prefixLength))}`;
|
|
|
988
1057
|
);
|
|
989
1058
|
return { ...decision2, tierConfigs, profile };
|
|
990
1059
|
}
|
|
991
|
-
const hasStructuredOutput = options.requiresStructuredOutput === true
|
|
1060
|
+
const hasStructuredOutput = options.requiresStructuredOutput === true;
|
|
992
1061
|
let tier;
|
|
993
1062
|
let confidence;
|
|
994
1063
|
const method = "rules";
|
|
@@ -1044,6 +1113,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
1044
1113
|
supportsTools: true,
|
|
1045
1114
|
supportsVision: true
|
|
1046
1115
|
},
|
|
1116
|
+
"anthropic/claude-fable-5.1": {
|
|
1117
|
+
contextWindow: 1e6,
|
|
1118
|
+
maxOutputTokens: 128e3,
|
|
1119
|
+
supportsTools: true,
|
|
1120
|
+
supportsVision: true
|
|
1121
|
+
},
|
|
1047
1122
|
"anthropic/claude-haiku-4.5": {
|
|
1048
1123
|
// 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
1124
|
contextWindow: 2e5,
|
|
@@ -1075,6 +1150,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
1075
1150
|
supportsTools: true,
|
|
1076
1151
|
supportsVision: true
|
|
1077
1152
|
},
|
|
1153
|
+
"anthropic/claude-opus-5.5": {
|
|
1154
|
+
contextWindow: 1e6,
|
|
1155
|
+
maxOutputTokens: 128e3,
|
|
1156
|
+
supportsTools: true,
|
|
1157
|
+
supportsVision: true
|
|
1158
|
+
},
|
|
1078
1159
|
"anthropic/claude-sonnet-4.5": {
|
|
1079
1160
|
contextWindow: 2e5,
|
|
1080
1161
|
maxOutputTokens: 64e3,
|
|
@@ -1094,6 +1175,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
1094
1175
|
supportsTools: true,
|
|
1095
1176
|
supportsVision: true
|
|
1096
1177
|
},
|
|
1178
|
+
"anthropic/claude-sonnet-5.5": {
|
|
1179
|
+
contextWindow: 1e6,
|
|
1180
|
+
maxOutputTokens: 128e3,
|
|
1181
|
+
supportsTools: true,
|
|
1182
|
+
supportsVision: true
|
|
1183
|
+
},
|
|
1097
1184
|
"cohere/north-mini-code": {
|
|
1098
1185
|
// supportsTools: not probed — fails closed
|
|
1099
1186
|
contextWindow: 256e3,
|
|
@@ -1113,6 +1200,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
1113
1200
|
supportsTools: true,
|
|
1114
1201
|
supportsVision: false
|
|
1115
1202
|
},
|
|
1203
|
+
"deepseek/deepseek-v4-flash-vision-exp": {
|
|
1204
|
+
contextWindow: 1048576,
|
|
1205
|
+
maxOutputTokens: 65536,
|
|
1206
|
+
supportsTools: false,
|
|
1207
|
+
supportsVision: true
|
|
1208
|
+
},
|
|
1116
1209
|
"deepseek/deepseek-v4-pro": {
|
|
1117
1210
|
contextWindow: 1048576,
|
|
1118
1211
|
maxOutputTokens: 65536,
|
|
@@ -1173,6 +1266,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
1173
1266
|
supportsTools: true,
|
|
1174
1267
|
supportsVision: true
|
|
1175
1268
|
},
|
|
1269
|
+
"google/gemini-3.8-flash": {
|
|
1270
|
+
contextWindow: 1048576,
|
|
1271
|
+
maxOutputTokens: 65536,
|
|
1272
|
+
supportsTools: true,
|
|
1273
|
+
supportsVision: true
|
|
1274
|
+
},
|
|
1176
1275
|
"minimax/minimax-m2.7": {
|
|
1177
1276
|
contextWindow: 204800,
|
|
1178
1277
|
maxOutputTokens: 16384,
|
|
@@ -1198,7 +1297,7 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
1198
1297
|
supportsTools: false,
|
|
1199
1298
|
supportsVision: true
|
|
1200
1299
|
},
|
|
1201
|
-
"nvidia/
|
|
1300
|
+
"nvidia/muse-glimmer-30b": {
|
|
1202
1301
|
// supportsTools: not probed — fails closed
|
|
1203
1302
|
contextWindow: 131072,
|
|
1204
1303
|
maxOutputTokens: 16384,
|
|
@@ -1206,10 +1305,11 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
1206
1305
|
supportsVision: false
|
|
1207
1306
|
},
|
|
1208
1307
|
"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning": {
|
|
1308
|
+
// 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
1309
|
contextWindow: 256e3,
|
|
1210
1310
|
maxOutputTokens: 16384,
|
|
1211
1311
|
supportsTools: false,
|
|
1212
|
-
supportsVision:
|
|
1312
|
+
supportsVision: false
|
|
1213
1313
|
},
|
|
1214
1314
|
"nvidia/nemotron-3-ultra-550b": {
|
|
1215
1315
|
// supportsTools: not probed — fails closed
|
|
@@ -1267,6 +1367,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
1267
1367
|
supportsTools: true,
|
|
1268
1368
|
supportsVision: false
|
|
1269
1369
|
},
|
|
1370
|
+
"openai/gpt-5.1": {
|
|
1371
|
+
contextWindow: 4e5,
|
|
1372
|
+
maxOutputTokens: 128e3,
|
|
1373
|
+
supportsTools: true,
|
|
1374
|
+
supportsVision: true
|
|
1375
|
+
},
|
|
1270
1376
|
"openai/gpt-5.2": {
|
|
1271
1377
|
contextWindow: 4e5,
|
|
1272
1378
|
maxOutputTokens: 128e3,
|
|
@@ -1361,6 +1467,24 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
1361
1467
|
supportsTools: true,
|
|
1362
1468
|
supportsVision: true
|
|
1363
1469
|
},
|
|
1470
|
+
"openai/gpt-6-astra": {
|
|
1471
|
+
contextWindow: 105e4,
|
|
1472
|
+
maxOutputTokens: 128e3,
|
|
1473
|
+
supportsTools: true,
|
|
1474
|
+
supportsVision: true
|
|
1475
|
+
},
|
|
1476
|
+
"openai/gpt-6-luna": {
|
|
1477
|
+
contextWindow: 105e4,
|
|
1478
|
+
maxOutputTokens: 128e3,
|
|
1479
|
+
supportsTools: true,
|
|
1480
|
+
supportsVision: true
|
|
1481
|
+
},
|
|
1482
|
+
"openai/gpt-6-sol": {
|
|
1483
|
+
contextWindow: 105e4,
|
|
1484
|
+
maxOutputTokens: 128e3,
|
|
1485
|
+
supportsTools: true,
|
|
1486
|
+
supportsVision: true
|
|
1487
|
+
},
|
|
1364
1488
|
"openai/o1": {
|
|
1365
1489
|
contextWindow: 2e5,
|
|
1366
1490
|
maxOutputTokens: 1e5,
|
|
@@ -1385,6 +1509,13 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
1385
1509
|
supportsTools: true,
|
|
1386
1510
|
supportsVision: false
|
|
1387
1511
|
},
|
|
1512
|
+
"poolside/laguna-s-2.1": {
|
|
1513
|
+
// supportsTools: not probed — fails closed
|
|
1514
|
+
contextWindow: 1048576,
|
|
1515
|
+
maxOutputTokens: 131072,
|
|
1516
|
+
supportsTools: false,
|
|
1517
|
+
supportsVision: false
|
|
1518
|
+
},
|
|
1388
1519
|
"poolside/laguna-xs-2.1": {
|
|
1389
1520
|
// supportsTools: not probed — fails closed
|
|
1390
1521
|
contextWindow: 131072,
|
|
@@ -1410,10 +1541,24 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
1410
1541
|
supportsTools: true,
|
|
1411
1542
|
supportsVision: false
|
|
1412
1543
|
},
|
|
1413
|
-
"
|
|
1414
|
-
contextWindow:
|
|
1415
|
-
maxOutputTokens:
|
|
1416
|
-
supportsTools:
|
|
1544
|
+
"qwen/qwen3.8-flash": {
|
|
1545
|
+
contextWindow: 1e6,
|
|
1546
|
+
maxOutputTokens: 131072,
|
|
1547
|
+
supportsTools: false,
|
|
1548
|
+
supportsVision: true
|
|
1549
|
+
},
|
|
1550
|
+
"qwen/qwen3.8-max": {
|
|
1551
|
+
// supportsTools: not probed — fails closed
|
|
1552
|
+
contextWindow: 1e6,
|
|
1553
|
+
maxOutputTokens: 131072,
|
|
1554
|
+
supportsTools: false,
|
|
1555
|
+
supportsVision: false
|
|
1556
|
+
},
|
|
1557
|
+
"tencent/hy4-preview": {
|
|
1558
|
+
// supportsTools: not probed — fails closed
|
|
1559
|
+
contextWindow: 1048576,
|
|
1560
|
+
maxOutputTokens: 64e3,
|
|
1561
|
+
supportsTools: false,
|
|
1417
1562
|
supportsVision: false
|
|
1418
1563
|
},
|
|
1419
1564
|
"xai/grok-4.3": {
|
|
@@ -1428,12 +1573,30 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
1428
1573
|
supportsTools: true,
|
|
1429
1574
|
supportsVision: true
|
|
1430
1575
|
},
|
|
1576
|
+
"xai/grok-4.6": {
|
|
1577
|
+
contextWindow: 5e5,
|
|
1578
|
+
maxOutputTokens: 16384,
|
|
1579
|
+
supportsTools: true,
|
|
1580
|
+
supportsVision: true
|
|
1581
|
+
},
|
|
1582
|
+
"xai/grok-4.7": {
|
|
1583
|
+
contextWindow: 5e5,
|
|
1584
|
+
maxOutputTokens: 16384,
|
|
1585
|
+
supportsTools: true,
|
|
1586
|
+
supportsVision: true
|
|
1587
|
+
},
|
|
1431
1588
|
"xai/grok-build-0.1": {
|
|
1432
1589
|
contextWindow: 256e3,
|
|
1433
1590
|
maxOutputTokens: 16384,
|
|
1434
1591
|
supportsTools: true,
|
|
1435
1592
|
supportsVision: false
|
|
1436
1593
|
},
|
|
1594
|
+
"xiaomi/mimo-v2.5": {
|
|
1595
|
+
contextWindow: 1048576,
|
|
1596
|
+
maxOutputTokens: 131072,
|
|
1597
|
+
supportsTools: true,
|
|
1598
|
+
supportsVision: true
|
|
1599
|
+
},
|
|
1437
1600
|
"xiaomi/mimo-v2.5-pro": {
|
|
1438
1601
|
contextWindow: 1048576,
|
|
1439
1602
|
maxOutputTokens: 131072,
|
|
@@ -2147,16 +2310,10 @@ function likelyNeedsParallelToolCalls(prompt, needsTools, toolCount, toolNames)
|
|
|
2147
2310
|
}
|
|
2148
2311
|
function classifyTask(prompt, systemPrompt, options) {
|
|
2149
2312
|
const fullText = `${systemPrompt ?? ""} ${prompt}`;
|
|
2150
|
-
const estimatedInputTokens =
|
|
2151
|
-
const
|
|
2152
|
-
const
|
|
2153
|
-
|
|
2154
|
-
const prefixLength = Math.ceil(scanLimit / 2);
|
|
2155
|
-
return `${value.slice(0, prefixLength)}
|
|
2156
|
-
${value.slice(-(scanLimit - prefixLength))}`;
|
|
2157
|
-
};
|
|
2158
|
-
const scannedPrompt = sample(prompt);
|
|
2159
|
-
const scannedSystemPrompt = sample(systemPrompt ?? "");
|
|
2313
|
+
const estimatedInputTokens = estimateTokens(fullText);
|
|
2314
|
+
const limit = scanLimit(options.config.classifier.promptTruncationChars);
|
|
2315
|
+
const scannedPrompt = sampleForScan(prompt, limit);
|
|
2316
|
+
const scannedSystemPrompt = sampleForScan(systemPrompt ?? "", limit);
|
|
2160
2317
|
const scannedFullText = `${scannedSystemPrompt} ${scannedPrompt}`;
|
|
2161
2318
|
const text = scannedPrompt.toLowerCase();
|
|
2162
2319
|
const explicitCodeSignal = /```|\b(?:typescript|javascript|python|rust|java|sql|stack trace|traceback|exception)\b|\.(?:ts|tsx|js|py|go|rs)\b/i.test(
|
|
@@ -2168,7 +2325,9 @@ ${value.slice(-(scanLimit - prefixLength))}`;
|
|
|
2168
2325
|
const nativeCodeSignal = /\b(?:programmed|written|implemented?|code)\s+(?:in|using)\s+(?:c\+\+|c|rust|go)\b/i.test(
|
|
2169
2326
|
scannedPrompt
|
|
2170
2327
|
);
|
|
2171
|
-
const
|
|
2328
|
+
const errorClassSignal = /\b[A-Z][A-Za-z]*(?:Error|Exception)\b/.test(scannedPrompt);
|
|
2329
|
+
const hasCode = explicitCodeSignal || codeConstructSignal || nativeCodeSignal || errorClassSignal;
|
|
2330
|
+
const codeArtifact = /```|\b(?:stack trace|traceback)\b|\.(?:ts|tsx|js|py|go|rs)\b/i.test(scannedPrompt) || codeConstructSignal || nativeCodeSignal || errorClassSignal;
|
|
2172
2331
|
const toolsAvailable = options.hasTools ?? false;
|
|
2173
2332
|
const needsTools = options.requiresTools ?? (toolsAvailable && inferToolRequirement(scannedPrompt, scannedSystemPrompt));
|
|
2174
2333
|
const likelyParallelToolCalls = likelyNeedsParallelToolCalls(
|
|
@@ -2240,10 +2399,10 @@ ${value.slice(-(scanLimit - prefixLength))}`;
|
|
|
2240
2399
|
const needsVision = options.hasVision ?? false;
|
|
2241
2400
|
const needsStructuredOutput = options.requiresStructuredOutput ?? false;
|
|
2242
2401
|
const latencySensitive = /\b(?:urgent|asap|fast|quick|low latency|real[- ]time)\b|尽快|马上|快速|低延迟/i.test(
|
|
2243
|
-
|
|
2402
|
+
scannedPrompt
|
|
2244
2403
|
);
|
|
2245
2404
|
const highStakes = /\b(?:production|security|payment|legal|medical|financial|audit)\b|生产|安全|支付|法律|医疗|财务|审计/i.test(
|
|
2246
|
-
|
|
2405
|
+
scannedPrompt
|
|
2247
2406
|
);
|
|
2248
2407
|
const terminalToolSignal = normalizedToolNames.some(
|
|
2249
2408
|
(name) => /^(?:terminalexec|terminalinspect|terminalsendkeys)$/.test(name)
|
|
@@ -2279,7 +2438,7 @@ ${value.slice(-(scanLimit - prefixLength))}`;
|
|
|
2279
2438
|
);
|
|
2280
2439
|
const terminalSafetySensitive = terminalToolSignal && (highStakes || terminalCredentialSignal);
|
|
2281
2440
|
const implicitTerminalCode = needsTools && terminalToolSignal && agentRisk === "standard" && !highStakes && !complexTerminalOperation && numberedSteps < 3 && prompt.length <= 1e3 && simpleTerminalArtifact;
|
|
2282
|
-
const language = /[\u3400-\u9fff]/.test(
|
|
2441
|
+
const language = /[\u3400-\u9fff]/.test(scannedPrompt) ? "zh" : "other";
|
|
2283
2442
|
const multipleChoiceSignals = (scannedPrompt.match(/(?:^|\n)\s*[A-D][.)]\s+/gim) ?? []).length;
|
|
2284
2443
|
const numericSignals = (scannedPrompt.match(/-?\d+(?:[.,]\d+)?/g) ?? []).length;
|
|
2285
2444
|
const compactMathProblem = !hasCode && prompt.length < 2500 && numericSignals >= 2 && (/[+×÷=%$€£¥]|\b(?:total|each|per|times|half|twice|percent|how many|how much|calculate)\b/i.test(
|
|
@@ -2294,7 +2453,7 @@ ${value.slice(-(scanLimit - prefixLength))}`;
|
|
|
2294
2453
|
else if (needsTools) taskType = "tool_agent";
|
|
2295
2454
|
else if (multipleChoiceSignals >= 3) taskType = "reasoning_mcq";
|
|
2296
2455
|
else if (compactMathProblem) taskType = "reasoning_math";
|
|
2297
|
-
else if (/\b(?:bug|debug|error|failure|failing|regression|crash|修复|报错|错误|调试)\b/i.test(text))
|
|
2456
|
+
else if (errorClassSignal || /\b(?:bug|debug|error|failure|failing|regression|crash|修复|报错|错误|调试)\b/i.test(text))
|
|
2298
2457
|
taskType = "debug";
|
|
2299
2458
|
else if (hasCode || /\b(?:refactor|implement|patch|edit|rewrite|重构|实现|修改)\b/i.test(text))
|
|
2300
2459
|
taskType = "code_edit";
|
|
@@ -2320,7 +2479,8 @@ ${value.slice(-(scanLimit - prefixLength))}`;
|
|
|
2320
2479
|
agentRisk,
|
|
2321
2480
|
terminalToolSignal,
|
|
2322
2481
|
terminalSafetySensitive,
|
|
2323
|
-
implicitTerminalCode
|
|
2482
|
+
implicitTerminalCode,
|
|
2483
|
+
codeArtifact
|
|
2324
2484
|
};
|
|
2325
2485
|
}
|
|
2326
2486
|
function affinity(modelId, task, language = "other", agentDomain = "other", deepWebResearch = false, agentRisk = "standard", terminalToolSignal = false, terminalSafetySensitive = false) {
|
|
@@ -2676,6 +2836,19 @@ function evidenceCandidates(task) {
|
|
|
2676
2836
|
}
|
|
2677
2837
|
return [];
|
|
2678
2838
|
}
|
|
2839
|
+
var VERIFIED_TOOL_FALLBACKS = [
|
|
2840
|
+
"anthropic/claude-fable-5.1",
|
|
2841
|
+
"anthropic/claude-opus-5.5",
|
|
2842
|
+
"anthropic/claude-sonnet-5.5",
|
|
2843
|
+
"google/gemini-3.8-flash",
|
|
2844
|
+
"openai/gpt-5.1",
|
|
2845
|
+
"openai/gpt-6-astra",
|
|
2846
|
+
"openai/gpt-6-luna",
|
|
2847
|
+
"openai/gpt-6-sol",
|
|
2848
|
+
"xai/grok-4.6",
|
|
2849
|
+
"xai/grok-4.7",
|
|
2850
|
+
"xiaomi/mimo-v2.5"
|
|
2851
|
+
];
|
|
2679
2852
|
function isEligible(modelId, features, maxOutputTokens, options) {
|
|
2680
2853
|
const model = options.modelCapabilities?.[modelId] ?? DEFAULT_MODEL_CAPABILITIES[modelId];
|
|
2681
2854
|
if (!model) return true;
|
|
@@ -2710,27 +2883,38 @@ function profileScore(modelId, options, now) {
|
|
|
2710
2883
|
}
|
|
2711
2884
|
var PortfolioStrategy = class {
|
|
2712
2885
|
name = "portfolio";
|
|
2886
|
+
// Rules set the capability tier; the strategy is stateless, so one instance serves every request.
|
|
2887
|
+
rules = new RulesStrategy();
|
|
2713
2888
|
route(prompt, systemPrompt, maxOutputTokens, options) {
|
|
2714
2889
|
const features = classifyTask(prompt, systemPrompt, options);
|
|
2715
|
-
const base =
|
|
2890
|
+
const base = this.rules.route(prompt, systemPrompt, maxOutputTokens, {
|
|
2716
2891
|
...options,
|
|
2717
2892
|
requiresTools: features.needsTools
|
|
2718
2893
|
});
|
|
2719
2894
|
const tierConfigs = base.tierConfigs;
|
|
2720
2895
|
if (!tierConfigs) return base;
|
|
2721
|
-
const targetTier = (features.taskType === "reasoning_mcq" || features.taskType === "reasoning_math") && (base.tier === "SIMPLE" || base.tier === "MEDIUM") ? "REASONING" : base.tier;
|
|
2896
|
+
const targetTier = (features.taskType === "reasoning_mcq" || features.taskType === "reasoning_math") && (base.tier === "SIMPLE" || base.tier === "MEDIUM") ? "REASONING" : (features.taskType === "debug" || features.taskType === "code_edit") && features.codeArtifact && base.tier === "SIMPLE" ? "MEDIUM" : base.tier;
|
|
2722
2897
|
const tierConfig = tierConfigs[targetTier];
|
|
2723
2898
|
const configuredCandidates = tierConfig ? getFallbackChain(targetTier, tierConfigs) : [];
|
|
2724
2899
|
const unavailable = new Set(options.unavailableModels ?? []);
|
|
2900
|
+
const verifiedToolFallbacks = features.needsTools ? VERIFIED_TOOL_FALLBACKS.filter((model2) => options.modelPricing.has(model2)) : [];
|
|
2901
|
+
if (options.routingProfile !== "premium") {
|
|
2902
|
+
const fallbackCost = (model2) => estimatedCost(model2, options, features.estimatedInputTokens, maxOutputTokens);
|
|
2903
|
+
verifiedToolFallbacks.sort((a, b) => fallbackCost(a) - fallbackCost(b));
|
|
2904
|
+
}
|
|
2725
2905
|
const chain = [
|
|
2726
|
-
.../* @__PURE__ */ new Set([
|
|
2906
|
+
.../* @__PURE__ */ new Set([
|
|
2907
|
+
...configuredCandidates,
|
|
2908
|
+
...evidenceCandidates(features.taskType),
|
|
2909
|
+
...verifiedToolFallbacks
|
|
2910
|
+
])
|
|
2727
2911
|
].filter(
|
|
2728
2912
|
(model2) => typeof model2 === "string" && model2.length > 0 && !unavailable.has(model2)
|
|
2729
2913
|
);
|
|
2730
|
-
const eligible =
|
|
2731
|
-
(model2) => isEligible(model2, features, maxOutputTokens, options)
|
|
2914
|
+
const eligible = new Set(
|
|
2915
|
+
chain.filter((model2) => isEligible(model2, features, maxOutputTokens, options))
|
|
2732
2916
|
);
|
|
2733
|
-
const eligibleCandidates = eligible.
|
|
2917
|
+
const eligibleCandidates = eligible.size > 0 ? [...eligible] : chain;
|
|
2734
2918
|
if (eligibleCandidates.length === 0) return base;
|
|
2735
2919
|
const profileName = options.routingProfile === "eco" ? "eco" : options.routingProfile === "premium" ? "premium" : "auto";
|
|
2736
2920
|
const portfolio = options.config.portfolio ?? DEFAULT_PORTFOLIO_WEIGHTS;
|
|
@@ -2765,11 +2949,11 @@ var PortfolioStrategy = class {
|
|
|
2765
2949
|
const rankedEntries = candidates.map((model2, index) => {
|
|
2766
2950
|
const cost = estimatedCost(model2, options, features.estimatedInputTokens, maxOutputTokens);
|
|
2767
2951
|
const costScore = Number.isFinite(cost) && maxCost > minCost ? 1 - (cost - minCost) / (maxCost - minCost) : 0.5;
|
|
2768
|
-
const capabilityScore =
|
|
2952
|
+
const capabilityScore = eligible.has(model2) ? 1 : 0;
|
|
2769
2953
|
const profile = profileScore(model2, options, now);
|
|
2770
2954
|
const observedQuality = profile?.quality === void 0 ? getAffinity(model2) : getAffinity(model2) * (1 - profile.freshness) + profile.quality * profile.freshness;
|
|
2771
|
-
const observedSpeed = profile ? profile.speed * profile.freshness : 0.5;
|
|
2772
|
-
const observedTailSpeed = profile ? profile.tailSpeed * profile.freshness : 0.5;
|
|
2955
|
+
const observedSpeed = profile ? profile.speed * profile.freshness + 0.5 * (1 - profile.freshness) : 0.5;
|
|
2956
|
+
const observedTailSpeed = profile ? profile.tailSpeed * profile.freshness + 0.5 * (1 - profile.freshness) : 0.5;
|
|
2773
2957
|
const observedReliability = profile ? profile.reliability * profile.freshness + (1 - profile.freshness) : 1;
|
|
2774
2958
|
const legacyScore = 1 - index / Math.max(1, candidates.length - 1);
|
|
2775
2959
|
const qualityWeight = profileWeights.quality + (features.highStakes ? portfolio.highStakesBoost.quality : 0);
|
|
@@ -4026,8 +4210,13 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
4026
4210
|
primary: "nvidia/nemotron-3.5-lightning",
|
|
4027
4211
|
// FREE — NVIDIA free tier flagship, 1M ctx
|
|
4028
4212
|
fallback: [
|
|
4029
|
-
|
|
4030
|
-
//
|
|
4213
|
+
// Was nvidia/nemotron-3-nano-30b until blockrun delisted it on 2026-09-08
|
|
4214
|
+
// (NVIDIA deprovisioned it for the account — a structured per-account
|
|
4215
|
+
// 404). nano-omni is blockrun's own redirect target for that id, so this
|
|
4216
|
+
// follows the same rule as the head: the router and the gateway name the
|
|
4217
|
+
// same model.
|
|
4218
|
+
"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
|
|
4219
|
+
// FREE — nano-30b's successor, 256K ctx
|
|
4031
4220
|
// The free head keeps rotting with NVIDIA's hosting (deepseek-v4-flash
|
|
4032
4221
|
// 410 2026-08-12, seed-oss-36b 410 2026-08-03, gpt-oss-120b/20b 400
|
|
4033
4222
|
// 2026-08-21, and on 2026-08-30 FOUR of the five visible free models at
|
|
@@ -4856,7 +5045,7 @@ function getCostSummary() {
|
|
|
4856
5045
|
}
|
|
4857
5046
|
|
|
4858
5047
|
// src/version.ts
|
|
4859
|
-
var SDK_VERSION = "3.24.
|
|
5048
|
+
var SDK_VERSION = "3.24.2";
|
|
4860
5049
|
var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
|
|
4861
5050
|
|
|
4862
5051
|
// src/client.ts
|
package/dist/index.js
CHANGED
|
@@ -440,7 +440,69 @@ 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+
|
|
443
|
+
// node_modules/.pnpm/@blockrun+router-core@https+++codeload.github.com+BlockRunAI+router-core+tar.gz+65a92c85ea1f1_ulf6q3mq2pzpwvcnu73z4krxvy/node_modules/@blockrun/router-core/dist/index.js
|
|
444
|
+
var UNSEGMENTED_SCRIPT = /[-ۿ -ヿ㐀-䶿一-鿿가-豈--]/;
|
|
445
|
+
var WORD_CHAR = /[\p{L}\p{N}]/u;
|
|
446
|
+
var ASCII_PHRASE = /^[a-z0-9' -]+$/;
|
|
447
|
+
var SEGMENTED_WORD_CHAR = "\\p{Script=Latin}\\p{Script=Cyrillic}\\p{Script=Greek}\\p{N}";
|
|
448
|
+
var BOUNDARY_BEFORE = `(?<![${SEGMENTED_WORD_CHAR}])`;
|
|
449
|
+
var BOUNDARY_AFTER = `(?![${SEGMENTED_WORD_CHAR}])`;
|
|
450
|
+
var BOUNDED_LAST_WORD_LENGTH = 6;
|
|
451
|
+
var SHORT_SUFFIXES = "(?:s|ed|ing)?";
|
|
452
|
+
var SIBILANT_PLURAL = "es";
|
|
453
|
+
var MID_SUFFIXES = "(?:s|es|ed|d|ing|er|ers|ly|ment|ments|ation|ations|e|en|n|a|as|o|os|em|et|ung|ungen)?";
|
|
454
|
+
var DROPPED_E_SUFFIXES = "(?:ing|ed|es|er|ers|ation|ations)";
|
|
455
|
+
var DOUBLED_CONSONANT_SUFFIXES = "(?:ing|ed|er|ers)";
|
|
456
|
+
var matcherCache = /* @__PURE__ */ new Map();
|
|
457
|
+
function escapeRegExp(value) {
|
|
458
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
459
|
+
}
|
|
460
|
+
function keywordMatcher(keyword) {
|
|
461
|
+
const cached = matcherCache.get(keyword);
|
|
462
|
+
if (cached) return cached;
|
|
463
|
+
const kw = keyword.toLowerCase();
|
|
464
|
+
let matcher;
|
|
465
|
+
if (UNSEGMENTED_SCRIPT.test(kw) || kw.length === 0) {
|
|
466
|
+
matcher = { needle: kw, pattern: null };
|
|
467
|
+
} else {
|
|
468
|
+
const lead = WORD_CHAR.test(kw[0]) ? BOUNDARY_BEFORE : "";
|
|
469
|
+
const escaped = escapeRegExp(kw);
|
|
470
|
+
let body = escaped;
|
|
471
|
+
let trail = "";
|
|
472
|
+
let needle = kw;
|
|
473
|
+
const lastWord = kw.slice(kw.lastIndexOf(" ") + 1);
|
|
474
|
+
if (WORD_CHAR.test(kw[kw.length - 1]) && ASCII_PHRASE.test(kw) && lastWord.length <= BOUNDED_LAST_WORD_LENGTH) {
|
|
475
|
+
const forms = [];
|
|
476
|
+
if (/\d$/.test(kw)) {
|
|
477
|
+
forms.push(escaped);
|
|
478
|
+
} else if (lastWord.length <= 3) {
|
|
479
|
+
forms.push(`${escaped}${SHORT_SUFFIXES}`);
|
|
480
|
+
if (/(?:x|s|z|ch|sh)$/.test(kw)) forms.push(`${escaped}${SIBILANT_PLURAL}`);
|
|
481
|
+
} else {
|
|
482
|
+
forms.push(`${escaped}${MID_SUFFIXES}`);
|
|
483
|
+
if (kw.endsWith("e")) {
|
|
484
|
+
forms.push(`${escaped.slice(0, -1)}${DROPPED_E_SUFFIXES}`);
|
|
485
|
+
needle = kw.slice(0, -1);
|
|
486
|
+
} else if (kw.endsWith("y")) {
|
|
487
|
+
forms.push(`${escaped.slice(0, -1)}(?:ies|ied)`);
|
|
488
|
+
needle = kw.slice(0, -1);
|
|
489
|
+
} else if (/[aeiou][bdgmnprt]$/.test(kw)) {
|
|
490
|
+
forms.push(`${escaped}${kw[kw.length - 1]}${DOUBLED_CONSONANT_SUFFIXES}`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
body = `(?:${forms.join("|")})`;
|
|
494
|
+
trail = BOUNDARY_AFTER;
|
|
495
|
+
}
|
|
496
|
+
matcher = { needle, pattern: new RegExp(`${lead}${body}${trail}`, "u") };
|
|
497
|
+
}
|
|
498
|
+
matcherCache.set(keyword, matcher);
|
|
499
|
+
return matcher;
|
|
500
|
+
}
|
|
501
|
+
function matchesKeyword(text, keyword) {
|
|
502
|
+
const { needle, pattern } = keywordMatcher(keyword);
|
|
503
|
+
if (!text.includes(needle)) return false;
|
|
504
|
+
return pattern === null ? true : pattern.test(text);
|
|
505
|
+
}
|
|
444
506
|
function scoreTokenCount(estimatedTokens, thresholds) {
|
|
445
507
|
if (estimatedTokens < thresholds.simple) {
|
|
446
508
|
return { name: "tokenCount", score: -1, signal: `short (${estimatedTokens} tokens)` };
|
|
@@ -451,7 +513,7 @@ function scoreTokenCount(estimatedTokens, thresholds) {
|
|
|
451
513
|
return { name: "tokenCount", score: 0, signal: null };
|
|
452
514
|
}
|
|
453
515
|
function scoreKeywordMatch(text, keywords, name, signalLabel, thresholds, scores) {
|
|
454
|
-
const matches = keywords.filter((kw) => text
|
|
516
|
+
const matches = keywords.filter((kw) => matchesKeyword(text, kw));
|
|
455
517
|
if (matches.length >= thresholds.high) {
|
|
456
518
|
return {
|
|
457
519
|
name,
|
|
@@ -487,7 +549,7 @@ function scoreAgenticTask(text, keywords) {
|
|
|
487
549
|
let matchCount = 0;
|
|
488
550
|
const signals = [];
|
|
489
551
|
for (const keyword of keywords) {
|
|
490
|
-
if (text
|
|
552
|
+
if (matchesKeyword(text, keyword)) {
|
|
491
553
|
matchCount++;
|
|
492
554
|
if (signals.length < 3) {
|
|
493
555
|
signals.push(keyword);
|
|
@@ -634,9 +696,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
|
|
|
634
696
|
const w = weights[d.name] ?? 0;
|
|
635
697
|
weightedScore += d.score * w;
|
|
636
698
|
}
|
|
637
|
-
const reasoningMatches = config.reasoningKeywords.filter(
|
|
638
|
-
(kw) => userText.includes(kw.toLowerCase())
|
|
639
|
-
);
|
|
699
|
+
const reasoningMatches = config.reasoningKeywords.filter((kw) => matchesKeyword(userText, kw));
|
|
640
700
|
if (reasoningMatches.length >= 2) {
|
|
641
701
|
const confidence2 = calibrateConfidence(
|
|
642
702
|
Math.max(weightedScore, 0.3),
|
|
@@ -701,7 +761,7 @@ function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPric
|
|
|
701
761
|
const baselineInput = estimatedInputTokens / 1e6 * opusInputPrice;
|
|
702
762
|
const baselineOutput = maxOutputTokens / 1e6 * opusOutputPrice;
|
|
703
763
|
const baselineCost = baselineInput + baselineOutput;
|
|
704
|
-
const savings = routingProfile === "premium" ? 0 : baselineCost > 0 ? Math.max(0, (baselineCost - costEstimate) / baselineCost) : 0;
|
|
764
|
+
const savings = routingProfile === "premium" || pricing === void 0 ? 0 : baselineCost > 0 ? Math.max(0, (baselineCost - costEstimate) / baselineCost) : 0;
|
|
705
765
|
return {
|
|
706
766
|
model,
|
|
707
767
|
tier,
|
|
@@ -752,6 +812,21 @@ function filterCandidatesByCapacity(models, estimatedInputTokens, requestedOutpu
|
|
|
752
812
|
});
|
|
753
813
|
return filtered;
|
|
754
814
|
}
|
|
815
|
+
var CJK_CHARS = /[ -ヿ㐀-䶿一-鿿가-豈--]/g;
|
|
816
|
+
function estimateTokens(text) {
|
|
817
|
+
if (text.length === 0) return 0;
|
|
818
|
+
const cjk = text.match(CJK_CHARS)?.length ?? 0;
|
|
819
|
+
return cjk + Math.ceil((text.length - cjk) / 4);
|
|
820
|
+
}
|
|
821
|
+
function scanLimit(promptTruncationChars) {
|
|
822
|
+
return Math.max(1, Math.min(8e3, promptTruncationChars));
|
|
823
|
+
}
|
|
824
|
+
function sampleForScan(value, limit) {
|
|
825
|
+
if (value.length <= limit) return value;
|
|
826
|
+
const prefixLength = Math.ceil(limit / 2);
|
|
827
|
+
return `${value.slice(0, prefixLength)}
|
|
828
|
+
${value.slice(-(limit - prefixLength))}`;
|
|
829
|
+
}
|
|
755
830
|
function applyPromotions(tierConfigs, promotions, profile, now = /* @__PURE__ */ new Date()) {
|
|
756
831
|
if (!promotions || promotions.length === 0) return tierConfigs;
|
|
757
832
|
let result = tierConfigs;
|
|
@@ -794,16 +869,10 @@ var RulesStrategy = class {
|
|
|
794
869
|
route(prompt, systemPrompt, maxOutputTokens, options) {
|
|
795
870
|
const { config, modelPricing } = options;
|
|
796
871
|
const fullText = `${systemPrompt ?? ""} ${prompt}`;
|
|
797
|
-
const estimatedTokens =
|
|
798
|
-
const
|
|
799
|
-
const
|
|
800
|
-
|
|
801
|
-
const prefixLength = Math.ceil(scanLimit / 2);
|
|
802
|
-
return `${value.slice(0, prefixLength)}
|
|
803
|
-
${value.slice(-(scanLimit - prefixLength))}`;
|
|
804
|
-
};
|
|
805
|
-
const scannedPrompt = sample(prompt);
|
|
806
|
-
const scannedSystemPrompt = systemPrompt ? sample(systemPrompt) : void 0;
|
|
872
|
+
const estimatedTokens = estimateTokens(fullText);
|
|
873
|
+
const limit = scanLimit(config.classifier.promptTruncationChars);
|
|
874
|
+
const scannedPrompt = sampleForScan(prompt, limit);
|
|
875
|
+
const scannedSystemPrompt = systemPrompt ? sampleForScan(systemPrompt, limit) : void 0;
|
|
807
876
|
const ruleResult = classifyByRules(
|
|
808
877
|
scannedPrompt,
|
|
809
878
|
scannedSystemPrompt,
|
|
@@ -857,7 +926,7 @@ ${value.slice(-(scanLimit - prefixLength))}`;
|
|
|
857
926
|
);
|
|
858
927
|
return { ...decision2, tierConfigs, profile };
|
|
859
928
|
}
|
|
860
|
-
const hasStructuredOutput = options.requiresStructuredOutput === true
|
|
929
|
+
const hasStructuredOutput = options.requiresStructuredOutput === true;
|
|
861
930
|
let tier;
|
|
862
931
|
let confidence;
|
|
863
932
|
const method = "rules";
|
|
@@ -913,6 +982,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
913
982
|
supportsTools: true,
|
|
914
983
|
supportsVision: true
|
|
915
984
|
},
|
|
985
|
+
"anthropic/claude-fable-5.1": {
|
|
986
|
+
contextWindow: 1e6,
|
|
987
|
+
maxOutputTokens: 128e3,
|
|
988
|
+
supportsTools: true,
|
|
989
|
+
supportsVision: true
|
|
990
|
+
},
|
|
916
991
|
"anthropic/claude-haiku-4.5": {
|
|
917
992
|
// 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
993
|
contextWindow: 2e5,
|
|
@@ -944,6 +1019,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
944
1019
|
supportsTools: true,
|
|
945
1020
|
supportsVision: true
|
|
946
1021
|
},
|
|
1022
|
+
"anthropic/claude-opus-5.5": {
|
|
1023
|
+
contextWindow: 1e6,
|
|
1024
|
+
maxOutputTokens: 128e3,
|
|
1025
|
+
supportsTools: true,
|
|
1026
|
+
supportsVision: true
|
|
1027
|
+
},
|
|
947
1028
|
"anthropic/claude-sonnet-4.5": {
|
|
948
1029
|
contextWindow: 2e5,
|
|
949
1030
|
maxOutputTokens: 64e3,
|
|
@@ -963,6 +1044,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
963
1044
|
supportsTools: true,
|
|
964
1045
|
supportsVision: true
|
|
965
1046
|
},
|
|
1047
|
+
"anthropic/claude-sonnet-5.5": {
|
|
1048
|
+
contextWindow: 1e6,
|
|
1049
|
+
maxOutputTokens: 128e3,
|
|
1050
|
+
supportsTools: true,
|
|
1051
|
+
supportsVision: true
|
|
1052
|
+
},
|
|
966
1053
|
"cohere/north-mini-code": {
|
|
967
1054
|
// supportsTools: not probed — fails closed
|
|
968
1055
|
contextWindow: 256e3,
|
|
@@ -982,6 +1069,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
982
1069
|
supportsTools: true,
|
|
983
1070
|
supportsVision: false
|
|
984
1071
|
},
|
|
1072
|
+
"deepseek/deepseek-v4-flash-vision-exp": {
|
|
1073
|
+
contextWindow: 1048576,
|
|
1074
|
+
maxOutputTokens: 65536,
|
|
1075
|
+
supportsTools: false,
|
|
1076
|
+
supportsVision: true
|
|
1077
|
+
},
|
|
985
1078
|
"deepseek/deepseek-v4-pro": {
|
|
986
1079
|
contextWindow: 1048576,
|
|
987
1080
|
maxOutputTokens: 65536,
|
|
@@ -1042,6 +1135,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
1042
1135
|
supportsTools: true,
|
|
1043
1136
|
supportsVision: true
|
|
1044
1137
|
},
|
|
1138
|
+
"google/gemini-3.8-flash": {
|
|
1139
|
+
contextWindow: 1048576,
|
|
1140
|
+
maxOutputTokens: 65536,
|
|
1141
|
+
supportsTools: true,
|
|
1142
|
+
supportsVision: true
|
|
1143
|
+
},
|
|
1045
1144
|
"minimax/minimax-m2.7": {
|
|
1046
1145
|
contextWindow: 204800,
|
|
1047
1146
|
maxOutputTokens: 16384,
|
|
@@ -1067,7 +1166,7 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
1067
1166
|
supportsTools: false,
|
|
1068
1167
|
supportsVision: true
|
|
1069
1168
|
},
|
|
1070
|
-
"nvidia/
|
|
1169
|
+
"nvidia/muse-glimmer-30b": {
|
|
1071
1170
|
// supportsTools: not probed — fails closed
|
|
1072
1171
|
contextWindow: 131072,
|
|
1073
1172
|
maxOutputTokens: 16384,
|
|
@@ -1075,10 +1174,11 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
1075
1174
|
supportsVision: false
|
|
1076
1175
|
},
|
|
1077
1176
|
"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning": {
|
|
1177
|
+
// 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
1178
|
contextWindow: 256e3,
|
|
1079
1179
|
maxOutputTokens: 16384,
|
|
1080
1180
|
supportsTools: false,
|
|
1081
|
-
supportsVision:
|
|
1181
|
+
supportsVision: false
|
|
1082
1182
|
},
|
|
1083
1183
|
"nvidia/nemotron-3-ultra-550b": {
|
|
1084
1184
|
// supportsTools: not probed — fails closed
|
|
@@ -1136,6 +1236,12 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
1136
1236
|
supportsTools: true,
|
|
1137
1237
|
supportsVision: false
|
|
1138
1238
|
},
|
|
1239
|
+
"openai/gpt-5.1": {
|
|
1240
|
+
contextWindow: 4e5,
|
|
1241
|
+
maxOutputTokens: 128e3,
|
|
1242
|
+
supportsTools: true,
|
|
1243
|
+
supportsVision: true
|
|
1244
|
+
},
|
|
1139
1245
|
"openai/gpt-5.2": {
|
|
1140
1246
|
contextWindow: 4e5,
|
|
1141
1247
|
maxOutputTokens: 128e3,
|
|
@@ -1230,6 +1336,24 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
1230
1336
|
supportsTools: true,
|
|
1231
1337
|
supportsVision: true
|
|
1232
1338
|
},
|
|
1339
|
+
"openai/gpt-6-astra": {
|
|
1340
|
+
contextWindow: 105e4,
|
|
1341
|
+
maxOutputTokens: 128e3,
|
|
1342
|
+
supportsTools: true,
|
|
1343
|
+
supportsVision: true
|
|
1344
|
+
},
|
|
1345
|
+
"openai/gpt-6-luna": {
|
|
1346
|
+
contextWindow: 105e4,
|
|
1347
|
+
maxOutputTokens: 128e3,
|
|
1348
|
+
supportsTools: true,
|
|
1349
|
+
supportsVision: true
|
|
1350
|
+
},
|
|
1351
|
+
"openai/gpt-6-sol": {
|
|
1352
|
+
contextWindow: 105e4,
|
|
1353
|
+
maxOutputTokens: 128e3,
|
|
1354
|
+
supportsTools: true,
|
|
1355
|
+
supportsVision: true
|
|
1356
|
+
},
|
|
1233
1357
|
"openai/o1": {
|
|
1234
1358
|
contextWindow: 2e5,
|
|
1235
1359
|
maxOutputTokens: 1e5,
|
|
@@ -1254,6 +1378,13 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
1254
1378
|
supportsTools: true,
|
|
1255
1379
|
supportsVision: false
|
|
1256
1380
|
},
|
|
1381
|
+
"poolside/laguna-s-2.1": {
|
|
1382
|
+
// supportsTools: not probed — fails closed
|
|
1383
|
+
contextWindow: 1048576,
|
|
1384
|
+
maxOutputTokens: 131072,
|
|
1385
|
+
supportsTools: false,
|
|
1386
|
+
supportsVision: false
|
|
1387
|
+
},
|
|
1257
1388
|
"poolside/laguna-xs-2.1": {
|
|
1258
1389
|
// supportsTools: not probed — fails closed
|
|
1259
1390
|
contextWindow: 131072,
|
|
@@ -1279,10 +1410,24 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
1279
1410
|
supportsTools: true,
|
|
1280
1411
|
supportsVision: false
|
|
1281
1412
|
},
|
|
1282
|
-
"
|
|
1283
|
-
contextWindow:
|
|
1284
|
-
maxOutputTokens:
|
|
1285
|
-
supportsTools:
|
|
1413
|
+
"qwen/qwen3.8-flash": {
|
|
1414
|
+
contextWindow: 1e6,
|
|
1415
|
+
maxOutputTokens: 131072,
|
|
1416
|
+
supportsTools: false,
|
|
1417
|
+
supportsVision: true
|
|
1418
|
+
},
|
|
1419
|
+
"qwen/qwen3.8-max": {
|
|
1420
|
+
// supportsTools: not probed — fails closed
|
|
1421
|
+
contextWindow: 1e6,
|
|
1422
|
+
maxOutputTokens: 131072,
|
|
1423
|
+
supportsTools: false,
|
|
1424
|
+
supportsVision: false
|
|
1425
|
+
},
|
|
1426
|
+
"tencent/hy4-preview": {
|
|
1427
|
+
// supportsTools: not probed — fails closed
|
|
1428
|
+
contextWindow: 1048576,
|
|
1429
|
+
maxOutputTokens: 64e3,
|
|
1430
|
+
supportsTools: false,
|
|
1286
1431
|
supportsVision: false
|
|
1287
1432
|
},
|
|
1288
1433
|
"xai/grok-4.3": {
|
|
@@ -1297,12 +1442,30 @@ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
|
|
|
1297
1442
|
supportsTools: true,
|
|
1298
1443
|
supportsVision: true
|
|
1299
1444
|
},
|
|
1445
|
+
"xai/grok-4.6": {
|
|
1446
|
+
contextWindow: 5e5,
|
|
1447
|
+
maxOutputTokens: 16384,
|
|
1448
|
+
supportsTools: true,
|
|
1449
|
+
supportsVision: true
|
|
1450
|
+
},
|
|
1451
|
+
"xai/grok-4.7": {
|
|
1452
|
+
contextWindow: 5e5,
|
|
1453
|
+
maxOutputTokens: 16384,
|
|
1454
|
+
supportsTools: true,
|
|
1455
|
+
supportsVision: true
|
|
1456
|
+
},
|
|
1300
1457
|
"xai/grok-build-0.1": {
|
|
1301
1458
|
contextWindow: 256e3,
|
|
1302
1459
|
maxOutputTokens: 16384,
|
|
1303
1460
|
supportsTools: true,
|
|
1304
1461
|
supportsVision: false
|
|
1305
1462
|
},
|
|
1463
|
+
"xiaomi/mimo-v2.5": {
|
|
1464
|
+
contextWindow: 1048576,
|
|
1465
|
+
maxOutputTokens: 131072,
|
|
1466
|
+
supportsTools: true,
|
|
1467
|
+
supportsVision: true
|
|
1468
|
+
},
|
|
1306
1469
|
"xiaomi/mimo-v2.5-pro": {
|
|
1307
1470
|
contextWindow: 1048576,
|
|
1308
1471
|
maxOutputTokens: 131072,
|
|
@@ -2016,16 +2179,10 @@ function likelyNeedsParallelToolCalls(prompt, needsTools, toolCount, toolNames)
|
|
|
2016
2179
|
}
|
|
2017
2180
|
function classifyTask(prompt, systemPrompt, options) {
|
|
2018
2181
|
const fullText = `${systemPrompt ?? ""} ${prompt}`;
|
|
2019
|
-
const estimatedInputTokens =
|
|
2020
|
-
const
|
|
2021
|
-
const
|
|
2022
|
-
|
|
2023
|
-
const prefixLength = Math.ceil(scanLimit / 2);
|
|
2024
|
-
return `${value.slice(0, prefixLength)}
|
|
2025
|
-
${value.slice(-(scanLimit - prefixLength))}`;
|
|
2026
|
-
};
|
|
2027
|
-
const scannedPrompt = sample(prompt);
|
|
2028
|
-
const scannedSystemPrompt = sample(systemPrompt ?? "");
|
|
2182
|
+
const estimatedInputTokens = estimateTokens(fullText);
|
|
2183
|
+
const limit = scanLimit(options.config.classifier.promptTruncationChars);
|
|
2184
|
+
const scannedPrompt = sampleForScan(prompt, limit);
|
|
2185
|
+
const scannedSystemPrompt = sampleForScan(systemPrompt ?? "", limit);
|
|
2029
2186
|
const scannedFullText = `${scannedSystemPrompt} ${scannedPrompt}`;
|
|
2030
2187
|
const text = scannedPrompt.toLowerCase();
|
|
2031
2188
|
const explicitCodeSignal = /```|\b(?:typescript|javascript|python|rust|java|sql|stack trace|traceback|exception)\b|\.(?:ts|tsx|js|py|go|rs)\b/i.test(
|
|
@@ -2037,7 +2194,9 @@ ${value.slice(-(scanLimit - prefixLength))}`;
|
|
|
2037
2194
|
const nativeCodeSignal = /\b(?:programmed|written|implemented?|code)\s+(?:in|using)\s+(?:c\+\+|c|rust|go)\b/i.test(
|
|
2038
2195
|
scannedPrompt
|
|
2039
2196
|
);
|
|
2040
|
-
const
|
|
2197
|
+
const errorClassSignal = /\b[A-Z][A-Za-z]*(?:Error|Exception)\b/.test(scannedPrompt);
|
|
2198
|
+
const hasCode = explicitCodeSignal || codeConstructSignal || nativeCodeSignal || errorClassSignal;
|
|
2199
|
+
const codeArtifact = /```|\b(?:stack trace|traceback)\b|\.(?:ts|tsx|js|py|go|rs)\b/i.test(scannedPrompt) || codeConstructSignal || nativeCodeSignal || errorClassSignal;
|
|
2041
2200
|
const toolsAvailable = options.hasTools ?? false;
|
|
2042
2201
|
const needsTools = options.requiresTools ?? (toolsAvailable && inferToolRequirement(scannedPrompt, scannedSystemPrompt));
|
|
2043
2202
|
const likelyParallelToolCalls = likelyNeedsParallelToolCalls(
|
|
@@ -2109,10 +2268,10 @@ ${value.slice(-(scanLimit - prefixLength))}`;
|
|
|
2109
2268
|
const needsVision = options.hasVision ?? false;
|
|
2110
2269
|
const needsStructuredOutput = options.requiresStructuredOutput ?? false;
|
|
2111
2270
|
const latencySensitive = /\b(?:urgent|asap|fast|quick|low latency|real[- ]time)\b|尽快|马上|快速|低延迟/i.test(
|
|
2112
|
-
|
|
2271
|
+
scannedPrompt
|
|
2113
2272
|
);
|
|
2114
2273
|
const highStakes = /\b(?:production|security|payment|legal|medical|financial|audit)\b|生产|安全|支付|法律|医疗|财务|审计/i.test(
|
|
2115
|
-
|
|
2274
|
+
scannedPrompt
|
|
2116
2275
|
);
|
|
2117
2276
|
const terminalToolSignal = normalizedToolNames.some(
|
|
2118
2277
|
(name) => /^(?:terminalexec|terminalinspect|terminalsendkeys)$/.test(name)
|
|
@@ -2148,7 +2307,7 @@ ${value.slice(-(scanLimit - prefixLength))}`;
|
|
|
2148
2307
|
);
|
|
2149
2308
|
const terminalSafetySensitive = terminalToolSignal && (highStakes || terminalCredentialSignal);
|
|
2150
2309
|
const implicitTerminalCode = needsTools && terminalToolSignal && agentRisk === "standard" && !highStakes && !complexTerminalOperation && numberedSteps < 3 && prompt.length <= 1e3 && simpleTerminalArtifact;
|
|
2151
|
-
const language = /[\u3400-\u9fff]/.test(
|
|
2310
|
+
const language = /[\u3400-\u9fff]/.test(scannedPrompt) ? "zh" : "other";
|
|
2152
2311
|
const multipleChoiceSignals = (scannedPrompt.match(/(?:^|\n)\s*[A-D][.)]\s+/gim) ?? []).length;
|
|
2153
2312
|
const numericSignals = (scannedPrompt.match(/-?\d+(?:[.,]\d+)?/g) ?? []).length;
|
|
2154
2313
|
const compactMathProblem = !hasCode && prompt.length < 2500 && numericSignals >= 2 && (/[+×÷=%$€£¥]|\b(?:total|each|per|times|half|twice|percent|how many|how much|calculate)\b/i.test(
|
|
@@ -2163,7 +2322,7 @@ ${value.slice(-(scanLimit - prefixLength))}`;
|
|
|
2163
2322
|
else if (needsTools) taskType = "tool_agent";
|
|
2164
2323
|
else if (multipleChoiceSignals >= 3) taskType = "reasoning_mcq";
|
|
2165
2324
|
else if (compactMathProblem) taskType = "reasoning_math";
|
|
2166
|
-
else if (/\b(?:bug|debug|error|failure|failing|regression|crash|修复|报错|错误|调试)\b/i.test(text))
|
|
2325
|
+
else if (errorClassSignal || /\b(?:bug|debug|error|failure|failing|regression|crash|修复|报错|错误|调试)\b/i.test(text))
|
|
2167
2326
|
taskType = "debug";
|
|
2168
2327
|
else if (hasCode || /\b(?:refactor|implement|patch|edit|rewrite|重构|实现|修改)\b/i.test(text))
|
|
2169
2328
|
taskType = "code_edit";
|
|
@@ -2189,7 +2348,8 @@ ${value.slice(-(scanLimit - prefixLength))}`;
|
|
|
2189
2348
|
agentRisk,
|
|
2190
2349
|
terminalToolSignal,
|
|
2191
2350
|
terminalSafetySensitive,
|
|
2192
|
-
implicitTerminalCode
|
|
2351
|
+
implicitTerminalCode,
|
|
2352
|
+
codeArtifact
|
|
2193
2353
|
};
|
|
2194
2354
|
}
|
|
2195
2355
|
function affinity(modelId, task, language = "other", agentDomain = "other", deepWebResearch = false, agentRisk = "standard", terminalToolSignal = false, terminalSafetySensitive = false) {
|
|
@@ -2545,6 +2705,19 @@ function evidenceCandidates(task) {
|
|
|
2545
2705
|
}
|
|
2546
2706
|
return [];
|
|
2547
2707
|
}
|
|
2708
|
+
var VERIFIED_TOOL_FALLBACKS = [
|
|
2709
|
+
"anthropic/claude-fable-5.1",
|
|
2710
|
+
"anthropic/claude-opus-5.5",
|
|
2711
|
+
"anthropic/claude-sonnet-5.5",
|
|
2712
|
+
"google/gemini-3.8-flash",
|
|
2713
|
+
"openai/gpt-5.1",
|
|
2714
|
+
"openai/gpt-6-astra",
|
|
2715
|
+
"openai/gpt-6-luna",
|
|
2716
|
+
"openai/gpt-6-sol",
|
|
2717
|
+
"xai/grok-4.6",
|
|
2718
|
+
"xai/grok-4.7",
|
|
2719
|
+
"xiaomi/mimo-v2.5"
|
|
2720
|
+
];
|
|
2548
2721
|
function isEligible(modelId, features, maxOutputTokens, options) {
|
|
2549
2722
|
const model = options.modelCapabilities?.[modelId] ?? DEFAULT_MODEL_CAPABILITIES[modelId];
|
|
2550
2723
|
if (!model) return true;
|
|
@@ -2579,27 +2752,38 @@ function profileScore(modelId, options, now) {
|
|
|
2579
2752
|
}
|
|
2580
2753
|
var PortfolioStrategy = class {
|
|
2581
2754
|
name = "portfolio";
|
|
2755
|
+
// Rules set the capability tier; the strategy is stateless, so one instance serves every request.
|
|
2756
|
+
rules = new RulesStrategy();
|
|
2582
2757
|
route(prompt, systemPrompt, maxOutputTokens, options) {
|
|
2583
2758
|
const features = classifyTask(prompt, systemPrompt, options);
|
|
2584
|
-
const base =
|
|
2759
|
+
const base = this.rules.route(prompt, systemPrompt, maxOutputTokens, {
|
|
2585
2760
|
...options,
|
|
2586
2761
|
requiresTools: features.needsTools
|
|
2587
2762
|
});
|
|
2588
2763
|
const tierConfigs = base.tierConfigs;
|
|
2589
2764
|
if (!tierConfigs) return base;
|
|
2590
|
-
const targetTier = (features.taskType === "reasoning_mcq" || features.taskType === "reasoning_math") && (base.tier === "SIMPLE" || base.tier === "MEDIUM") ? "REASONING" : base.tier;
|
|
2765
|
+
const targetTier = (features.taskType === "reasoning_mcq" || features.taskType === "reasoning_math") && (base.tier === "SIMPLE" || base.tier === "MEDIUM") ? "REASONING" : (features.taskType === "debug" || features.taskType === "code_edit") && features.codeArtifact && base.tier === "SIMPLE" ? "MEDIUM" : base.tier;
|
|
2591
2766
|
const tierConfig = tierConfigs[targetTier];
|
|
2592
2767
|
const configuredCandidates = tierConfig ? getFallbackChain(targetTier, tierConfigs) : [];
|
|
2593
2768
|
const unavailable = new Set(options.unavailableModels ?? []);
|
|
2769
|
+
const verifiedToolFallbacks = features.needsTools ? VERIFIED_TOOL_FALLBACKS.filter((model2) => options.modelPricing.has(model2)) : [];
|
|
2770
|
+
if (options.routingProfile !== "premium") {
|
|
2771
|
+
const fallbackCost = (model2) => estimatedCost(model2, options, features.estimatedInputTokens, maxOutputTokens);
|
|
2772
|
+
verifiedToolFallbacks.sort((a, b) => fallbackCost(a) - fallbackCost(b));
|
|
2773
|
+
}
|
|
2594
2774
|
const chain = [
|
|
2595
|
-
.../* @__PURE__ */ new Set([
|
|
2775
|
+
.../* @__PURE__ */ new Set([
|
|
2776
|
+
...configuredCandidates,
|
|
2777
|
+
...evidenceCandidates(features.taskType),
|
|
2778
|
+
...verifiedToolFallbacks
|
|
2779
|
+
])
|
|
2596
2780
|
].filter(
|
|
2597
2781
|
(model2) => typeof model2 === "string" && model2.length > 0 && !unavailable.has(model2)
|
|
2598
2782
|
);
|
|
2599
|
-
const eligible =
|
|
2600
|
-
(model2) => isEligible(model2, features, maxOutputTokens, options)
|
|
2783
|
+
const eligible = new Set(
|
|
2784
|
+
chain.filter((model2) => isEligible(model2, features, maxOutputTokens, options))
|
|
2601
2785
|
);
|
|
2602
|
-
const eligibleCandidates = eligible.
|
|
2786
|
+
const eligibleCandidates = eligible.size > 0 ? [...eligible] : chain;
|
|
2603
2787
|
if (eligibleCandidates.length === 0) return base;
|
|
2604
2788
|
const profileName = options.routingProfile === "eco" ? "eco" : options.routingProfile === "premium" ? "premium" : "auto";
|
|
2605
2789
|
const portfolio = options.config.portfolio ?? DEFAULT_PORTFOLIO_WEIGHTS;
|
|
@@ -2634,11 +2818,11 @@ var PortfolioStrategy = class {
|
|
|
2634
2818
|
const rankedEntries = candidates.map((model2, index) => {
|
|
2635
2819
|
const cost = estimatedCost(model2, options, features.estimatedInputTokens, maxOutputTokens);
|
|
2636
2820
|
const costScore = Number.isFinite(cost) && maxCost > minCost ? 1 - (cost - minCost) / (maxCost - minCost) : 0.5;
|
|
2637
|
-
const capabilityScore =
|
|
2821
|
+
const capabilityScore = eligible.has(model2) ? 1 : 0;
|
|
2638
2822
|
const profile = profileScore(model2, options, now);
|
|
2639
2823
|
const observedQuality = profile?.quality === void 0 ? getAffinity(model2) : getAffinity(model2) * (1 - profile.freshness) + profile.quality * profile.freshness;
|
|
2640
|
-
const observedSpeed = profile ? profile.speed * profile.freshness : 0.5;
|
|
2641
|
-
const observedTailSpeed = profile ? profile.tailSpeed * profile.freshness : 0.5;
|
|
2824
|
+
const observedSpeed = profile ? profile.speed * profile.freshness + 0.5 * (1 - profile.freshness) : 0.5;
|
|
2825
|
+
const observedTailSpeed = profile ? profile.tailSpeed * profile.freshness + 0.5 * (1 - profile.freshness) : 0.5;
|
|
2642
2826
|
const observedReliability = profile ? profile.reliability * profile.freshness + (1 - profile.freshness) : 1;
|
|
2643
2827
|
const legacyScore = 1 - index / Math.max(1, candidates.length - 1);
|
|
2644
2828
|
const qualityWeight = profileWeights.quality + (features.highStakes ? portfolio.highStakesBoost.quality : 0);
|
|
@@ -3895,8 +4079,13 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
3895
4079
|
primary: "nvidia/nemotron-3.5-lightning",
|
|
3896
4080
|
// FREE — NVIDIA free tier flagship, 1M ctx
|
|
3897
4081
|
fallback: [
|
|
3898
|
-
|
|
3899
|
-
//
|
|
4082
|
+
// Was nvidia/nemotron-3-nano-30b until blockrun delisted it on 2026-09-08
|
|
4083
|
+
// (NVIDIA deprovisioned it for the account — a structured per-account
|
|
4084
|
+
// 404). nano-omni is blockrun's own redirect target for that id, so this
|
|
4085
|
+
// follows the same rule as the head: the router and the gateway name the
|
|
4086
|
+
// same model.
|
|
4087
|
+
"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
|
|
4088
|
+
// FREE — nano-30b's successor, 256K ctx
|
|
3900
4089
|
// The free head keeps rotting with NVIDIA's hosting (deepseek-v4-flash
|
|
3901
4090
|
// 410 2026-08-12, seed-oss-36b 410 2026-08-03, gpt-oss-120b/20b 400
|
|
3902
4091
|
// 2026-08-21, and on 2026-08-30 FOUR of the five visible free models at
|
|
@@ -4725,7 +4914,7 @@ function getCostSummary() {
|
|
|
4725
4914
|
}
|
|
4726
4915
|
|
|
4727
4916
|
// src/version.ts
|
|
4728
|
-
var SDK_VERSION = "3.24.
|
|
4917
|
+
var SDK_VERSION = "3.24.2";
|
|
4729
4918
|
var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
|
|
4730
4919
|
|
|
4731
4920
|
// src/client.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/llm",
|
|
3
|
-
"version": "3.24.
|
|
3
|
+
"version": "3.24.2",
|
|
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/
|
|
61
|
+
"@blockrun/router-core": "https://codeload.github.com/BlockRunAI/router-core/tar.gz/65a92c85ea1f187a64e7611302ef6add236b0cba",
|
|
62
62
|
"@eslint/js": "^10.0.1",
|
|
63
63
|
"@solana/kit": "^5.5.1",
|
|
64
64
|
"@types/node": "^26.4.1",
|