@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/cli.js CHANGED
@@ -33163,7 +33163,7 @@ var init_esm5 = __esm({
33163
33163
  }
33164
33164
  });
33165
33165
 
33166
- // src/router/rules.ts
33166
+ // node_modules/@blockrun/router-core/dist/index.js
33167
33167
  function scoreTokenCount(estimatedTokens, thresholds) {
33168
33168
  if (estimatedTokens < thresholds.simple) {
33169
33169
  return { name: "tokenCount", score: -1, signal: `short (${estimatedTokens} tokens)` };
@@ -33403,13 +33403,6 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
33403
33403
  function calibrateConfidence(distance, steepness) {
33404
33404
  return 1 / (1 + Math.exp(-steepness * distance));
33405
33405
  }
33406
- var init_rules = __esm({
33407
- "src/router/rules.ts"() {
33408
- "use strict";
33409
- }
33410
- });
33411
-
33412
- // src/router/selector.ts
33413
33406
  function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPricing, estimatedInputTokens, maxOutputTokens, routingProfile, agenticScore) {
33414
33407
  const tierConfig = tierConfigs[tier];
33415
33408
  const model = tierConfig.primary;
@@ -33498,19 +33491,14 @@ function getFallbackChainFiltered(tier, tierConfigs, estimatedTotalTokens, getCo
33498
33491
  }
33499
33492
  return filtered;
33500
33493
  }
33501
- var BASELINE_MODEL_ID, BASELINE_INPUT_PRICE, BASELINE_OUTPUT_PRICE, SERVER_MARGIN_PERCENT, MIN_PAYMENT_USD;
33502
- var init_selector = __esm({
33503
- "src/router/selector.ts"() {
33504
- "use strict";
33505
- BASELINE_MODEL_ID = "anthropic/claude-opus-4.7";
33506
- BASELINE_INPUT_PRICE = 5;
33507
- BASELINE_OUTPUT_PRICE = 25;
33508
- SERVER_MARGIN_PERCENT = 5;
33509
- MIN_PAYMENT_USD = 1e-3;
33510
- }
33511
- });
33512
-
33513
- // src/router/strategy.ts
33494
+ function filterCandidatesByCapacity(models, estimatedInputTokens, requestedOutputTokens, getCapabilities2) {
33495
+ const filtered = models.filter((modelId) => {
33496
+ const capabilities = getCapabilities2(modelId);
33497
+ if (!capabilities) return true;
33498
+ return capabilities.contextWindow >= (estimatedInputTokens + requestedOutputTokens) * 1.1 && capabilities.maxOutput >= requestedOutputTokens;
33499
+ });
33500
+ return filtered;
33501
+ }
33514
33502
  function applyPromotions(tierConfigs, promotions, profile, now2 = /* @__PURE__ */ new Date()) {
33515
33503
  if (!promotions || promotions.length === 0) return tierConfigs;
33516
33504
  let result = tierConfigs;
@@ -33540,19 +33528,666 @@ function getStrategy(name) {
33540
33528
  }
33541
33529
  return strategy;
33542
33530
  }
33543
- var RulesStrategy, registry;
33544
- var init_strategy = __esm({
33545
- "src/router/strategy.ts"() {
33531
+ function registerStrategy(strategy) {
33532
+ registry.set(strategy.name, strategy);
33533
+ }
33534
+ function inferToolRequirement(prompt, _systemPrompt, toolChoice) {
33535
+ if (toolChoice === "none") return false;
33536
+ if (toolChoice === "required") return true;
33537
+ if (typeof toolChoice === "object" && toolChoice !== null && toolChoice.type === "function") {
33538
+ return true;
33539
+ }
33540
+ const text = prompt;
33541
+ const explicitTool = /\b(?:use|call|invoke)\s+(?:the\s+)?[\w.-]+\s+(?:tool|function|api)\b|\btool[_ -]?call\b|使用.{0,20}(?:工具|函数|接口)|调用.{0,20}(?:工具|函数|接口)/i;
33542
+ 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;
33543
+ 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;
33544
+ 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;
33545
+ return explicitTool.test(text) || codeEnvironment.test(text) || webAction.test(text) || statefulAction.test(text);
33546
+ }
33547
+ function likelyNeedsParallelToolCalls(prompt, needsTools, toolCount, toolNames) {
33548
+ if (!needsTools || toolCount === void 0 || toolCount < 1) return false;
33549
+ const text = prompt.trim();
33550
+ 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(
33551
+ text
33552
+ );
33553
+ if (explicitRepeat) return true;
33554
+ const sentenceClauses = text.split(/[.!?。!?]+/).map((part) => part.trim()).filter((part) => part.length >= 8);
33555
+ if (/\b(?:also|additionally|furthermore)\b|另外|此外|그리고/i.test(text) && sentenceClauses.length >= 2 || /\band\s+(?:also|for the)\b/i.test(text))
33556
+ return true;
33557
+ 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(
33558
+ text
33559
+ );
33560
+ if (pairedQuantity) return true;
33561
+ const operationTokens = /* @__PURE__ */ new Set([
33562
+ "add",
33563
+ "delete",
33564
+ "remove",
33565
+ "cancel",
33566
+ "return",
33567
+ "exchange",
33568
+ "modify",
33569
+ "book",
33570
+ "transfer",
33571
+ "send",
33572
+ "upload",
33573
+ "download",
33574
+ "create",
33575
+ "close"
33576
+ ]);
33577
+ const lowered = text.toLowerCase();
33578
+ const matchedOperationTokens = new Set(
33579
+ (toolNames ?? []).flatMap((name) => name.toLowerCase().split(/[^a-z0-9\u3400-\u9fff]+/)).filter((token) => operationTokens.has(token) && lowered.includes(token))
33580
+ );
33581
+ if (matchedOperationTokens.size >= 2) return true;
33582
+ const nonEmptyLines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
33583
+ const quantityMentions = text.match(
33584
+ /\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
33585
+ ) ?? [];
33586
+ if (nonEmptyLines.length >= 2 && quantityMentions.length >= 2) return true;
33587
+ const repeatedLookup = /\b(?:weather|climate|clima|tiempo|temperature|snow|news|report)\b|天气|气象|温度|降雪|新闻|报告/i.test(
33588
+ text
33589
+ );
33590
+ const multiLocationConnector = /\b(?:and also|both|y|e)\b|还有|以及|和|、/i.test(text);
33591
+ const commaSeparatedLocations = (text.match(/[,,]/g) ?? []).length >= 2;
33592
+ if (repeatedLookup && (multiLocationConnector || commaSeparatedLocations)) return true;
33593
+ const distinctOrderParts = /\b(?:food|meal)\b[\s\S]*\bdrink\b|\bdrink\b[\s\S]*\b(?:food|meal)\b/i.test(text);
33594
+ const koreanParallelClauses = (text.match(/,/g) ?? []).length >= 3 && /하고|그리고/.test(text);
33595
+ return distinctOrderParts || koreanParallelClauses;
33596
+ }
33597
+ function classifyTask(prompt, systemPrompt, options) {
33598
+ const fullText = `${systemPrompt ?? ""} ${prompt}`;
33599
+ const estimatedInputTokens = Math.ceil(fullText.length / 4);
33600
+ const scanLimit = Math.max(1, Math.min(8e3, options.config.classifier.promptTruncationChars));
33601
+ const sample = (value) => {
33602
+ if (value.length <= scanLimit) return value;
33603
+ const prefixLength = Math.ceil(scanLimit / 2);
33604
+ return `${value.slice(0, prefixLength)}
33605
+ ${value.slice(-(scanLimit - prefixLength))}`;
33606
+ };
33607
+ const scannedPrompt = sample(prompt);
33608
+ const scannedSystemPrompt = sample(systemPrompt ?? "");
33609
+ const scannedFullText = `${scannedSystemPrompt} ${scannedPrompt}`;
33610
+ const text = scannedPrompt.toLowerCase();
33611
+ const explicitCodeSignal = /```|\b(?:typescript|javascript|python|rust|java|sql|stack trace|traceback|exception)\b|\.(?:ts|tsx|js|py|go|rs)\b/i.test(
33612
+ scannedPrompt
33613
+ );
33614
+ 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(
33615
+ scannedPrompt
33616
+ );
33617
+ const nativeCodeSignal = /\b(?:programmed|written|implemented?|code)\s+(?:in|using)\s+(?:c\+\+|c|rust|go)\b/i.test(
33618
+ scannedPrompt
33619
+ );
33620
+ const hasCode = explicitCodeSignal || codeConstructSignal || nativeCodeSignal;
33621
+ const toolsAvailable = options.hasTools ?? false;
33622
+ const needsTools = options.requiresTools ?? (toolsAvailable && inferToolRequirement(scannedPrompt, scannedSystemPrompt));
33623
+ const likelyParallelToolCalls = likelyNeedsParallelToolCalls(
33624
+ scannedPrompt,
33625
+ needsTools,
33626
+ options.toolCount,
33627
+ options.toolNames
33628
+ );
33629
+ const normalizedToolNames = (options.toolNames ?? []).map((name) => name.toLowerCase());
33630
+ const airlineToolSignal = normalizedToolNames.some(
33631
+ (name) => /(?:flight|reservation|airport|baggage|passenger)/.test(name)
33632
+ );
33633
+ const retailToolSignal = normalizedToolNames.some(
33634
+ (name) => /(?:order|product|item|return|exchange|address)/.test(name)
33635
+ );
33636
+ const webResearchToolSignal = normalizedToolNames.some(
33637
+ (name) => /^(?:web_?search|web_?fetch)$/.test(name)
33638
+ );
33639
+ const agentDomain = airlineToolSignal && !retailToolSignal ? "airline" : retailToolSignal && !airlineToolSignal ? "retail" : webResearchToolSignal ? "web_research" : "other";
33640
+ const clueConnectors = scannedFullText.match(
33641
+ /\b(?:after|before|while|where|whose|which|in \d{4}|as of|over \d+|another|also|furthermore)\b|(?:之后|之前|其中|截至|超过|另一个|此外)/gi
33642
+ ) ?? [];
33643
+ 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(
33644
+ scannedFullText
33645
+ );
33646
+ const exactAnswerSignal = /\b(?:exact answer|single best-supported answer|following clues|multiple public sources)\b|(?:精确答案|根据.*线索|多个公开来源)/i.test(
33647
+ scannedFullText
33648
+ );
33649
+ const deepWebResearch = agentDomain === "web_research" && (exactAnswerSignal || entityResolutionSignal && (clueConnectors.length >= 3 || prompt.length >= 320));
33650
+ 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(
33651
+ scannedPrompt
33652
+ );
33653
+ 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(
33654
+ scannedPrompt
33655
+ );
33656
+ const globalChoiceSignal = globalOptimizationSignal || globalScopeSignal;
33657
+ const crossRecordSignal = /\b(?:another|other|different|previous)\s+(?:order|reservation|booking|account|address)\b|另一(?:个)?(?:订单|预订|账户|地址)|其他(?:订单|预订|账户|地址)/i.test(
33658
+ scannedPrompt
33659
+ );
33660
+ const reservationIds = scannedPrompt.match(/\b[A-Z0-9]{6}\b/g) ?? [];
33661
+ 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(
33662
+ scannedPrompt
33663
+ ) || new Set(reservationIds).size >= 2);
33664
+ const conditionalGlobalWorkflowSignal = agentDomain === "airline" && globalScopeSignal && /\b(?:if|that (?:contain|have)|longer than|shorter than|under|over|at (?:most|least)|wherever possible)\b|如果|超过|少于|不超过|尽可能/i.test(
33665
+ scannedPrompt
33666
+ ) && /\b(?:cancel|change|upgrade|move|book)\b[\s\S]*\b(?:cancel|change|upgrade|move|book)\b|取消[\s\S]*(?:升级|更改)|升级[\s\S]*(?:取消|更改)/i.test(
33667
+ scannedPrompt
33668
+ );
33669
+ const policyExceptionSignal = agentDomain === "retail" && /\b(?:return|refund|send back|get (?:my |the )?money back)\b|退货|退款|退回/i.test(
33670
+ scannedPrompt
33671
+ ) && /\b(?:amex|american express|visa|mastercard|credit card|debit card|different card|another card|other card)\b|信用卡|借记卡|其他卡|另一张卡/i.test(
33672
+ scannedPrompt
33673
+ );
33674
+ const singleSelectedPolicyException = policyExceptionSignal && /\b(?:return|refund|send back)\b[^.!?。!?]{0,96}\b(?:the )?(?:pricier|cheaper|more expensive|less expensive|costlier|one)\b/i.test(
33675
+ scannedPrompt
33676
+ );
33677
+ const negotiatedWorkflowSignal = agentDomain === "retail" && /\b(?:return|exchange)\b|退货|退回|换货|交换/i.test(scannedPrompt);
33678
+ const numberedSteps = (scannedPrompt.match(/(?:^|\s)\d+(?:\.\d+)*[.)]\s+/g) ?? []).length;
33679
+ const complexMultiToolPlan = likelyParallelToolCalls && ((options.toolCount ?? 0) >= 6 || numberedSteps >= 3 || prompt.length > 1200);
33680
+ let agentRisk = needsTools && singleSelectedPolicyException ? "policy_exception_simple" : needsTools && policyExceptionSignal ? "policy_exception" : (
33681
+ // Airline prompts that require a global optimum (for example the
33682
+ // cheapest itinerary across several candidates) are materially harder
33683
+ // than applying one change to every passenger in a known reservation.
33684
+ // Full-session evidence supports Sonnet for the former, while upgrading
33685
+ // the latter merely because it says "all passengers" caused a large cost
33686
+ // increase without a quality gain.
33687
+ needsTools && agentDomain === "airline" && (globalOptimizationSignal || conditionalGlobalWorkflowSignal) ? "complex_high" : needsTools && (likelyParallelToolCalls || globalChoiceSignal || crossRecordSignal || crossReservationBatchSignal || negotiatedWorkflowSignal) ? "high" : "standard"
33688
+ );
33689
+ const needsVision = options.hasVision ?? false;
33690
+ const needsStructuredOutput = options.requiresStructuredOutput ?? false;
33691
+ const latencySensitive = /\b(?:urgent|asap|fast|quick|low latency|real[- ]time)\b|尽快|马上|快速|低延迟/i.test(
33692
+ scannedFullText
33693
+ );
33694
+ const highStakes = /\b(?:production|security|payment|legal|medical|financial|audit)\b|生产|安全|支付|法律|医疗|财务|审计/i.test(
33695
+ scannedFullText
33696
+ );
33697
+ const terminalToolSignal = normalizedToolNames.some(
33698
+ (name) => /^(?:terminalexec|terminalinspect|terminalsendkeys)$/.test(name)
33699
+ );
33700
+ 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(
33701
+ scannedPrompt
33702
+ );
33703
+ 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(
33704
+ scannedPrompt
33705
+ );
33706
+ const mentionedTerminalRuntimes = new Set(
33707
+ (scannedPrompt.match(/\b(?:gcc|clang|rustc|javac|go\s+build|node|python)\b/gi) ?? []).map(
33708
+ (name) => name.toLowerCase().replace(/\s+/g, " ")
33709
+ )
33710
+ );
33711
+ const terminalCrossRuntimeArtifact = terminalToolSignal && (/\bpolyglot\b/i.test(scannedPrompt) || /\b(?:both|each)\b[\s\S]{0,120}\b(?:compilers?|runtimes?|toolchains?)\b/i.test(
33712
+ scannedPrompt
33713
+ ) || mentionedTerminalRuntimes.size >= 2 && /\b(?:compile|build|run|execute)\b/i.test(scannedPrompt));
33714
+ const terminalFrameworkToNativeArtifact = terminalToolSignal && /\b(?:pytorch|tensorflow|jax|onnx|state[_ -]?dict|checkpoint|safetensors?)\b|\.(?:pth|pt|onnx)\b/i.test(
33715
+ scannedPrompt
33716
+ ) && /\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(
33717
+ scannedPrompt
33718
+ ) && /\b(?:inference|model|weights?|tensor|export|convert|load)\b/i.test(scannedPrompt);
33719
+ if (needsTools && (terminalComplexRepair || terminalCrossRuntimeArtifact || terminalFrameworkToNativeArtifact) && (agentRisk === "standard" || agentRisk === "high"))
33720
+ agentRisk = "complex_high";
33721
+ 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(
33722
+ scannedPrompt
33723
+ );
33724
+ const terminalCredentialSignal = /\b(?:ssh|nginx|certificate|authentication|credentials?|passwords?|api keys?|deploy|production|encrypt|gpg|shred|securely delete|decommission)\b/i.test(
33725
+ scannedPrompt
33726
+ ) || /\b(?:access|auth|authentication|bearer|secret|api)\s+tokens?\b|\btokens?\s+(?:secret|credential|authentication)\b/i.test(
33727
+ scannedPrompt
33728
+ );
33729
+ const terminalSafetySensitive = terminalToolSignal && (highStakes || terminalCredentialSignal);
33730
+ const implicitTerminalCode = needsTools && terminalToolSignal && agentRisk === "standard" && !highStakes && !complexTerminalOperation && numberedSteps < 3 && prompt.length <= 1e3 && simpleTerminalArtifact;
33731
+ const language = /[\u3400-\u9fff]/.test(scannedFullText) ? "zh" : "other";
33732
+ const multipleChoiceSignals = (scannedPrompt.match(/(?:^|\n)\s*[A-D][.)]\s+/gim) ?? []).length;
33733
+ const numericSignals = (scannedPrompt.match(/-?\d+(?:[.,]\d+)?/g) ?? []).length;
33734
+ const compactMathProblem = !hasCode && prompt.length < 2500 && numericSignals >= 2 && (/[+×÷=%$€£¥]|\b(?:total|each|per|times|half|twice|percent|how many|how much|calculate)\b/i.test(
33735
+ scannedPrompt
33736
+ ) || /[??]\s*$/.test(scannedPrompt.trim()) || numericSignals >= 3);
33737
+ let taskType = "chat";
33738
+ if (needsVision) taskType = "vision";
33739
+ else if (estimatedInputTokens > 8e4) taskType = "long_context";
33740
+ else if (needsTools && (hasCode || implicitTerminalCode)) taskType = "code_agent";
33741
+ else if (needsTools && likelyParallelToolCalls && !complexMultiToolPlan)
33742
+ taskType = "tool_agent_parallel";
33743
+ else if (needsTools) taskType = "tool_agent";
33744
+ else if (multipleChoiceSignals >= 3) taskType = "reasoning_mcq";
33745
+ else if (compactMathProblem) taskType = "reasoning_math";
33746
+ else if (/\b(?:bug|debug|error|failure|failing|regression|crash|修复|报错|错误|调试)\b/i.test(text))
33747
+ taskType = "debug";
33748
+ else if (hasCode || /\b(?:refactor|implement|patch|edit|rewrite|重构|实现|修改)\b/i.test(text))
33749
+ taskType = "code_edit";
33750
+ else if (needsStructuredOutput || /\b(?:extract|json|schema|csv|字段|提取)\b/i.test(text))
33751
+ taskType = "extraction";
33752
+ else if (/\b(?:prove|derive|theorem|formal|mathematical|reasoning|证明|推导|定理|数学)\b/i.test(text))
33753
+ taskType = "reasoning";
33754
+ return {
33755
+ taskType,
33756
+ estimatedInputTokens,
33757
+ hasCode,
33758
+ needsTools,
33759
+ toolsAvailable,
33760
+ needsVision,
33761
+ needsStructuredOutput,
33762
+ latencySensitive,
33763
+ highStakes,
33764
+ language,
33765
+ likelyParallelToolCalls,
33766
+ complexMultiToolPlan,
33767
+ agentDomain,
33768
+ deepWebResearch,
33769
+ agentRisk,
33770
+ terminalToolSignal,
33771
+ terminalSafetySensitive,
33772
+ implicitTerminalCode
33773
+ };
33774
+ }
33775
+ function affinity(modelId, task, language = "other", agentDomain = "other", deepWebResearch = false, agentRisk = "standard", terminalToolSignal = false, terminalSafetySensitive = false) {
33776
+ const id2 = modelId.toLowerCase();
33777
+ const modelName = id2.slice(id2.indexOf("/") + 1);
33778
+ const match = (values, score) => values.some((value) => modelName === value) ? score : 0;
33779
+ const base3 = 0.68;
33780
+ switch (task) {
33781
+ case "code_agent":
33782
+ if (terminalToolSignal && agentRisk === "complex_high") {
33783
+ return Math.max(
33784
+ base3,
33785
+ match(["claude-sonnet-5"], 1),
33786
+ match(["gpt-5.3-codex"], 0.87),
33787
+ match(["gpt-5-mini"], 0.78),
33788
+ match(["gemini-3.5-flash"], 0.76)
33789
+ );
33790
+ }
33791
+ return Math.max(
33792
+ base3,
33793
+ match(["gpt-5.3-codex"], 1),
33794
+ match(["claude-sonnet-5"], 0.98),
33795
+ match(["gpt-5-mini"], 0.96),
33796
+ match(["gemini-3.5-flash"], 0.92),
33797
+ match(["kimi-k3"], 0.9),
33798
+ match(["deepseek-v4-pro", "glm-5.2"], 0.88)
33799
+ );
33800
+ case "tool_agent":
33801
+ if (terminalToolSignal && agentRisk === "complex_high") {
33802
+ return Math.max(
33803
+ base3,
33804
+ match(["claude-sonnet-5"], 1),
33805
+ match(["gpt-5.3-codex"], 0.87),
33806
+ match(["gpt-5-mini"], 0.78),
33807
+ match(["gemini-3.5-flash"], 0.76)
33808
+ );
33809
+ }
33810
+ if (terminalToolSignal && !terminalSafetySensitive) {
33811
+ return Math.max(
33812
+ base3,
33813
+ match(["gpt-5-mini"], 1),
33814
+ match(["gpt-5.3-codex"], 0.98),
33815
+ match(["claude-sonnet-5"], 0.9),
33816
+ match(["gemini-3.5-flash"], 0.89)
33817
+ );
33818
+ }
33819
+ if (terminalToolSignal && terminalSafetySensitive) {
33820
+ return Math.max(
33821
+ base3,
33822
+ match(["claude-sonnet-5"], 1),
33823
+ match(["claude-opus-4.8"], 0.9),
33824
+ match(["gpt-5.3-codex"], 0.84)
33825
+ );
33826
+ }
33827
+ if (agentDomain === "web_research") {
33828
+ return deepWebResearch ? Math.max(
33829
+ base3,
33830
+ match(["claude-sonnet-5"], 1),
33831
+ match(["gpt-5-mini"], 0.88),
33832
+ match(["gemini-3.5-flash"], 0.84),
33833
+ match(["claude-opus-5"], 0.8),
33834
+ match(["claude-opus-4.8"], 0.78)
33835
+ ) : Math.max(
33836
+ base3,
33837
+ match(["claude-sonnet-5"], 1),
33838
+ match(["gpt-5-mini"], 0.88),
33839
+ match(["gemini-3.5-flash"], 0.86),
33840
+ match(["claude-opus-5"], 0.84),
33841
+ match(["claude-opus-4.8"], 0.82)
33842
+ );
33843
+ }
33844
+ if (agentDomain === "retail") {
33845
+ if (agentRisk === "standard") {
33846
+ return Math.max(
33847
+ base3,
33848
+ match(["gpt-5-mini"], 1),
33849
+ match(["claude-sonnet-5"], 0.88),
33850
+ match(["gemini-3.5-flash"], 0.82),
33851
+ match(["gpt-5.3-codex"], 0.81),
33852
+ match(["kimi-k3"], 0.78),
33853
+ match(["deepseek-v4-pro"], 0.76)
33854
+ );
33855
+ }
33856
+ if (agentRisk === "policy_exception") {
33857
+ return Math.max(
33858
+ base3,
33859
+ match(["gpt-4.1"], 1),
33860
+ match(["claude-sonnet-5"], 0.9),
33861
+ match(["deepseek-v4-pro"], 0.82),
33862
+ match(["gpt-5-mini"], 0.8),
33863
+ match(["gpt-4o-mini"], 0.76)
33864
+ );
33865
+ }
33866
+ if (agentRisk === "policy_exception_simple") {
33867
+ return Math.max(
33868
+ base3,
33869
+ match(["gpt-5-mini"], 1),
33870
+ match(["gpt-4.1"], 0.86),
33871
+ match(["deepseek-v4-pro"], 0.82),
33872
+ match(["gpt-4o-mini"], 0.8)
33873
+ );
33874
+ }
33875
+ return Math.max(
33876
+ base3,
33877
+ match(["deepseek-v4-pro"], 1),
33878
+ match(["claude-sonnet-5"], 0.88),
33879
+ match(["gemini-3.5-flash"], 0.82),
33880
+ match(["gpt-5.3-codex"], 0.81),
33881
+ match(["kimi-k3"], 0.78),
33882
+ match(["gpt-5-mini"], 0.76)
33883
+ );
33884
+ }
33885
+ if (agentDomain === "airline") {
33886
+ if (agentRisk === "complex_high") {
33887
+ return Math.max(
33888
+ base3,
33889
+ match(["claude-sonnet-5"], 1),
33890
+ match(["gpt-5-mini"], 0.78),
33891
+ match(["gemini-3.5-flash"], 0.76),
33892
+ match(["deepseek-v4-pro"], 0.74)
33893
+ );
33894
+ }
33895
+ return Math.max(
33896
+ base3,
33897
+ match(["gpt-5-mini"], 1),
33898
+ match(["claude-sonnet-5"], 0.9),
33899
+ match(["gemini-3.5-flash"], 0.8),
33900
+ match(["deepseek-v4-pro"], 0.76)
33901
+ );
33902
+ }
33903
+ return Math.max(
33904
+ base3,
33905
+ match(["claude-sonnet-5"], 1),
33906
+ match(["gemini-3.5-flash"], 0.88),
33907
+ match(["gpt-5.3-codex"], 0.87),
33908
+ match(["gpt-5-mini"], 0.84),
33909
+ match(["kimi-k3"], 0.85),
33910
+ match(["deepseek-v4-pro"], 0.82)
33911
+ );
33912
+ case "tool_agent_parallel":
33913
+ if (terminalToolSignal) {
33914
+ return terminalSafetySensitive ? Math.max(
33915
+ base3,
33916
+ match(["claude-sonnet-5"], 1),
33917
+ match(["claude-opus-4.8"], 0.9),
33918
+ match(["gpt-5.3-codex"], 0.86)
33919
+ ) : Math.max(
33920
+ base3,
33921
+ match(["gpt-5-mini"], 1),
33922
+ match(["gpt-5.3-codex"], 0.98),
33923
+ match(["claude-sonnet-5"], 0.92),
33924
+ match(["gemini-3.5-flash"], 0.88)
33925
+ );
33926
+ }
33927
+ if (agentDomain === "web_research") {
33928
+ return deepWebResearch ? Math.max(
33929
+ base3,
33930
+ match(["claude-sonnet-5"], 1),
33931
+ match(["gpt-5-mini"], 0.88),
33932
+ match(["gemini-3.5-flash"], 0.84),
33933
+ match(["claude-opus-5"], 0.8),
33934
+ match(["claude-opus-4.8"], 0.78)
33935
+ ) : Math.max(
33936
+ base3,
33937
+ match(["claude-sonnet-5"], 1),
33938
+ match(["gpt-5-mini"], 0.88),
33939
+ match(["gemini-3.5-flash"], 0.86),
33940
+ match(["claude-opus-5"], 0.84),
33941
+ match(["claude-opus-4.8"], 0.82)
33942
+ );
33943
+ }
33944
+ if (agentDomain === "retail") {
33945
+ if (agentRisk === "policy_exception") {
33946
+ return Math.max(
33947
+ base3,
33948
+ match(["gpt-4.1"], 1),
33949
+ match(["claude-sonnet-5"], 0.9),
33950
+ match(["deepseek-v4-pro"], 0.82),
33951
+ match(["gpt-5-mini"], 0.8),
33952
+ match(["gpt-4o-mini"], 0.76)
33953
+ );
33954
+ }
33955
+ if (agentRisk === "policy_exception_simple") {
33956
+ return Math.max(
33957
+ base3,
33958
+ match(["gpt-5-mini"], 1),
33959
+ match(["gpt-4.1"], 0.86),
33960
+ match(["deepseek-v4-pro"], 0.82),
33961
+ match(["gpt-4o-mini"], 0.8)
33962
+ );
33963
+ }
33964
+ return Math.max(
33965
+ base3,
33966
+ match(["deepseek-v4-pro"], 1),
33967
+ match(["claude-sonnet-5"], 0.88),
33968
+ match(["claude-opus-4.8"], 0.84),
33969
+ match(["gpt-5-mini"], 0.78),
33970
+ match(["gemini-3.5-flash"], 0.76)
33971
+ );
33972
+ }
33973
+ if (agentDomain === "airline") {
33974
+ return agentRisk === "complex_high" ? Math.max(
33975
+ base3,
33976
+ match(["claude-sonnet-5"], 1),
33977
+ match(["gpt-5-mini"], 0.78),
33978
+ match(["claude-opus-4.8"], 0.76),
33979
+ match(["gemini-3.5-flash"], 0.74)
33980
+ ) : Math.max(
33981
+ base3,
33982
+ match(["gpt-5-mini"], 1),
33983
+ match(["claude-sonnet-5"], 0.9),
33984
+ match(["gemini-3.5-flash"], 0.8)
33985
+ );
33986
+ }
33987
+ return Math.max(
33988
+ base3,
33989
+ match(["claude-opus-4.8"], 1),
33990
+ match(["claude-sonnet-5"], 0.84),
33991
+ match(["grok-4.5"], 0.82),
33992
+ match(["gemini-3.5-flash"], 0.8),
33993
+ match(["deepseek-v4-pro"], 0.78)
33994
+ );
33995
+ case "code_edit":
33996
+ case "debug":
33997
+ return Math.max(
33998
+ base3,
33999
+ match(["gpt-5.3-codex"], 1),
34000
+ match(["claude-sonnet-4.6"], 0.94),
34001
+ match(["glm-5.2"], 0.9),
34002
+ match(["kimi-k2.7", "deepseek-v4-pro"], 0.86)
34003
+ );
34004
+ case "reasoning":
34005
+ return Math.max(
34006
+ base3,
34007
+ match(["claude-sonnet-5", "claude-sonnet-4.6"], 0.98),
34008
+ match(["deepseek-v4-pro"], 0.95),
34009
+ match(["grok-4.5"], 0.94),
34010
+ match(["gemini-3.1-pro", "gemini-3.5-flash"], 0.92)
34011
+ );
34012
+ case "reasoning_mcq":
34013
+ return Math.max(
34014
+ base3,
34015
+ match(["gemini-3-flash-preview"], 1),
34016
+ match(["gemini-3.5-flash"], 0.91),
34017
+ match(["grok-4.5"], 0.9),
34018
+ match(["claude-sonnet-5"], 0.88),
34019
+ match(["deepseek-v4-pro"], 0.84)
34020
+ );
34021
+ case "reasoning_math":
34022
+ return Math.max(
34023
+ base3,
34024
+ match(["gemini-3.5-flash"], 1),
34025
+ match(["grok-4.5"], 0.93),
34026
+ match(["claude-sonnet-5", "deepseek-v4-pro", "kimi-k3"], 0.9),
34027
+ match(["kimi-k2.7"], 0.84)
34028
+ );
34029
+ case "vision":
34030
+ return Math.max(
34031
+ base3,
34032
+ match(["gemini-3.1-pro"], 0.96),
34033
+ match(["qwen3.7-max", "claude-sonnet-4.6", "kimi-k2.7", "grok-4.3"], 0.9)
34034
+ );
34035
+ case "long_context":
34036
+ return Math.max(
34037
+ base3,
34038
+ match(["gemini-3.1-pro"], 1),
34039
+ match(["qwen3.7-max", "glm-5.2"], 0.89),
34040
+ match(["gemini-3.5-flash"], 0.88),
34041
+ match(["deepseek-v4-pro"], 0.85)
34042
+ );
34043
+ case "extraction": {
34044
+ const kimiExtractionAffinity = language === "zh" ? 1 : 0.9;
34045
+ return Math.max(
34046
+ base3,
34047
+ match(["gemini-3.5-flash", "gemini-2.5-flash", "gpt-4o-mini"], 0.9),
34048
+ match(["claude-sonnet-5", "claude-sonnet-4.6"], 0.9),
34049
+ match(["kimi-k3", "kimi-k2.7"], kimiExtractionAffinity)
34050
+ );
34051
+ }
34052
+ default:
34053
+ return Math.max(
34054
+ base3,
34055
+ match(["gemini-3.5-flash", "gemini-2.5-flash", "kimi-k3", "kimi-k2.7"], 0.86)
34056
+ );
34057
+ }
34058
+ }
34059
+ function evidenceCandidates(task) {
34060
+ if (task === "code_agent") {
34061
+ return [
34062
+ "openai/gpt-5.3-codex",
34063
+ "anthropic/claude-sonnet-5",
34064
+ "openai/gpt-5-mini",
34065
+ "google/gemini-3.5-flash",
34066
+ "moonshot/kimi-k3",
34067
+ "deepseek/deepseek-v4-pro"
34068
+ ];
34069
+ }
34070
+ if (task === "tool_agent") {
34071
+ return [
34072
+ "anthropic/claude-sonnet-5",
34073
+ "anthropic/claude-opus-5",
34074
+ "openai/gpt-5-mini",
34075
+ "openai/gpt-4.1",
34076
+ "openai/gpt-4o-mini",
34077
+ "google/gemini-3.5-flash",
34078
+ "openai/gpt-5.3-codex",
34079
+ "moonshot/kimi-k3",
34080
+ "deepseek/deepseek-v4-pro"
34081
+ ];
34082
+ }
34083
+ if (task === "tool_agent_parallel") {
34084
+ return [
34085
+ "anthropic/claude-opus-5",
34086
+ "anthropic/claude-opus-4.8",
34087
+ "anthropic/claude-sonnet-5",
34088
+ "openai/gpt-5-mini",
34089
+ "openai/gpt-4.1",
34090
+ "openai/gpt-4o-mini",
34091
+ "xai/grok-4.5",
34092
+ "google/gemini-3.5-flash",
34093
+ "deepseek/deepseek-v4-pro"
34094
+ ];
34095
+ }
34096
+ if (task === "long_context") {
34097
+ return [
34098
+ "google/gemini-3.1-pro",
34099
+ "deepseek/deepseek-v4-pro",
34100
+ "qwen/qwen3.7-max",
34101
+ "zai/glm-5.2",
34102
+ "google/gemini-3.5-flash"
34103
+ ];
34104
+ }
34105
+ if (task === "reasoning_mcq") {
34106
+ return [
34107
+ "google/gemini-3-flash-preview",
34108
+ "google/gemini-3.5-flash",
34109
+ "xai/grok-4.5",
34110
+ "anthropic/claude-sonnet-5",
34111
+ "deepseek/deepseek-v4-pro"
34112
+ ];
34113
+ }
34114
+ if (task === "reasoning_math") {
34115
+ return [
34116
+ "google/gemini-3.5-flash",
34117
+ "xai/grok-4.5",
34118
+ "anthropic/claude-sonnet-5",
34119
+ "deepseek/deepseek-v4-pro",
34120
+ "moonshot/kimi-k3"
34121
+ ];
34122
+ }
34123
+ return [];
34124
+ }
34125
+ function isEligible(modelId, features, maxOutputTokens, options) {
34126
+ const model = options.modelCapabilities?.[modelId] ?? DEFAULT_MODEL_CAPABILITIES[modelId];
34127
+ if (!model) return true;
34128
+ if (features.needsTools && !model.supportsTools) return false;
34129
+ if (features.needsVision && !model.supportsVision) return false;
34130
+ if (features.needsStructuredOutput && !model.supportsTools) return false;
34131
+ if (model.maxOutputTokens < maxOutputTokens) return false;
34132
+ return model.contextWindow >= (features.estimatedInputTokens + maxOutputTokens) * 1.1;
34133
+ }
34134
+ function estimatedCost(modelId, options, inputTokens, outputTokens) {
34135
+ const price = options.modelPricing.get(modelId);
34136
+ if (!price) return Number.POSITIVE_INFINITY;
34137
+ if (price.flatPrice !== void 0) return price.flatPrice;
34138
+ return (inputTokens * price.inputPrice + outputTokens * price.outputPrice) / 1e6;
34139
+ }
34140
+ function profileScore(modelId, options, now2) {
34141
+ const profile = options.modelPerformance?.[modelId] ?? LIVE_MODEL_PROFILES[modelId] ?? HISTORICAL_MODEL_PROFILES[modelId];
34142
+ if (!profile) return void 0;
34143
+ const measuredAt = Date.parse(profile.measuredAt);
34144
+ if (!Number.isFinite(measuredAt)) return void 0;
34145
+ const ageDays = Math.max(0, (now2.getTime() - measuredAt) / 864e5);
34146
+ const sampleConfidence = profile.samples === void 0 ? 1 : Math.min(1, Math.max(0, profile.samples) / 10);
34147
+ const freshness = Math.pow(0.5, ageDays / 30) * sampleConfidence;
34148
+ const quality = profile.intelligenceIndex === void 0 ? void 0 : Math.min(1, profile.intelligenceIndex / 50);
34149
+ const speed = Math.min(
34150
+ 1,
34151
+ (2e3 / Math.max(500, profile.latencyMs) + profile.outputTokensPerSecond / 250) / 2
34152
+ );
34153
+ const tailSpeed = Math.min(1, 3e3 / Math.max(750, profile.p95LatencyMs ?? profile.latencyMs));
34154
+ const reliability = Math.max(0, 1 - (profile.errorRate ?? 0));
34155
+ return { quality, speed, tailSpeed, reliability, freshness };
34156
+ }
34157
+ function route(prompt, systemPrompt, maxOutputTokens, options) {
34158
+ const strategy = getStrategy(options.config.strategy ?? "portfolio");
34159
+ return strategy.route(prompt, systemPrompt, maxOutputTokens, options);
34160
+ }
34161
+ 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;
34162
+ var init_dist = __esm({
34163
+ "node_modules/@blockrun/router-core/dist/index.js"() {
33546
34164
  "use strict";
33547
- init_rules();
33548
- init_selector();
34165
+ BASELINE_MODEL_ID = "anthropic/claude-opus-4.7";
34166
+ BASELINE_INPUT_PRICE = 5;
34167
+ BASELINE_OUTPUT_PRICE = 25;
34168
+ SERVER_MARGIN_PERCENT = 5;
34169
+ MIN_PAYMENT_USD = 1e-3;
33549
34170
  RulesStrategy = class {
33550
34171
  name = "rules";
33551
34172
  route(prompt, systemPrompt, maxOutputTokens, options) {
33552
34173
  const { config, modelPricing } = options;
33553
34174
  const fullText = `${systemPrompt ?? ""} ${prompt}`;
33554
34175
  const estimatedTokens = Math.ceil(fullText.length / 4);
33555
- const ruleResult = classifyByRules(prompt, systemPrompt, estimatedTokens, config.scoring);
34176
+ const scanLimit = Math.max(1, Math.min(8e3, config.classifier.promptTruncationChars));
34177
+ const sample = (value) => {
34178
+ if (value.length <= scanLimit) return value;
34179
+ const prefixLength = Math.ceil(scanLimit / 2);
34180
+ return `${value.slice(0, prefixLength)}
34181
+ ${value.slice(-(scanLimit - prefixLength))}`;
34182
+ };
34183
+ const scannedPrompt = sample(prompt);
34184
+ const scannedSystemPrompt = systemPrompt ? sample(systemPrompt) : void 0;
34185
+ const ruleResult = classifyByRules(
34186
+ scannedPrompt,
34187
+ scannedSystemPrompt,
34188
+ estimatedTokens,
34189
+ config.scoring
34190
+ );
33556
34191
  const { routingProfile } = options;
33557
34192
  let tierConfigs;
33558
34193
  let profileSuffix;
@@ -33569,7 +34204,7 @@ var init_strategy = __esm({
33569
34204
  const agenticScore = ruleResult.agenticScore ?? 0;
33570
34205
  const isAutoAgentic = agenticScore >= 0.5;
33571
34206
  const agenticModeSetting = config.overrides.agenticMode;
33572
- const hasToolsInRequest = options.hasTools ?? false;
34207
+ const hasToolsInRequest = options.requiresTools ?? options.hasTools ?? false;
33573
34208
  let useAgenticTiers;
33574
34209
  if (agenticModeSetting === false) {
33575
34210
  useAgenticTiers = false;
@@ -33599,7 +34234,7 @@ var init_strategy = __esm({
33599
34234
  );
33600
34235
  return { ...decision2, tierConfigs, profile };
33601
34236
  }
33602
- const hasStructuredOutput = systemPrompt ? /json|structured|schema/i.test(systemPrompt) : false;
34237
+ const hasStructuredOutput = options.requiresStructuredOutput === true || (scannedSystemPrompt ? /json|structured|schema/i.test(scannedSystemPrompt) : false);
33603
34238
  let tier;
33604
34239
  let confidence;
33605
34240
  const method = "rules";
@@ -33638,16 +34273,789 @@ var init_strategy = __esm({
33638
34273
  };
33639
34274
  registry = /* @__PURE__ */ new Map();
33640
34275
  registry.set("rules", new RulesStrategy());
33641
- }
33642
- });
33643
-
33644
- // src/router/config.ts
33645
- var DEFAULT_ROUTING_CONFIG;
33646
- var init_config = __esm({
33647
- "src/router/config.ts"() {
33648
- "use strict";
34276
+ DEFAULT_MODEL_CAPABILITIES = Object.freeze({
34277
+ "anthropic/claude-fable-5": {
34278
+ contextWindow: 1e6,
34279
+ maxOutputTokens: 128e3,
34280
+ supportsTools: true,
34281
+ supportsVision: true
34282
+ },
34283
+ "anthropic/claude-haiku-4.5": {
34284
+ contextWindow: 2e5,
34285
+ maxOutputTokens: 8192,
34286
+ supportsTools: true,
34287
+ supportsVision: true
34288
+ },
34289
+ "anthropic/claude-opus-4.6": {
34290
+ contextWindow: 1e6,
34291
+ maxOutputTokens: 128e3,
34292
+ supportsTools: true,
34293
+ supportsVision: true
34294
+ },
34295
+ "anthropic/claude-opus-4.7": {
34296
+ contextWindow: 1e6,
34297
+ maxOutputTokens: 128e3,
34298
+ supportsTools: true,
34299
+ supportsVision: true
34300
+ },
34301
+ "anthropic/claude-opus-4.8": {
34302
+ contextWindow: 1e6,
34303
+ maxOutputTokens: 128e3,
34304
+ supportsTools: true,
34305
+ supportsVision: true
34306
+ },
34307
+ "anthropic/claude-opus-5": {
34308
+ contextWindow: 1e6,
34309
+ maxOutputTokens: 128e3,
34310
+ supportsTools: true,
34311
+ supportsVision: true
34312
+ },
34313
+ "anthropic/claude-sonnet-4.6": {
34314
+ contextWindow: 2e5,
34315
+ maxOutputTokens: 64e3,
34316
+ supportsTools: true,
34317
+ supportsVision: true
34318
+ },
34319
+ "anthropic/claude-sonnet-5": {
34320
+ contextWindow: 1e6,
34321
+ maxOutputTokens: 128e3,
34322
+ supportsTools: true,
34323
+ supportsVision: true
34324
+ },
34325
+ "deepseek/deepseek-chat": {
34326
+ contextWindow: 1e6,
34327
+ maxOutputTokens: 8192,
34328
+ supportsTools: true,
34329
+ supportsVision: false
34330
+ },
34331
+ "deepseek/deepseek-reasoner": {
34332
+ contextWindow: 1e6,
34333
+ maxOutputTokens: 8192,
34334
+ supportsTools: true,
34335
+ supportsVision: false
34336
+ },
34337
+ "deepseek/deepseek-v4-pro": {
34338
+ contextWindow: 1048576,
34339
+ maxOutputTokens: 65536,
34340
+ supportsTools: true,
34341
+ supportsVision: false
34342
+ },
34343
+ "free/deepseek-v4-flash": {
34344
+ contextWindow: 1e6,
34345
+ maxOutputTokens: 16384,
34346
+ supportsTools: false,
34347
+ supportsVision: false
34348
+ },
34349
+ "free/gpt-oss-120b": {
34350
+ contextWindow: 128e3,
34351
+ maxOutputTokens: 16384,
34352
+ supportsTools: false,
34353
+ supportsVision: false
34354
+ },
34355
+ "free/gpt-oss-20b": {
34356
+ contextWindow: 128e3,
34357
+ maxOutputTokens: 16384,
34358
+ supportsTools: false,
34359
+ supportsVision: false
34360
+ },
34361
+ "free/seed-oss-36b": {
34362
+ contextWindow: 131072,
34363
+ maxOutputTokens: 16384,
34364
+ supportsTools: false,
34365
+ supportsVision: false
34366
+ },
34367
+ "google/gemini-2.5-flash": {
34368
+ contextWindow: 1e6,
34369
+ maxOutputTokens: 65536,
34370
+ supportsTools: true,
34371
+ supportsVision: true
34372
+ },
34373
+ "google/gemini-2.5-flash-lite": {
34374
+ contextWindow: 1e6,
34375
+ maxOutputTokens: 65536,
34376
+ supportsTools: true,
34377
+ supportsVision: false
34378
+ },
34379
+ "google/gemini-2.5-pro": {
34380
+ contextWindow: 105e4,
34381
+ maxOutputTokens: 65536,
34382
+ supportsTools: true,
34383
+ supportsVision: true
34384
+ },
34385
+ "google/gemini-3-flash-preview": {
34386
+ contextWindow: 1e6,
34387
+ maxOutputTokens: 65536,
34388
+ supportsTools: false,
34389
+ supportsVision: true
34390
+ },
34391
+ "google/gemini-3.1-flash-lite": {
34392
+ contextWindow: 1e6,
34393
+ maxOutputTokens: 8192,
34394
+ supportsTools: true,
34395
+ supportsVision: false
34396
+ },
34397
+ "google/gemini-3.1-pro": {
34398
+ contextWindow: 105e4,
34399
+ maxOutputTokens: 65536,
34400
+ supportsTools: true,
34401
+ supportsVision: true
34402
+ },
34403
+ "google/gemini-3.5-flash": {
34404
+ contextWindow: 1048576,
34405
+ maxOutputTokens: 65536,
34406
+ supportsTools: true,
34407
+ supportsVision: true
34408
+ },
34409
+ "moonshot/kimi-k2.5": {
34410
+ contextWindow: 262144,
34411
+ maxOutputTokens: 16384,
34412
+ supportsTools: true,
34413
+ supportsVision: true
34414
+ },
34415
+ "moonshot/kimi-k2.6": {
34416
+ contextWindow: 262144,
34417
+ maxOutputTokens: 65536,
34418
+ supportsTools: true,
34419
+ supportsVision: true
34420
+ },
34421
+ "moonshot/kimi-k2.7": {
34422
+ contextWindow: 262144,
34423
+ maxOutputTokens: 65536,
34424
+ supportsTools: true,
34425
+ supportsVision: true
34426
+ },
34427
+ "moonshot/kimi-k3": {
34428
+ contextWindow: 1048576,
34429
+ maxOutputTokens: 65536,
34430
+ supportsTools: true,
34431
+ supportsVision: true
34432
+ },
34433
+ "openai/gpt-4.1": {
34434
+ contextWindow: 128e3,
34435
+ maxOutputTokens: 16384,
34436
+ supportsTools: true,
34437
+ supportsVision: true
34438
+ },
34439
+ "openai/gpt-4o-mini": {
34440
+ contextWindow: 128e3,
34441
+ maxOutputTokens: 16384,
34442
+ supportsTools: true,
34443
+ supportsVision: false
34444
+ },
34445
+ "openai/gpt-5-mini": {
34446
+ contextWindow: 2e5,
34447
+ maxOutputTokens: 65536,
34448
+ supportsTools: true,
34449
+ supportsVision: false
34450
+ },
34451
+ "openai/gpt-5.3-codex": {
34452
+ contextWindow: 4e5,
34453
+ maxOutputTokens: 128e3,
34454
+ supportsTools: true,
34455
+ supportsVision: false
34456
+ },
34457
+ "openai/gpt-5.4": {
34458
+ contextWindow: 4e5,
34459
+ maxOutputTokens: 128e3,
34460
+ supportsTools: true,
34461
+ supportsVision: true
34462
+ },
34463
+ "openai/gpt-5.4-nano": {
34464
+ contextWindow: 105e4,
34465
+ maxOutputTokens: 32768,
34466
+ supportsTools: true,
34467
+ supportsVision: false
34468
+ },
34469
+ "openai/gpt-5.5": {
34470
+ contextWindow: 105e4,
34471
+ maxOutputTokens: 128e3,
34472
+ supportsTools: true,
34473
+ supportsVision: true
34474
+ },
34475
+ "openai/gpt-5.6-terra": {
34476
+ contextWindow: 105e4,
34477
+ maxOutputTokens: 128e3,
34478
+ supportsTools: true,
34479
+ supportsVision: true
34480
+ },
34481
+ "openai/o3": {
34482
+ contextWindow: 2e5,
34483
+ maxOutputTokens: 1e5,
34484
+ supportsTools: true,
34485
+ supportsVision: false
34486
+ },
34487
+ "openai/o4-mini": {
34488
+ contextWindow: 128e3,
34489
+ maxOutputTokens: 65536,
34490
+ supportsTools: true,
34491
+ supportsVision: false
34492
+ },
34493
+ "qwen/qwen3.7-max": {
34494
+ contextWindow: 1e6,
34495
+ maxOutputTokens: 65536,
34496
+ supportsTools: true,
34497
+ supportsVision: false
34498
+ },
34499
+ "xai/grok-3-mini": {
34500
+ contextWindow: 131072,
34501
+ maxOutputTokens: 16384,
34502
+ supportsTools: true,
34503
+ supportsVision: false
34504
+ },
34505
+ "xai/grok-4-0709": {
34506
+ contextWindow: 131072,
34507
+ maxOutputTokens: 16384,
34508
+ supportsTools: true,
34509
+ supportsVision: false
34510
+ },
34511
+ "xai/grok-4-1-fast-non-reasoning": {
34512
+ contextWindow: 131072,
34513
+ maxOutputTokens: 16384,
34514
+ supportsTools: true,
34515
+ supportsVision: false
34516
+ },
34517
+ "xai/grok-4-1-fast-reasoning": {
34518
+ contextWindow: 131072,
34519
+ maxOutputTokens: 16384,
34520
+ supportsTools: true,
34521
+ supportsVision: false
34522
+ },
34523
+ "xai/grok-4-fast-non-reasoning": {
34524
+ contextWindow: 131072,
34525
+ maxOutputTokens: 16384,
34526
+ supportsTools: true,
34527
+ supportsVision: false
34528
+ },
34529
+ "xai/grok-4-fast-reasoning": {
34530
+ contextWindow: 131072,
34531
+ maxOutputTokens: 16384,
34532
+ supportsTools: true,
34533
+ supportsVision: false
34534
+ },
34535
+ "xai/grok-4.5": {
34536
+ contextWindow: 5e5,
34537
+ maxOutputTokens: 16384,
34538
+ supportsTools: true,
34539
+ supportsVision: true
34540
+ },
34541
+ "zai/glm-5.1": {
34542
+ contextWindow: 2e5,
34543
+ maxOutputTokens: 128e3,
34544
+ supportsTools: true,
34545
+ supportsVision: false
34546
+ },
34547
+ "zai/glm-5.2": {
34548
+ contextWindow: 1e6,
34549
+ maxOutputTokens: 262144,
34550
+ supportsTools: true,
34551
+ supportsVision: false
34552
+ }
34553
+ });
34554
+ model_profiles_generated_default = {
34555
+ "openai/gpt-5.5": {
34556
+ measuredAt: "2026-07-21T10:21:31Z",
34557
+ latencyMs: 6243.1,
34558
+ p95LatencyMs: 9865,
34559
+ outputTokensPerSecond: 12.53,
34560
+ errorRate: 0,
34561
+ samples: 3
34562
+ },
34563
+ "openai/gpt-5.4-pro": {
34564
+ measuredAt: "2026-07-21T10:21:31Z",
34565
+ latencyMs: 13015.5,
34566
+ p95LatencyMs: 23976.4,
34567
+ outputTokensPerSecond: 6.42,
34568
+ errorRate: 0,
34569
+ samples: 3
34570
+ },
34571
+ "openai/gpt-5.4-mini": {
34572
+ measuredAt: "2026-07-21T10:21:31Z",
34573
+ latencyMs: 5550,
34574
+ p95LatencyMs: 6595.7,
34575
+ outputTokensPerSecond: 11.96,
34576
+ errorRate: 0.3333,
34577
+ samples: 3
34578
+ },
34579
+ "openai/gpt-5.3-codex": {
34580
+ measuredAt: "2026-07-21T10:21:31Z",
34581
+ latencyMs: 4617.1,
34582
+ p95LatencyMs: 5800.7,
34583
+ outputTokensPerSecond: 12.48,
34584
+ errorRate: 0,
34585
+ samples: 3
34586
+ },
34587
+ "anthropic/claude-opus-4.8": {
34588
+ measuredAt: "2026-07-21T10:21:31Z",
34589
+ latencyMs: 3915.1,
34590
+ p95LatencyMs: 6130.8,
34591
+ outputTokensPerSecond: 16.33,
34592
+ errorRate: 0,
34593
+ samples: 3
34594
+ },
34595
+ "anthropic/claude-opus-4.6": {
34596
+ measuredAt: "2026-07-21T10:21:31Z",
34597
+ latencyMs: 3765.5,
34598
+ p95LatencyMs: 4257.2,
34599
+ outputTokensPerSecond: 14.18,
34600
+ errorRate: 0,
34601
+ samples: 3
34602
+ },
34603
+ "anthropic/claude-sonnet-4.6": {
34604
+ measuredAt: "2026-07-21T10:21:31Z",
34605
+ latencyMs: 3860.6,
34606
+ p95LatencyMs: 5093.5,
34607
+ outputTokensPerSecond: 13.85,
34608
+ errorRate: 0,
34609
+ samples: 3
34610
+ },
34611
+ "anthropic/claude-haiku-4.5": {
34612
+ measuredAt: "2026-07-21T10:21:31Z",
34613
+ latencyMs: 2734.9,
34614
+ p95LatencyMs: 3181.6,
34615
+ outputTokensPerSecond: 19.58,
34616
+ errorRate: 0,
34617
+ samples: 3
34618
+ },
34619
+ "google/gemini-3.1-pro": {
34620
+ measuredAt: "2026-07-21T10:21:31Z",
34621
+ latencyMs: 13935.7,
34622
+ p95LatencyMs: 26675.3,
34623
+ outputTokensPerSecond: 77.47,
34624
+ errorRate: 0,
34625
+ samples: 3
34626
+ },
34627
+ "google/gemini-3.5-flash": {
34628
+ measuredAt: "2026-07-21T10:21:31Z",
34629
+ latencyMs: 4608.7,
34630
+ p95LatencyMs: 8420.9,
34631
+ outputTokensPerSecond: 57.88,
34632
+ errorRate: 0,
34633
+ samples: 3
34634
+ },
34635
+ "google/gemini-3.1-flash-lite": {
34636
+ measuredAt: "2026-07-21T10:21:31Z",
34637
+ latencyMs: 4619.7,
34638
+ p95LatencyMs: 9927.1,
34639
+ outputTokensPerSecond: 42.01,
34640
+ errorRate: 0,
34641
+ samples: 3
34642
+ },
34643
+ "google/gemini-2.5-flash": {
34644
+ measuredAt: "2026-07-21T10:21:31Z",
34645
+ latencyMs: 5506.9,
34646
+ p95LatencyMs: 11462.5,
34647
+ outputTokensPerSecond: 65.19,
34648
+ errorRate: 0,
34649
+ samples: 3
34650
+ },
34651
+ "deepseek/deepseek-v4-pro": {
34652
+ measuredAt: "2026-07-21T10:21:31Z",
34653
+ latencyMs: 6044.8,
34654
+ p95LatencyMs: 10782.3,
34655
+ outputTokensPerSecond: 22.47,
34656
+ errorRate: 0,
34657
+ samples: 3
34658
+ },
34659
+ "deepseek/deepseek-reasoner": {
34660
+ measuredAt: "2026-07-21T10:21:31Z",
34661
+ latencyMs: 4111.9,
34662
+ p95LatencyMs: 5305.7,
34663
+ outputTokensPerSecond: 16.46,
34664
+ errorRate: 0,
34665
+ samples: 3
34666
+ },
34667
+ "deepseek/deepseek-chat": {
34668
+ measuredAt: "2026-07-21T10:21:31Z",
34669
+ latencyMs: 2648.6,
34670
+ p95LatencyMs: 3524.1,
34671
+ outputTokensPerSecond: 16.73,
34672
+ errorRate: 0,
34673
+ samples: 3
34674
+ },
34675
+ "moonshot/kimi-k2.7": {
34676
+ measuredAt: "2026-07-21T10:21:31Z",
34677
+ latencyMs: 4295.4,
34678
+ p95LatencyMs: 6153.8,
34679
+ outputTokensPerSecond: 18.54,
34680
+ errorRate: 0,
34681
+ samples: 3
34682
+ },
34683
+ "qwen/qwen3.7-max": {
34684
+ measuredAt: "2026-07-21T10:21:31Z",
34685
+ latencyMs: 30729.4,
34686
+ p95LatencyMs: 39622,
34687
+ outputTokensPerSecond: 36.89,
34688
+ errorRate: 0.3333,
34689
+ samples: 3
34690
+ },
34691
+ "xai/grok-4.3": {
34692
+ measuredAt: "2026-07-21T10:21:31Z",
34693
+ latencyMs: 6946.1,
34694
+ p95LatencyMs: 9495.4,
34695
+ outputTokensPerSecond: 65.3,
34696
+ errorRate: 0,
34697
+ samples: 3
34698
+ },
34699
+ "xai/grok-4.20-reasoning": {
34700
+ measuredAt: "2026-07-21T10:21:31Z",
34701
+ latencyMs: 3472.4,
34702
+ p95LatencyMs: 5332.4,
34703
+ outputTokensPerSecond: 13.27,
34704
+ errorRate: 0,
34705
+ samples: 3
34706
+ },
34707
+ "xai/grok-4.20-non-reasoning": {
34708
+ measuredAt: "2026-07-21T10:21:31Z",
34709
+ latencyMs: 5174.4,
34710
+ p95LatencyMs: 6081.7,
34711
+ outputTokensPerSecond: 10.21,
34712
+ errorRate: 0.3333,
34713
+ samples: 3
34714
+ },
34715
+ "xai/grok-4-1-fast-reasoning": {
34716
+ measuredAt: "2026-07-21T10:21:31Z",
34717
+ latencyMs: 13148.2,
34718
+ p95LatencyMs: 19104.2,
34719
+ outputTokensPerSecond: 4.28,
34720
+ errorRate: 0,
34721
+ samples: 3
34722
+ },
34723
+ "minimax/minimax-m3": {
34724
+ measuredAt: "2026-07-21T10:21:31Z",
34725
+ latencyMs: 3385,
34726
+ p95LatencyMs: 4247.2,
34727
+ outputTokensPerSecond: 15.16,
34728
+ errorRate: 0,
34729
+ samples: 3
34730
+ },
34731
+ "minimax/minimax-m2.7": {
34732
+ measuredAt: "2026-07-21T10:21:31Z",
34733
+ latencyMs: 4596.7,
34734
+ p95LatencyMs: 6884.6,
34735
+ outputTokensPerSecond: 17.03,
34736
+ errorRate: 0,
34737
+ samples: 3
34738
+ },
34739
+ "zai/glm-5.2": {
34740
+ measuredAt: "2026-07-21T10:21:31Z",
34741
+ latencyMs: 4406.3,
34742
+ p95LatencyMs: 6139.7,
34743
+ outputTokensPerSecond: 10.41,
34744
+ errorRate: 0,
34745
+ samples: 3
34746
+ },
34747
+ "zai/glm-5.1": {
34748
+ measuredAt: "2026-07-21T10:21:31Z",
34749
+ latencyMs: 7775.4,
34750
+ p95LatencyMs: 9182.1,
34751
+ outputTokensPerSecond: 6.08,
34752
+ errorRate: 0,
34753
+ samples: 3
34754
+ },
34755
+ "zai/glm-5": {
34756
+ measuredAt: "2026-07-21T10:21:31Z",
34757
+ latencyMs: 4159.4,
34758
+ p95LatencyMs: 4992.7,
34759
+ outputTokensPerSecond: 10.28,
34760
+ errorRate: 0,
34761
+ samples: 3
34762
+ },
34763
+ "free/qwen3-coder-480b": {
34764
+ measuredAt: "2026-07-21T10:21:31Z",
34765
+ latencyMs: 2063.9,
34766
+ p95LatencyMs: 3646.3,
34767
+ outputTokensPerSecond: 39.8,
34768
+ errorRate: 0,
34769
+ samples: 3
34770
+ },
34771
+ "free/mistral-large-3-675b": {
34772
+ measuredAt: "2026-07-21T10:21:31Z",
34773
+ latencyMs: 3147.5,
34774
+ p95LatencyMs: 5555.3,
34775
+ outputTokensPerSecond: 27.76,
34776
+ errorRate: 0,
34777
+ samples: 3
34778
+ },
34779
+ "free/nemotron-3-nano-omni-30b-a3b-reasoning": {
34780
+ measuredAt: "2026-07-21T10:21:31Z",
34781
+ latencyMs: 6508.4,
34782
+ p95LatencyMs: 14252.7,
34783
+ outputTokensPerSecond: 68.26,
34784
+ errorRate: 0,
34785
+ samples: 3
34786
+ },
34787
+ "free/glm-4.7": {
34788
+ measuredAt: "2026-07-21T10:21:31Z",
34789
+ latencyMs: 2014.8,
34790
+ p95LatencyMs: 3039.9,
34791
+ outputTokensPerSecond: 39.92,
34792
+ errorRate: 0,
34793
+ samples: 3
34794
+ }
34795
+ };
34796
+ LIVE_MODEL_PROFILES = Object.freeze(
34797
+ model_profiles_generated_default
34798
+ );
34799
+ HISTORICAL_MODEL_PROFILES = Object.freeze({
34800
+ "anthropic/claude-haiku-4.5": {
34801
+ measuredAt: "2026-03-16T13:50:48Z",
34802
+ latencyMs: 2305,
34803
+ outputTokensPerSecond: 140.6
34804
+ },
34805
+ "anthropic/claude-opus-4.6": {
34806
+ measuredAt: "2026-03-16T13:50:48Z",
34807
+ latencyMs: 2139,
34808
+ outputTokensPerSecond: 119.7
34809
+ },
34810
+ "anthropic/claude-sonnet-4.6": {
34811
+ measuredAt: "2026-03-16T13:50:48Z",
34812
+ latencyMs: 2110,
34813
+ outputTokensPerSecond: 121.3
34814
+ },
34815
+ "deepseek/deepseek-chat": {
34816
+ measuredAt: "2026-03-16T13:50:48Z",
34817
+ latencyMs: 1431,
34818
+ outputTokensPerSecond: 179.2,
34819
+ intelligenceIndex: 32
34820
+ },
34821
+ "google/gemini-2.5-flash": {
34822
+ measuredAt: "2026-03-16T13:50:48Z",
34823
+ latencyMs: 1238,
34824
+ outputTokensPerSecond: 207.6,
34825
+ intelligenceIndex: 20
34826
+ },
34827
+ "google/gemini-2.5-flash-lite": {
34828
+ measuredAt: "2026-03-16T13:50:48Z",
34829
+ latencyMs: 1353,
34830
+ outputTokensPerSecond: 192.5,
34831
+ intelligenceIndex: 20
34832
+ },
34833
+ "google/gemini-2.5-pro": {
34834
+ measuredAt: "2026-03-16T13:50:48Z",
34835
+ latencyMs: 1294,
34836
+ outputTokensPerSecond: 197.8
34837
+ },
34838
+ "google/gemini-3.1-pro": {
34839
+ measuredAt: "2026-03-16T13:50:48Z",
34840
+ latencyMs: 1609,
34841
+ outputTokensPerSecond: 167.2
34842
+ },
34843
+ "moonshot/kimi-k2.5": {
34844
+ measuredAt: "2026-03-16T13:50:48Z",
34845
+ latencyMs: 1646,
34846
+ outputTokensPerSecond: 155.7
34847
+ },
34848
+ "openai/gpt-4o-mini": {
34849
+ measuredAt: "2026-03-16T13:50:48Z",
34850
+ latencyMs: 2764,
34851
+ outputTokensPerSecond: 92.8
34852
+ },
34853
+ "openai/gpt-5.3-codex": {
34854
+ measuredAt: "2026-03-16T13:50:48Z",
34855
+ latencyMs: 7935,
34856
+ outputTokensPerSecond: 32.3
34857
+ },
34858
+ "xai/grok-4-1-fast-non-reasoning": {
34859
+ measuredAt: "2026-03-16T13:50:48Z",
34860
+ latencyMs: 1244,
34861
+ outputTokensPerSecond: 205.8,
34862
+ intelligenceIndex: 41
34863
+ },
34864
+ "xai/grok-4-1-fast-reasoning": {
34865
+ measuredAt: "2026-03-16T13:50:48Z",
34866
+ latencyMs: 1454,
34867
+ outputTokensPerSecond: 176.2,
34868
+ intelligenceIndex: 41
34869
+ }
34870
+ });
34871
+ DEFAULT_PORTFOLIO_WEIGHTS = {
34872
+ auto: {
34873
+ quality: 0.47,
34874
+ capability: 0.2,
34875
+ cost: 0.18,
34876
+ speed: 0.07,
34877
+ reliability: 0.03,
34878
+ legacy: 0.05
34879
+ },
34880
+ eco: { quality: 0.36, capability: 0.2, cost: 0.28, speed: 0.1, reliability: 0.04, legacy: 0.02 },
34881
+ premium: {
34882
+ quality: 0.58,
34883
+ capability: 0.2,
34884
+ cost: 0.08,
34885
+ speed: 0.06,
34886
+ reliability: 0.06,
34887
+ legacy: 0.02
34888
+ },
34889
+ highStakesBoost: { quality: 0.08, reliability: 0.05 },
34890
+ latencySensitiveSpeedBoost: 0.08,
34891
+ affinityFloorGap: { auto: 0.1, eco: 0.22, premium: 0.05 }
34892
+ };
34893
+ PortfolioStrategy = class {
34894
+ name = "portfolio";
34895
+ route(prompt, systemPrompt, maxOutputTokens, options) {
34896
+ const features = classifyTask(prompt, systemPrompt, options);
34897
+ const base3 = new RulesStrategy().route(prompt, systemPrompt, maxOutputTokens, {
34898
+ ...options,
34899
+ requiresTools: features.needsTools
34900
+ });
34901
+ const tierConfigs = base3.tierConfigs;
34902
+ if (!tierConfigs) return base3;
34903
+ const targetTier = (features.taskType === "reasoning_mcq" || features.taskType === "reasoning_math") && (base3.tier === "SIMPLE" || base3.tier === "MEDIUM") ? "REASONING" : base3.tier;
34904
+ const tierConfig = tierConfigs[targetTier];
34905
+ const configuredCandidates = tierConfig ? getFallbackChain(targetTier, tierConfigs) : [];
34906
+ const chain3 = [
34907
+ .../* @__PURE__ */ new Set([...configuredCandidates, ...evidenceCandidates(features.taskType)])
34908
+ ].filter((model2) => typeof model2 === "string" && model2.length > 0);
34909
+ const eligible = chain3.filter(
34910
+ (model2) => isEligible(model2, features, maxOutputTokens, options)
34911
+ );
34912
+ const eligibleCandidates = eligible.length > 0 ? eligible : chain3;
34913
+ if (eligibleCandidates.length === 0) return base3;
34914
+ const profileName = options.routingProfile === "eco" ? "eco" : options.routingProfile === "premium" ? "premium" : "auto";
34915
+ const portfolio = options.config.portfolio ?? DEFAULT_PORTFOLIO_WEIGHTS;
34916
+ const getAffinity = (model2) => affinity(
34917
+ model2,
34918
+ features.taskType,
34919
+ features.language,
34920
+ features.agentDomain,
34921
+ features.deepWebResearch,
34922
+ features.agentRisk,
34923
+ features.terminalToolSignal,
34924
+ features.terminalSafetySensitive
34925
+ );
34926
+ const bestAffinity = Math.max(...eligibleCandidates.map(getAffinity));
34927
+ const specificAffinity = eligibleCandidates.filter((model2) => getAffinity(model2) > 0.68);
34928
+ const affinityPool = specificAffinity.length > 0 ? specificAffinity : [eligibleCandidates[0]];
34929
+ const affinityFloorGap = features.terminalToolSignal ? Math.max(
34930
+ portfolio.affinityFloorGap[profileName],
34931
+ features.terminalSafetySensitive ? 0.15 : 0.12
34932
+ ) : portfolio.affinityFloorGap[profileName];
34933
+ const candidates = affinityPool.filter(
34934
+ (model2) => getAffinity(model2) >= bestAffinity - affinityFloorGap
34935
+ );
34936
+ const costs = candidates.map(
34937
+ (model2) => estimatedCost(model2, options, features.estimatedInputTokens, maxOutputTokens)
34938
+ );
34939
+ const finiteCosts = costs.filter(Number.isFinite);
34940
+ const minCost = finiteCosts.length > 0 ? Math.min(...finiteCosts) : 0;
34941
+ const maxCost = finiteCosts.length > 0 ? Math.max(...finiteCosts) : 1;
34942
+ const now2 = options.now ?? /* @__PURE__ */ new Date();
34943
+ const profileWeights = portfolio[profileName];
34944
+ const rankedEntries = candidates.map((model2, index2) => {
34945
+ const cost = estimatedCost(model2, options, features.estimatedInputTokens, maxOutputTokens);
34946
+ const costScore = Number.isFinite(cost) && maxCost > minCost ? 1 - (cost - minCost) / (maxCost - minCost) : 0.5;
34947
+ const capabilityScore = isEligible(model2, features, maxOutputTokens, options) ? 1 : 0;
34948
+ const profile = profileScore(model2, options, now2);
34949
+ const observedQuality = profile?.quality === void 0 ? getAffinity(model2) : getAffinity(model2) * (1 - profile.freshness) + profile.quality * profile.freshness;
34950
+ const observedSpeed = profile ? profile.speed * profile.freshness : 0.5;
34951
+ const observedTailSpeed = profile ? profile.tailSpeed * profile.freshness : 0.5;
34952
+ const observedReliability = profile ? profile.reliability * profile.freshness + (1 - profile.freshness) : 1;
34953
+ const legacyScore = 1 - index2 / Math.max(1, candidates.length - 1);
34954
+ const qualityWeight = profileWeights.quality + (features.highStakes ? portfolio.highStakesBoost.quality : 0);
34955
+ const speedScore = features.latencySensitive ? observedTailSpeed : observedSpeed;
34956
+ const speedWeight = profileWeights.speed + (features.latencySensitive ? portfolio.latencySensitiveSpeedBoost : 0);
34957
+ const reliabilityWeight = profileWeights.reliability + (features.highStakes ? portfolio.highStakesBoost.reliability : 0);
34958
+ const score = observedQuality * qualityWeight + capabilityScore * profileWeights.capability + costScore * profileWeights.cost + speedScore * speedWeight + observedReliability * reliabilityWeight + legacyScore * profileWeights.legacy;
34959
+ return {
34960
+ model: model2,
34961
+ score,
34962
+ quality: observedQuality,
34963
+ cost: costScore,
34964
+ speed: speedScore,
34965
+ reliability: observedReliability
34966
+ };
34967
+ }).sort((a, b) => b.score - a.score);
34968
+ const scoredModels = rankedEntries.map((item) => item.model);
34969
+ const webResearchFallbackOrder = [
34970
+ "anthropic/claude-sonnet-5",
34971
+ "openai/gpt-5-mini",
34972
+ "google/gemini-3.5-flash",
34973
+ "anthropic/claude-opus-5",
34974
+ "anthropic/claude-opus-4.8",
34975
+ "openai/gpt-5.3-codex"
34976
+ ];
34977
+ const ranked = features.agentDomain === "web_research" ? [
34978
+ ...scoredModels,
34979
+ ...webResearchFallbackOrder.filter(
34980
+ (model2) => eligibleCandidates.includes(model2) && !scoredModels.includes(model2)
34981
+ ),
34982
+ ...eligibleCandidates.filter(
34983
+ (model2) => !scoredModels.includes(model2) && !webResearchFallbackOrder.includes(model2)
34984
+ )
34985
+ ] : features.taskType === "tool_agent" || features.taskType === "tool_agent_parallel" && features.agentDomain !== "other" ? [
34986
+ ...scoredModels,
34987
+ ...eligibleCandidates.filter((model2) => !scoredModels.includes(model2))
34988
+ ] : [
34989
+ ...scoredModels,
34990
+ ...eligibleCandidates.filter((model2) => !scoredModels.includes(model2))
34991
+ ];
34992
+ const model = ranked[0] ?? base3.model;
34993
+ const selectedTierConfigs = {
34994
+ ...tierConfigs,
34995
+ [targetTier]: { primary: model, fallback: ranked.slice(1) }
34996
+ };
34997
+ const decision = selectModel(
34998
+ targetTier,
34999
+ base3.confidence,
35000
+ "portfolio",
35001
+ `${base3.reasoning} | v3 task=${features.taskType} agentRisk=${features.agentRisk} deepWebResearch=${features.deepWebResearch} terminalCode=${features.implicitTerminalCode} terminalSafety=${features.terminalSafetySensitive} candidates=${ranked.length}`,
35002
+ selectedTierConfigs,
35003
+ options.modelPricing,
35004
+ features.estimatedInputTokens,
35005
+ maxOutputTokens,
35006
+ options.routingProfile,
35007
+ base3.agenticScore
35008
+ );
35009
+ return {
35010
+ ...decision,
35011
+ tierConfigs: selectedTierConfigs,
35012
+ profile: base3.profile,
35013
+ candidates: ranked,
35014
+ candidateScores: rankedEntries.map(({ model: model2, score, quality, cost, speed, reliability }) => ({
35015
+ model: model2,
35016
+ score,
35017
+ quality,
35018
+ cost,
35019
+ speed,
35020
+ reliability
35021
+ })),
35022
+ taskType: features.taskType,
35023
+ routerVersion: "v3-portfolio"
35024
+ };
35025
+ }
35026
+ };
33649
35027
  DEFAULT_ROUTING_CONFIG = {
33650
- version: "2.0",
35028
+ version: "3.4",
35029
+ strategy: "portfolio",
35030
+ portfolio: {
35031
+ auto: {
35032
+ quality: 0.47,
35033
+ capability: 0.2,
35034
+ cost: 0.18,
35035
+ speed: 0.07,
35036
+ reliability: 0.03,
35037
+ legacy: 0.05
35038
+ },
35039
+ eco: {
35040
+ quality: 0.36,
35041
+ capability: 0.2,
35042
+ cost: 0.28,
35043
+ speed: 0.1,
35044
+ reliability: 0.04,
35045
+ legacy: 0.02
35046
+ },
35047
+ premium: {
35048
+ quality: 0.58,
35049
+ capability: 0.2,
35050
+ cost: 0.08,
35051
+ speed: 0.06,
35052
+ reliability: 0.06,
35053
+ legacy: 0.02
35054
+ },
35055
+ highStakesBoost: { quality: 0.08, reliability: 0.05 },
35056
+ latencySensitiveSpeedBoost: 0.08,
35057
+ affinityFloorGap: { auto: 0.1, eco: 0.22, premium: 0.05 }
35058
+ },
33651
35059
  classifier: {
33652
35060
  llmModel: "google/gemini-2.5-flash",
33653
35061
  llmMaxTokens: 10,
@@ -34773,8 +36181,8 @@ var init_config = __esm({
34773
36181
  // FREE — smaller, faster
34774
36182
  "free/deepseek-v4-flash",
34775
36183
  // FREE — 1M ctx; slow (~10 tok/s, 07-28 probe) but completes
34776
- "free/seed-oss-36b",
34777
- // FREE — live coder (qwen3.5-122b + qwen3-next died 2026-07; mistral-large-3-675b EOL'd 07-28)
36184
+ // seed-oss-36b sat here as the free coder until it EOL'd 2026-08-03 (HTTP 410).
36185
+ // gpt-oss-120b/20b already head this chain, so the rung is dropped, not retargeted.
34778
36186
  "google/gemini-3.1-flash-lite",
34779
36187
  // $0.25/$1.50 — newest flash-lite
34780
36188
  "openai/gpt-5.4-nano",
@@ -34891,8 +36299,8 @@ var init_config = __esm({
34891
36299
  "openai/gpt-5.3-codex",
34892
36300
  "deepseek/deepseek-chat",
34893
36301
  // Cheap, reliable
34894
- "free/seed-oss-36b"
34895
- // NVIDIA free ultimate backstop (qwen3-coder-480b retired)
36302
+ "free/gpt-oss-120b"
36303
+ // NVIDIA free ultimate backstop (was seed-oss-36b; EOL'd 2026-08-03)
34896
36304
  ]
34897
36305
  },
34898
36306
  REASONING: {
@@ -34981,8 +36389,8 @@ var init_config = __esm({
34981
36389
  // Previous flagship — 6,213ms, reliable
34982
36390
  "deepseek/deepseek-chat",
34983
36391
  // 1,431ms — cheap, reliable
34984
- "free/seed-oss-36b"
34985
- // NVIDIA free ultimate backstop (qwen3-coder-480b retired)
36392
+ "free/gpt-oss-120b"
36393
+ // NVIDIA free ultimate backstop (was seed-oss-36b; EOL'd 2026-08-03)
34986
36394
  ]
34987
36395
  },
34988
36396
  REASONING: {
@@ -35027,21 +36435,15 @@ var init_config = __esm({
35027
36435
  // Set to `true` to force agentic tiers; `false` to disable them entirely.
35028
36436
  }
35029
36437
  };
36438
+ registerStrategy(new PortfolioStrategy());
35030
36439
  }
35031
36440
  });
35032
36441
 
35033
36442
  // src/router/index.ts
35034
- function route(prompt, systemPrompt, maxOutputTokens, options) {
35035
- const strategy = getStrategy("rules");
35036
- return strategy.route(prompt, systemPrompt, maxOutputTokens, options);
35037
- }
35038
36443
  var init_router = __esm({
35039
36444
  "src/router/index.ts"() {
35040
36445
  "use strict";
35041
- init_strategy();
35042
- init_strategy();
35043
- init_selector();
35044
- init_config();
36446
+ init_dist();
35045
36447
  }
35046
36448
  });
35047
36449
 
@@ -35090,7 +36492,6 @@ var init_top_models = __esm({
35090
36492
  "deepseek/deepseek-chat",
35091
36493
  "deepseek/deepseek-reasoner",
35092
36494
  "free/deepseek-v4-flash",
35093
- "free/seed-oss-36b",
35094
36495
  "free/nemotron-3-nano-omni-30b-a3b-reasoning",
35095
36496
  "free/mistral-nemotron",
35096
36497
  "free/step-3.7-flash",
@@ -35400,9 +36801,10 @@ var init_models = __esm({
35400
36801
  "free/deepseek-v4-pro": "free/deepseek-v4-flash",
35401
36802
  "nvidia/deepseek-v4-flash": "free/deepseek-v4-flash",
35402
36803
  "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning": "free/nemotron-3-nano-omni-30b-a3b-reasoning",
35403
- // qwen3-coder-480b retired (NVIDIA EOL 2026-06-14) → redirects to seed-oss-36b
35404
- // server-side; keep the explicit id mappings for pinned callers (BlockRun still
35405
- // resolves them), but point the generic coding shorthands at the live successor.
36804
+ // qwen3-coder-480b retired (NVIDIA EOL 2026-06-14). Its server-side redirect used
36805
+ // to be seed-oss-36b, but that EOL'd too on 2026-08-03, so blockrun re-pointed it
36806
+ // at gpt-oss-120b. Keep the explicit id mappings for pinned callers (BlockRun still
36807
+ // resolves them); the generic coding shorthands follow the gateway to gpt-oss-120b.
35406
36808
  "nvidia/qwen3-coder-480b": "free/qwen3-coder-480b",
35407
36809
  "qwen/qwen3-coder-480b-a35b-instruct": "free/qwen3-coder-480b",
35408
36810
  // glm-4.7 pin keeps the real id — blockrun redirects it server-side (→ gpt-oss-120b).
@@ -35448,13 +36850,14 @@ var init_models = __esm({
35448
36850
  "v4-flash": "free/deepseek-v4-flash",
35449
36851
  "mistral-free": "free/mistral-nemotron",
35450
36852
  // mistral-large-3-675b EOL'd 2026-07-28 → last live free Mistral (blockrun demoted its /free-mistral primary the same way)
35451
- "glm-free": "free/seed-oss-36b",
35452
- // qwen3-coder retired → live coder successor
36853
+ "glm-free": "free/gpt-oss-120b",
36854
+ // seed-oss-36b (the prior target) EOL'd 2026-08-03
35453
36855
  "llama-free": "free/gpt-oss-120b",
35454
36856
  // no live free Llama left (maverick died 2026-07)
35455
- "qwen-coder": "free/seed-oss-36b",
35456
- // qwen3-coder-480b retired → seed-oss-36b
35457
- "qwen-coder-free": "free/seed-oss-36b",
36857
+ // qwen3-coder-480b retired 2026-06-14 → seed-oss-36b, which then EOL'd 2026-08-03.
36858
+ // Follow the gateway's own retarget rather than chaining to a second dead model.
36859
+ "qwen-coder": "free/gpt-oss-120b",
36860
+ "qwen-coder-free": "free/gpt-oss-120b",
35458
36861
  "qwen-thinking": "free/gpt-oss-120b",
35459
36862
  // qwen3-next died 2026-07-17; no live free Qwen left
35460
36863
  "qwen3-next": "free/qwen3-next-80b-a3b-instruct",
@@ -35463,9 +36866,11 @@ var init_models = __esm({
35463
36866
  "mistral-small": "free/mistral-nemotron",
35464
36867
  // closest live Mistral-family free model
35465
36868
  // New live free models (2026-06-14 BlockRun free-tier refresh)
36869
+ // seed-oss pins name the model itself — kept routable, the gateway redirects them.
35466
36870
  "seed-oss": "free/seed-oss-36b",
35467
36871
  "seed-oss-36b": "free/seed-oss-36b",
35468
- "coder-free": "free/seed-oss-36b",
36872
+ "coder-free": "free/gpt-oss-120b",
36873
+ // generic "a free coder" → follows the gateway retarget
35469
36874
  "mistral-nemotron": "free/mistral-nemotron",
35470
36875
  "step-flash": "free/step-3.7-flash",
35471
36876
  "step-3.7-flash": "free/step-3.7-flash",
@@ -35486,8 +36891,9 @@ var init_models = __esm({
35486
36891
  "nemotron-super": "free/gpt-oss-120b",
35487
36892
  "nemotron-49b": "free/gpt-oss-120b",
35488
36893
  "nemotron-120b": "free/gpt-oss-120b",
35489
- devstral: "free/seed-oss-36b",
35490
- "devstral-2": "free/seed-oss-36b",
36894
+ devstral: "free/gpt-oss-120b",
36895
+ // seed-oss-36b EOL'd 2026-08-03; matches blockrun's own retarget
36896
+ "devstral-2": "free/gpt-oss-120b",
35491
36897
  maverick: "free/llama-4-maverick",
35492
36898
  // explicit-ish pin — gateway redirects
35493
36899
  free: "free/gpt-oss-120b",
@@ -36610,8 +38016,13 @@ var init_models = __esm({
36610
38016
  reasoning: true
36611
38017
  },
36612
38018
  {
36613
- // ByteDance Seed-OSS 36B: strong open-source coder, 131K context. Successor
36614
- // to the retired qwen3-coder-480b (BlockRun redirects qwen3-coder → seed-oss).
38019
+ // ByteDance Seed-OSS 36B: was the free coder, and the redirect target for the
38020
+ // retired qwen3-coder-480b. EOL'd 2026-08-03 — blockrun's probe got HTTP 410
38021
+ // Gone from NVIDIA on both passes and the prod health gate fired
38022
+ // [ALERT][free-model-dead] kind=gone the same day; upstream now hides it and
38023
+ // re-pointed its own dependents (qwen3-coder-480b, devstral-2) at gpt-oss-120b.
38024
+ // Entry kept so explicit pins stay routable; off the picker, the FREE_MODELS
38025
+ // cascade, and the router fallback chains.
36615
38026
  id: "free/seed-oss-36b",
36616
38027
  name: "[Free] Seed-OSS 36B",
36617
38028
  version: "oss-36b",
@@ -56787,7 +58198,7 @@ var init_exclude_models = __esm({
56787
58198
 
56788
58199
  // src/config.ts
56789
58200
  var DEFAULT_PORT, PROXY_PORT;
56790
- var init_config2 = __esm({
58201
+ var init_config = __esm({
56791
58202
  "src/config.ts"() {
56792
58203
  "use strict";
56793
58204
  DEFAULT_PORT = 8402;
@@ -73185,27 +74596,27 @@ var require_dns = __commonJS({
73185
74596
  }
73186
74597
  );
73187
74598
  }
73188
- #defaultPick(origin2, hostnameRecords, affinity) {
74599
+ #defaultPick(origin2, hostnameRecords, affinity2) {
73189
74600
  let ip = null;
73190
74601
  const { records, offset } = hostnameRecords;
73191
74602
  let family;
73192
74603
  if (this.dualStack) {
73193
- if (affinity == null) {
74604
+ if (affinity2 == null) {
73194
74605
  if (offset == null || offset === maxInt) {
73195
74606
  hostnameRecords.offset = 0;
73196
- affinity = 4;
74607
+ affinity2 = 4;
73197
74608
  } else {
73198
74609
  hostnameRecords.offset++;
73199
- affinity = (hostnameRecords.offset & 1) === 1 ? 6 : 4;
74610
+ affinity2 = (hostnameRecords.offset & 1) === 1 ? 6 : 4;
73200
74611
  }
73201
74612
  }
73202
- if (records[affinity] != null && records[affinity].ips.length > 0) {
73203
- family = records[affinity];
74613
+ if (records[affinity2] != null && records[affinity2].ips.length > 0) {
74614
+ family = records[affinity2];
73204
74615
  } else {
73205
- family = records[affinity === 4 ? 6 : 4];
74616
+ family = records[affinity2 === 4 ? 6 : 4];
73206
74617
  }
73207
74618
  } else {
73208
- family = records[affinity];
74619
+ family = records[affinity2];
73209
74620
  }
73210
74621
  if (family == null || family.ips.length === 0) {
73211
74622
  return ip;
@@ -73222,7 +74633,7 @@ var require_dns = __commonJS({
73222
74633
  }
73223
74634
  if (Date.now() - ip.timestamp > ip.ttl) {
73224
74635
  family.ips.splice(position, 1);
73225
- return this.pick(origin2, hostnameRecords, affinity);
74636
+ return this.pick(origin2, hostnameRecords, affinity2);
73226
74637
  }
73227
74638
  return ip;
73228
74639
  }
@@ -73361,11 +74772,11 @@ var require_dns = __commonJS({
73361
74772
  throw new InvalidArgumentError("Invalid storage. Must be a object with methods: { get, set, full, delete }");
73362
74773
  }
73363
74774
  const dualStack = interceptorOpts?.dualStack ?? true;
73364
- let affinity;
74775
+ let affinity2;
73365
74776
  if (dualStack) {
73366
- affinity = interceptorOpts?.affinity ?? null;
74777
+ affinity2 = interceptorOpts?.affinity ?? null;
73367
74778
  } else {
73368
- affinity = interceptorOpts?.affinity ?? 4;
74779
+ affinity2 = interceptorOpts?.affinity ?? 4;
73369
74780
  }
73370
74781
  const opts = {
73371
74782
  maxTTL: interceptorOpts?.maxTTL ?? 1e4,
@@ -73373,7 +74784,7 @@ var require_dns = __commonJS({
73373
74784
  lookup: interceptorOpts?.lookup ?? null,
73374
74785
  pick: interceptorOpts?.pick ?? null,
73375
74786
  dualStack,
73376
- affinity,
74787
+ affinity: affinity2,
73377
74788
  maxItems: interceptorOpts?.maxItems ?? Infinity,
73378
74789
  storage: interceptorOpts?.storage
73379
74790
  };
@@ -86668,6 +88079,19 @@ function buildModelPricing() {
86668
88079
  }
86669
88080
  return map;
86670
88081
  }
88082
+ function buildModelCapabilities() {
88083
+ return Object.fromEntries(
88084
+ BLOCKRUN_MODELS.map((model) => [
88085
+ model.id,
88086
+ {
88087
+ contextWindow: model.contextWindow,
88088
+ maxOutputTokens: model.maxOutput,
88089
+ supportsTools: model.toolCalling === true,
88090
+ supportsVision: model.vision === true
88091
+ }
88092
+ ])
88093
+ );
88094
+ }
86671
88095
  function buildProxyModelList(createdAt = Math.floor(Date.now() / 1e3)) {
86672
88096
  const seen = /* @__PURE__ */ new Set();
86673
88097
  return OPENCLAW_MODELS.filter((model) => {
@@ -87013,7 +88437,8 @@ async function startProxy(options) {
87013
88437
  const modelPricing = buildModelPricing();
87014
88438
  const routerOpts = {
87015
88439
  config: routingConfig,
87016
- modelPricing
88440
+ modelPricing,
88441
+ modelCapabilities: buildModelCapabilities()
87017
88442
  };
87018
88443
  const deduplicator = new RequestDeduplicator();
87019
88444
  const responseCache2 = new ResponseCache(options.cacheConfig);
@@ -88159,6 +89584,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
88159
89584
  let maxTokens = DEFAULT_MAX_TOKENS;
88160
89585
  let routingProfile = null;
88161
89586
  let stickyExplicitModel;
89587
+ let pendingShadowComparison;
88162
89588
  let balanceFallbackNotice;
88163
89589
  let budgetDowngradeNotice;
88164
89590
  let budgetDowngradeHeaderMode;
@@ -88754,8 +90180,16 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
88754
90180
  const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
88755
90181
  const tools = parsed.tools;
88756
90182
  hasTools = Array.isArray(tools) && tools.length > 0;
90183
+ const requiresTools = hasTools && inferToolRequirement(prompt, systemPrompt, parsed.tool_choice);
90184
+ const toolNames = (tools ?? []).map((tool) => {
90185
+ if (typeof tool !== "object" || tool === null) return void 0;
90186
+ const fn = tool.function;
90187
+ if (typeof fn !== "object" || fn === null) return void 0;
90188
+ const name = fn.name;
90189
+ return typeof name === "string" ? name : void 0;
90190
+ }).filter((name) => name !== void 0);
88757
90191
  if (hasTools && tools) {
88758
- console.log(`[ClawRouter] Tools detected (${tools.length}), forcing agentic tiers`);
90192
+ console.log(`[ClawRouter] Tools detected (${tools.length}), required=${requiresTools}`);
88759
90193
  }
88760
90194
  hasVision = parsedMessages.some((m) => {
88761
90195
  if (Array.isArray(m.content)) {
@@ -88769,8 +90203,36 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
88769
90203
  routingDecision = route(prompt, systemPrompt, maxTokens, {
88770
90204
  ...routerOpts,
88771
90205
  routingProfile: routingProfile ?? void 0,
88772
- hasTools
90206
+ hasTools,
90207
+ toolCount: tools?.length ?? 0,
90208
+ toolNames,
90209
+ requiresTools,
90210
+ hasVision,
90211
+ requiresStructuredOutput: typeof parsed.response_format === "object" && parsed.response_format !== null
88773
90212
  });
90213
+ const shadow = routerOpts.config.shadow;
90214
+ const activeStrategy = routerOpts.config.strategy ?? "portfolio";
90215
+ const shadowRate = Math.max(0, Math.min(1, shadow?.sampleRate ?? 1));
90216
+ if (shadow && shadow.strategy !== activeStrategy && Math.random() < shadowRate) {
90217
+ const requiresStructuredOutput = typeof parsed.response_format === "object" && parsed.response_format !== null;
90218
+ const shadowDecision = route(prompt, systemPrompt, maxTokens, {
90219
+ ...routerOpts,
90220
+ config: { ...routerOpts.config, strategy: shadow.strategy },
90221
+ routingProfile: routingProfile ?? void 0,
90222
+ hasTools,
90223
+ toolCount: tools?.length ?? 0,
90224
+ toolNames,
90225
+ requiresTools,
90226
+ hasVision,
90227
+ requiresStructuredOutput
90228
+ });
90229
+ pendingShadowComparison = {
90230
+ shadow: shadowDecision,
90231
+ hasTools,
90232
+ hasVision,
90233
+ requiresStructuredOutput
90234
+ };
90235
+ }
88774
90236
  if (hasTools && routingDecision.tier === "SIMPLE") {
88775
90237
  console.log(
88776
90238
  `[ClawRouter] SIMPLE+tools: keeping agentic model ${routingDecision.model} (tools need reliable function-call support)`
@@ -88879,6 +90341,21 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
88879
90341
  );
88880
90342
  }
88881
90343
  }
90344
+ if (pendingShadowComparison) {
90345
+ try {
90346
+ options.onShadowRouted?.({
90347
+ executed: routingDecision,
90348
+ shadow: pendingShadowComparison.shadow,
90349
+ sameModel: routingDecision.model === pendingShadowComparison.shadow.model,
90350
+ hasTools: pendingShadowComparison.hasTools,
90351
+ hasVision: pendingShadowComparison.hasVision,
90352
+ requiresStructuredOutput: pendingShadowComparison.requiresStructuredOutput
90353
+ });
90354
+ } catch (err) {
90355
+ const message = err instanceof Error ? err.message : String(err);
90356
+ console.warn(`[ClawRouter] Shadow routing callback failed: ${message}`);
90357
+ }
90358
+ }
88882
90359
  options.onRouted?.(routingDecision);
88883
90360
  }
88884
90361
  }
@@ -89226,35 +90703,64 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
89226
90703
  const estimatedInputTokens = Math.ceil(body.length / 4);
89227
90704
  const estimatedTotalTokens = estimatedInputTokens + maxTokens;
89228
90705
  const tierConfigs = routingDecision.tierConfigs ?? routerOpts.config.tiers;
89229
- const fullChain = prependStickyExplicitModel(
89230
- getFallbackChain(routingDecision.tier, tierConfigs)
89231
- );
89232
- const contextFiltered = prependStickyExplicitModel(
89233
- getFallbackChainFiltered(
89234
- routingDecision.tier,
89235
- tierConfigs,
89236
- estimatedTotalTokens,
89237
- getModelContextWindow
89238
- )
90706
+ const rankedCandidates = routingDecision.candidates ?? getFallbackChain(routingDecision.tier, tierConfigs);
90707
+ const selectedModel = routingDecision.model;
90708
+ const fullChain = prependStickyExplicitModel([
90709
+ selectedModel,
90710
+ ...rankedCandidates.filter((model) => model !== selectedModel)
90711
+ ]);
90712
+ const contextFiltered = filterCandidatesByCapacity(
90713
+ fullChain,
90714
+ estimatedInputTokens,
90715
+ maxTokens,
90716
+ (modelId2) => {
90717
+ const model = BLOCKRUN_MODEL_BY_ID.get(modelId2);
90718
+ return model ? { contextWindow: model.contextWindow, maxOutput: model.maxOutput } : void 0;
90719
+ }
89239
90720
  );
90721
+ if (contextFiltered.length === 0) {
90722
+ const errPayload = JSON.stringify({
90723
+ error: {
90724
+ message: `No routed model can satisfy the requested context and output capacity (~${estimatedTotalTokens} tokens). Reduce the input or max_tokens and retry.`,
90725
+ type: "invalid_request_error",
90726
+ code: "context_capacity_exceeded"
90727
+ }
90728
+ });
90729
+ if (heartbeatInterval) {
90730
+ clearInterval(heartbeatInterval);
90731
+ heartbeatInterval = void 0;
90732
+ }
90733
+ if (headersSentEarly) {
90734
+ safeWrite(res, `data: ${errPayload}
90735
+
90736
+ data: [DONE]
90737
+
90738
+ `);
90739
+ res.end();
90740
+ } else {
90741
+ res.writeHead(400, { "Content-Type": "application/json" });
90742
+ res.end(errPayload);
90743
+ }
90744
+ completed = true;
90745
+ deduplicator.removeInflight(dedupKey);
90746
+ clearTimeout(timeoutId);
90747
+ req.removeListener("close", onClientClose);
90748
+ return;
90749
+ }
89240
90750
  const contextExcluded = fullChain.filter((m) => !contextFiltered.includes(m));
89241
90751
  if (contextExcluded.length > 0) {
89242
90752
  console.log(
89243
90753
  `[ClawRouter] Context filter (~${estimatedTotalTokens} tokens): excluded ${contextExcluded.join(", ")}`
89244
90754
  );
89245
90755
  }
89246
- const excludeFiltered = prependStickyExplicitModel(
89247
- filterByExcludeList(contextFiltered, excludeList)
89248
- );
90756
+ const excludeFiltered = filterByExcludeList(contextFiltered, excludeList);
89249
90757
  const excludeExcluded = contextFiltered.filter((m) => !excludeFiltered.includes(m));
89250
90758
  if (excludeExcluded.length > 0) {
89251
90759
  console.log(
89252
90760
  `[ClawRouter] Exclude filter: excluded ${excludeExcluded.join(", ")} (user preference)`
89253
90761
  );
89254
90762
  }
89255
- let toolFiltered = prependStickyExplicitModel(
89256
- filterByToolCalling(excludeFiltered, hasTools, supportsToolCalling)
89257
- );
90763
+ let toolFiltered = filterByToolCalling(excludeFiltered, hasTools, supportsToolCalling);
89258
90764
  const toolExcluded = excludeFiltered.filter((m) => !toolFiltered.includes(m));
89259
90765
  if (toolExcluded.length > 0) {
89260
90766
  console.log(
@@ -89273,19 +90779,17 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
89273
90779
  console.log(
89274
90780
  `[ClawRouter] Tool-compliance filter: excluded ${dropped.join(", ")} (unreliable tool schema handling)`
89275
90781
  );
89276
- toolFiltered = prependStickyExplicitModel(compliant);
90782
+ toolFiltered = compliant;
89277
90783
  }
89278
90784
  }
89279
- const visionFiltered = prependStickyExplicitModel(
89280
- filterByVision(toolFiltered, hasVision, supportsVision)
89281
- );
90785
+ const visionFiltered = filterByVision(toolFiltered, hasVision, supportsVision);
89282
90786
  const visionExcluded = toolFiltered.filter((m) => !visionFiltered.includes(m));
89283
90787
  if (visionExcluded.length > 0) {
89284
90788
  console.log(
89285
90789
  `[ClawRouter] Vision filter: excluded ${visionExcluded.join(", ")} (no vision support)`
89286
90790
  );
89287
90791
  }
89288
- modelsToTry = prependStickyExplicitModel(visionFiltered).slice(0, MAX_FALLBACK_ATTEMPTS);
90792
+ modelsToTry = visionFiltered.slice(0, MAX_FALLBACK_ATTEMPTS);
89289
90793
  modelsToTry = prioritizeNonRateLimited(modelsToTry);
89290
90794
  } else {
89291
90795
  modelsToTry = modelId ? [modelId] : [];
@@ -90175,7 +91679,7 @@ var init_proxy = __esm({
90175
91679
  init_client2();
90176
91680
  init_esm5();
90177
91681
  init_router();
90178
- init_rules();
91682
+ init_router();
90179
91683
  init_models();
90180
91684
  init_max_tokens();
90181
91685
  init_logger();
@@ -90189,7 +91693,7 @@ var init_proxy = __esm({
90189
91693
  init_session();
90190
91694
  init_updater();
90191
91695
  init_exclude_models();
90192
- init_config2();
91696
+ init_config();
90193
91697
  init_journal();
90194
91698
  init_upstream_proxy();
90195
91699
  init_textual_tool_calls();
@@ -90216,8 +91720,6 @@ var init_proxy = __esm({
90216
91720
  "free/gpt-oss-20b",
90217
91721
  "free/deepseek-v4-flash",
90218
91722
  // 1M ctx; slow (~10 tok/s, 07-28 probe) but completes
90219
- "free/seed-oss-36b",
90220
- // live coder (successor to retired qwen3-coder-480b)
90221
91723
  "free/mistral-nemotron",
90222
91724
  // strong instruction following
90223
91725
  "free/step-3.7-flash",
@@ -91823,7 +93325,7 @@ var init_headers = __esm({
91823
93325
 
91824
93326
  // node_modules/@polymarket/clob-client-v2/dist/config.js
91825
93327
  var AMOY_CONTRACTS, MATIC_CONTRACTS, COLLATERAL_TOKEN_DECIMALS, getContractConfig;
91826
- var init_config3 = __esm({
93328
+ var init_config2 = __esm({
91827
93329
  "node_modules/@polymarket/clob-client-v2/dist/config.js"() {
91828
93330
  "use strict";
91829
93331
  AMOY_CONTRACTS = {
@@ -92123,7 +93625,7 @@ var init_buildMarketOrderCreationArgs = __esm({
92123
93625
  "node_modules/@polymarket/clob-client-v2/dist/order-builder/helpers/buildMarketOrderCreationArgs.js"() {
92124
93626
  "use strict";
92125
93627
  init_constants2();
92126
- init_config3();
93628
+ init_config2();
92127
93629
  init_getMarketOrderRawAmounts();
92128
93630
  init_esm();
92129
93631
  }
@@ -92684,7 +94186,7 @@ var init_createMarketOrder = __esm({
92684
94186
  "node_modules/@polymarket/clob-client-v2/dist/order-builder/helpers/createMarketOrder.js"() {
92685
94187
  "use strict";
92686
94188
  init_signer();
92687
- init_config3();
94189
+ init_config2();
92688
94190
  init_buildMarketOrderCreationArgs();
92689
94191
  init_signatureTypeV2();
92690
94192
  init_buildOrder();
@@ -92792,7 +94294,7 @@ var init_buildOrderCreationArgs = __esm({
92792
94294
  "node_modules/@polymarket/clob-client-v2/dist/order-builder/helpers/buildOrderCreationArgs.js"() {
92793
94295
  "use strict";
92794
94296
  init_constants2();
92795
- init_config3();
94297
+ init_config2();
92796
94298
  init_getOrderRawAmounts();
92797
94299
  init_esm();
92798
94300
  }
@@ -92804,7 +94306,7 @@ var init_createOrder = __esm({
92804
94306
  "node_modules/@polymarket/clob-client-v2/dist/order-builder/helpers/createOrder.js"() {
92805
94307
  "use strict";
92806
94308
  init_signer();
92807
- init_config3();
94309
+ init_config2();
92808
94310
  init_signatureTypeV2();
92809
94311
  init_buildOrder();
92810
94312
  init_buildOrderCreationArgs();
@@ -92842,7 +94344,7 @@ var init_createExchangeV3OrderFromAmounts = __esm({
92842
94344
  "use strict";
92843
94345
  init_constants2();
92844
94346
  init_signer();
92845
- init_config3();
94347
+ init_config2();
92846
94348
  init_signatureTypeV2();
92847
94349
  init_buildOrder();
92848
94350
  createExchangeV3OrderFromAmounts = async (eoaSigner, chainId, signatureType, funderAddress, userOrder) => {
@@ -110644,7 +112146,7 @@ var init_axios2 = __esm({
110644
112146
 
110645
112147
  // node_modules/browser-or-node/dist/index.mjs
110646
112148
  var isBrowser, isNode, isWebWorker, isJsDom, isDeno, isBun;
110647
- var init_dist = __esm({
112149
+ var init_dist2 = __esm({
110648
112150
  "node_modules/browser-or-node/dist/index.mjs"() {
110649
112151
  "use strict";
110650
112152
  isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined";
@@ -110668,7 +112170,7 @@ var init_http_helpers = __esm({
110668
112170
  "node_modules/@polymarket/clob-client-v2/dist/http-helpers/index.js"() {
110669
112171
  "use strict";
110670
112172
  init_axios2();
110671
- init_dist();
112173
+ init_dist2();
110672
112174
  GET = "GET";
110673
112175
  POST = "POST";
110674
112176
  DELETE = "DELETE";
@@ -111700,10 +113202,10 @@ var init_client4 = __esm({
111700
113202
  });
111701
113203
 
111702
113204
  // node_modules/@polymarket/clob-client-v2/dist/index.js
111703
- var init_dist2 = __esm({
113205
+ var init_dist3 = __esm({
111704
113206
  "node_modules/@polymarket/clob-client-v2/dist/index.js"() {
111705
113207
  "use strict";
111706
- init_config3();
113208
+ init_config2();
111707
113209
  init_signatureTypeV2();
111708
113210
  init_side();
111709
113211
  init_clob();
@@ -111723,7 +113225,7 @@ import * as net from "net";
111723
113225
  import * as http4 from "http";
111724
113226
  import { Agent as HttpsAgent } from "https";
111725
113227
  var INTERNAL, Agent2;
111726
- var init_dist3 = __esm({
113228
+ var init_dist4 = __esm({
111727
113229
  "node_modules/agent-base/dist/index.js"() {
111728
113230
  "use strict";
111729
113231
  init_helpers();
@@ -111962,7 +113464,7 @@ function createNegotiateAuth() {
111962
113464
  };
111963
113465
  };
111964
113466
  }
111965
- var init_dist4 = __esm({
113467
+ var init_dist5 = __esm({
111966
113468
  "node_modules/proxy-agent-negotiate/dist/index.js"() {
111967
113469
  "use strict";
111968
113470
  }
@@ -111989,13 +113491,13 @@ function omit(obj, ...keys) {
111989
113491
  return ret;
111990
113492
  }
111991
113493
  var import_debug2, debug2, setServernameFromNonIpHost, HttpsProxyAgent2;
111992
- var init_dist5 = __esm({
113494
+ var init_dist6 = __esm({
111993
113495
  "node_modules/https-proxy-agent/dist/index.js"() {
111994
113496
  "use strict";
111995
113497
  import_debug2 = __toESM(require_src(), 1);
111996
- init_dist3();
111997
- init_parse_proxy_response();
111998
113498
  init_dist4();
113499
+ init_parse_proxy_response();
113500
+ init_dist5();
111999
113501
  debug2 = (0, import_debug2.default)("https-proxy-agent");
112000
113502
  setServernameFromNonIpHost = (options) => {
112001
113503
  if (options.servername === void 0 && options.host && !net2.isIP(options.host)) {
@@ -112208,7 +113710,7 @@ var POLYGON_CHAIN_ID, CTF_EXCHANGE_V2, NEG_RISK_CTF_EXCHANGE_V2, NEG_RISK_ADAPTE
112208
113710
  var init_constants3 = __esm({
112209
113711
  "src/polymarket/constants.ts"() {
112210
113712
  "use strict";
112211
- init_dist2();
113713
+ init_dist3();
112212
113714
  POLYGON_CHAIN_ID = 137;
112213
113715
  CTF_EXCHANGE_V2 = "0xE111180000d2663C0091e4f400237545B87B996B";
112214
113716
  NEG_RISK_CTF_EXCHANGE_V2 = "0xe2222d279d744050d28e00520010520000310F59";
@@ -112745,11 +114247,11 @@ var init_client5 = __esm({
112745
114247
  "src/polymarket/client.ts"() {
112746
114248
  "use strict";
112747
114249
  init_axios2();
112748
- init_dist5();
114250
+ init_dist6();
112749
114251
  init_esm();
112750
114252
  init_accounts();
112751
114253
  init_chains();
112752
- init_dist2();
114254
+ init_dist3();
112753
114255
  init_wallet_adapter();
112754
114256
  init_constants3();
112755
114257
  init_creds();
@@ -113119,7 +114621,7 @@ var ledger;
113119
114621
  var init_orders = __esm({
113120
114622
  "src/polymarket/orders.ts"() {
113121
114623
  "use strict";
113122
- init_dist2();
114624
+ init_dist3();
113123
114625
  init_client5();
113124
114626
  init_constants3();
113125
114627
  init_creds();
@@ -220301,7 +221803,7 @@ var init_relayer = __esm({
220301
221803
  "use strict";
220302
221804
  import_builder_relayer_client = __toESM(require_dist3(), 1);
220303
221805
  import_builder_signing_sdk = __toESM(require_dist4(), 1);
220304
- init_dist2();
221806
+ init_dist3();
220305
221807
  init_esm();
220306
221808
  init_chains();
220307
221809
  init_client5();
@@ -220641,7 +222143,7 @@ var init_setup = __esm({
220641
222143
  "use strict";
220642
222144
  init_esm();
220643
222145
  init_chains();
220644
- init_dist2();
222146
+ init_dist3();
220645
222147
  init_client5();
220646
222148
  init_constants3();
220647
222149
  init_creds();
@@ -221095,7 +222597,7 @@ function sleep2(ms) {
221095
222597
  return new Promise((r2) => setTimeout(r2, ms));
221096
222598
  }
221097
222599
  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;
221098
- var init_dist6 = __esm({
222600
+ var init_dist7 = __esm({
221099
222601
  "node_modules/@blockrun/llm/dist/index.js"() {
221100
222602
  "use strict";
221101
222603
  init_accounts();
@@ -221663,7 +223165,7 @@ var init_fund = __esm({
221663
223165
  "src/polymarket/fund.ts"() {
221664
223166
  "use strict";
221665
223167
  init_axios2();
221666
- init_dist6();
223168
+ init_dist7();
221667
223169
  init_wallet_adapter();
221668
223170
  init_client5();
221669
223171
  init_constants3();
@@ -222142,29 +223644,29 @@ var init_spend_control = __esm({
222142
223644
  getLimits() {
222143
223645
  return { ...this.limits };
222144
223646
  }
222145
- check(estimatedCost) {
223647
+ check(estimatedCost2) {
222146
223648
  const now2 = this.now();
222147
223649
  if (this.limits.perRequest !== void 0) {
222148
- if (estimatedCost > this.limits.perRequest) {
223650
+ if (estimatedCost2 > this.limits.perRequest) {
222149
223651
  return {
222150
223652
  allowed: false,
222151
223653
  blockedBy: "perRequest",
222152
223654
  remaining: this.limits.perRequest,
222153
- reason: `Per-request limit exceeded: $${estimatedCost.toFixed(4)} > $${this.limits.perRequest.toFixed(2)} max`
223655
+ reason: `Per-request limit exceeded: $${estimatedCost2.toFixed(4)} > $${this.limits.perRequest.toFixed(2)} max`
222154
223656
  };
222155
223657
  }
222156
223658
  }
222157
223659
  if (this.limits.hourly !== void 0) {
222158
223660
  const hourlySpent = this.getSpendingInWindow(now2 - HOUR_MS, now2);
222159
223661
  const remaining = this.limits.hourly - hourlySpent;
222160
- if (estimatedCost > remaining) {
223662
+ if (estimatedCost2 > remaining) {
222161
223663
  const oldestInWindow = this.history.find((r2) => r2.timestamp >= now2 - HOUR_MS);
222162
223664
  const resetIn = oldestInWindow ? Math.ceil((oldestInWindow.timestamp + HOUR_MS - now2) / 1e3) : 0;
222163
223665
  return {
222164
223666
  allowed: false,
222165
223667
  blockedBy: "hourly",
222166
223668
  remaining,
222167
- reason: `Hourly limit exceeded: $${(hourlySpent + estimatedCost).toFixed(2)} > $${this.limits.hourly.toFixed(2)} max`,
223669
+ reason: `Hourly limit exceeded: $${(hourlySpent + estimatedCost2).toFixed(2)} > $${this.limits.hourly.toFixed(2)} max`,
222168
223670
  resetIn
222169
223671
  };
222170
223672
  }
@@ -222172,26 +223674,26 @@ var init_spend_control = __esm({
222172
223674
  if (this.limits.daily !== void 0) {
222173
223675
  const dailySpent = this.getSpendingInWindow(now2 - DAY_MS, now2);
222174
223676
  const remaining = this.limits.daily - dailySpent;
222175
- if (estimatedCost > remaining) {
223677
+ if (estimatedCost2 > remaining) {
222176
223678
  const oldestInWindow = this.history.find((r2) => r2.timestamp >= now2 - DAY_MS);
222177
223679
  const resetIn = oldestInWindow ? Math.ceil((oldestInWindow.timestamp + DAY_MS - now2) / 1e3) : 0;
222178
223680
  return {
222179
223681
  allowed: false,
222180
223682
  blockedBy: "daily",
222181
223683
  remaining,
222182
- reason: `Daily limit exceeded: $${(dailySpent + estimatedCost).toFixed(2)} > $${this.limits.daily.toFixed(2)} max`,
223684
+ reason: `Daily limit exceeded: $${(dailySpent + estimatedCost2).toFixed(2)} > $${this.limits.daily.toFixed(2)} max`,
222183
223685
  resetIn
222184
223686
  };
222185
223687
  }
222186
223688
  }
222187
223689
  if (this.limits.session !== void 0) {
222188
223690
  const remaining = this.limits.session - this.sessionSpent;
222189
- if (estimatedCost > remaining) {
223691
+ if (estimatedCost2 > remaining) {
222190
223692
  return {
222191
223693
  allowed: false,
222192
223694
  blockedBy: "session",
222193
223695
  remaining,
222194
- reason: `Session limit exceeded: $${(this.sessionSpent + estimatedCost).toFixed(2)} > $${this.limits.session.toFixed(2)} max`
223696
+ reason: `Session limit exceeded: $${(this.sessionSpent + estimatedCost2).toFixed(2)} > $${this.limits.session.toFixed(2)} max`
222195
223697
  };
222196
223698
  }
222197
223699
  }
@@ -222376,6 +223878,7 @@ __export(index_exports, {
222376
223878
  deriveEvmKey: () => deriveEvmKey,
222377
223879
  deriveSolanaKeyBytes: () => deriveSolanaKeyBytes,
222378
223880
  fetchWithRetry: () => fetchWithRetry,
223881
+ filterCandidatesByCapacity: () => filterCandidatesByCapacity,
222379
223882
  formatDuration: () => formatDuration,
222380
223883
  formatStatsAscii: () => formatStatsAscii,
222381
223884
  generateWalletMnemonic: () => generateWalletMnemonic,
@@ -222388,6 +223891,7 @@ __export(index_exports, {
222388
223891
  getSessionId: () => getSessionId,
222389
223892
  getStats: () => getStats,
222390
223893
  hashRequestContent: () => hashRequestContent,
223894
+ inferToolRequirement: () => inferToolRequirement,
222391
223895
  injectAuthProfile: () => injectAuthProfile,
222392
223896
  injectModelsConfig: () => injectModelsConfig,
222393
223897
  isAgenticModel: () => isAgenticModel,
@@ -222932,6 +224436,11 @@ async function startProxyInBackground(api, startupGeneration) {
222932
224436
  `[${decision.tier}] ${decision.model} $${cost} (saved ${saved}%) | ${decision.reasoning}`
222933
224437
  );
222934
224438
  },
224439
+ onShadowRouted: (comparison) => {
224440
+ api.logger.info(
224441
+ `[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}`
224442
+ );
224443
+ },
222935
224444
  onLowBalance: (info) => {
222936
224445
  api.logger.warn(`[!] Low balance: ${info.balanceUSD}. Fund wallet: ${info.walletAddress}`);
222937
224446
  },