@blockrun/clawrouter 0.12.240 → 0.12.242

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.js CHANGED
@@ -93,7 +93,6 @@ var init_top_models = __esm({
93
93
  "deepseek/deepseek-chat",
94
94
  "deepseek/deepseek-reasoner",
95
95
  "free/deepseek-v4-flash",
96
- "free/seed-oss-36b",
97
96
  "free/nemotron-3-nano-omni-30b-a3b-reasoning",
98
97
  "free/mistral-nemotron",
99
98
  "free/step-3.7-flash",
@@ -403,9 +402,10 @@ var init_models = __esm({
403
402
  "free/deepseek-v4-pro": "free/deepseek-v4-flash",
404
403
  "nvidia/deepseek-v4-flash": "free/deepseek-v4-flash",
405
404
  "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning": "free/nemotron-3-nano-omni-30b-a3b-reasoning",
406
- // qwen3-coder-480b retired (NVIDIA EOL 2026-06-14) → redirects to seed-oss-36b
407
- // server-side; keep the explicit id mappings for pinned callers (BlockRun still
408
- // resolves them), but point the generic coding shorthands at the live successor.
405
+ // qwen3-coder-480b retired (NVIDIA EOL 2026-06-14). Its server-side redirect used
406
+ // to be seed-oss-36b, but that EOL'd too on 2026-08-03, so blockrun re-pointed it
407
+ // at gpt-oss-120b. Keep the explicit id mappings for pinned callers (BlockRun still
408
+ // resolves them); the generic coding shorthands follow the gateway to gpt-oss-120b.
409
409
  "nvidia/qwen3-coder-480b": "free/qwen3-coder-480b",
410
410
  "qwen/qwen3-coder-480b-a35b-instruct": "free/qwen3-coder-480b",
411
411
  // glm-4.7 pin keeps the real id — blockrun redirects it server-side (→ gpt-oss-120b).
@@ -451,13 +451,14 @@ var init_models = __esm({
451
451
  "v4-flash": "free/deepseek-v4-flash",
452
452
  "mistral-free": "free/mistral-nemotron",
453
453
  // mistral-large-3-675b EOL'd 2026-07-28 → last live free Mistral (blockrun demoted its /free-mistral primary the same way)
454
- "glm-free": "free/seed-oss-36b",
455
- // qwen3-coder retired → live coder successor
454
+ "glm-free": "free/gpt-oss-120b",
455
+ // seed-oss-36b (the prior target) EOL'd 2026-08-03
456
456
  "llama-free": "free/gpt-oss-120b",
457
457
  // no live free Llama left (maverick died 2026-07)
458
- "qwen-coder": "free/seed-oss-36b",
459
- // qwen3-coder-480b retired → seed-oss-36b
460
- "qwen-coder-free": "free/seed-oss-36b",
458
+ // qwen3-coder-480b retired 2026-06-14 → seed-oss-36b, which then EOL'd 2026-08-03.
459
+ // Follow the gateway's own retarget rather than chaining to a second dead model.
460
+ "qwen-coder": "free/gpt-oss-120b",
461
+ "qwen-coder-free": "free/gpt-oss-120b",
461
462
  "qwen-thinking": "free/gpt-oss-120b",
462
463
  // qwen3-next died 2026-07-17; no live free Qwen left
463
464
  "qwen3-next": "free/qwen3-next-80b-a3b-instruct",
@@ -466,9 +467,11 @@ var init_models = __esm({
466
467
  "mistral-small": "free/mistral-nemotron",
467
468
  // closest live Mistral-family free model
468
469
  // New live free models (2026-06-14 BlockRun free-tier refresh)
470
+ // seed-oss pins name the model itself — kept routable, the gateway redirects them.
469
471
  "seed-oss": "free/seed-oss-36b",
470
472
  "seed-oss-36b": "free/seed-oss-36b",
471
- "coder-free": "free/seed-oss-36b",
473
+ "coder-free": "free/gpt-oss-120b",
474
+ // generic "a free coder" → follows the gateway retarget
472
475
  "mistral-nemotron": "free/mistral-nemotron",
473
476
  "step-flash": "free/step-3.7-flash",
474
477
  "step-3.7-flash": "free/step-3.7-flash",
@@ -489,8 +492,9 @@ var init_models = __esm({
489
492
  "nemotron-super": "free/gpt-oss-120b",
490
493
  "nemotron-49b": "free/gpt-oss-120b",
491
494
  "nemotron-120b": "free/gpt-oss-120b",
492
- devstral: "free/seed-oss-36b",
493
- "devstral-2": "free/seed-oss-36b",
495
+ devstral: "free/gpt-oss-120b",
496
+ // seed-oss-36b EOL'd 2026-08-03; matches blockrun's own retarget
497
+ "devstral-2": "free/gpt-oss-120b",
494
498
  maverick: "free/llama-4-maverick",
495
499
  // explicit-ish pin — gateway redirects
496
500
  free: "free/gpt-oss-120b",
@@ -1613,8 +1617,13 @@ var init_models = __esm({
1613
1617
  reasoning: true
1614
1618
  },
1615
1619
  {
1616
- // ByteDance Seed-OSS 36B: strong open-source coder, 131K context. Successor
1617
- // to the retired qwen3-coder-480b (BlockRun redirects qwen3-coder → seed-oss).
1620
+ // ByteDance Seed-OSS 36B: was the free coder, and the redirect target for the
1621
+ // retired qwen3-coder-480b. EOL'd 2026-08-03 — blockrun's probe got HTTP 410
1622
+ // Gone from NVIDIA on both passes and the prod health gate fired
1623
+ // [ALERT][free-model-dead] kind=gone the same day; upstream now hides it and
1624
+ // re-pointed its own dependents (qwen3-coder-480b, devstral-2) at gpt-oss-120b.
1625
+ // Entry kept so explicit pins stay routable; off the picker, the FREE_MODELS
1626
+ // cascade, and the router fallback chains.
1618
1627
  id: "free/seed-oss-36b",
1619
1628
  name: "[Free] Seed-OSS 36B",
1620
1629
  version: "oss-36b",
@@ -34768,7 +34777,7 @@ var init_esm5 = __esm({
34768
34777
  }
34769
34778
  });
34770
34779
 
34771
- // src/router/rules.ts
34780
+ // node_modules/@blockrun/router-core/dist/index.js
34772
34781
  function scoreTokenCount(estimatedTokens, thresholds) {
34773
34782
  if (estimatedTokens < thresholds.simple) {
34774
34783
  return { name: "tokenCount", score: -1, signal: `short (${estimatedTokens} tokens)` };
@@ -35008,13 +35017,6 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
35008
35017
  function calibrateConfidence(distance, steepness) {
35009
35018
  return 1 / (1 + Math.exp(-steepness * distance));
35010
35019
  }
35011
- var init_rules = __esm({
35012
- "src/router/rules.ts"() {
35013
- "use strict";
35014
- }
35015
- });
35016
-
35017
- // src/router/selector.ts
35018
35020
  function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPricing, estimatedInputTokens, maxOutputTokens, routingProfile, agenticScore) {
35019
35021
  const tierConfig = tierConfigs[tier];
35020
35022
  const model = tierConfig.primary;
@@ -35103,19 +35105,14 @@ function getFallbackChainFiltered(tier, tierConfigs, estimatedTotalTokens, getCo
35103
35105
  }
35104
35106
  return filtered;
35105
35107
  }
35106
- var BASELINE_MODEL_ID, BASELINE_INPUT_PRICE, BASELINE_OUTPUT_PRICE, SERVER_MARGIN_PERCENT, MIN_PAYMENT_USD;
35107
- var init_selector = __esm({
35108
- "src/router/selector.ts"() {
35109
- "use strict";
35110
- BASELINE_MODEL_ID = "anthropic/claude-opus-4.7";
35111
- BASELINE_INPUT_PRICE = 5;
35112
- BASELINE_OUTPUT_PRICE = 25;
35113
- SERVER_MARGIN_PERCENT = 5;
35114
- MIN_PAYMENT_USD = 1e-3;
35115
- }
35116
- });
35117
-
35118
- // src/router/strategy.ts
35108
+ function filterCandidatesByCapacity(models, estimatedInputTokens, requestedOutputTokens, getCapabilities2) {
35109
+ const filtered = models.filter((modelId) => {
35110
+ const capabilities = getCapabilities2(modelId);
35111
+ if (!capabilities) return true;
35112
+ return capabilities.contextWindow >= (estimatedInputTokens + requestedOutputTokens) * 1.1 && capabilities.maxOutput >= requestedOutputTokens;
35113
+ });
35114
+ return filtered;
35115
+ }
35119
35116
  function applyPromotions(tierConfigs, promotions, profile, now2 = /* @__PURE__ */ new Date()) {
35120
35117
  if (!promotions || promotions.length === 0) return tierConfigs;
35121
35118
  let result = tierConfigs;
@@ -35145,19 +35142,666 @@ function getStrategy(name) {
35145
35142
  }
35146
35143
  return strategy;
35147
35144
  }
35148
- var RulesStrategy, registry;
35149
- var init_strategy = __esm({
35150
- "src/router/strategy.ts"() {
35145
+ function registerStrategy(strategy) {
35146
+ registry.set(strategy.name, strategy);
35147
+ }
35148
+ function inferToolRequirement(prompt, _systemPrompt, toolChoice) {
35149
+ if (toolChoice === "none") return false;
35150
+ if (toolChoice === "required") return true;
35151
+ if (typeof toolChoice === "object" && toolChoice !== null && toolChoice.type === "function") {
35152
+ return true;
35153
+ }
35154
+ const text = prompt;
35155
+ const explicitTool = /\b(?:use|call|invoke)\s+(?:the\s+)?[\w.-]+\s+(?:tool|function|api)\b|\btool[_ -]?call\b|使用.{0,20}(?:工具|函数|接口)|调用.{0,20}(?:工具|函数|接口)/i;
35156
+ const codeEnvironment = /\b(?:run|execute)\s+(?:the\s+)?(?:tests?|command|script|build|linter)|\b(?:edit|modify|patch|create|write|save|delete|rename|move|inspect|read)\b.{0,60}\b(?:file|repository|repo|codebase|directory|folder)\b|\b(?:terminal|shell|bash|zsh|pytest|npm test|pnpm test|git\s+(?:status|diff|commit)|docker)\b|(?:运行|执行).{0,20}(?:测试|命令|脚本|构建)|(?:修改|编辑|修复|创建|读取|检查|保存).{0,30}(?:文件|仓库|代码库|目录)/i;
35157
+ const webAction = /\b(?:browse|search|look up|fetch|open)\b.{0,80}\b(?:web|website|url|online|documentation|docs|news|weather|price)\b|(?:浏览|搜索|查询|打开).{0,30}(?:网页|网站|链接|文档|新闻|天气|价格)/i;
35158
+ const statefulAction = /\b(?:refund|cancel|book|reserve|purchase|buy|return|exchange|transfer|update|change)\b.{0,80}\b(?:order|booking|reservation|account|address|payment|subscription|ticket|flight|item)\b|(?:退款|取消|预订|购买|退货|换货|转账|更新|修改).{0,30}(?:订单|预订|账户|地址|付款|订阅|票|航班|商品)/i;
35159
+ return explicitTool.test(text) || codeEnvironment.test(text) || webAction.test(text) || statefulAction.test(text);
35160
+ }
35161
+ function likelyNeedsParallelToolCalls(prompt, needsTools, toolCount, toolNames) {
35162
+ if (!needsTools || toolCount === void 0 || toolCount < 1) return false;
35163
+ const text = prompt.trim();
35164
+ const explicitRepeat = /\b(?:in parallel|simultaneously|concurrently|for each|each of|every one|both|(?:two|three|multiple|several)\s+(?:cities|locations|items|tasks|orders|users|files))\b|并行|同时|分别|每个|各自|(?:两个|三个|多个)(?:城市|地点|项目|任务|订单|用户|文件)|cada uno|para cada|simult[aá]neamente/i.test(
35165
+ text
35166
+ );
35167
+ if (explicitRepeat) return true;
35168
+ const sentenceClauses = text.split(/[.!?。!?]+/).map((part) => part.trim()).filter((part) => part.length >= 8);
35169
+ if (/\b(?:also|additionally|furthermore)\b|另外|此外|그리고/i.test(text) && sentenceClauses.length >= 2 || /\band\s+(?:also|for the)\b/i.test(text))
35170
+ return true;
35171
+ const pairedQuantity = /\b\d+(?:\.\d+)?\s+(?:and|or)\s+\d+(?:\.\d+)?\s*(?:gb|mb|tb|kg|g|ml|oz|cups?|cores?|cpus?)\b/i.test(
35172
+ text
35173
+ );
35174
+ if (pairedQuantity) return true;
35175
+ const operationTokens = /* @__PURE__ */ new Set([
35176
+ "add",
35177
+ "delete",
35178
+ "remove",
35179
+ "cancel",
35180
+ "return",
35181
+ "exchange",
35182
+ "modify",
35183
+ "book",
35184
+ "transfer",
35185
+ "send",
35186
+ "upload",
35187
+ "download",
35188
+ "create",
35189
+ "close"
35190
+ ]);
35191
+ const lowered = text.toLowerCase();
35192
+ const matchedOperationTokens = new Set(
35193
+ (toolNames ?? []).flatMap((name) => name.toLowerCase().split(/[^a-z0-9\u3400-\u9fff]+/)).filter((token) => operationTokens.has(token) && lowered.includes(token))
35194
+ );
35195
+ if (matchedOperationTokens.size >= 2) return true;
35196
+ const nonEmptyLines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
35197
+ const quantityMentions = text.match(
35198
+ /\b(?:\d+(?:\.\d+)?|one|two|three|four|five|six|seven|eight|nine|ten)\s*(?:oz|ounce|ounces|g|gram|grams|kg|ml|cups?|pieces?|tablespoons?)\b/gi
35199
+ ) ?? [];
35200
+ if (nonEmptyLines.length >= 2 && quantityMentions.length >= 2) return true;
35201
+ const repeatedLookup = /\b(?:weather|climate|clima|tiempo|temperature|snow|news|report)\b|天气|气象|温度|降雪|新闻|报告/i.test(
35202
+ text
35203
+ );
35204
+ const multiLocationConnector = /\b(?:and also|both|y|e)\b|还有|以及|和|、/i.test(text);
35205
+ const commaSeparatedLocations = (text.match(/[,,]/g) ?? []).length >= 2;
35206
+ if (repeatedLookup && (multiLocationConnector || commaSeparatedLocations)) return true;
35207
+ const distinctOrderParts = /\b(?:food|meal)\b[\s\S]*\bdrink\b|\bdrink\b[\s\S]*\b(?:food|meal)\b/i.test(text);
35208
+ const koreanParallelClauses = (text.match(/,/g) ?? []).length >= 3 && /하고|그리고/.test(text);
35209
+ return distinctOrderParts || koreanParallelClauses;
35210
+ }
35211
+ function classifyTask(prompt, systemPrompt, options) {
35212
+ const fullText = `${systemPrompt ?? ""} ${prompt}`;
35213
+ const estimatedInputTokens = Math.ceil(fullText.length / 4);
35214
+ const scanLimit = Math.max(1, Math.min(8e3, options.config.classifier.promptTruncationChars));
35215
+ const sample = (value) => {
35216
+ if (value.length <= scanLimit) return value;
35217
+ const prefixLength = Math.ceil(scanLimit / 2);
35218
+ return `${value.slice(0, prefixLength)}
35219
+ ${value.slice(-(scanLimit - prefixLength))}`;
35220
+ };
35221
+ const scannedPrompt = sample(prompt);
35222
+ const scannedSystemPrompt = sample(systemPrompt ?? "");
35223
+ const scannedFullText = `${scannedSystemPrompt} ${scannedPrompt}`;
35224
+ const text = scannedPrompt.toLowerCase();
35225
+ const explicitCodeSignal = /```|\b(?:typescript|javascript|python|rust|java|sql|stack trace|traceback|exception)\b|\.(?:ts|tsx|js|py|go|rs)\b/i.test(
35226
+ scannedPrompt
35227
+ );
35228
+ const codeConstructSignal = /\b(?:implement|refactor|debug|write|edit|modify|create|define|review|fix)\b[\s\S]{0,48}\b(?:api|function|class|method)\b|\b(?:api|function|class|method)\b[\s\S]{0,48}\b(?:code|implementation|typescript|javascript|python|rust|java)\b/i.test(
35229
+ scannedPrompt
35230
+ );
35231
+ const nativeCodeSignal = /\b(?:programmed|written|implemented?|code)\s+(?:in|using)\s+(?:c\+\+|c|rust|go)\b/i.test(
35232
+ scannedPrompt
35233
+ );
35234
+ const hasCode = explicitCodeSignal || codeConstructSignal || nativeCodeSignal;
35235
+ const toolsAvailable = options.hasTools ?? false;
35236
+ const needsTools = options.requiresTools ?? (toolsAvailable && inferToolRequirement(scannedPrompt, scannedSystemPrompt));
35237
+ const likelyParallelToolCalls = likelyNeedsParallelToolCalls(
35238
+ scannedPrompt,
35239
+ needsTools,
35240
+ options.toolCount,
35241
+ options.toolNames
35242
+ );
35243
+ const normalizedToolNames = (options.toolNames ?? []).map((name) => name.toLowerCase());
35244
+ const airlineToolSignal = normalizedToolNames.some(
35245
+ (name) => /(?:flight|reservation|airport|baggage|passenger)/.test(name)
35246
+ );
35247
+ const retailToolSignal = normalizedToolNames.some(
35248
+ (name) => /(?:order|product|item|return|exchange|address)/.test(name)
35249
+ );
35250
+ const webResearchToolSignal = normalizedToolNames.some(
35251
+ (name) => /^(?:web_?search|web_?fetch)$/.test(name)
35252
+ );
35253
+ const agentDomain = airlineToolSignal && !retailToolSignal ? "airline" : retailToolSignal && !airlineToolSignal ? "retail" : webResearchToolSignal ? "web_research" : "other";
35254
+ const clueConnectors = scannedFullText.match(
35255
+ /\b(?:after|before|while|where|whose|which|in \d{4}|as of|over \d+|another|also|furthermore)\b|(?:之后|之前|其中|截至|超过|另一个|此外)/gi
35256
+ ) ?? [];
35257
+ const entityResolutionSignal = /\b(?:identify|who (?:is|was)|what (?:is|was) the name|which (?:person|player|company|country|city)|find the (?:person|player|name|entity))\b|(?:找出|识别|是谁|哪位|名称是什么)/i.test(
35258
+ scannedFullText
35259
+ );
35260
+ const exactAnswerSignal = /\b(?:exact answer|single best-supported answer|following clues|multiple public sources)\b|(?:精确答案|根据.*线索|多个公开来源)/i.test(
35261
+ scannedFullText
35262
+ );
35263
+ const deepWebResearch = agentDomain === "web_research" && (exactAnswerSignal || entityResolutionSignal && (clueConnectors.length >= 3 || prompt.length >= 320));
35264
+ const globalOptimizationSignal = /\b(?:cheapest|lowest[- ]price|least expensive|most expensive|highest(?:[- ]priced)?|largest|smallest|maximum|minimum|best available|closest|not (?:cost|exceed))\b|最便宜|最低价|最贵|最高价|最大|最小/i.test(
35265
+ scannedPrompt
35266
+ );
35267
+ const globalScopeSignal = /\b(?:everything|all (?:(?:my|your|their|the) )?(?:future |upcoming )?(?:items|orders|passengers|flights|reservations|bookings)|every (?:item|order|passenger|flight|reservation|booking))\b|全部|所有|每个/i.test(
35268
+ scannedPrompt
35269
+ );
35270
+ const globalChoiceSignal = globalOptimizationSignal || globalScopeSignal;
35271
+ const crossRecordSignal = /\b(?:another|other|different|previous)\s+(?:order|reservation|booking|account|address)\b|另一(?:个)?(?:订单|预订|账户|地址)|其他(?:订单|预订|账户|地址)/i.test(
35272
+ scannedPrompt
35273
+ );
35274
+ const reservationIds = scannedPrompt.match(/\b[A-Z0-9]{6}\b/g) ?? [];
35275
+ const crossReservationBatchSignal = agentDomain === "airline" && (/\b(?:two|three|multiple|several)(?:\s+of\s+(?:my|our|the))?\s+(?:upcoming\s+)?(?:reservations?|bookings?)\b|\b(?:a\s+)?(?:second|third)\s+(?:reservation|booking)\b/i.test(
35276
+ scannedPrompt
35277
+ ) || new Set(reservationIds).size >= 2);
35278
+ const conditionalGlobalWorkflowSignal = agentDomain === "airline" && globalScopeSignal && /\b(?:if|that (?:contain|have)|longer than|shorter than|under|over|at (?:most|least)|wherever possible)\b|如果|超过|少于|不超过|尽可能/i.test(
35279
+ scannedPrompt
35280
+ ) && /\b(?:cancel|change|upgrade|move|book)\b[\s\S]*\b(?:cancel|change|upgrade|move|book)\b|取消[\s\S]*(?:升级|更改)|升级[\s\S]*(?:取消|更改)/i.test(
35281
+ scannedPrompt
35282
+ );
35283
+ const policyExceptionSignal = agentDomain === "retail" && /\b(?:return|refund|send back|get (?:my |the )?money back)\b|退货|退款|退回/i.test(
35284
+ scannedPrompt
35285
+ ) && /\b(?:amex|american express|visa|mastercard|credit card|debit card|different card|another card|other card)\b|信用卡|借记卡|其他卡|另一张卡/i.test(
35286
+ scannedPrompt
35287
+ );
35288
+ const singleSelectedPolicyException = policyExceptionSignal && /\b(?:return|refund|send back)\b[^.!?。!?]{0,96}\b(?:the )?(?:pricier|cheaper|more expensive|less expensive|costlier|one)\b/i.test(
35289
+ scannedPrompt
35290
+ );
35291
+ const negotiatedWorkflowSignal = agentDomain === "retail" && /\b(?:return|exchange)\b|退货|退回|换货|交换/i.test(scannedPrompt);
35292
+ const numberedSteps = (scannedPrompt.match(/(?:^|\s)\d+(?:\.\d+)*[.)]\s+/g) ?? []).length;
35293
+ const complexMultiToolPlan = likelyParallelToolCalls && ((options.toolCount ?? 0) >= 6 || numberedSteps >= 3 || prompt.length > 1200);
35294
+ let agentRisk = needsTools && singleSelectedPolicyException ? "policy_exception_simple" : needsTools && policyExceptionSignal ? "policy_exception" : (
35295
+ // Airline prompts that require a global optimum (for example the
35296
+ // cheapest itinerary across several candidates) are materially harder
35297
+ // than applying one change to every passenger in a known reservation.
35298
+ // Full-session evidence supports Sonnet for the former, while upgrading
35299
+ // the latter merely because it says "all passengers" caused a large cost
35300
+ // increase without a quality gain.
35301
+ needsTools && agentDomain === "airline" && (globalOptimizationSignal || conditionalGlobalWorkflowSignal) ? "complex_high" : needsTools && (likelyParallelToolCalls || globalChoiceSignal || crossRecordSignal || crossReservationBatchSignal || negotiatedWorkflowSignal) ? "high" : "standard"
35302
+ );
35303
+ const needsVision = options.hasVision ?? false;
35304
+ const needsStructuredOutput = options.requiresStructuredOutput ?? false;
35305
+ const latencySensitive = /\b(?:urgent|asap|fast|quick|low latency|real[- ]time)\b|尽快|马上|快速|低延迟/i.test(
35306
+ scannedFullText
35307
+ );
35308
+ const highStakes = /\b(?:production|security|payment|legal|medical|financial|audit)\b|生产|安全|支付|法律|医疗|财务|审计/i.test(
35309
+ scannedFullText
35310
+ );
35311
+ const terminalToolSignal = normalizedToolNames.some(
35312
+ (name) => /^(?:terminalexec|terminalinspect|terminalsendkeys)$/.test(name)
35313
+ );
35314
+ const simpleTerminalArtifact = /\b(?:create|write|convert|generate|build|implement|run|fix|repair|debug|make)\b[\s\S]{0,120}\b(?:file|script|csv|parquet|json|txt|server|endpoint)\b/i.test(
35315
+ scannedPrompt
35316
+ );
35317
+ const terminalComplexRepair = terminalToolSignal && /\b(?:multiple|several)\s+(?:scripts?|files?|components?)\b|\b(?:pipeline|dependencies)\b[\s\S]{0,100}\b(?:fail|issue|fix|repair|run|execute)\b|\b(?:identify|find|fix|repair)\s+(?:and\s+)?(?:fix\s+)?all\s+(?:the\s+)?issues\b/i.test(
35318
+ scannedPrompt
35319
+ );
35320
+ const mentionedTerminalRuntimes = new Set(
35321
+ (scannedPrompt.match(/\b(?:gcc|clang|rustc|javac|go\s+build|node|python)\b/gi) ?? []).map(
35322
+ (name) => name.toLowerCase().replace(/\s+/g, " ")
35323
+ )
35324
+ );
35325
+ const terminalCrossRuntimeArtifact = terminalToolSignal && (/\bpolyglot\b/i.test(scannedPrompt) || /\b(?:both|each)\b[\s\S]{0,120}\b(?:compilers?|runtimes?|toolchains?)\b/i.test(
35326
+ scannedPrompt
35327
+ ) || mentionedTerminalRuntimes.size >= 2 && /\b(?:compile|build|run|execute)\b/i.test(scannedPrompt));
35328
+ const terminalFrameworkToNativeArtifact = terminalToolSignal && /\b(?:pytorch|tensorflow|jax|onnx|state[_ -]?dict|checkpoint|safetensors?)\b|\.(?:pth|pt|onnx)\b/i.test(
35329
+ scannedPrompt
35330
+ ) && /\b(?:pure|native|programmed|written|implemented?)\s+(?:in|using)\s+(?:c\+\+|c|rust|go)\b|\b(?:c\+\+|c|rust|go)\s+(?:program|binary|executable|cli|tool|implementation)\b/i.test(
35331
+ scannedPrompt
35332
+ ) && /\b(?:inference|model|weights?|tensor|export|convert|load)\b/i.test(scannedPrompt);
35333
+ if (needsTools && (terminalComplexRepair || terminalCrossRuntimeArtifact || terminalFrameworkToNativeArtifact) && (agentRisk === "standard" || agentRisk === "high"))
35334
+ agentRisk = "complex_high";
35335
+ const complexTerminalOperation = /\b(?:git|ssh|nginx|https|certificate|authentication|credential|deploy|production|encrypt|gpg|shred|securely delete|decommission|benchmark|evaluate|embedding|chess|image|search the web|schema|statistical|statistics|aggregate|join|multiple inputs?)\b/i.test(
35336
+ scannedPrompt
35337
+ );
35338
+ const terminalCredentialSignal = /\b(?:ssh|nginx|certificate|authentication|credentials?|passwords?|api keys?|deploy|production|encrypt|gpg|shred|securely delete|decommission)\b/i.test(
35339
+ scannedPrompt
35340
+ ) || /\b(?:access|auth|authentication|bearer|secret|api)\s+tokens?\b|\btokens?\s+(?:secret|credential|authentication)\b/i.test(
35341
+ scannedPrompt
35342
+ );
35343
+ const terminalSafetySensitive = terminalToolSignal && (highStakes || terminalCredentialSignal);
35344
+ const implicitTerminalCode = needsTools && terminalToolSignal && agentRisk === "standard" && !highStakes && !complexTerminalOperation && numberedSteps < 3 && prompt.length <= 1e3 && simpleTerminalArtifact;
35345
+ const language = /[\u3400-\u9fff]/.test(scannedFullText) ? "zh" : "other";
35346
+ const multipleChoiceSignals = (scannedPrompt.match(/(?:^|\n)\s*[A-D][.)]\s+/gim) ?? []).length;
35347
+ const numericSignals = (scannedPrompt.match(/-?\d+(?:[.,]\d+)?/g) ?? []).length;
35348
+ const compactMathProblem = !hasCode && prompt.length < 2500 && numericSignals >= 2 && (/[+×÷=%$€£¥]|\b(?:total|each|per|times|half|twice|percent|how many|how much|calculate)\b/i.test(
35349
+ scannedPrompt
35350
+ ) || /[??]\s*$/.test(scannedPrompt.trim()) || numericSignals >= 3);
35351
+ let taskType = "chat";
35352
+ if (needsVision) taskType = "vision";
35353
+ else if (estimatedInputTokens > 8e4) taskType = "long_context";
35354
+ else if (needsTools && (hasCode || implicitTerminalCode)) taskType = "code_agent";
35355
+ else if (needsTools && likelyParallelToolCalls && !complexMultiToolPlan)
35356
+ taskType = "tool_agent_parallel";
35357
+ else if (needsTools) taskType = "tool_agent";
35358
+ else if (multipleChoiceSignals >= 3) taskType = "reasoning_mcq";
35359
+ else if (compactMathProblem) taskType = "reasoning_math";
35360
+ else if (/\b(?:bug|debug|error|failure|failing|regression|crash|修复|报错|错误|调试)\b/i.test(text))
35361
+ taskType = "debug";
35362
+ else if (hasCode || /\b(?:refactor|implement|patch|edit|rewrite|重构|实现|修改)\b/i.test(text))
35363
+ taskType = "code_edit";
35364
+ else if (needsStructuredOutput || /\b(?:extract|json|schema|csv|字段|提取)\b/i.test(text))
35365
+ taskType = "extraction";
35366
+ else if (/\b(?:prove|derive|theorem|formal|mathematical|reasoning|证明|推导|定理|数学)\b/i.test(text))
35367
+ taskType = "reasoning";
35368
+ return {
35369
+ taskType,
35370
+ estimatedInputTokens,
35371
+ hasCode,
35372
+ needsTools,
35373
+ toolsAvailable,
35374
+ needsVision,
35375
+ needsStructuredOutput,
35376
+ latencySensitive,
35377
+ highStakes,
35378
+ language,
35379
+ likelyParallelToolCalls,
35380
+ complexMultiToolPlan,
35381
+ agentDomain,
35382
+ deepWebResearch,
35383
+ agentRisk,
35384
+ terminalToolSignal,
35385
+ terminalSafetySensitive,
35386
+ implicitTerminalCode
35387
+ };
35388
+ }
35389
+ function affinity(modelId, task, language = "other", agentDomain = "other", deepWebResearch = false, agentRisk = "standard", terminalToolSignal = false, terminalSafetySensitive = false) {
35390
+ const id2 = modelId.toLowerCase();
35391
+ const modelName = id2.slice(id2.indexOf("/") + 1);
35392
+ const match = (values, score) => values.some((value) => modelName === value) ? score : 0;
35393
+ const base3 = 0.68;
35394
+ switch (task) {
35395
+ case "code_agent":
35396
+ if (terminalToolSignal && agentRisk === "complex_high") {
35397
+ return Math.max(
35398
+ base3,
35399
+ match(["claude-sonnet-5"], 1),
35400
+ match(["gpt-5.3-codex"], 0.87),
35401
+ match(["gpt-5-mini"], 0.78),
35402
+ match(["gemini-3.5-flash"], 0.76)
35403
+ );
35404
+ }
35405
+ return Math.max(
35406
+ base3,
35407
+ match(["gpt-5.3-codex"], 1),
35408
+ match(["claude-sonnet-5"], 0.98),
35409
+ match(["gpt-5-mini"], 0.96),
35410
+ match(["gemini-3.5-flash"], 0.92),
35411
+ match(["kimi-k3"], 0.9),
35412
+ match(["deepseek-v4-pro", "glm-5.2"], 0.88)
35413
+ );
35414
+ case "tool_agent":
35415
+ if (terminalToolSignal && agentRisk === "complex_high") {
35416
+ return Math.max(
35417
+ base3,
35418
+ match(["claude-sonnet-5"], 1),
35419
+ match(["gpt-5.3-codex"], 0.87),
35420
+ match(["gpt-5-mini"], 0.78),
35421
+ match(["gemini-3.5-flash"], 0.76)
35422
+ );
35423
+ }
35424
+ if (terminalToolSignal && !terminalSafetySensitive) {
35425
+ return Math.max(
35426
+ base3,
35427
+ match(["gpt-5-mini"], 1),
35428
+ match(["gpt-5.3-codex"], 0.98),
35429
+ match(["claude-sonnet-5"], 0.9),
35430
+ match(["gemini-3.5-flash"], 0.89)
35431
+ );
35432
+ }
35433
+ if (terminalToolSignal && terminalSafetySensitive) {
35434
+ return Math.max(
35435
+ base3,
35436
+ match(["claude-sonnet-5"], 1),
35437
+ match(["claude-opus-4.8"], 0.9),
35438
+ match(["gpt-5.3-codex"], 0.84)
35439
+ );
35440
+ }
35441
+ if (agentDomain === "web_research") {
35442
+ return deepWebResearch ? Math.max(
35443
+ base3,
35444
+ match(["claude-sonnet-5"], 1),
35445
+ match(["gpt-5-mini"], 0.88),
35446
+ match(["gemini-3.5-flash"], 0.84),
35447
+ match(["claude-opus-5"], 0.8),
35448
+ match(["claude-opus-4.8"], 0.78)
35449
+ ) : Math.max(
35450
+ base3,
35451
+ match(["claude-sonnet-5"], 1),
35452
+ match(["gpt-5-mini"], 0.88),
35453
+ match(["gemini-3.5-flash"], 0.86),
35454
+ match(["claude-opus-5"], 0.84),
35455
+ match(["claude-opus-4.8"], 0.82)
35456
+ );
35457
+ }
35458
+ if (agentDomain === "retail") {
35459
+ if (agentRisk === "standard") {
35460
+ return Math.max(
35461
+ base3,
35462
+ match(["gpt-5-mini"], 1),
35463
+ match(["claude-sonnet-5"], 0.88),
35464
+ match(["gemini-3.5-flash"], 0.82),
35465
+ match(["gpt-5.3-codex"], 0.81),
35466
+ match(["kimi-k3"], 0.78),
35467
+ match(["deepseek-v4-pro"], 0.76)
35468
+ );
35469
+ }
35470
+ if (agentRisk === "policy_exception") {
35471
+ return Math.max(
35472
+ base3,
35473
+ match(["gpt-4.1"], 1),
35474
+ match(["claude-sonnet-5"], 0.9),
35475
+ match(["deepseek-v4-pro"], 0.82),
35476
+ match(["gpt-5-mini"], 0.8),
35477
+ match(["gpt-4o-mini"], 0.76)
35478
+ );
35479
+ }
35480
+ if (agentRisk === "policy_exception_simple") {
35481
+ return Math.max(
35482
+ base3,
35483
+ match(["gpt-5-mini"], 1),
35484
+ match(["gpt-4.1"], 0.86),
35485
+ match(["deepseek-v4-pro"], 0.82),
35486
+ match(["gpt-4o-mini"], 0.8)
35487
+ );
35488
+ }
35489
+ return Math.max(
35490
+ base3,
35491
+ match(["deepseek-v4-pro"], 1),
35492
+ match(["claude-sonnet-5"], 0.88),
35493
+ match(["gemini-3.5-flash"], 0.82),
35494
+ match(["gpt-5.3-codex"], 0.81),
35495
+ match(["kimi-k3"], 0.78),
35496
+ match(["gpt-5-mini"], 0.76)
35497
+ );
35498
+ }
35499
+ if (agentDomain === "airline") {
35500
+ if (agentRisk === "complex_high") {
35501
+ return Math.max(
35502
+ base3,
35503
+ match(["claude-sonnet-5"], 1),
35504
+ match(["gpt-5-mini"], 0.78),
35505
+ match(["gemini-3.5-flash"], 0.76),
35506
+ match(["deepseek-v4-pro"], 0.74)
35507
+ );
35508
+ }
35509
+ return Math.max(
35510
+ base3,
35511
+ match(["gpt-5-mini"], 1),
35512
+ match(["claude-sonnet-5"], 0.9),
35513
+ match(["gemini-3.5-flash"], 0.8),
35514
+ match(["deepseek-v4-pro"], 0.76)
35515
+ );
35516
+ }
35517
+ return Math.max(
35518
+ base3,
35519
+ match(["claude-sonnet-5"], 1),
35520
+ match(["gemini-3.5-flash"], 0.88),
35521
+ match(["gpt-5.3-codex"], 0.87),
35522
+ match(["gpt-5-mini"], 0.84),
35523
+ match(["kimi-k3"], 0.85),
35524
+ match(["deepseek-v4-pro"], 0.82)
35525
+ );
35526
+ case "tool_agent_parallel":
35527
+ if (terminalToolSignal) {
35528
+ return terminalSafetySensitive ? Math.max(
35529
+ base3,
35530
+ match(["claude-sonnet-5"], 1),
35531
+ match(["claude-opus-4.8"], 0.9),
35532
+ match(["gpt-5.3-codex"], 0.86)
35533
+ ) : Math.max(
35534
+ base3,
35535
+ match(["gpt-5-mini"], 1),
35536
+ match(["gpt-5.3-codex"], 0.98),
35537
+ match(["claude-sonnet-5"], 0.92),
35538
+ match(["gemini-3.5-flash"], 0.88)
35539
+ );
35540
+ }
35541
+ if (agentDomain === "web_research") {
35542
+ return deepWebResearch ? Math.max(
35543
+ base3,
35544
+ match(["claude-sonnet-5"], 1),
35545
+ match(["gpt-5-mini"], 0.88),
35546
+ match(["gemini-3.5-flash"], 0.84),
35547
+ match(["claude-opus-5"], 0.8),
35548
+ match(["claude-opus-4.8"], 0.78)
35549
+ ) : Math.max(
35550
+ base3,
35551
+ match(["claude-sonnet-5"], 1),
35552
+ match(["gpt-5-mini"], 0.88),
35553
+ match(["gemini-3.5-flash"], 0.86),
35554
+ match(["claude-opus-5"], 0.84),
35555
+ match(["claude-opus-4.8"], 0.82)
35556
+ );
35557
+ }
35558
+ if (agentDomain === "retail") {
35559
+ if (agentRisk === "policy_exception") {
35560
+ return Math.max(
35561
+ base3,
35562
+ match(["gpt-4.1"], 1),
35563
+ match(["claude-sonnet-5"], 0.9),
35564
+ match(["deepseek-v4-pro"], 0.82),
35565
+ match(["gpt-5-mini"], 0.8),
35566
+ match(["gpt-4o-mini"], 0.76)
35567
+ );
35568
+ }
35569
+ if (agentRisk === "policy_exception_simple") {
35570
+ return Math.max(
35571
+ base3,
35572
+ match(["gpt-5-mini"], 1),
35573
+ match(["gpt-4.1"], 0.86),
35574
+ match(["deepseek-v4-pro"], 0.82),
35575
+ match(["gpt-4o-mini"], 0.8)
35576
+ );
35577
+ }
35578
+ return Math.max(
35579
+ base3,
35580
+ match(["deepseek-v4-pro"], 1),
35581
+ match(["claude-sonnet-5"], 0.88),
35582
+ match(["claude-opus-4.8"], 0.84),
35583
+ match(["gpt-5-mini"], 0.78),
35584
+ match(["gemini-3.5-flash"], 0.76)
35585
+ );
35586
+ }
35587
+ if (agentDomain === "airline") {
35588
+ return agentRisk === "complex_high" ? Math.max(
35589
+ base3,
35590
+ match(["claude-sonnet-5"], 1),
35591
+ match(["gpt-5-mini"], 0.78),
35592
+ match(["claude-opus-4.8"], 0.76),
35593
+ match(["gemini-3.5-flash"], 0.74)
35594
+ ) : Math.max(
35595
+ base3,
35596
+ match(["gpt-5-mini"], 1),
35597
+ match(["claude-sonnet-5"], 0.9),
35598
+ match(["gemini-3.5-flash"], 0.8)
35599
+ );
35600
+ }
35601
+ return Math.max(
35602
+ base3,
35603
+ match(["claude-opus-4.8"], 1),
35604
+ match(["claude-sonnet-5"], 0.84),
35605
+ match(["grok-4.5"], 0.82),
35606
+ match(["gemini-3.5-flash"], 0.8),
35607
+ match(["deepseek-v4-pro"], 0.78)
35608
+ );
35609
+ case "code_edit":
35610
+ case "debug":
35611
+ return Math.max(
35612
+ base3,
35613
+ match(["gpt-5.3-codex"], 1),
35614
+ match(["claude-sonnet-4.6"], 0.94),
35615
+ match(["glm-5.2"], 0.9),
35616
+ match(["kimi-k2.7", "deepseek-v4-pro"], 0.86)
35617
+ );
35618
+ case "reasoning":
35619
+ return Math.max(
35620
+ base3,
35621
+ match(["claude-sonnet-5", "claude-sonnet-4.6"], 0.98),
35622
+ match(["deepseek-v4-pro"], 0.95),
35623
+ match(["grok-4.5"], 0.94),
35624
+ match(["gemini-3.1-pro", "gemini-3.5-flash"], 0.92)
35625
+ );
35626
+ case "reasoning_mcq":
35627
+ return Math.max(
35628
+ base3,
35629
+ match(["gemini-3-flash-preview"], 1),
35630
+ match(["gemini-3.5-flash"], 0.91),
35631
+ match(["grok-4.5"], 0.9),
35632
+ match(["claude-sonnet-5"], 0.88),
35633
+ match(["deepseek-v4-pro"], 0.84)
35634
+ );
35635
+ case "reasoning_math":
35636
+ return Math.max(
35637
+ base3,
35638
+ match(["gemini-3.5-flash"], 1),
35639
+ match(["grok-4.5"], 0.93),
35640
+ match(["claude-sonnet-5", "deepseek-v4-pro", "kimi-k3"], 0.9),
35641
+ match(["kimi-k2.7"], 0.84)
35642
+ );
35643
+ case "vision":
35644
+ return Math.max(
35645
+ base3,
35646
+ match(["gemini-3.1-pro"], 0.96),
35647
+ match(["qwen3.7-max", "claude-sonnet-4.6", "kimi-k2.7", "grok-4.3"], 0.9)
35648
+ );
35649
+ case "long_context":
35650
+ return Math.max(
35651
+ base3,
35652
+ match(["gemini-3.1-pro"], 1),
35653
+ match(["qwen3.7-max", "glm-5.2"], 0.89),
35654
+ match(["gemini-3.5-flash"], 0.88),
35655
+ match(["deepseek-v4-pro"], 0.85)
35656
+ );
35657
+ case "extraction": {
35658
+ const kimiExtractionAffinity = language === "zh" ? 1 : 0.9;
35659
+ return Math.max(
35660
+ base3,
35661
+ match(["gemini-3.5-flash", "gemini-2.5-flash", "gpt-4o-mini"], 0.9),
35662
+ match(["claude-sonnet-5", "claude-sonnet-4.6"], 0.9),
35663
+ match(["kimi-k3", "kimi-k2.7"], kimiExtractionAffinity)
35664
+ );
35665
+ }
35666
+ default:
35667
+ return Math.max(
35668
+ base3,
35669
+ match(["gemini-3.5-flash", "gemini-2.5-flash", "kimi-k3", "kimi-k2.7"], 0.86)
35670
+ );
35671
+ }
35672
+ }
35673
+ function evidenceCandidates(task) {
35674
+ if (task === "code_agent") {
35675
+ return [
35676
+ "openai/gpt-5.3-codex",
35677
+ "anthropic/claude-sonnet-5",
35678
+ "openai/gpt-5-mini",
35679
+ "google/gemini-3.5-flash",
35680
+ "moonshot/kimi-k3",
35681
+ "deepseek/deepseek-v4-pro"
35682
+ ];
35683
+ }
35684
+ if (task === "tool_agent") {
35685
+ return [
35686
+ "anthropic/claude-sonnet-5",
35687
+ "anthropic/claude-opus-5",
35688
+ "openai/gpt-5-mini",
35689
+ "openai/gpt-4.1",
35690
+ "openai/gpt-4o-mini",
35691
+ "google/gemini-3.5-flash",
35692
+ "openai/gpt-5.3-codex",
35693
+ "moonshot/kimi-k3",
35694
+ "deepseek/deepseek-v4-pro"
35695
+ ];
35696
+ }
35697
+ if (task === "tool_agent_parallel") {
35698
+ return [
35699
+ "anthropic/claude-opus-5",
35700
+ "anthropic/claude-opus-4.8",
35701
+ "anthropic/claude-sonnet-5",
35702
+ "openai/gpt-5-mini",
35703
+ "openai/gpt-4.1",
35704
+ "openai/gpt-4o-mini",
35705
+ "xai/grok-4.5",
35706
+ "google/gemini-3.5-flash",
35707
+ "deepseek/deepseek-v4-pro"
35708
+ ];
35709
+ }
35710
+ if (task === "long_context") {
35711
+ return [
35712
+ "google/gemini-3.1-pro",
35713
+ "deepseek/deepseek-v4-pro",
35714
+ "qwen/qwen3.7-max",
35715
+ "zai/glm-5.2",
35716
+ "google/gemini-3.5-flash"
35717
+ ];
35718
+ }
35719
+ if (task === "reasoning_mcq") {
35720
+ return [
35721
+ "google/gemini-3-flash-preview",
35722
+ "google/gemini-3.5-flash",
35723
+ "xai/grok-4.5",
35724
+ "anthropic/claude-sonnet-5",
35725
+ "deepseek/deepseek-v4-pro"
35726
+ ];
35727
+ }
35728
+ if (task === "reasoning_math") {
35729
+ return [
35730
+ "google/gemini-3.5-flash",
35731
+ "xai/grok-4.5",
35732
+ "anthropic/claude-sonnet-5",
35733
+ "deepseek/deepseek-v4-pro",
35734
+ "moonshot/kimi-k3"
35735
+ ];
35736
+ }
35737
+ return [];
35738
+ }
35739
+ function isEligible(modelId, features, maxOutputTokens, options) {
35740
+ const model = options.modelCapabilities?.[modelId] ?? DEFAULT_MODEL_CAPABILITIES[modelId];
35741
+ if (!model) return true;
35742
+ if (features.needsTools && !model.supportsTools) return false;
35743
+ if (features.needsVision && !model.supportsVision) return false;
35744
+ if (features.needsStructuredOutput && !model.supportsTools) return false;
35745
+ if (model.maxOutputTokens < maxOutputTokens) return false;
35746
+ return model.contextWindow >= (features.estimatedInputTokens + maxOutputTokens) * 1.1;
35747
+ }
35748
+ function estimatedCost(modelId, options, inputTokens, outputTokens) {
35749
+ const price = options.modelPricing.get(modelId);
35750
+ if (!price) return Number.POSITIVE_INFINITY;
35751
+ if (price.flatPrice !== void 0) return price.flatPrice;
35752
+ return (inputTokens * price.inputPrice + outputTokens * price.outputPrice) / 1e6;
35753
+ }
35754
+ function profileScore(modelId, options, now2) {
35755
+ const profile = options.modelPerformance?.[modelId] ?? LIVE_MODEL_PROFILES[modelId] ?? HISTORICAL_MODEL_PROFILES[modelId];
35756
+ if (!profile) return void 0;
35757
+ const measuredAt = Date.parse(profile.measuredAt);
35758
+ if (!Number.isFinite(measuredAt)) return void 0;
35759
+ const ageDays = Math.max(0, (now2.getTime() - measuredAt) / 864e5);
35760
+ const sampleConfidence = profile.samples === void 0 ? 1 : Math.min(1, Math.max(0, profile.samples) / 10);
35761
+ const freshness = Math.pow(0.5, ageDays / 30) * sampleConfidence;
35762
+ const quality = profile.intelligenceIndex === void 0 ? void 0 : Math.min(1, profile.intelligenceIndex / 50);
35763
+ const speed = Math.min(
35764
+ 1,
35765
+ (2e3 / Math.max(500, profile.latencyMs) + profile.outputTokensPerSecond / 250) / 2
35766
+ );
35767
+ const tailSpeed = Math.min(1, 3e3 / Math.max(750, profile.p95LatencyMs ?? profile.latencyMs));
35768
+ const reliability = Math.max(0, 1 - (profile.errorRate ?? 0));
35769
+ return { quality, speed, tailSpeed, reliability, freshness };
35770
+ }
35771
+ function route(prompt, systemPrompt, maxOutputTokens, options) {
35772
+ const strategy = getStrategy(options.config.strategy ?? "portfolio");
35773
+ return strategy.route(prompt, systemPrompt, maxOutputTokens, options);
35774
+ }
35775
+ var BASELINE_MODEL_ID, BASELINE_INPUT_PRICE, BASELINE_OUTPUT_PRICE, SERVER_MARGIN_PERCENT, MIN_PAYMENT_USD, RulesStrategy, registry, DEFAULT_MODEL_CAPABILITIES, model_profiles_generated_default, LIVE_MODEL_PROFILES, HISTORICAL_MODEL_PROFILES, DEFAULT_PORTFOLIO_WEIGHTS, PortfolioStrategy, DEFAULT_ROUTING_CONFIG;
35776
+ var init_dist = __esm({
35777
+ "node_modules/@blockrun/router-core/dist/index.js"() {
35151
35778
  "use strict";
35152
- init_rules();
35153
- init_selector();
35779
+ BASELINE_MODEL_ID = "anthropic/claude-opus-4.7";
35780
+ BASELINE_INPUT_PRICE = 5;
35781
+ BASELINE_OUTPUT_PRICE = 25;
35782
+ SERVER_MARGIN_PERCENT = 5;
35783
+ MIN_PAYMENT_USD = 1e-3;
35154
35784
  RulesStrategy = class {
35155
35785
  name = "rules";
35156
35786
  route(prompt, systemPrompt, maxOutputTokens, options) {
35157
35787
  const { config, modelPricing } = options;
35158
35788
  const fullText = `${systemPrompt ?? ""} ${prompt}`;
35159
35789
  const estimatedTokens = Math.ceil(fullText.length / 4);
35160
- const ruleResult = classifyByRules(prompt, systemPrompt, estimatedTokens, config.scoring);
35790
+ const scanLimit = Math.max(1, Math.min(8e3, config.classifier.promptTruncationChars));
35791
+ const sample = (value) => {
35792
+ if (value.length <= scanLimit) return value;
35793
+ const prefixLength = Math.ceil(scanLimit / 2);
35794
+ return `${value.slice(0, prefixLength)}
35795
+ ${value.slice(-(scanLimit - prefixLength))}`;
35796
+ };
35797
+ const scannedPrompt = sample(prompt);
35798
+ const scannedSystemPrompt = systemPrompt ? sample(systemPrompt) : void 0;
35799
+ const ruleResult = classifyByRules(
35800
+ scannedPrompt,
35801
+ scannedSystemPrompt,
35802
+ estimatedTokens,
35803
+ config.scoring
35804
+ );
35161
35805
  const { routingProfile } = options;
35162
35806
  let tierConfigs;
35163
35807
  let profileSuffix;
@@ -35174,7 +35818,7 @@ var init_strategy = __esm({
35174
35818
  const agenticScore = ruleResult.agenticScore ?? 0;
35175
35819
  const isAutoAgentic = agenticScore >= 0.5;
35176
35820
  const agenticModeSetting = config.overrides.agenticMode;
35177
- const hasToolsInRequest = options.hasTools ?? false;
35821
+ const hasToolsInRequest = options.requiresTools ?? options.hasTools ?? false;
35178
35822
  let useAgenticTiers;
35179
35823
  if (agenticModeSetting === false) {
35180
35824
  useAgenticTiers = false;
@@ -35204,7 +35848,7 @@ var init_strategy = __esm({
35204
35848
  );
35205
35849
  return { ...decision2, tierConfigs, profile };
35206
35850
  }
35207
- const hasStructuredOutput = systemPrompt ? /json|structured|schema/i.test(systemPrompt) : false;
35851
+ const hasStructuredOutput = options.requiresStructuredOutput === true || (scannedSystemPrompt ? /json|structured|schema/i.test(scannedSystemPrompt) : false);
35208
35852
  let tier;
35209
35853
  let confidence;
35210
35854
  const method = "rules";
@@ -35243,16 +35887,789 @@ var init_strategy = __esm({
35243
35887
  };
35244
35888
  registry = /* @__PURE__ */ new Map();
35245
35889
  registry.set("rules", new RulesStrategy());
35246
- }
35247
- });
35248
-
35249
- // src/router/config.ts
35250
- var DEFAULT_ROUTING_CONFIG;
35251
- var init_config = __esm({
35252
- "src/router/config.ts"() {
35253
- "use strict";
35890
+ DEFAULT_MODEL_CAPABILITIES = Object.freeze({
35891
+ "anthropic/claude-fable-5": {
35892
+ contextWindow: 1e6,
35893
+ maxOutputTokens: 128e3,
35894
+ supportsTools: true,
35895
+ supportsVision: true
35896
+ },
35897
+ "anthropic/claude-haiku-4.5": {
35898
+ contextWindow: 2e5,
35899
+ maxOutputTokens: 8192,
35900
+ supportsTools: true,
35901
+ supportsVision: true
35902
+ },
35903
+ "anthropic/claude-opus-4.6": {
35904
+ contextWindow: 1e6,
35905
+ maxOutputTokens: 128e3,
35906
+ supportsTools: true,
35907
+ supportsVision: true
35908
+ },
35909
+ "anthropic/claude-opus-4.7": {
35910
+ contextWindow: 1e6,
35911
+ maxOutputTokens: 128e3,
35912
+ supportsTools: true,
35913
+ supportsVision: true
35914
+ },
35915
+ "anthropic/claude-opus-4.8": {
35916
+ contextWindow: 1e6,
35917
+ maxOutputTokens: 128e3,
35918
+ supportsTools: true,
35919
+ supportsVision: true
35920
+ },
35921
+ "anthropic/claude-opus-5": {
35922
+ contextWindow: 1e6,
35923
+ maxOutputTokens: 128e3,
35924
+ supportsTools: true,
35925
+ supportsVision: true
35926
+ },
35927
+ "anthropic/claude-sonnet-4.6": {
35928
+ contextWindow: 2e5,
35929
+ maxOutputTokens: 64e3,
35930
+ supportsTools: true,
35931
+ supportsVision: true
35932
+ },
35933
+ "anthropic/claude-sonnet-5": {
35934
+ contextWindow: 1e6,
35935
+ maxOutputTokens: 128e3,
35936
+ supportsTools: true,
35937
+ supportsVision: true
35938
+ },
35939
+ "deepseek/deepseek-chat": {
35940
+ contextWindow: 1e6,
35941
+ maxOutputTokens: 8192,
35942
+ supportsTools: true,
35943
+ supportsVision: false
35944
+ },
35945
+ "deepseek/deepseek-reasoner": {
35946
+ contextWindow: 1e6,
35947
+ maxOutputTokens: 8192,
35948
+ supportsTools: true,
35949
+ supportsVision: false
35950
+ },
35951
+ "deepseek/deepseek-v4-pro": {
35952
+ contextWindow: 1048576,
35953
+ maxOutputTokens: 65536,
35954
+ supportsTools: true,
35955
+ supportsVision: false
35956
+ },
35957
+ "free/deepseek-v4-flash": {
35958
+ contextWindow: 1e6,
35959
+ maxOutputTokens: 16384,
35960
+ supportsTools: false,
35961
+ supportsVision: false
35962
+ },
35963
+ "free/gpt-oss-120b": {
35964
+ contextWindow: 128e3,
35965
+ maxOutputTokens: 16384,
35966
+ supportsTools: false,
35967
+ supportsVision: false
35968
+ },
35969
+ "free/gpt-oss-20b": {
35970
+ contextWindow: 128e3,
35971
+ maxOutputTokens: 16384,
35972
+ supportsTools: false,
35973
+ supportsVision: false
35974
+ },
35975
+ "free/seed-oss-36b": {
35976
+ contextWindow: 131072,
35977
+ maxOutputTokens: 16384,
35978
+ supportsTools: false,
35979
+ supportsVision: false
35980
+ },
35981
+ "google/gemini-2.5-flash": {
35982
+ contextWindow: 1e6,
35983
+ maxOutputTokens: 65536,
35984
+ supportsTools: true,
35985
+ supportsVision: true
35986
+ },
35987
+ "google/gemini-2.5-flash-lite": {
35988
+ contextWindow: 1e6,
35989
+ maxOutputTokens: 65536,
35990
+ supportsTools: true,
35991
+ supportsVision: false
35992
+ },
35993
+ "google/gemini-2.5-pro": {
35994
+ contextWindow: 105e4,
35995
+ maxOutputTokens: 65536,
35996
+ supportsTools: true,
35997
+ supportsVision: true
35998
+ },
35999
+ "google/gemini-3-flash-preview": {
36000
+ contextWindow: 1e6,
36001
+ maxOutputTokens: 65536,
36002
+ supportsTools: false,
36003
+ supportsVision: true
36004
+ },
36005
+ "google/gemini-3.1-flash-lite": {
36006
+ contextWindow: 1e6,
36007
+ maxOutputTokens: 8192,
36008
+ supportsTools: true,
36009
+ supportsVision: false
36010
+ },
36011
+ "google/gemini-3.1-pro": {
36012
+ contextWindow: 105e4,
36013
+ maxOutputTokens: 65536,
36014
+ supportsTools: true,
36015
+ supportsVision: true
36016
+ },
36017
+ "google/gemini-3.5-flash": {
36018
+ contextWindow: 1048576,
36019
+ maxOutputTokens: 65536,
36020
+ supportsTools: true,
36021
+ supportsVision: true
36022
+ },
36023
+ "moonshot/kimi-k2.5": {
36024
+ contextWindow: 262144,
36025
+ maxOutputTokens: 16384,
36026
+ supportsTools: true,
36027
+ supportsVision: true
36028
+ },
36029
+ "moonshot/kimi-k2.6": {
36030
+ contextWindow: 262144,
36031
+ maxOutputTokens: 65536,
36032
+ supportsTools: true,
36033
+ supportsVision: true
36034
+ },
36035
+ "moonshot/kimi-k2.7": {
36036
+ contextWindow: 262144,
36037
+ maxOutputTokens: 65536,
36038
+ supportsTools: true,
36039
+ supportsVision: true
36040
+ },
36041
+ "moonshot/kimi-k3": {
36042
+ contextWindow: 1048576,
36043
+ maxOutputTokens: 65536,
36044
+ supportsTools: true,
36045
+ supportsVision: true
36046
+ },
36047
+ "openai/gpt-4.1": {
36048
+ contextWindow: 128e3,
36049
+ maxOutputTokens: 16384,
36050
+ supportsTools: true,
36051
+ supportsVision: true
36052
+ },
36053
+ "openai/gpt-4o-mini": {
36054
+ contextWindow: 128e3,
36055
+ maxOutputTokens: 16384,
36056
+ supportsTools: true,
36057
+ supportsVision: false
36058
+ },
36059
+ "openai/gpt-5-mini": {
36060
+ contextWindow: 2e5,
36061
+ maxOutputTokens: 65536,
36062
+ supportsTools: true,
36063
+ supportsVision: false
36064
+ },
36065
+ "openai/gpt-5.3-codex": {
36066
+ contextWindow: 4e5,
36067
+ maxOutputTokens: 128e3,
36068
+ supportsTools: true,
36069
+ supportsVision: false
36070
+ },
36071
+ "openai/gpt-5.4": {
36072
+ contextWindow: 4e5,
36073
+ maxOutputTokens: 128e3,
36074
+ supportsTools: true,
36075
+ supportsVision: true
36076
+ },
36077
+ "openai/gpt-5.4-nano": {
36078
+ contextWindow: 105e4,
36079
+ maxOutputTokens: 32768,
36080
+ supportsTools: true,
36081
+ supportsVision: false
36082
+ },
36083
+ "openai/gpt-5.5": {
36084
+ contextWindow: 105e4,
36085
+ maxOutputTokens: 128e3,
36086
+ supportsTools: true,
36087
+ supportsVision: true
36088
+ },
36089
+ "openai/gpt-5.6-terra": {
36090
+ contextWindow: 105e4,
36091
+ maxOutputTokens: 128e3,
36092
+ supportsTools: true,
36093
+ supportsVision: true
36094
+ },
36095
+ "openai/o3": {
36096
+ contextWindow: 2e5,
36097
+ maxOutputTokens: 1e5,
36098
+ supportsTools: true,
36099
+ supportsVision: false
36100
+ },
36101
+ "openai/o4-mini": {
36102
+ contextWindow: 128e3,
36103
+ maxOutputTokens: 65536,
36104
+ supportsTools: true,
36105
+ supportsVision: false
36106
+ },
36107
+ "qwen/qwen3.7-max": {
36108
+ contextWindow: 1e6,
36109
+ maxOutputTokens: 65536,
36110
+ supportsTools: true,
36111
+ supportsVision: false
36112
+ },
36113
+ "xai/grok-3-mini": {
36114
+ contextWindow: 131072,
36115
+ maxOutputTokens: 16384,
36116
+ supportsTools: true,
36117
+ supportsVision: false
36118
+ },
36119
+ "xai/grok-4-0709": {
36120
+ contextWindow: 131072,
36121
+ maxOutputTokens: 16384,
36122
+ supportsTools: true,
36123
+ supportsVision: false
36124
+ },
36125
+ "xai/grok-4-1-fast-non-reasoning": {
36126
+ contextWindow: 131072,
36127
+ maxOutputTokens: 16384,
36128
+ supportsTools: true,
36129
+ supportsVision: false
36130
+ },
36131
+ "xai/grok-4-1-fast-reasoning": {
36132
+ contextWindow: 131072,
36133
+ maxOutputTokens: 16384,
36134
+ supportsTools: true,
36135
+ supportsVision: false
36136
+ },
36137
+ "xai/grok-4-fast-non-reasoning": {
36138
+ contextWindow: 131072,
36139
+ maxOutputTokens: 16384,
36140
+ supportsTools: true,
36141
+ supportsVision: false
36142
+ },
36143
+ "xai/grok-4-fast-reasoning": {
36144
+ contextWindow: 131072,
36145
+ maxOutputTokens: 16384,
36146
+ supportsTools: true,
36147
+ supportsVision: false
36148
+ },
36149
+ "xai/grok-4.5": {
36150
+ contextWindow: 5e5,
36151
+ maxOutputTokens: 16384,
36152
+ supportsTools: true,
36153
+ supportsVision: true
36154
+ },
36155
+ "zai/glm-5.1": {
36156
+ contextWindow: 2e5,
36157
+ maxOutputTokens: 128e3,
36158
+ supportsTools: true,
36159
+ supportsVision: false
36160
+ },
36161
+ "zai/glm-5.2": {
36162
+ contextWindow: 1e6,
36163
+ maxOutputTokens: 262144,
36164
+ supportsTools: true,
36165
+ supportsVision: false
36166
+ }
36167
+ });
36168
+ model_profiles_generated_default = {
36169
+ "openai/gpt-5.5": {
36170
+ measuredAt: "2026-07-21T10:21:31Z",
36171
+ latencyMs: 6243.1,
36172
+ p95LatencyMs: 9865,
36173
+ outputTokensPerSecond: 12.53,
36174
+ errorRate: 0,
36175
+ samples: 3
36176
+ },
36177
+ "openai/gpt-5.4-pro": {
36178
+ measuredAt: "2026-07-21T10:21:31Z",
36179
+ latencyMs: 13015.5,
36180
+ p95LatencyMs: 23976.4,
36181
+ outputTokensPerSecond: 6.42,
36182
+ errorRate: 0,
36183
+ samples: 3
36184
+ },
36185
+ "openai/gpt-5.4-mini": {
36186
+ measuredAt: "2026-07-21T10:21:31Z",
36187
+ latencyMs: 5550,
36188
+ p95LatencyMs: 6595.7,
36189
+ outputTokensPerSecond: 11.96,
36190
+ errorRate: 0.3333,
36191
+ samples: 3
36192
+ },
36193
+ "openai/gpt-5.3-codex": {
36194
+ measuredAt: "2026-07-21T10:21:31Z",
36195
+ latencyMs: 4617.1,
36196
+ p95LatencyMs: 5800.7,
36197
+ outputTokensPerSecond: 12.48,
36198
+ errorRate: 0,
36199
+ samples: 3
36200
+ },
36201
+ "anthropic/claude-opus-4.8": {
36202
+ measuredAt: "2026-07-21T10:21:31Z",
36203
+ latencyMs: 3915.1,
36204
+ p95LatencyMs: 6130.8,
36205
+ outputTokensPerSecond: 16.33,
36206
+ errorRate: 0,
36207
+ samples: 3
36208
+ },
36209
+ "anthropic/claude-opus-4.6": {
36210
+ measuredAt: "2026-07-21T10:21:31Z",
36211
+ latencyMs: 3765.5,
36212
+ p95LatencyMs: 4257.2,
36213
+ outputTokensPerSecond: 14.18,
36214
+ errorRate: 0,
36215
+ samples: 3
36216
+ },
36217
+ "anthropic/claude-sonnet-4.6": {
36218
+ measuredAt: "2026-07-21T10:21:31Z",
36219
+ latencyMs: 3860.6,
36220
+ p95LatencyMs: 5093.5,
36221
+ outputTokensPerSecond: 13.85,
36222
+ errorRate: 0,
36223
+ samples: 3
36224
+ },
36225
+ "anthropic/claude-haiku-4.5": {
36226
+ measuredAt: "2026-07-21T10:21:31Z",
36227
+ latencyMs: 2734.9,
36228
+ p95LatencyMs: 3181.6,
36229
+ outputTokensPerSecond: 19.58,
36230
+ errorRate: 0,
36231
+ samples: 3
36232
+ },
36233
+ "google/gemini-3.1-pro": {
36234
+ measuredAt: "2026-07-21T10:21:31Z",
36235
+ latencyMs: 13935.7,
36236
+ p95LatencyMs: 26675.3,
36237
+ outputTokensPerSecond: 77.47,
36238
+ errorRate: 0,
36239
+ samples: 3
36240
+ },
36241
+ "google/gemini-3.5-flash": {
36242
+ measuredAt: "2026-07-21T10:21:31Z",
36243
+ latencyMs: 4608.7,
36244
+ p95LatencyMs: 8420.9,
36245
+ outputTokensPerSecond: 57.88,
36246
+ errorRate: 0,
36247
+ samples: 3
36248
+ },
36249
+ "google/gemini-3.1-flash-lite": {
36250
+ measuredAt: "2026-07-21T10:21:31Z",
36251
+ latencyMs: 4619.7,
36252
+ p95LatencyMs: 9927.1,
36253
+ outputTokensPerSecond: 42.01,
36254
+ errorRate: 0,
36255
+ samples: 3
36256
+ },
36257
+ "google/gemini-2.5-flash": {
36258
+ measuredAt: "2026-07-21T10:21:31Z",
36259
+ latencyMs: 5506.9,
36260
+ p95LatencyMs: 11462.5,
36261
+ outputTokensPerSecond: 65.19,
36262
+ errorRate: 0,
36263
+ samples: 3
36264
+ },
36265
+ "deepseek/deepseek-v4-pro": {
36266
+ measuredAt: "2026-07-21T10:21:31Z",
36267
+ latencyMs: 6044.8,
36268
+ p95LatencyMs: 10782.3,
36269
+ outputTokensPerSecond: 22.47,
36270
+ errorRate: 0,
36271
+ samples: 3
36272
+ },
36273
+ "deepseek/deepseek-reasoner": {
36274
+ measuredAt: "2026-07-21T10:21:31Z",
36275
+ latencyMs: 4111.9,
36276
+ p95LatencyMs: 5305.7,
36277
+ outputTokensPerSecond: 16.46,
36278
+ errorRate: 0,
36279
+ samples: 3
36280
+ },
36281
+ "deepseek/deepseek-chat": {
36282
+ measuredAt: "2026-07-21T10:21:31Z",
36283
+ latencyMs: 2648.6,
36284
+ p95LatencyMs: 3524.1,
36285
+ outputTokensPerSecond: 16.73,
36286
+ errorRate: 0,
36287
+ samples: 3
36288
+ },
36289
+ "moonshot/kimi-k2.7": {
36290
+ measuredAt: "2026-07-21T10:21:31Z",
36291
+ latencyMs: 4295.4,
36292
+ p95LatencyMs: 6153.8,
36293
+ outputTokensPerSecond: 18.54,
36294
+ errorRate: 0,
36295
+ samples: 3
36296
+ },
36297
+ "qwen/qwen3.7-max": {
36298
+ measuredAt: "2026-07-21T10:21:31Z",
36299
+ latencyMs: 30729.4,
36300
+ p95LatencyMs: 39622,
36301
+ outputTokensPerSecond: 36.89,
36302
+ errorRate: 0.3333,
36303
+ samples: 3
36304
+ },
36305
+ "xai/grok-4.3": {
36306
+ measuredAt: "2026-07-21T10:21:31Z",
36307
+ latencyMs: 6946.1,
36308
+ p95LatencyMs: 9495.4,
36309
+ outputTokensPerSecond: 65.3,
36310
+ errorRate: 0,
36311
+ samples: 3
36312
+ },
36313
+ "xai/grok-4.20-reasoning": {
36314
+ measuredAt: "2026-07-21T10:21:31Z",
36315
+ latencyMs: 3472.4,
36316
+ p95LatencyMs: 5332.4,
36317
+ outputTokensPerSecond: 13.27,
36318
+ errorRate: 0,
36319
+ samples: 3
36320
+ },
36321
+ "xai/grok-4.20-non-reasoning": {
36322
+ measuredAt: "2026-07-21T10:21:31Z",
36323
+ latencyMs: 5174.4,
36324
+ p95LatencyMs: 6081.7,
36325
+ outputTokensPerSecond: 10.21,
36326
+ errorRate: 0.3333,
36327
+ samples: 3
36328
+ },
36329
+ "xai/grok-4-1-fast-reasoning": {
36330
+ measuredAt: "2026-07-21T10:21:31Z",
36331
+ latencyMs: 13148.2,
36332
+ p95LatencyMs: 19104.2,
36333
+ outputTokensPerSecond: 4.28,
36334
+ errorRate: 0,
36335
+ samples: 3
36336
+ },
36337
+ "minimax/minimax-m3": {
36338
+ measuredAt: "2026-07-21T10:21:31Z",
36339
+ latencyMs: 3385,
36340
+ p95LatencyMs: 4247.2,
36341
+ outputTokensPerSecond: 15.16,
36342
+ errorRate: 0,
36343
+ samples: 3
36344
+ },
36345
+ "minimax/minimax-m2.7": {
36346
+ measuredAt: "2026-07-21T10:21:31Z",
36347
+ latencyMs: 4596.7,
36348
+ p95LatencyMs: 6884.6,
36349
+ outputTokensPerSecond: 17.03,
36350
+ errorRate: 0,
36351
+ samples: 3
36352
+ },
36353
+ "zai/glm-5.2": {
36354
+ measuredAt: "2026-07-21T10:21:31Z",
36355
+ latencyMs: 4406.3,
36356
+ p95LatencyMs: 6139.7,
36357
+ outputTokensPerSecond: 10.41,
36358
+ errorRate: 0,
36359
+ samples: 3
36360
+ },
36361
+ "zai/glm-5.1": {
36362
+ measuredAt: "2026-07-21T10:21:31Z",
36363
+ latencyMs: 7775.4,
36364
+ p95LatencyMs: 9182.1,
36365
+ outputTokensPerSecond: 6.08,
36366
+ errorRate: 0,
36367
+ samples: 3
36368
+ },
36369
+ "zai/glm-5": {
36370
+ measuredAt: "2026-07-21T10:21:31Z",
36371
+ latencyMs: 4159.4,
36372
+ p95LatencyMs: 4992.7,
36373
+ outputTokensPerSecond: 10.28,
36374
+ errorRate: 0,
36375
+ samples: 3
36376
+ },
36377
+ "free/qwen3-coder-480b": {
36378
+ measuredAt: "2026-07-21T10:21:31Z",
36379
+ latencyMs: 2063.9,
36380
+ p95LatencyMs: 3646.3,
36381
+ outputTokensPerSecond: 39.8,
36382
+ errorRate: 0,
36383
+ samples: 3
36384
+ },
36385
+ "free/mistral-large-3-675b": {
36386
+ measuredAt: "2026-07-21T10:21:31Z",
36387
+ latencyMs: 3147.5,
36388
+ p95LatencyMs: 5555.3,
36389
+ outputTokensPerSecond: 27.76,
36390
+ errorRate: 0,
36391
+ samples: 3
36392
+ },
36393
+ "free/nemotron-3-nano-omni-30b-a3b-reasoning": {
36394
+ measuredAt: "2026-07-21T10:21:31Z",
36395
+ latencyMs: 6508.4,
36396
+ p95LatencyMs: 14252.7,
36397
+ outputTokensPerSecond: 68.26,
36398
+ errorRate: 0,
36399
+ samples: 3
36400
+ },
36401
+ "free/glm-4.7": {
36402
+ measuredAt: "2026-07-21T10:21:31Z",
36403
+ latencyMs: 2014.8,
36404
+ p95LatencyMs: 3039.9,
36405
+ outputTokensPerSecond: 39.92,
36406
+ errorRate: 0,
36407
+ samples: 3
36408
+ }
36409
+ };
36410
+ LIVE_MODEL_PROFILES = Object.freeze(
36411
+ model_profiles_generated_default
36412
+ );
36413
+ HISTORICAL_MODEL_PROFILES = Object.freeze({
36414
+ "anthropic/claude-haiku-4.5": {
36415
+ measuredAt: "2026-03-16T13:50:48Z",
36416
+ latencyMs: 2305,
36417
+ outputTokensPerSecond: 140.6
36418
+ },
36419
+ "anthropic/claude-opus-4.6": {
36420
+ measuredAt: "2026-03-16T13:50:48Z",
36421
+ latencyMs: 2139,
36422
+ outputTokensPerSecond: 119.7
36423
+ },
36424
+ "anthropic/claude-sonnet-4.6": {
36425
+ measuredAt: "2026-03-16T13:50:48Z",
36426
+ latencyMs: 2110,
36427
+ outputTokensPerSecond: 121.3
36428
+ },
36429
+ "deepseek/deepseek-chat": {
36430
+ measuredAt: "2026-03-16T13:50:48Z",
36431
+ latencyMs: 1431,
36432
+ outputTokensPerSecond: 179.2,
36433
+ intelligenceIndex: 32
36434
+ },
36435
+ "google/gemini-2.5-flash": {
36436
+ measuredAt: "2026-03-16T13:50:48Z",
36437
+ latencyMs: 1238,
36438
+ outputTokensPerSecond: 207.6,
36439
+ intelligenceIndex: 20
36440
+ },
36441
+ "google/gemini-2.5-flash-lite": {
36442
+ measuredAt: "2026-03-16T13:50:48Z",
36443
+ latencyMs: 1353,
36444
+ outputTokensPerSecond: 192.5,
36445
+ intelligenceIndex: 20
36446
+ },
36447
+ "google/gemini-2.5-pro": {
36448
+ measuredAt: "2026-03-16T13:50:48Z",
36449
+ latencyMs: 1294,
36450
+ outputTokensPerSecond: 197.8
36451
+ },
36452
+ "google/gemini-3.1-pro": {
36453
+ measuredAt: "2026-03-16T13:50:48Z",
36454
+ latencyMs: 1609,
36455
+ outputTokensPerSecond: 167.2
36456
+ },
36457
+ "moonshot/kimi-k2.5": {
36458
+ measuredAt: "2026-03-16T13:50:48Z",
36459
+ latencyMs: 1646,
36460
+ outputTokensPerSecond: 155.7
36461
+ },
36462
+ "openai/gpt-4o-mini": {
36463
+ measuredAt: "2026-03-16T13:50:48Z",
36464
+ latencyMs: 2764,
36465
+ outputTokensPerSecond: 92.8
36466
+ },
36467
+ "openai/gpt-5.3-codex": {
36468
+ measuredAt: "2026-03-16T13:50:48Z",
36469
+ latencyMs: 7935,
36470
+ outputTokensPerSecond: 32.3
36471
+ },
36472
+ "xai/grok-4-1-fast-non-reasoning": {
36473
+ measuredAt: "2026-03-16T13:50:48Z",
36474
+ latencyMs: 1244,
36475
+ outputTokensPerSecond: 205.8,
36476
+ intelligenceIndex: 41
36477
+ },
36478
+ "xai/grok-4-1-fast-reasoning": {
36479
+ measuredAt: "2026-03-16T13:50:48Z",
36480
+ latencyMs: 1454,
36481
+ outputTokensPerSecond: 176.2,
36482
+ intelligenceIndex: 41
36483
+ }
36484
+ });
36485
+ DEFAULT_PORTFOLIO_WEIGHTS = {
36486
+ auto: {
36487
+ quality: 0.47,
36488
+ capability: 0.2,
36489
+ cost: 0.18,
36490
+ speed: 0.07,
36491
+ reliability: 0.03,
36492
+ legacy: 0.05
36493
+ },
36494
+ eco: { quality: 0.36, capability: 0.2, cost: 0.28, speed: 0.1, reliability: 0.04, legacy: 0.02 },
36495
+ premium: {
36496
+ quality: 0.58,
36497
+ capability: 0.2,
36498
+ cost: 0.08,
36499
+ speed: 0.06,
36500
+ reliability: 0.06,
36501
+ legacy: 0.02
36502
+ },
36503
+ highStakesBoost: { quality: 0.08, reliability: 0.05 },
36504
+ latencySensitiveSpeedBoost: 0.08,
36505
+ affinityFloorGap: { auto: 0.1, eco: 0.22, premium: 0.05 }
36506
+ };
36507
+ PortfolioStrategy = class {
36508
+ name = "portfolio";
36509
+ route(prompt, systemPrompt, maxOutputTokens, options) {
36510
+ const features = classifyTask(prompt, systemPrompt, options);
36511
+ const base3 = new RulesStrategy().route(prompt, systemPrompt, maxOutputTokens, {
36512
+ ...options,
36513
+ requiresTools: features.needsTools
36514
+ });
36515
+ const tierConfigs = base3.tierConfigs;
36516
+ if (!tierConfigs) return base3;
36517
+ const targetTier = (features.taskType === "reasoning_mcq" || features.taskType === "reasoning_math") && (base3.tier === "SIMPLE" || base3.tier === "MEDIUM") ? "REASONING" : base3.tier;
36518
+ const tierConfig = tierConfigs[targetTier];
36519
+ const configuredCandidates = tierConfig ? getFallbackChain(targetTier, tierConfigs) : [];
36520
+ const chain3 = [
36521
+ .../* @__PURE__ */ new Set([...configuredCandidates, ...evidenceCandidates(features.taskType)])
36522
+ ].filter((model2) => typeof model2 === "string" && model2.length > 0);
36523
+ const eligible = chain3.filter(
36524
+ (model2) => isEligible(model2, features, maxOutputTokens, options)
36525
+ );
36526
+ const eligibleCandidates = eligible.length > 0 ? eligible : chain3;
36527
+ if (eligibleCandidates.length === 0) return base3;
36528
+ const profileName = options.routingProfile === "eco" ? "eco" : options.routingProfile === "premium" ? "premium" : "auto";
36529
+ const portfolio = options.config.portfolio ?? DEFAULT_PORTFOLIO_WEIGHTS;
36530
+ const getAffinity = (model2) => affinity(
36531
+ model2,
36532
+ features.taskType,
36533
+ features.language,
36534
+ features.agentDomain,
36535
+ features.deepWebResearch,
36536
+ features.agentRisk,
36537
+ features.terminalToolSignal,
36538
+ features.terminalSafetySensitive
36539
+ );
36540
+ const bestAffinity = Math.max(...eligibleCandidates.map(getAffinity));
36541
+ const specificAffinity = eligibleCandidates.filter((model2) => getAffinity(model2) > 0.68);
36542
+ const affinityPool = specificAffinity.length > 0 ? specificAffinity : [eligibleCandidates[0]];
36543
+ const affinityFloorGap = features.terminalToolSignal ? Math.max(
36544
+ portfolio.affinityFloorGap[profileName],
36545
+ features.terminalSafetySensitive ? 0.15 : 0.12
36546
+ ) : portfolio.affinityFloorGap[profileName];
36547
+ const candidates = affinityPool.filter(
36548
+ (model2) => getAffinity(model2) >= bestAffinity - affinityFloorGap
36549
+ );
36550
+ const costs = candidates.map(
36551
+ (model2) => estimatedCost(model2, options, features.estimatedInputTokens, maxOutputTokens)
36552
+ );
36553
+ const finiteCosts = costs.filter(Number.isFinite);
36554
+ const minCost = finiteCosts.length > 0 ? Math.min(...finiteCosts) : 0;
36555
+ const maxCost = finiteCosts.length > 0 ? Math.max(...finiteCosts) : 1;
36556
+ const now2 = options.now ?? /* @__PURE__ */ new Date();
36557
+ const profileWeights = portfolio[profileName];
36558
+ const rankedEntries = candidates.map((model2, index2) => {
36559
+ const cost = estimatedCost(model2, options, features.estimatedInputTokens, maxOutputTokens);
36560
+ const costScore = Number.isFinite(cost) && maxCost > minCost ? 1 - (cost - minCost) / (maxCost - minCost) : 0.5;
36561
+ const capabilityScore = isEligible(model2, features, maxOutputTokens, options) ? 1 : 0;
36562
+ const profile = profileScore(model2, options, now2);
36563
+ const observedQuality = profile?.quality === void 0 ? getAffinity(model2) : getAffinity(model2) * (1 - profile.freshness) + profile.quality * profile.freshness;
36564
+ const observedSpeed = profile ? profile.speed * profile.freshness : 0.5;
36565
+ const observedTailSpeed = profile ? profile.tailSpeed * profile.freshness : 0.5;
36566
+ const observedReliability = profile ? profile.reliability * profile.freshness + (1 - profile.freshness) : 1;
36567
+ const legacyScore = 1 - index2 / Math.max(1, candidates.length - 1);
36568
+ const qualityWeight = profileWeights.quality + (features.highStakes ? portfolio.highStakesBoost.quality : 0);
36569
+ const speedScore = features.latencySensitive ? observedTailSpeed : observedSpeed;
36570
+ const speedWeight = profileWeights.speed + (features.latencySensitive ? portfolio.latencySensitiveSpeedBoost : 0);
36571
+ const reliabilityWeight = profileWeights.reliability + (features.highStakes ? portfolio.highStakesBoost.reliability : 0);
36572
+ const score = observedQuality * qualityWeight + capabilityScore * profileWeights.capability + costScore * profileWeights.cost + speedScore * speedWeight + observedReliability * reliabilityWeight + legacyScore * profileWeights.legacy;
36573
+ return {
36574
+ model: model2,
36575
+ score,
36576
+ quality: observedQuality,
36577
+ cost: costScore,
36578
+ speed: speedScore,
36579
+ reliability: observedReliability
36580
+ };
36581
+ }).sort((a, b) => b.score - a.score);
36582
+ const scoredModels = rankedEntries.map((item) => item.model);
36583
+ const webResearchFallbackOrder = [
36584
+ "anthropic/claude-sonnet-5",
36585
+ "openai/gpt-5-mini",
36586
+ "google/gemini-3.5-flash",
36587
+ "anthropic/claude-opus-5",
36588
+ "anthropic/claude-opus-4.8",
36589
+ "openai/gpt-5.3-codex"
36590
+ ];
36591
+ const ranked = features.agentDomain === "web_research" ? [
36592
+ ...scoredModels,
36593
+ ...webResearchFallbackOrder.filter(
36594
+ (model2) => eligibleCandidates.includes(model2) && !scoredModels.includes(model2)
36595
+ ),
36596
+ ...eligibleCandidates.filter(
36597
+ (model2) => !scoredModels.includes(model2) && !webResearchFallbackOrder.includes(model2)
36598
+ )
36599
+ ] : features.taskType === "tool_agent" || features.taskType === "tool_agent_parallel" && features.agentDomain !== "other" ? [
36600
+ ...scoredModels,
36601
+ ...eligibleCandidates.filter((model2) => !scoredModels.includes(model2))
36602
+ ] : [
36603
+ ...scoredModels,
36604
+ ...eligibleCandidates.filter((model2) => !scoredModels.includes(model2))
36605
+ ];
36606
+ const model = ranked[0] ?? base3.model;
36607
+ const selectedTierConfigs = {
36608
+ ...tierConfigs,
36609
+ [targetTier]: { primary: model, fallback: ranked.slice(1) }
36610
+ };
36611
+ const decision = selectModel(
36612
+ targetTier,
36613
+ base3.confidence,
36614
+ "portfolio",
36615
+ `${base3.reasoning} | v3 task=${features.taskType} agentRisk=${features.agentRisk} deepWebResearch=${features.deepWebResearch} terminalCode=${features.implicitTerminalCode} terminalSafety=${features.terminalSafetySensitive} candidates=${ranked.length}`,
36616
+ selectedTierConfigs,
36617
+ options.modelPricing,
36618
+ features.estimatedInputTokens,
36619
+ maxOutputTokens,
36620
+ options.routingProfile,
36621
+ base3.agenticScore
36622
+ );
36623
+ return {
36624
+ ...decision,
36625
+ tierConfigs: selectedTierConfigs,
36626
+ profile: base3.profile,
36627
+ candidates: ranked,
36628
+ candidateScores: rankedEntries.map(({ model: model2, score, quality, cost, speed, reliability }) => ({
36629
+ model: model2,
36630
+ score,
36631
+ quality,
36632
+ cost,
36633
+ speed,
36634
+ reliability
36635
+ })),
36636
+ taskType: features.taskType,
36637
+ routerVersion: "v3-portfolio"
36638
+ };
36639
+ }
36640
+ };
35254
36641
  DEFAULT_ROUTING_CONFIG = {
35255
- version: "2.0",
36642
+ version: "3.4",
36643
+ strategy: "portfolio",
36644
+ portfolio: {
36645
+ auto: {
36646
+ quality: 0.47,
36647
+ capability: 0.2,
36648
+ cost: 0.18,
36649
+ speed: 0.07,
36650
+ reliability: 0.03,
36651
+ legacy: 0.05
36652
+ },
36653
+ eco: {
36654
+ quality: 0.36,
36655
+ capability: 0.2,
36656
+ cost: 0.28,
36657
+ speed: 0.1,
36658
+ reliability: 0.04,
36659
+ legacy: 0.02
36660
+ },
36661
+ premium: {
36662
+ quality: 0.58,
36663
+ capability: 0.2,
36664
+ cost: 0.08,
36665
+ speed: 0.06,
36666
+ reliability: 0.06,
36667
+ legacy: 0.02
36668
+ },
36669
+ highStakesBoost: { quality: 0.08, reliability: 0.05 },
36670
+ latencySensitiveSpeedBoost: 0.08,
36671
+ affinityFloorGap: { auto: 0.1, eco: 0.22, premium: 0.05 }
36672
+ },
35256
36673
  classifier: {
35257
36674
  llmModel: "google/gemini-2.5-flash",
35258
36675
  llmMaxTokens: 10,
@@ -36378,8 +37795,8 @@ var init_config = __esm({
36378
37795
  // FREE — smaller, faster
36379
37796
  "free/deepseek-v4-flash",
36380
37797
  // FREE — 1M ctx; slow (~10 tok/s, 07-28 probe) but completes
36381
- "free/seed-oss-36b",
36382
- // FREE — live coder (qwen3.5-122b + qwen3-next died 2026-07; mistral-large-3-675b EOL'd 07-28)
37798
+ // seed-oss-36b sat here as the free coder until it EOL'd 2026-08-03 (HTTP 410).
37799
+ // gpt-oss-120b/20b already head this chain, so the rung is dropped, not retargeted.
36383
37800
  "google/gemini-3.1-flash-lite",
36384
37801
  // $0.25/$1.50 — newest flash-lite
36385
37802
  "openai/gpt-5.4-nano",
@@ -36496,8 +37913,8 @@ var init_config = __esm({
36496
37913
  "openai/gpt-5.3-codex",
36497
37914
  "deepseek/deepseek-chat",
36498
37915
  // Cheap, reliable
36499
- "free/seed-oss-36b"
36500
- // NVIDIA free ultimate backstop (qwen3-coder-480b retired)
37916
+ "free/gpt-oss-120b"
37917
+ // NVIDIA free ultimate backstop (was seed-oss-36b; EOL'd 2026-08-03)
36501
37918
  ]
36502
37919
  },
36503
37920
  REASONING: {
@@ -36586,8 +38003,8 @@ var init_config = __esm({
36586
38003
  // Previous flagship — 6,213ms, reliable
36587
38004
  "deepseek/deepseek-chat",
36588
38005
  // 1,431ms — cheap, reliable
36589
- "free/seed-oss-36b"
36590
- // NVIDIA free ultimate backstop (qwen3-coder-480b retired)
38006
+ "free/gpt-oss-120b"
38007
+ // NVIDIA free ultimate backstop (was seed-oss-36b; EOL'd 2026-08-03)
36591
38008
  ]
36592
38009
  },
36593
38010
  REASONING: {
@@ -36632,21 +38049,15 @@ var init_config = __esm({
36632
38049
  // Set to `true` to force agentic tiers; `false` to disable them entirely.
36633
38050
  }
36634
38051
  };
38052
+ registerStrategy(new PortfolioStrategy());
36635
38053
  }
36636
38054
  });
36637
38055
 
36638
38056
  // src/router/index.ts
36639
- function route(prompt, systemPrompt, maxOutputTokens, options) {
36640
- const strategy = getStrategy("rules");
36641
- return strategy.route(prompt, systemPrompt, maxOutputTokens, options);
36642
- }
36643
38057
  var init_router = __esm({
36644
38058
  "src/router/index.ts"() {
36645
38059
  "use strict";
36646
- init_strategy();
36647
- init_strategy();
36648
- init_selector();
36649
- init_config();
38060
+ init_dist();
36650
38061
  }
36651
38062
  });
36652
38063
 
@@ -56607,7 +58018,7 @@ var init_exclude_models = __esm({
56607
58018
 
56608
58019
  // src/config.ts
56609
58020
  var DEFAULT_PORT, PROXY_PORT;
56610
- var init_config2 = __esm({
58021
+ var init_config = __esm({
56611
58022
  "src/config.ts"() {
56612
58023
  "use strict";
56613
58024
  DEFAULT_PORT = 8402;
@@ -73005,27 +74416,27 @@ var require_dns = __commonJS({
73005
74416
  }
73006
74417
  );
73007
74418
  }
73008
- #defaultPick(origin2, hostnameRecords, affinity) {
74419
+ #defaultPick(origin2, hostnameRecords, affinity2) {
73009
74420
  let ip = null;
73010
74421
  const { records, offset } = hostnameRecords;
73011
74422
  let family;
73012
74423
  if (this.dualStack) {
73013
- if (affinity == null) {
74424
+ if (affinity2 == null) {
73014
74425
  if (offset == null || offset === maxInt) {
73015
74426
  hostnameRecords.offset = 0;
73016
- affinity = 4;
74427
+ affinity2 = 4;
73017
74428
  } else {
73018
74429
  hostnameRecords.offset++;
73019
- affinity = (hostnameRecords.offset & 1) === 1 ? 6 : 4;
74430
+ affinity2 = (hostnameRecords.offset & 1) === 1 ? 6 : 4;
73020
74431
  }
73021
74432
  }
73022
- if (records[affinity] != null && records[affinity].ips.length > 0) {
73023
- family = records[affinity];
74433
+ if (records[affinity2] != null && records[affinity2].ips.length > 0) {
74434
+ family = records[affinity2];
73024
74435
  } else {
73025
- family = records[affinity === 4 ? 6 : 4];
74436
+ family = records[affinity2 === 4 ? 6 : 4];
73026
74437
  }
73027
74438
  } else {
73028
- family = records[affinity];
74439
+ family = records[affinity2];
73029
74440
  }
73030
74441
  if (family == null || family.ips.length === 0) {
73031
74442
  return ip;
@@ -73042,7 +74453,7 @@ var require_dns = __commonJS({
73042
74453
  }
73043
74454
  if (Date.now() - ip.timestamp > ip.ttl) {
73044
74455
  family.ips.splice(position, 1);
73045
- return this.pick(origin2, hostnameRecords, affinity);
74456
+ return this.pick(origin2, hostnameRecords, affinity2);
73046
74457
  }
73047
74458
  return ip;
73048
74459
  }
@@ -73181,11 +74592,11 @@ var require_dns = __commonJS({
73181
74592
  throw new InvalidArgumentError("Invalid storage. Must be a object with methods: { get, set, full, delete }");
73182
74593
  }
73183
74594
  const dualStack = interceptorOpts?.dualStack ?? true;
73184
- let affinity;
74595
+ let affinity2;
73185
74596
  if (dualStack) {
73186
- affinity = interceptorOpts?.affinity ?? null;
74597
+ affinity2 = interceptorOpts?.affinity ?? null;
73187
74598
  } else {
73188
- affinity = interceptorOpts?.affinity ?? 4;
74599
+ affinity2 = interceptorOpts?.affinity ?? 4;
73189
74600
  }
73190
74601
  const opts = {
73191
74602
  maxTTL: interceptorOpts?.maxTTL ?? 1e4,
@@ -73193,7 +74604,7 @@ var require_dns = __commonJS({
73193
74604
  lookup: interceptorOpts?.lookup ?? null,
73194
74605
  pick: interceptorOpts?.pick ?? null,
73195
74606
  dualStack,
73196
- affinity,
74607
+ affinity: affinity2,
73197
74608
  maxItems: interceptorOpts?.maxItems ?? Infinity,
73198
74609
  storage: interceptorOpts?.storage
73199
74610
  };
@@ -86488,6 +87899,19 @@ function buildModelPricing() {
86488
87899
  }
86489
87900
  return map;
86490
87901
  }
87902
+ function buildModelCapabilities() {
87903
+ return Object.fromEntries(
87904
+ BLOCKRUN_MODELS.map((model) => [
87905
+ model.id,
87906
+ {
87907
+ contextWindow: model.contextWindow,
87908
+ maxOutputTokens: model.maxOutput,
87909
+ supportsTools: model.toolCalling === true,
87910
+ supportsVision: model.vision === true
87911
+ }
87912
+ ])
87913
+ );
87914
+ }
86491
87915
  function buildProxyModelList(createdAt = Math.floor(Date.now() / 1e3)) {
86492
87916
  const seen = /* @__PURE__ */ new Set();
86493
87917
  return OPENCLAW_MODELS.filter((model) => {
@@ -86833,7 +88257,8 @@ async function startProxy(options) {
86833
88257
  const modelPricing = buildModelPricing();
86834
88258
  const routerOpts = {
86835
88259
  config: routingConfig,
86836
- modelPricing
88260
+ modelPricing,
88261
+ modelCapabilities: buildModelCapabilities()
86837
88262
  };
86838
88263
  const deduplicator = new RequestDeduplicator();
86839
88264
  const responseCache2 = new ResponseCache(options.cacheConfig);
@@ -87979,6 +89404,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
87979
89404
  let maxTokens = DEFAULT_MAX_TOKENS;
87980
89405
  let routingProfile = null;
87981
89406
  let stickyExplicitModel;
89407
+ let pendingShadowComparison;
87982
89408
  let balanceFallbackNotice;
87983
89409
  let budgetDowngradeNotice;
87984
89410
  let budgetDowngradeHeaderMode;
@@ -88574,8 +90000,16 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
88574
90000
  const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
88575
90001
  const tools = parsed.tools;
88576
90002
  hasTools = Array.isArray(tools) && tools.length > 0;
90003
+ const requiresTools = hasTools && inferToolRequirement(prompt, systemPrompt, parsed.tool_choice);
90004
+ const toolNames = (tools ?? []).map((tool) => {
90005
+ if (typeof tool !== "object" || tool === null) return void 0;
90006
+ const fn = tool.function;
90007
+ if (typeof fn !== "object" || fn === null) return void 0;
90008
+ const name = fn.name;
90009
+ return typeof name === "string" ? name : void 0;
90010
+ }).filter((name) => name !== void 0);
88577
90011
  if (hasTools && tools) {
88578
- console.log(`[ClawRouter] Tools detected (${tools.length}), forcing agentic tiers`);
90012
+ console.log(`[ClawRouter] Tools detected (${tools.length}), required=${requiresTools}`);
88579
90013
  }
88580
90014
  hasVision = parsedMessages.some((m) => {
88581
90015
  if (Array.isArray(m.content)) {
@@ -88589,8 +90023,36 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
88589
90023
  routingDecision = route(prompt, systemPrompt, maxTokens, {
88590
90024
  ...routerOpts,
88591
90025
  routingProfile: routingProfile ?? void 0,
88592
- hasTools
90026
+ hasTools,
90027
+ toolCount: tools?.length ?? 0,
90028
+ toolNames,
90029
+ requiresTools,
90030
+ hasVision,
90031
+ requiresStructuredOutput: typeof parsed.response_format === "object" && parsed.response_format !== null
88593
90032
  });
90033
+ const shadow = routerOpts.config.shadow;
90034
+ const activeStrategy = routerOpts.config.strategy ?? "portfolio";
90035
+ const shadowRate = Math.max(0, Math.min(1, shadow?.sampleRate ?? 1));
90036
+ if (shadow && shadow.strategy !== activeStrategy && Math.random() < shadowRate) {
90037
+ const requiresStructuredOutput = typeof parsed.response_format === "object" && parsed.response_format !== null;
90038
+ const shadowDecision = route(prompt, systemPrompt, maxTokens, {
90039
+ ...routerOpts,
90040
+ config: { ...routerOpts.config, strategy: shadow.strategy },
90041
+ routingProfile: routingProfile ?? void 0,
90042
+ hasTools,
90043
+ toolCount: tools?.length ?? 0,
90044
+ toolNames,
90045
+ requiresTools,
90046
+ hasVision,
90047
+ requiresStructuredOutput
90048
+ });
90049
+ pendingShadowComparison = {
90050
+ shadow: shadowDecision,
90051
+ hasTools,
90052
+ hasVision,
90053
+ requiresStructuredOutput
90054
+ };
90055
+ }
88594
90056
  if (hasTools && routingDecision.tier === "SIMPLE") {
88595
90057
  console.log(
88596
90058
  `[ClawRouter] SIMPLE+tools: keeping agentic model ${routingDecision.model} (tools need reliable function-call support)`
@@ -88699,6 +90161,21 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
88699
90161
  );
88700
90162
  }
88701
90163
  }
90164
+ if (pendingShadowComparison) {
90165
+ try {
90166
+ options.onShadowRouted?.({
90167
+ executed: routingDecision,
90168
+ shadow: pendingShadowComparison.shadow,
90169
+ sameModel: routingDecision.model === pendingShadowComparison.shadow.model,
90170
+ hasTools: pendingShadowComparison.hasTools,
90171
+ hasVision: pendingShadowComparison.hasVision,
90172
+ requiresStructuredOutput: pendingShadowComparison.requiresStructuredOutput
90173
+ });
90174
+ } catch (err) {
90175
+ const message = err instanceof Error ? err.message : String(err);
90176
+ console.warn(`[ClawRouter] Shadow routing callback failed: ${message}`);
90177
+ }
90178
+ }
88702
90179
  options.onRouted?.(routingDecision);
88703
90180
  }
88704
90181
  }
@@ -89046,35 +90523,64 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
89046
90523
  const estimatedInputTokens = Math.ceil(body.length / 4);
89047
90524
  const estimatedTotalTokens = estimatedInputTokens + maxTokens;
89048
90525
  const tierConfigs = routingDecision.tierConfigs ?? routerOpts.config.tiers;
89049
- const fullChain = prependStickyExplicitModel(
89050
- getFallbackChain(routingDecision.tier, tierConfigs)
89051
- );
89052
- const contextFiltered = prependStickyExplicitModel(
89053
- getFallbackChainFiltered(
89054
- routingDecision.tier,
89055
- tierConfigs,
89056
- estimatedTotalTokens,
89057
- getModelContextWindow
89058
- )
90526
+ const rankedCandidates = routingDecision.candidates ?? getFallbackChain(routingDecision.tier, tierConfigs);
90527
+ const selectedModel = routingDecision.model;
90528
+ const fullChain = prependStickyExplicitModel([
90529
+ selectedModel,
90530
+ ...rankedCandidates.filter((model) => model !== selectedModel)
90531
+ ]);
90532
+ const contextFiltered = filterCandidatesByCapacity(
90533
+ fullChain,
90534
+ estimatedInputTokens,
90535
+ maxTokens,
90536
+ (modelId2) => {
90537
+ const model = BLOCKRUN_MODEL_BY_ID.get(modelId2);
90538
+ return model ? { contextWindow: model.contextWindow, maxOutput: model.maxOutput } : void 0;
90539
+ }
89059
90540
  );
90541
+ if (contextFiltered.length === 0) {
90542
+ const errPayload = JSON.stringify({
90543
+ error: {
90544
+ message: `No routed model can satisfy the requested context and output capacity (~${estimatedTotalTokens} tokens). Reduce the input or max_tokens and retry.`,
90545
+ type: "invalid_request_error",
90546
+ code: "context_capacity_exceeded"
90547
+ }
90548
+ });
90549
+ if (heartbeatInterval) {
90550
+ clearInterval(heartbeatInterval);
90551
+ heartbeatInterval = void 0;
90552
+ }
90553
+ if (headersSentEarly) {
90554
+ safeWrite(res, `data: ${errPayload}
90555
+
90556
+ data: [DONE]
90557
+
90558
+ `);
90559
+ res.end();
90560
+ } else {
90561
+ res.writeHead(400, { "Content-Type": "application/json" });
90562
+ res.end(errPayload);
90563
+ }
90564
+ completed = true;
90565
+ deduplicator.removeInflight(dedupKey);
90566
+ clearTimeout(timeoutId);
90567
+ req.removeListener("close", onClientClose);
90568
+ return;
90569
+ }
89060
90570
  const contextExcluded = fullChain.filter((m) => !contextFiltered.includes(m));
89061
90571
  if (contextExcluded.length > 0) {
89062
90572
  console.log(
89063
90573
  `[ClawRouter] Context filter (~${estimatedTotalTokens} tokens): excluded ${contextExcluded.join(", ")}`
89064
90574
  );
89065
90575
  }
89066
- const excludeFiltered = prependStickyExplicitModel(
89067
- filterByExcludeList(contextFiltered, excludeList)
89068
- );
90576
+ const excludeFiltered = filterByExcludeList(contextFiltered, excludeList);
89069
90577
  const excludeExcluded = contextFiltered.filter((m) => !excludeFiltered.includes(m));
89070
90578
  if (excludeExcluded.length > 0) {
89071
90579
  console.log(
89072
90580
  `[ClawRouter] Exclude filter: excluded ${excludeExcluded.join(", ")} (user preference)`
89073
90581
  );
89074
90582
  }
89075
- let toolFiltered = prependStickyExplicitModel(
89076
- filterByToolCalling(excludeFiltered, hasTools, supportsToolCalling)
89077
- );
90583
+ let toolFiltered = filterByToolCalling(excludeFiltered, hasTools, supportsToolCalling);
89078
90584
  const toolExcluded = excludeFiltered.filter((m) => !toolFiltered.includes(m));
89079
90585
  if (toolExcluded.length > 0) {
89080
90586
  console.log(
@@ -89093,19 +90599,17 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
89093
90599
  console.log(
89094
90600
  `[ClawRouter] Tool-compliance filter: excluded ${dropped.join(", ")} (unreliable tool schema handling)`
89095
90601
  );
89096
- toolFiltered = prependStickyExplicitModel(compliant);
90602
+ toolFiltered = compliant;
89097
90603
  }
89098
90604
  }
89099
- const visionFiltered = prependStickyExplicitModel(
89100
- filterByVision(toolFiltered, hasVision, supportsVision)
89101
- );
90605
+ const visionFiltered = filterByVision(toolFiltered, hasVision, supportsVision);
89102
90606
  const visionExcluded = toolFiltered.filter((m) => !visionFiltered.includes(m));
89103
90607
  if (visionExcluded.length > 0) {
89104
90608
  console.log(
89105
90609
  `[ClawRouter] Vision filter: excluded ${visionExcluded.join(", ")} (no vision support)`
89106
90610
  );
89107
90611
  }
89108
- modelsToTry = prependStickyExplicitModel(visionFiltered).slice(0, MAX_FALLBACK_ATTEMPTS);
90612
+ modelsToTry = visionFiltered.slice(0, MAX_FALLBACK_ATTEMPTS);
89109
90613
  modelsToTry = prioritizeNonRateLimited(modelsToTry);
89110
90614
  } else {
89111
90615
  modelsToTry = modelId ? [modelId] : [];
@@ -89995,7 +91499,7 @@ var init_proxy = __esm({
89995
91499
  init_client2();
89996
91500
  init_esm5();
89997
91501
  init_router();
89998
- init_rules();
91502
+ init_router();
89999
91503
  init_models();
90000
91504
  init_max_tokens();
90001
91505
  init_logger();
@@ -90009,7 +91513,7 @@ var init_proxy = __esm({
90009
91513
  init_session();
90010
91514
  init_updater();
90011
91515
  init_exclude_models();
90012
- init_config2();
91516
+ init_config();
90013
91517
  init_journal();
90014
91518
  init_upstream_proxy();
90015
91519
  init_textual_tool_calls();
@@ -90036,8 +91540,6 @@ var init_proxy = __esm({
90036
91540
  "free/gpt-oss-20b",
90037
91541
  "free/deepseek-v4-flash",
90038
91542
  // 1M ctx; slow (~10 tok/s, 07-28 probe) but completes
90039
- "free/seed-oss-36b",
90040
- // live coder (successor to retired qwen3-coder-480b)
90041
91543
  "free/mistral-nemotron",
90042
91544
  // strong instruction following
90043
91545
  "free/step-3.7-flash",
@@ -91643,7 +93145,7 @@ var init_headers = __esm({
91643
93145
 
91644
93146
  // node_modules/@polymarket/clob-client-v2/dist/config.js
91645
93147
  var AMOY_CONTRACTS, MATIC_CONTRACTS, COLLATERAL_TOKEN_DECIMALS, getContractConfig;
91646
- var init_config3 = __esm({
93148
+ var init_config2 = __esm({
91647
93149
  "node_modules/@polymarket/clob-client-v2/dist/config.js"() {
91648
93150
  "use strict";
91649
93151
  AMOY_CONTRACTS = {
@@ -91943,7 +93445,7 @@ var init_buildMarketOrderCreationArgs = __esm({
91943
93445
  "node_modules/@polymarket/clob-client-v2/dist/order-builder/helpers/buildMarketOrderCreationArgs.js"() {
91944
93446
  "use strict";
91945
93447
  init_constants2();
91946
- init_config3();
93448
+ init_config2();
91947
93449
  init_getMarketOrderRawAmounts();
91948
93450
  init_esm();
91949
93451
  }
@@ -92504,7 +94006,7 @@ var init_createMarketOrder = __esm({
92504
94006
  "node_modules/@polymarket/clob-client-v2/dist/order-builder/helpers/createMarketOrder.js"() {
92505
94007
  "use strict";
92506
94008
  init_signer();
92507
- init_config3();
94009
+ init_config2();
92508
94010
  init_buildMarketOrderCreationArgs();
92509
94011
  init_signatureTypeV2();
92510
94012
  init_buildOrder();
@@ -92612,7 +94114,7 @@ var init_buildOrderCreationArgs = __esm({
92612
94114
  "node_modules/@polymarket/clob-client-v2/dist/order-builder/helpers/buildOrderCreationArgs.js"() {
92613
94115
  "use strict";
92614
94116
  init_constants2();
92615
- init_config3();
94117
+ init_config2();
92616
94118
  init_getOrderRawAmounts();
92617
94119
  init_esm();
92618
94120
  }
@@ -92624,7 +94126,7 @@ var init_createOrder = __esm({
92624
94126
  "node_modules/@polymarket/clob-client-v2/dist/order-builder/helpers/createOrder.js"() {
92625
94127
  "use strict";
92626
94128
  init_signer();
92627
- init_config3();
94129
+ init_config2();
92628
94130
  init_signatureTypeV2();
92629
94131
  init_buildOrder();
92630
94132
  init_buildOrderCreationArgs();
@@ -92662,7 +94164,7 @@ var init_createExchangeV3OrderFromAmounts = __esm({
92662
94164
  "use strict";
92663
94165
  init_constants2();
92664
94166
  init_signer();
92665
- init_config3();
94167
+ init_config2();
92666
94168
  init_signatureTypeV2();
92667
94169
  init_buildOrder();
92668
94170
  createExchangeV3OrderFromAmounts = async (eoaSigner, chainId, signatureType, funderAddress, userOrder) => {
@@ -110464,7 +111966,7 @@ var init_axios2 = __esm({
110464
111966
 
110465
111967
  // node_modules/browser-or-node/dist/index.mjs
110466
111968
  var isBrowser, isNode, isWebWorker, isJsDom, isDeno, isBun;
110467
- var init_dist = __esm({
111969
+ var init_dist2 = __esm({
110468
111970
  "node_modules/browser-or-node/dist/index.mjs"() {
110469
111971
  "use strict";
110470
111972
  isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined";
@@ -110488,7 +111990,7 @@ var init_http_helpers = __esm({
110488
111990
  "node_modules/@polymarket/clob-client-v2/dist/http-helpers/index.js"() {
110489
111991
  "use strict";
110490
111992
  init_axios2();
110491
- init_dist();
111993
+ init_dist2();
110492
111994
  GET = "GET";
110493
111995
  POST = "POST";
110494
111996
  DELETE = "DELETE";
@@ -111520,10 +113022,10 @@ var init_client4 = __esm({
111520
113022
  });
111521
113023
 
111522
113024
  // node_modules/@polymarket/clob-client-v2/dist/index.js
111523
- var init_dist2 = __esm({
113025
+ var init_dist3 = __esm({
111524
113026
  "node_modules/@polymarket/clob-client-v2/dist/index.js"() {
111525
113027
  "use strict";
111526
- init_config3();
113028
+ init_config2();
111527
113029
  init_signatureTypeV2();
111528
113030
  init_side();
111529
113031
  init_clob();
@@ -111543,7 +113045,7 @@ import * as net from "net";
111543
113045
  import * as http4 from "http";
111544
113046
  import { Agent as HttpsAgent } from "https";
111545
113047
  var INTERNAL, Agent2;
111546
- var init_dist3 = __esm({
113048
+ var init_dist4 = __esm({
111547
113049
  "node_modules/agent-base/dist/index.js"() {
111548
113050
  "use strict";
111549
113051
  init_helpers();
@@ -111782,7 +113284,7 @@ function createNegotiateAuth() {
111782
113284
  };
111783
113285
  };
111784
113286
  }
111785
- var init_dist4 = __esm({
113287
+ var init_dist5 = __esm({
111786
113288
  "node_modules/proxy-agent-negotiate/dist/index.js"() {
111787
113289
  "use strict";
111788
113290
  }
@@ -111809,13 +113311,13 @@ function omit(obj, ...keys) {
111809
113311
  return ret;
111810
113312
  }
111811
113313
  var import_debug2, debug2, setServernameFromNonIpHost, HttpsProxyAgent2;
111812
- var init_dist5 = __esm({
113314
+ var init_dist6 = __esm({
111813
113315
  "node_modules/https-proxy-agent/dist/index.js"() {
111814
113316
  "use strict";
111815
113317
  import_debug2 = __toESM(require_src(), 1);
111816
- init_dist3();
111817
- init_parse_proxy_response();
111818
113318
  init_dist4();
113319
+ init_parse_proxy_response();
113320
+ init_dist5();
111819
113321
  debug2 = (0, import_debug2.default)("https-proxy-agent");
111820
113322
  setServernameFromNonIpHost = (options) => {
111821
113323
  if (options.servername === void 0 && options.host && !net2.isIP(options.host)) {
@@ -112028,7 +113530,7 @@ var POLYGON_CHAIN_ID, CTF_EXCHANGE_V2, NEG_RISK_CTF_EXCHANGE_V2, NEG_RISK_ADAPTE
112028
113530
  var init_constants3 = __esm({
112029
113531
  "src/polymarket/constants.ts"() {
112030
113532
  "use strict";
112031
- init_dist2();
113533
+ init_dist3();
112032
113534
  POLYGON_CHAIN_ID = 137;
112033
113535
  CTF_EXCHANGE_V2 = "0xE111180000d2663C0091e4f400237545B87B996B";
112034
113536
  NEG_RISK_CTF_EXCHANGE_V2 = "0xe2222d279d744050d28e00520010520000310F59";
@@ -112565,11 +114067,11 @@ var init_client5 = __esm({
112565
114067
  "src/polymarket/client.ts"() {
112566
114068
  "use strict";
112567
114069
  init_axios2();
112568
- init_dist5();
114070
+ init_dist6();
112569
114071
  init_esm();
112570
114072
  init_accounts();
112571
114073
  init_chains();
112572
- init_dist2();
114074
+ init_dist3();
112573
114075
  init_wallet_adapter();
112574
114076
  init_constants3();
112575
114077
  init_creds();
@@ -112939,7 +114441,7 @@ var ledger;
112939
114441
  var init_orders = __esm({
112940
114442
  "src/polymarket/orders.ts"() {
112941
114443
  "use strict";
112942
- init_dist2();
114444
+ init_dist3();
112943
114445
  init_client5();
112944
114446
  init_constants3();
112945
114447
  init_creds();
@@ -220121,7 +221623,7 @@ var init_relayer = __esm({
220121
221623
  "use strict";
220122
221624
  import_builder_relayer_client = __toESM(require_dist3(), 1);
220123
221625
  import_builder_signing_sdk = __toESM(require_dist4(), 1);
220124
- init_dist2();
221626
+ init_dist3();
220125
221627
  init_esm();
220126
221628
  init_chains();
220127
221629
  init_client5();
@@ -220461,7 +221963,7 @@ var init_setup = __esm({
220461
221963
  "use strict";
220462
221964
  init_esm();
220463
221965
  init_chains();
220464
- init_dist2();
221966
+ init_dist3();
220465
221967
  init_client5();
220466
221968
  init_constants3();
220467
221969
  init_creds();
@@ -220915,7 +222417,7 @@ function sleep2(ms) {
220915
222417
  return new Promise((r2) => setTimeout(r2, ms));
220916
222418
  }
220917
222419
  var BlockrunError, PaymentError, APIError, BASE_CHAIN_ID2, USDC_BASE2, USDC_DOMAIN, TRANSFER_TYPES, BLOCKRUN_SERVICE_CODE2, LOCALHOST_DOMAINS, BLOCKRUN_DIR2, COST_LOG_FILE, SDK_VERSION, USER_AGENT2, DEFAULT_TIMEOUT, PHONE_PRICES, DEFAULT_API_URL13, DEFAULT_TIMEOUT13, DEFAULT_POLL_INTERVAL_MS, DEFAULT_POLL_BUDGET_MS, MAX_SIGNED_AUTH_SECONDS, BlockrunClient, WALLET_DIR2, WALLET_FILE3, WALLET_DIR22, SOLANA_WALLET_FILE, CACHE_DIR, DATA_DIR, COST_LOG_FILE2, DEFAULT_TTL;
220918
- var init_dist6 = __esm({
222420
+ var init_dist7 = __esm({
220919
222421
  "node_modules/@blockrun/llm/dist/index.js"() {
220920
222422
  "use strict";
220921
222423
  init_accounts();
@@ -221483,7 +222985,7 @@ var init_fund = __esm({
221483
222985
  "src/polymarket/fund.ts"() {
221484
222986
  "use strict";
221485
222987
  init_axios2();
221486
- init_dist6();
222988
+ init_dist7();
221487
222989
  init_wallet_adapter();
221488
222990
  init_client5();
221489
222991
  init_constants3();
@@ -221962,29 +223464,29 @@ var init_spend_control = __esm({
221962
223464
  getLimits() {
221963
223465
  return { ...this.limits };
221964
223466
  }
221965
- check(estimatedCost) {
223467
+ check(estimatedCost2) {
221966
223468
  const now2 = this.now();
221967
223469
  if (this.limits.perRequest !== void 0) {
221968
- if (estimatedCost > this.limits.perRequest) {
223470
+ if (estimatedCost2 > this.limits.perRequest) {
221969
223471
  return {
221970
223472
  allowed: false,
221971
223473
  blockedBy: "perRequest",
221972
223474
  remaining: this.limits.perRequest,
221973
- reason: `Per-request limit exceeded: $${estimatedCost.toFixed(4)} > $${this.limits.perRequest.toFixed(2)} max`
223475
+ reason: `Per-request limit exceeded: $${estimatedCost2.toFixed(4)} > $${this.limits.perRequest.toFixed(2)} max`
221974
223476
  };
221975
223477
  }
221976
223478
  }
221977
223479
  if (this.limits.hourly !== void 0) {
221978
223480
  const hourlySpent = this.getSpendingInWindow(now2 - HOUR_MS, now2);
221979
223481
  const remaining = this.limits.hourly - hourlySpent;
221980
- if (estimatedCost > remaining) {
223482
+ if (estimatedCost2 > remaining) {
221981
223483
  const oldestInWindow = this.history.find((r2) => r2.timestamp >= now2 - HOUR_MS);
221982
223484
  const resetIn = oldestInWindow ? Math.ceil((oldestInWindow.timestamp + HOUR_MS - now2) / 1e3) : 0;
221983
223485
  return {
221984
223486
  allowed: false,
221985
223487
  blockedBy: "hourly",
221986
223488
  remaining,
221987
- reason: `Hourly limit exceeded: $${(hourlySpent + estimatedCost).toFixed(2)} > $${this.limits.hourly.toFixed(2)} max`,
223489
+ reason: `Hourly limit exceeded: $${(hourlySpent + estimatedCost2).toFixed(2)} > $${this.limits.hourly.toFixed(2)} max`,
221988
223490
  resetIn
221989
223491
  };
221990
223492
  }
@@ -221992,26 +223494,26 @@ var init_spend_control = __esm({
221992
223494
  if (this.limits.daily !== void 0) {
221993
223495
  const dailySpent = this.getSpendingInWindow(now2 - DAY_MS, now2);
221994
223496
  const remaining = this.limits.daily - dailySpent;
221995
- if (estimatedCost > remaining) {
223497
+ if (estimatedCost2 > remaining) {
221996
223498
  const oldestInWindow = this.history.find((r2) => r2.timestamp >= now2 - DAY_MS);
221997
223499
  const resetIn = oldestInWindow ? Math.ceil((oldestInWindow.timestamp + DAY_MS - now2) / 1e3) : 0;
221998
223500
  return {
221999
223501
  allowed: false,
222000
223502
  blockedBy: "daily",
222001
223503
  remaining,
222002
- reason: `Daily limit exceeded: $${(dailySpent + estimatedCost).toFixed(2)} > $${this.limits.daily.toFixed(2)} max`,
223504
+ reason: `Daily limit exceeded: $${(dailySpent + estimatedCost2).toFixed(2)} > $${this.limits.daily.toFixed(2)} max`,
222003
223505
  resetIn
222004
223506
  };
222005
223507
  }
222006
223508
  }
222007
223509
  if (this.limits.session !== void 0) {
222008
223510
  const remaining = this.limits.session - this.sessionSpent;
222009
- if (estimatedCost > remaining) {
223511
+ if (estimatedCost2 > remaining) {
222010
223512
  return {
222011
223513
  allowed: false,
222012
223514
  blockedBy: "session",
222013
223515
  remaining,
222014
- reason: `Session limit exceeded: $${(this.sessionSpent + estimatedCost).toFixed(2)} > $${this.limits.session.toFixed(2)} max`
223516
+ reason: `Session limit exceeded: $${(this.sessionSpent + estimatedCost2).toFixed(2)} > $${this.limits.session.toFixed(2)} max`
222015
223517
  };
222016
223518
  }
222017
223519
  }
@@ -222687,6 +224189,11 @@ async function startProxyInBackground(api, startupGeneration) {
222687
224189
  `[${decision.tier}] ${decision.model} $${cost} (saved ${saved}%) | ${decision.reasoning}`
222688
224190
  );
222689
224191
  },
224192
+ onShadowRouted: (comparison) => {
224193
+ api.logger.info(
224194
+ `[router-shadow] executed=${comparison.executed.model} (${comparison.executed.method}) candidate=${comparison.shadow.model} (${comparison.shadow.method}) same=${comparison.sameModel} tools=${comparison.hasTools} vision=${comparison.hasVision} structured=${comparison.requiresStructuredOutput}`
224195
+ );
224196
+ },
222690
224197
  onLowBalance: (info) => {
222691
224198
  api.logger.warn(`[!] Low balance: ${info.balanceUSD}. Fund wallet: ${info.walletAddress}`);
222692
224199
  },
@@ -223884,6 +225391,7 @@ export {
223884
225391
  deriveEvmKey,
223885
225392
  deriveSolanaKeyBytes,
223886
225393
  fetchWithRetry,
225394
+ filterCandidatesByCapacity,
223887
225395
  formatDuration,
223888
225396
  formatStatsAscii,
223889
225397
  generateWalletMnemonic,
@@ -223896,6 +225404,7 @@ export {
223896
225404
  getSessionId,
223897
225405
  getStats,
223898
225406
  hashRequestContent,
225407
+ inferToolRequirement,
223899
225408
  injectAuthProfile,
223900
225409
  injectModelsConfig,
223901
225410
  isAgenticModel,