@blockrun/llm 3.11.0 → 3.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -31,6 +31,3362 @@ var APIError = class extends BlockrunError {
31
31
  }
32
32
  };
33
33
 
34
+ // node_modules/.pnpm/@blockrun+router-core@https+++codeload.github.com+BlockRunAI+router-core+tar.gz+d4308049348e1_gni7wlazdyzf7iqax5nhqdpoqi/node_modules/@blockrun/router-core/dist/index.js
35
+ function scoreTokenCount(estimatedTokens, thresholds) {
36
+ if (estimatedTokens < thresholds.simple) {
37
+ return { name: "tokenCount", score: -1, signal: `short (${estimatedTokens} tokens)` };
38
+ }
39
+ if (estimatedTokens > thresholds.complex) {
40
+ return { name: "tokenCount", score: 1, signal: `long (${estimatedTokens} tokens)` };
41
+ }
42
+ return { name: "tokenCount", score: 0, signal: null };
43
+ }
44
+ function scoreKeywordMatch(text, keywords, name, signalLabel, thresholds, scores) {
45
+ const matches = keywords.filter((kw) => text.includes(kw.toLowerCase()));
46
+ if (matches.length >= thresholds.high) {
47
+ return {
48
+ name,
49
+ score: scores.high,
50
+ signal: `${signalLabel} (${matches.slice(0, 3).join(", ")})`
51
+ };
52
+ }
53
+ if (matches.length >= thresholds.low) {
54
+ return {
55
+ name,
56
+ score: scores.low,
57
+ signal: `${signalLabel} (${matches.slice(0, 3).join(", ")})`
58
+ };
59
+ }
60
+ return { name, score: scores.none, signal: null };
61
+ }
62
+ function scoreMultiStep(text) {
63
+ const patterns = [/first.*then/i, /step \d/i, /\d\.\s/];
64
+ const hits = patterns.filter((p) => p.test(text));
65
+ if (hits.length > 0) {
66
+ return { name: "multiStepPatterns", score: 0.5, signal: "multi-step" };
67
+ }
68
+ return { name: "multiStepPatterns", score: 0, signal: null };
69
+ }
70
+ function scoreQuestionComplexity(prompt) {
71
+ const count = (prompt.match(/\?/g) || []).length;
72
+ if (count > 3) {
73
+ return { name: "questionComplexity", score: 0.5, signal: `${count} questions` };
74
+ }
75
+ return { name: "questionComplexity", score: 0, signal: null };
76
+ }
77
+ function scoreAgenticTask(text, keywords) {
78
+ let matchCount = 0;
79
+ const signals = [];
80
+ for (const keyword of keywords) {
81
+ if (text.includes(keyword.toLowerCase())) {
82
+ matchCount++;
83
+ if (signals.length < 3) {
84
+ signals.push(keyword);
85
+ }
86
+ }
87
+ }
88
+ if (matchCount >= 4) {
89
+ return {
90
+ dimensionScore: {
91
+ name: "agenticTask",
92
+ score: 1,
93
+ signal: `agentic (${signals.join(", ")})`
94
+ },
95
+ agenticScore: 1
96
+ };
97
+ } else if (matchCount >= 3) {
98
+ return {
99
+ dimensionScore: {
100
+ name: "agenticTask",
101
+ score: 0.6,
102
+ signal: `agentic (${signals.join(", ")})`
103
+ },
104
+ agenticScore: 0.6
105
+ };
106
+ } else if (matchCount >= 1) {
107
+ return {
108
+ dimensionScore: {
109
+ name: "agenticTask",
110
+ score: 0.2,
111
+ signal: `agentic-light (${signals.join(", ")})`
112
+ },
113
+ agenticScore: 0.2
114
+ };
115
+ }
116
+ return {
117
+ dimensionScore: { name: "agenticTask", score: 0, signal: null },
118
+ agenticScore: 0
119
+ };
120
+ }
121
+ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
122
+ const userText = prompt.toLowerCase();
123
+ const dimensions = [
124
+ // Token count uses total estimated tokens (system + user) — context size matters for model selection
125
+ scoreTokenCount(estimatedTokens, config.tokenCountThresholds),
126
+ scoreKeywordMatch(
127
+ userText,
128
+ config.codeKeywords,
129
+ "codePresence",
130
+ "code",
131
+ { low: 1, high: 2 },
132
+ { none: 0, low: 0.5, high: 1 }
133
+ ),
134
+ scoreKeywordMatch(
135
+ userText,
136
+ config.reasoningKeywords,
137
+ "reasoningMarkers",
138
+ "reasoning",
139
+ { low: 1, high: 2 },
140
+ { none: 0, low: 0.7, high: 1 }
141
+ ),
142
+ scoreKeywordMatch(
143
+ userText,
144
+ config.technicalKeywords,
145
+ "technicalTerms",
146
+ "technical",
147
+ { low: 2, high: 4 },
148
+ { none: 0, low: 0.5, high: 1 }
149
+ ),
150
+ scoreKeywordMatch(
151
+ userText,
152
+ config.creativeKeywords,
153
+ "creativeMarkers",
154
+ "creative",
155
+ { low: 1, high: 2 },
156
+ { none: 0, low: 0.5, high: 0.7 }
157
+ ),
158
+ scoreKeywordMatch(
159
+ userText,
160
+ config.simpleKeywords,
161
+ "simpleIndicators",
162
+ "simple",
163
+ { low: 1, high: 2 },
164
+ { none: 0, low: -1, high: -1 }
165
+ ),
166
+ scoreMultiStep(userText),
167
+ scoreQuestionComplexity(prompt),
168
+ // 6 new dimensions
169
+ scoreKeywordMatch(
170
+ userText,
171
+ config.imperativeVerbs,
172
+ "imperativeVerbs",
173
+ "imperative",
174
+ { low: 1, high: 2 },
175
+ { none: 0, low: 0.3, high: 0.5 }
176
+ ),
177
+ scoreKeywordMatch(
178
+ userText,
179
+ config.constraintIndicators,
180
+ "constraintCount",
181
+ "constraints",
182
+ { low: 1, high: 3 },
183
+ { none: 0, low: 0.3, high: 0.7 }
184
+ ),
185
+ scoreKeywordMatch(
186
+ userText,
187
+ config.outputFormatKeywords,
188
+ "outputFormat",
189
+ "format",
190
+ { low: 1, high: 2 },
191
+ { none: 0, low: 0.4, high: 0.7 }
192
+ ),
193
+ scoreKeywordMatch(
194
+ userText,
195
+ config.referenceKeywords,
196
+ "referenceComplexity",
197
+ "references",
198
+ { low: 1, high: 2 },
199
+ { none: 0, low: 0.3, high: 0.5 }
200
+ ),
201
+ scoreKeywordMatch(
202
+ userText,
203
+ config.negationKeywords,
204
+ "negationComplexity",
205
+ "negation",
206
+ { low: 2, high: 3 },
207
+ { none: 0, low: 0.3, high: 0.5 }
208
+ ),
209
+ scoreKeywordMatch(
210
+ userText,
211
+ config.domainSpecificKeywords,
212
+ "domainSpecificity",
213
+ "domain-specific",
214
+ { low: 1, high: 2 },
215
+ { none: 0, low: 0.5, high: 0.8 }
216
+ )
217
+ ];
218
+ const agenticResult = scoreAgenticTask(userText, config.agenticTaskKeywords);
219
+ dimensions.push(agenticResult.dimensionScore);
220
+ const agenticScore = agenticResult.agenticScore;
221
+ const signals = dimensions.filter((d) => d.signal !== null).map((d) => d.signal);
222
+ const weights = config.dimensionWeights;
223
+ let weightedScore = 0;
224
+ for (const d of dimensions) {
225
+ const w = weights[d.name] ?? 0;
226
+ weightedScore += d.score * w;
227
+ }
228
+ const reasoningMatches = config.reasoningKeywords.filter(
229
+ (kw) => userText.includes(kw.toLowerCase())
230
+ );
231
+ if (reasoningMatches.length >= 2) {
232
+ const confidence2 = calibrateConfidence(
233
+ Math.max(weightedScore, 0.3),
234
+ // ensure positive for confidence calc
235
+ config.confidenceSteepness
236
+ );
237
+ return {
238
+ score: weightedScore,
239
+ tier: "REASONING",
240
+ confidence: Math.max(confidence2, 0.85),
241
+ signals,
242
+ agenticScore,
243
+ dimensions
244
+ };
245
+ }
246
+ const { simpleMedium, mediumComplex, complexReasoning } = config.tierBoundaries;
247
+ let tier;
248
+ let distanceFromBoundary;
249
+ if (weightedScore < simpleMedium) {
250
+ tier = "SIMPLE";
251
+ distanceFromBoundary = simpleMedium - weightedScore;
252
+ } else if (weightedScore < mediumComplex) {
253
+ tier = "MEDIUM";
254
+ distanceFromBoundary = Math.min(weightedScore - simpleMedium, mediumComplex - weightedScore);
255
+ } else if (weightedScore < complexReasoning) {
256
+ tier = "COMPLEX";
257
+ distanceFromBoundary = Math.min(
258
+ weightedScore - mediumComplex,
259
+ complexReasoning - weightedScore
260
+ );
261
+ } else {
262
+ tier = "REASONING";
263
+ distanceFromBoundary = weightedScore - complexReasoning;
264
+ }
265
+ const confidence = calibrateConfidence(distanceFromBoundary, config.confidenceSteepness);
266
+ if (confidence < config.confidenceThreshold) {
267
+ return { score: weightedScore, tier: null, confidence, signals, agenticScore, dimensions };
268
+ }
269
+ return { score: weightedScore, tier, confidence, signals, agenticScore, dimensions };
270
+ }
271
+ function calibrateConfidence(distance, steepness) {
272
+ return 1 / (1 + Math.exp(-steepness * distance));
273
+ }
274
+ var BASELINE_MODEL_ID = "anthropic/claude-opus-4.7";
275
+ var BASELINE_INPUT_PRICE = 5;
276
+ var BASELINE_OUTPUT_PRICE = 25;
277
+ function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPricing, estimatedInputTokens, maxOutputTokens, routingProfile, agenticScore) {
278
+ const tierConfig = tierConfigs[tier];
279
+ const model = tierConfig.primary;
280
+ const pricing = modelPricing.get(model);
281
+ let costEstimate;
282
+ if (pricing?.flatPrice !== void 0) {
283
+ costEstimate = pricing.flatPrice;
284
+ } else {
285
+ const inputPrice = pricing?.inputPrice ?? 0;
286
+ const outputPrice = pricing?.outputPrice ?? 0;
287
+ costEstimate = estimatedInputTokens / 1e6 * inputPrice + maxOutputTokens / 1e6 * outputPrice;
288
+ }
289
+ const opusPricing = modelPricing.get(BASELINE_MODEL_ID);
290
+ const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE;
291
+ const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE;
292
+ const baselineInput = estimatedInputTokens / 1e6 * opusInputPrice;
293
+ const baselineOutput = maxOutputTokens / 1e6 * opusOutputPrice;
294
+ const baselineCost = baselineInput + baselineOutput;
295
+ const savings = routingProfile === "premium" ? 0 : baselineCost > 0 ? Math.max(0, (baselineCost - costEstimate) / baselineCost) : 0;
296
+ return {
297
+ model,
298
+ tier,
299
+ confidence,
300
+ method,
301
+ reasoning,
302
+ costEstimate,
303
+ baselineCost,
304
+ savings,
305
+ ...agenticScore !== void 0 && { agenticScore }
306
+ };
307
+ }
308
+ function getFallbackChain(tier, tierConfigs) {
309
+ const config = tierConfigs[tier];
310
+ return [config.primary, ...config.fallback];
311
+ }
312
+ var SERVER_MARGIN_PERCENT = 5;
313
+ var MIN_PAYMENT_USD = 1e-3;
314
+ function calculateModelCost(model, modelPricing, estimatedInputTokens, maxOutputTokens, routingProfile) {
315
+ const pricing = modelPricing.get(model);
316
+ let costEstimate;
317
+ if (pricing?.flatPrice !== void 0) {
318
+ costEstimate = Math.max(pricing.flatPrice * (1 + SERVER_MARGIN_PERCENT / 100), MIN_PAYMENT_USD);
319
+ } else {
320
+ const inputPrice = pricing?.inputPrice ?? 0;
321
+ const outputPrice = pricing?.outputPrice ?? 0;
322
+ const inputCost = estimatedInputTokens / 1e6 * inputPrice;
323
+ const outputCost = maxOutputTokens / 1e6 * outputPrice;
324
+ costEstimate = Math.max(
325
+ (inputCost + outputCost) * (1 + SERVER_MARGIN_PERCENT / 100),
326
+ MIN_PAYMENT_USD
327
+ );
328
+ }
329
+ const opusPricing = modelPricing.get(BASELINE_MODEL_ID);
330
+ const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE;
331
+ const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE;
332
+ const baselineInput = estimatedInputTokens / 1e6 * opusInputPrice;
333
+ const baselineOutput = maxOutputTokens / 1e6 * opusOutputPrice;
334
+ const baselineCost = baselineInput + baselineOutput;
335
+ const savings = routingProfile === "premium" ? 0 : baselineCost > 0 ? Math.max(0, (baselineCost - costEstimate) / baselineCost) : 0;
336
+ return { costEstimate, baselineCost, savings };
337
+ }
338
+ function filterCandidatesByCapacity(models, estimatedInputTokens, requestedOutputTokens, getCapabilities) {
339
+ const filtered = models.filter((modelId) => {
340
+ const capabilities = getCapabilities(modelId);
341
+ if (!capabilities) return true;
342
+ return capabilities.contextWindow >= (estimatedInputTokens + requestedOutputTokens) * 1.1 && capabilities.maxOutput >= requestedOutputTokens;
343
+ });
344
+ return filtered;
345
+ }
346
+ function applyPromotions(tierConfigs, promotions, profile, now = /* @__PURE__ */ new Date()) {
347
+ if (!promotions || promotions.length === 0) return tierConfigs;
348
+ let result = tierConfigs;
349
+ for (const promo of promotions) {
350
+ const start = new Date(promo.startDate);
351
+ const end = new Date(promo.endDate);
352
+ if (now < start || now >= end) continue;
353
+ if (promo.profiles && !promo.profiles.includes(profile)) continue;
354
+ if (result === tierConfigs) {
355
+ result = { ...tierConfigs };
356
+ for (const t of Object.keys(result)) {
357
+ result[t] = { ...result[t] };
358
+ }
359
+ }
360
+ for (const [tier, override] of Object.entries(promo.tierOverrides)) {
361
+ if (!result[tier]) continue;
362
+ if (override.primary) result[tier].primary = override.primary;
363
+ if (override.fallback) result[tier].fallback = override.fallback;
364
+ }
365
+ }
366
+ return result;
367
+ }
368
+ var RulesStrategy = class {
369
+ name = "rules";
370
+ route(prompt, systemPrompt, maxOutputTokens, options) {
371
+ const { config, modelPricing } = options;
372
+ const fullText = `${systemPrompt ?? ""} ${prompt}`;
373
+ const estimatedTokens = Math.ceil(fullText.length / 4);
374
+ const scanLimit = Math.max(1, Math.min(8e3, config.classifier.promptTruncationChars));
375
+ const sample = (value) => {
376
+ if (value.length <= scanLimit) return value;
377
+ const prefixLength = Math.ceil(scanLimit / 2);
378
+ return `${value.slice(0, prefixLength)}
379
+ ${value.slice(-(scanLimit - prefixLength))}`;
380
+ };
381
+ const scannedPrompt = sample(prompt);
382
+ const scannedSystemPrompt = systemPrompt ? sample(systemPrompt) : void 0;
383
+ const ruleResult = classifyByRules(
384
+ scannedPrompt,
385
+ scannedSystemPrompt,
386
+ estimatedTokens,
387
+ config.scoring
388
+ );
389
+ const { routingProfile } = options;
390
+ let tierConfigs;
391
+ let profileSuffix;
392
+ let profile;
393
+ if (routingProfile === "eco") {
394
+ tierConfigs = config.ecoTiers ?? config.tiers;
395
+ profileSuffix = config.ecoTiers ? " | eco" : " | eco (default tiers)";
396
+ profile = "eco";
397
+ } else if (routingProfile === "premium") {
398
+ tierConfigs = config.premiumTiers ?? config.tiers;
399
+ profileSuffix = config.premiumTiers ? " | premium" : " | premium (default tiers)";
400
+ profile = "premium";
401
+ } else {
402
+ const agenticScore = ruleResult.agenticScore ?? 0;
403
+ const isAutoAgentic = agenticScore >= 0.5;
404
+ const agenticModeSetting = config.overrides.agenticMode;
405
+ const hasToolsInRequest = options.requiresTools ?? options.hasTools ?? false;
406
+ let useAgenticTiers;
407
+ if (agenticModeSetting === false) {
408
+ useAgenticTiers = false;
409
+ } else if (agenticModeSetting === true) {
410
+ useAgenticTiers = config.agenticTiers != null;
411
+ } else {
412
+ useAgenticTiers = (hasToolsInRequest || isAutoAgentic) && config.agenticTiers != null;
413
+ }
414
+ tierConfigs = useAgenticTiers ? config.agenticTiers : config.tiers;
415
+ profileSuffix = useAgenticTiers ? ` | agentic${hasToolsInRequest ? " (tools)" : ""}` : "";
416
+ profile = useAgenticTiers ? "agentic" : "auto";
417
+ }
418
+ tierConfigs = applyPromotions(tierConfigs, config.promotions, profile, options.now);
419
+ const agenticScoreValue = ruleResult.agenticScore;
420
+ if (estimatedTokens > config.overrides.maxTokensForceComplex) {
421
+ const decision2 = selectModel(
422
+ "COMPLEX",
423
+ 0.95,
424
+ "rules",
425
+ `Input exceeds ${config.overrides.maxTokensForceComplex} tokens${profileSuffix}`,
426
+ tierConfigs,
427
+ modelPricing,
428
+ estimatedTokens,
429
+ maxOutputTokens,
430
+ routingProfile,
431
+ agenticScoreValue
432
+ );
433
+ return { ...decision2, tierConfigs, profile };
434
+ }
435
+ const hasStructuredOutput = options.requiresStructuredOutput === true || (scannedSystemPrompt ? /json|structured|schema/i.test(scannedSystemPrompt) : false);
436
+ let tier;
437
+ let confidence;
438
+ const method = "rules";
439
+ let reasoning = `score=${ruleResult.score.toFixed(2)} | ${ruleResult.signals.join(", ")}`;
440
+ if (ruleResult.tier !== null) {
441
+ tier = ruleResult.tier;
442
+ confidence = ruleResult.confidence;
443
+ } else {
444
+ tier = config.overrides.ambiguousDefaultTier;
445
+ confidence = 0.5;
446
+ reasoning += ` | ambiguous -> default: ${tier}`;
447
+ }
448
+ if (hasStructuredOutput) {
449
+ const tierRank = { SIMPLE: 0, MEDIUM: 1, COMPLEX: 2, REASONING: 3 };
450
+ const minTier = config.overrides.structuredOutputMinTier;
451
+ if (tierRank[tier] < tierRank[minTier]) {
452
+ reasoning += ` | upgraded to ${minTier} (structured output)`;
453
+ tier = minTier;
454
+ }
455
+ }
456
+ reasoning += profileSuffix;
457
+ const decision = selectModel(
458
+ tier,
459
+ confidence,
460
+ method,
461
+ reasoning,
462
+ tierConfigs,
463
+ modelPricing,
464
+ estimatedTokens,
465
+ maxOutputTokens,
466
+ routingProfile,
467
+ agenticScoreValue
468
+ );
469
+ return { ...decision, tierConfigs, profile };
470
+ }
471
+ };
472
+ var registry = /* @__PURE__ */ new Map();
473
+ registry.set("rules", new RulesStrategy());
474
+ function getStrategy(name) {
475
+ const strategy = registry.get(name);
476
+ if (!strategy) {
477
+ throw new Error(`Unknown routing strategy: ${name}`);
478
+ }
479
+ return strategy;
480
+ }
481
+ function registerStrategy(strategy) {
482
+ registry.set(strategy.name, strategy);
483
+ }
484
+ var DEFAULT_MODEL_CAPABILITIES = Object.freeze({
485
+ "anthropic/claude-fable-5": {
486
+ contextWindow: 1e6,
487
+ maxOutputTokens: 128e3,
488
+ supportsTools: true,
489
+ supportsVision: true
490
+ },
491
+ "anthropic/claude-haiku-4.5": {
492
+ contextWindow: 2e5,
493
+ maxOutputTokens: 8192,
494
+ supportsTools: true,
495
+ supportsVision: true
496
+ },
497
+ "anthropic/claude-opus-4.6": {
498
+ contextWindow: 1e6,
499
+ maxOutputTokens: 128e3,
500
+ supportsTools: true,
501
+ supportsVision: true
502
+ },
503
+ "anthropic/claude-opus-4.7": {
504
+ contextWindow: 1e6,
505
+ maxOutputTokens: 128e3,
506
+ supportsTools: true,
507
+ supportsVision: true
508
+ },
509
+ "anthropic/claude-opus-4.8": {
510
+ contextWindow: 1e6,
511
+ maxOutputTokens: 128e3,
512
+ supportsTools: true,
513
+ supportsVision: true
514
+ },
515
+ "anthropic/claude-opus-5": {
516
+ contextWindow: 1e6,
517
+ maxOutputTokens: 128e3,
518
+ supportsTools: true,
519
+ supportsVision: true
520
+ },
521
+ "anthropic/claude-sonnet-4.6": {
522
+ contextWindow: 2e5,
523
+ maxOutputTokens: 64e3,
524
+ supportsTools: true,
525
+ supportsVision: true
526
+ },
527
+ "anthropic/claude-sonnet-5": {
528
+ contextWindow: 1e6,
529
+ maxOutputTokens: 128e3,
530
+ supportsTools: true,
531
+ supportsVision: true
532
+ },
533
+ "deepseek/deepseek-chat": {
534
+ contextWindow: 1e6,
535
+ maxOutputTokens: 8192,
536
+ supportsTools: true,
537
+ supportsVision: false
538
+ },
539
+ "deepseek/deepseek-reasoner": {
540
+ contextWindow: 1e6,
541
+ maxOutputTokens: 8192,
542
+ supportsTools: true,
543
+ supportsVision: false
544
+ },
545
+ "deepseek/deepseek-v4-pro": {
546
+ contextWindow: 1048576,
547
+ maxOutputTokens: 65536,
548
+ supportsTools: true,
549
+ supportsVision: false
550
+ },
551
+ "free/deepseek-v4-flash": {
552
+ contextWindow: 1e6,
553
+ maxOutputTokens: 16384,
554
+ supportsTools: false,
555
+ supportsVision: false
556
+ },
557
+ "free/gpt-oss-120b": {
558
+ contextWindow: 128e3,
559
+ maxOutputTokens: 16384,
560
+ supportsTools: false,
561
+ supportsVision: false
562
+ },
563
+ "free/gpt-oss-20b": {
564
+ contextWindow: 128e3,
565
+ maxOutputTokens: 16384,
566
+ supportsTools: false,
567
+ supportsVision: false
568
+ },
569
+ "free/seed-oss-36b": {
570
+ contextWindow: 131072,
571
+ maxOutputTokens: 16384,
572
+ supportsTools: false,
573
+ supportsVision: false
574
+ },
575
+ "google/gemini-2.5-flash": {
576
+ contextWindow: 1e6,
577
+ maxOutputTokens: 65536,
578
+ supportsTools: true,
579
+ supportsVision: true
580
+ },
581
+ "google/gemini-2.5-flash-lite": {
582
+ contextWindow: 1e6,
583
+ maxOutputTokens: 65536,
584
+ supportsTools: true,
585
+ supportsVision: false
586
+ },
587
+ "google/gemini-2.5-pro": {
588
+ contextWindow: 105e4,
589
+ maxOutputTokens: 65536,
590
+ supportsTools: true,
591
+ supportsVision: true
592
+ },
593
+ "google/gemini-3-flash-preview": {
594
+ contextWindow: 1e6,
595
+ maxOutputTokens: 65536,
596
+ supportsTools: false,
597
+ supportsVision: true
598
+ },
599
+ "google/gemini-3.1-flash-lite": {
600
+ contextWindow: 1e6,
601
+ maxOutputTokens: 8192,
602
+ supportsTools: true,
603
+ supportsVision: false
604
+ },
605
+ "google/gemini-3.1-pro": {
606
+ contextWindow: 105e4,
607
+ maxOutputTokens: 65536,
608
+ supportsTools: true,
609
+ supportsVision: true
610
+ },
611
+ "google/gemini-3.5-flash": {
612
+ contextWindow: 1048576,
613
+ maxOutputTokens: 65536,
614
+ supportsTools: true,
615
+ supportsVision: true
616
+ },
617
+ "moonshot/kimi-k2.5": {
618
+ contextWindow: 262144,
619
+ maxOutputTokens: 16384,
620
+ supportsTools: true,
621
+ supportsVision: true
622
+ },
623
+ "moonshot/kimi-k2.6": {
624
+ contextWindow: 262144,
625
+ maxOutputTokens: 65536,
626
+ supportsTools: true,
627
+ supportsVision: true
628
+ },
629
+ "moonshot/kimi-k2.7": {
630
+ contextWindow: 262144,
631
+ maxOutputTokens: 65536,
632
+ supportsTools: true,
633
+ supportsVision: true
634
+ },
635
+ "moonshot/kimi-k3": {
636
+ contextWindow: 1048576,
637
+ maxOutputTokens: 65536,
638
+ supportsTools: true,
639
+ supportsVision: true
640
+ },
641
+ "openai/gpt-4.1": {
642
+ contextWindow: 128e3,
643
+ maxOutputTokens: 16384,
644
+ supportsTools: true,
645
+ supportsVision: true
646
+ },
647
+ "openai/gpt-4o-mini": {
648
+ contextWindow: 128e3,
649
+ maxOutputTokens: 16384,
650
+ supportsTools: true,
651
+ supportsVision: false
652
+ },
653
+ "openai/gpt-5-mini": {
654
+ contextWindow: 2e5,
655
+ maxOutputTokens: 65536,
656
+ supportsTools: true,
657
+ supportsVision: false
658
+ },
659
+ "openai/gpt-5.3-codex": {
660
+ contextWindow: 4e5,
661
+ maxOutputTokens: 128e3,
662
+ supportsTools: true,
663
+ supportsVision: false
664
+ },
665
+ "openai/gpt-5.4": {
666
+ contextWindow: 4e5,
667
+ maxOutputTokens: 128e3,
668
+ supportsTools: true,
669
+ supportsVision: true
670
+ },
671
+ "openai/gpt-5.4-nano": {
672
+ contextWindow: 105e4,
673
+ maxOutputTokens: 32768,
674
+ supportsTools: true,
675
+ supportsVision: false
676
+ },
677
+ "openai/gpt-5.5": {
678
+ contextWindow: 105e4,
679
+ maxOutputTokens: 128e3,
680
+ supportsTools: true,
681
+ supportsVision: true
682
+ },
683
+ "openai/gpt-5.6-terra": {
684
+ contextWindow: 105e4,
685
+ maxOutputTokens: 128e3,
686
+ supportsTools: true,
687
+ supportsVision: true
688
+ },
689
+ "openai/o3": {
690
+ contextWindow: 2e5,
691
+ maxOutputTokens: 1e5,
692
+ supportsTools: true,
693
+ supportsVision: false
694
+ },
695
+ "openai/o4-mini": {
696
+ contextWindow: 128e3,
697
+ maxOutputTokens: 65536,
698
+ supportsTools: true,
699
+ supportsVision: false
700
+ },
701
+ "qwen/qwen3.7-max": {
702
+ contextWindow: 1e6,
703
+ maxOutputTokens: 65536,
704
+ supportsTools: true,
705
+ supportsVision: false
706
+ },
707
+ "xai/grok-3-mini": {
708
+ contextWindow: 131072,
709
+ maxOutputTokens: 16384,
710
+ supportsTools: true,
711
+ supportsVision: false
712
+ },
713
+ "xai/grok-4-0709": {
714
+ contextWindow: 131072,
715
+ maxOutputTokens: 16384,
716
+ supportsTools: true,
717
+ supportsVision: false
718
+ },
719
+ "xai/grok-4-1-fast-non-reasoning": {
720
+ contextWindow: 131072,
721
+ maxOutputTokens: 16384,
722
+ supportsTools: true,
723
+ supportsVision: false
724
+ },
725
+ "xai/grok-4-1-fast-reasoning": {
726
+ contextWindow: 131072,
727
+ maxOutputTokens: 16384,
728
+ supportsTools: true,
729
+ supportsVision: false
730
+ },
731
+ "xai/grok-4-fast-non-reasoning": {
732
+ contextWindow: 131072,
733
+ maxOutputTokens: 16384,
734
+ supportsTools: true,
735
+ supportsVision: false
736
+ },
737
+ "xai/grok-4-fast-reasoning": {
738
+ contextWindow: 131072,
739
+ maxOutputTokens: 16384,
740
+ supportsTools: true,
741
+ supportsVision: false
742
+ },
743
+ "xai/grok-4.5": {
744
+ contextWindow: 5e5,
745
+ maxOutputTokens: 16384,
746
+ supportsTools: true,
747
+ supportsVision: true
748
+ },
749
+ "zai/glm-5.1": {
750
+ contextWindow: 2e5,
751
+ maxOutputTokens: 128e3,
752
+ supportsTools: true,
753
+ supportsVision: false
754
+ },
755
+ "zai/glm-5.2": {
756
+ contextWindow: 1e6,
757
+ maxOutputTokens: 262144,
758
+ supportsTools: true,
759
+ supportsVision: false
760
+ }
761
+ });
762
+ var model_profiles_generated_default = {
763
+ "openai/gpt-5.5": {
764
+ measuredAt: "2026-07-21T10:21:31Z",
765
+ latencyMs: 6243.1,
766
+ p95LatencyMs: 9865,
767
+ outputTokensPerSecond: 12.53,
768
+ errorRate: 0,
769
+ samples: 3
770
+ },
771
+ "openai/gpt-5.4-pro": {
772
+ measuredAt: "2026-07-21T10:21:31Z",
773
+ latencyMs: 13015.5,
774
+ p95LatencyMs: 23976.4,
775
+ outputTokensPerSecond: 6.42,
776
+ errorRate: 0,
777
+ samples: 3
778
+ },
779
+ "openai/gpt-5.4-mini": {
780
+ measuredAt: "2026-07-21T10:21:31Z",
781
+ latencyMs: 5550,
782
+ p95LatencyMs: 6595.7,
783
+ outputTokensPerSecond: 11.96,
784
+ errorRate: 0.3333,
785
+ samples: 3
786
+ },
787
+ "openai/gpt-5.3-codex": {
788
+ measuredAt: "2026-07-21T10:21:31Z",
789
+ latencyMs: 4617.1,
790
+ p95LatencyMs: 5800.7,
791
+ outputTokensPerSecond: 12.48,
792
+ errorRate: 0,
793
+ samples: 3
794
+ },
795
+ "anthropic/claude-opus-4.8": {
796
+ measuredAt: "2026-07-21T10:21:31Z",
797
+ latencyMs: 3915.1,
798
+ p95LatencyMs: 6130.8,
799
+ outputTokensPerSecond: 16.33,
800
+ errorRate: 0,
801
+ samples: 3
802
+ },
803
+ "anthropic/claude-opus-4.6": {
804
+ measuredAt: "2026-07-21T10:21:31Z",
805
+ latencyMs: 3765.5,
806
+ p95LatencyMs: 4257.2,
807
+ outputTokensPerSecond: 14.18,
808
+ errorRate: 0,
809
+ samples: 3
810
+ },
811
+ "anthropic/claude-sonnet-4.6": {
812
+ measuredAt: "2026-07-21T10:21:31Z",
813
+ latencyMs: 3860.6,
814
+ p95LatencyMs: 5093.5,
815
+ outputTokensPerSecond: 13.85,
816
+ errorRate: 0,
817
+ samples: 3
818
+ },
819
+ "anthropic/claude-haiku-4.5": {
820
+ measuredAt: "2026-07-21T10:21:31Z",
821
+ latencyMs: 2734.9,
822
+ p95LatencyMs: 3181.6,
823
+ outputTokensPerSecond: 19.58,
824
+ errorRate: 0,
825
+ samples: 3
826
+ },
827
+ "google/gemini-3.1-pro": {
828
+ measuredAt: "2026-07-21T10:21:31Z",
829
+ latencyMs: 13935.7,
830
+ p95LatencyMs: 26675.3,
831
+ outputTokensPerSecond: 77.47,
832
+ errorRate: 0,
833
+ samples: 3
834
+ },
835
+ "google/gemini-3.5-flash": {
836
+ measuredAt: "2026-07-21T10:21:31Z",
837
+ latencyMs: 4608.7,
838
+ p95LatencyMs: 8420.9,
839
+ outputTokensPerSecond: 57.88,
840
+ errorRate: 0,
841
+ samples: 3
842
+ },
843
+ "google/gemini-3.1-flash-lite": {
844
+ measuredAt: "2026-07-21T10:21:31Z",
845
+ latencyMs: 4619.7,
846
+ p95LatencyMs: 9927.1,
847
+ outputTokensPerSecond: 42.01,
848
+ errorRate: 0,
849
+ samples: 3
850
+ },
851
+ "google/gemini-2.5-flash": {
852
+ measuredAt: "2026-07-21T10:21:31Z",
853
+ latencyMs: 5506.9,
854
+ p95LatencyMs: 11462.5,
855
+ outputTokensPerSecond: 65.19,
856
+ errorRate: 0,
857
+ samples: 3
858
+ },
859
+ "deepseek/deepseek-v4-pro": {
860
+ measuredAt: "2026-07-21T10:21:31Z",
861
+ latencyMs: 6044.8,
862
+ p95LatencyMs: 10782.3,
863
+ outputTokensPerSecond: 22.47,
864
+ errorRate: 0,
865
+ samples: 3
866
+ },
867
+ "deepseek/deepseek-reasoner": {
868
+ measuredAt: "2026-07-21T10:21:31Z",
869
+ latencyMs: 4111.9,
870
+ p95LatencyMs: 5305.7,
871
+ outputTokensPerSecond: 16.46,
872
+ errorRate: 0,
873
+ samples: 3
874
+ },
875
+ "deepseek/deepseek-chat": {
876
+ measuredAt: "2026-07-21T10:21:31Z",
877
+ latencyMs: 2648.6,
878
+ p95LatencyMs: 3524.1,
879
+ outputTokensPerSecond: 16.73,
880
+ errorRate: 0,
881
+ samples: 3
882
+ },
883
+ "moonshot/kimi-k2.7": {
884
+ measuredAt: "2026-07-21T10:21:31Z",
885
+ latencyMs: 4295.4,
886
+ p95LatencyMs: 6153.8,
887
+ outputTokensPerSecond: 18.54,
888
+ errorRate: 0,
889
+ samples: 3
890
+ },
891
+ "qwen/qwen3.7-max": {
892
+ measuredAt: "2026-07-21T10:21:31Z",
893
+ latencyMs: 30729.4,
894
+ p95LatencyMs: 39622,
895
+ outputTokensPerSecond: 36.89,
896
+ errorRate: 0.3333,
897
+ samples: 3
898
+ },
899
+ "xai/grok-4.3": {
900
+ measuredAt: "2026-07-21T10:21:31Z",
901
+ latencyMs: 6946.1,
902
+ p95LatencyMs: 9495.4,
903
+ outputTokensPerSecond: 65.3,
904
+ errorRate: 0,
905
+ samples: 3
906
+ },
907
+ "xai/grok-4.20-reasoning": {
908
+ measuredAt: "2026-07-21T10:21:31Z",
909
+ latencyMs: 3472.4,
910
+ p95LatencyMs: 5332.4,
911
+ outputTokensPerSecond: 13.27,
912
+ errorRate: 0,
913
+ samples: 3
914
+ },
915
+ "xai/grok-4.20-non-reasoning": {
916
+ measuredAt: "2026-07-21T10:21:31Z",
917
+ latencyMs: 5174.4,
918
+ p95LatencyMs: 6081.7,
919
+ outputTokensPerSecond: 10.21,
920
+ errorRate: 0.3333,
921
+ samples: 3
922
+ },
923
+ "xai/grok-4-1-fast-reasoning": {
924
+ measuredAt: "2026-07-21T10:21:31Z",
925
+ latencyMs: 13148.2,
926
+ p95LatencyMs: 19104.2,
927
+ outputTokensPerSecond: 4.28,
928
+ errorRate: 0,
929
+ samples: 3
930
+ },
931
+ "minimax/minimax-m3": {
932
+ measuredAt: "2026-07-21T10:21:31Z",
933
+ latencyMs: 3385,
934
+ p95LatencyMs: 4247.2,
935
+ outputTokensPerSecond: 15.16,
936
+ errorRate: 0,
937
+ samples: 3
938
+ },
939
+ "minimax/minimax-m2.7": {
940
+ measuredAt: "2026-07-21T10:21:31Z",
941
+ latencyMs: 4596.7,
942
+ p95LatencyMs: 6884.6,
943
+ outputTokensPerSecond: 17.03,
944
+ errorRate: 0,
945
+ samples: 3
946
+ },
947
+ "zai/glm-5.2": {
948
+ measuredAt: "2026-07-21T10:21:31Z",
949
+ latencyMs: 4406.3,
950
+ p95LatencyMs: 6139.7,
951
+ outputTokensPerSecond: 10.41,
952
+ errorRate: 0,
953
+ samples: 3
954
+ },
955
+ "zai/glm-5.1": {
956
+ measuredAt: "2026-07-21T10:21:31Z",
957
+ latencyMs: 7775.4,
958
+ p95LatencyMs: 9182.1,
959
+ outputTokensPerSecond: 6.08,
960
+ errorRate: 0,
961
+ samples: 3
962
+ },
963
+ "zai/glm-5": {
964
+ measuredAt: "2026-07-21T10:21:31Z",
965
+ latencyMs: 4159.4,
966
+ p95LatencyMs: 4992.7,
967
+ outputTokensPerSecond: 10.28,
968
+ errorRate: 0,
969
+ samples: 3
970
+ },
971
+ "free/qwen3-coder-480b": {
972
+ measuredAt: "2026-07-21T10:21:31Z",
973
+ latencyMs: 2063.9,
974
+ p95LatencyMs: 3646.3,
975
+ outputTokensPerSecond: 39.8,
976
+ errorRate: 0,
977
+ samples: 3
978
+ },
979
+ "free/mistral-large-3-675b": {
980
+ measuredAt: "2026-07-21T10:21:31Z",
981
+ latencyMs: 3147.5,
982
+ p95LatencyMs: 5555.3,
983
+ outputTokensPerSecond: 27.76,
984
+ errorRate: 0,
985
+ samples: 3
986
+ },
987
+ "free/nemotron-3-nano-omni-30b-a3b-reasoning": {
988
+ measuredAt: "2026-07-21T10:21:31Z",
989
+ latencyMs: 6508.4,
990
+ p95LatencyMs: 14252.7,
991
+ outputTokensPerSecond: 68.26,
992
+ errorRate: 0,
993
+ samples: 3
994
+ },
995
+ "free/glm-4.7": {
996
+ measuredAt: "2026-07-21T10:21:31Z",
997
+ latencyMs: 2014.8,
998
+ p95LatencyMs: 3039.9,
999
+ outputTokensPerSecond: 39.92,
1000
+ errorRate: 0,
1001
+ samples: 3
1002
+ }
1003
+ };
1004
+ var LIVE_MODEL_PROFILES = Object.freeze(
1005
+ model_profiles_generated_default
1006
+ );
1007
+ var HISTORICAL_MODEL_PROFILES = Object.freeze({
1008
+ "anthropic/claude-haiku-4.5": {
1009
+ measuredAt: "2026-03-16T13:50:48Z",
1010
+ latencyMs: 2305,
1011
+ outputTokensPerSecond: 140.6
1012
+ },
1013
+ "anthropic/claude-opus-4.6": {
1014
+ measuredAt: "2026-03-16T13:50:48Z",
1015
+ latencyMs: 2139,
1016
+ outputTokensPerSecond: 119.7
1017
+ },
1018
+ "anthropic/claude-sonnet-4.6": {
1019
+ measuredAt: "2026-03-16T13:50:48Z",
1020
+ latencyMs: 2110,
1021
+ outputTokensPerSecond: 121.3
1022
+ },
1023
+ "deepseek/deepseek-chat": {
1024
+ measuredAt: "2026-03-16T13:50:48Z",
1025
+ latencyMs: 1431,
1026
+ outputTokensPerSecond: 179.2,
1027
+ intelligenceIndex: 32
1028
+ },
1029
+ "google/gemini-2.5-flash": {
1030
+ measuredAt: "2026-03-16T13:50:48Z",
1031
+ latencyMs: 1238,
1032
+ outputTokensPerSecond: 207.6,
1033
+ intelligenceIndex: 20
1034
+ },
1035
+ "google/gemini-2.5-flash-lite": {
1036
+ measuredAt: "2026-03-16T13:50:48Z",
1037
+ latencyMs: 1353,
1038
+ outputTokensPerSecond: 192.5,
1039
+ intelligenceIndex: 20
1040
+ },
1041
+ "google/gemini-2.5-pro": {
1042
+ measuredAt: "2026-03-16T13:50:48Z",
1043
+ latencyMs: 1294,
1044
+ outputTokensPerSecond: 197.8
1045
+ },
1046
+ "google/gemini-3.1-pro": {
1047
+ measuredAt: "2026-03-16T13:50:48Z",
1048
+ latencyMs: 1609,
1049
+ outputTokensPerSecond: 167.2
1050
+ },
1051
+ "moonshot/kimi-k2.5": {
1052
+ measuredAt: "2026-03-16T13:50:48Z",
1053
+ latencyMs: 1646,
1054
+ outputTokensPerSecond: 155.7
1055
+ },
1056
+ "openai/gpt-4o-mini": {
1057
+ measuredAt: "2026-03-16T13:50:48Z",
1058
+ latencyMs: 2764,
1059
+ outputTokensPerSecond: 92.8
1060
+ },
1061
+ "openai/gpt-5.3-codex": {
1062
+ measuredAt: "2026-03-16T13:50:48Z",
1063
+ latencyMs: 7935,
1064
+ outputTokensPerSecond: 32.3
1065
+ },
1066
+ "xai/grok-4-1-fast-non-reasoning": {
1067
+ measuredAt: "2026-03-16T13:50:48Z",
1068
+ latencyMs: 1244,
1069
+ outputTokensPerSecond: 205.8,
1070
+ intelligenceIndex: 41
1071
+ },
1072
+ "xai/grok-4-1-fast-reasoning": {
1073
+ measuredAt: "2026-03-16T13:50:48Z",
1074
+ latencyMs: 1454,
1075
+ outputTokensPerSecond: 176.2,
1076
+ intelligenceIndex: 41
1077
+ }
1078
+ });
1079
+ function inferToolRequirement(prompt, _systemPrompt, toolChoice) {
1080
+ if (toolChoice === "none") return false;
1081
+ if (toolChoice === "required") return true;
1082
+ if (typeof toolChoice === "object" && toolChoice !== null && toolChoice.type === "function") {
1083
+ return true;
1084
+ }
1085
+ const text = prompt;
1086
+ const explicitTool = /\b(?:use|call|invoke)\s+(?:the\s+)?[\w.-]+\s+(?:tool|function|api)\b|\btool[_ -]?call\b|使用.{0,20}(?:工具|函数|接口)|调用.{0,20}(?:工具|函数|接口)/i;
1087
+ 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;
1088
+ 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;
1089
+ 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;
1090
+ return explicitTool.test(text) || codeEnvironment.test(text) || webAction.test(text) || statefulAction.test(text);
1091
+ }
1092
+ var DEFAULT_PORTFOLIO_WEIGHTS = {
1093
+ auto: {
1094
+ quality: 0.47,
1095
+ capability: 0.2,
1096
+ cost: 0.18,
1097
+ speed: 0.07,
1098
+ reliability: 0.03,
1099
+ legacy: 0.05
1100
+ },
1101
+ eco: { quality: 0.36, capability: 0.2, cost: 0.28, speed: 0.1, reliability: 0.04, legacy: 0.02 },
1102
+ premium: {
1103
+ quality: 0.58,
1104
+ capability: 0.2,
1105
+ cost: 0.08,
1106
+ speed: 0.06,
1107
+ reliability: 0.06,
1108
+ legacy: 0.02
1109
+ },
1110
+ highStakesBoost: { quality: 0.08, reliability: 0.05 },
1111
+ latencySensitiveSpeedBoost: 0.08,
1112
+ affinityFloorGap: { auto: 0.1, eco: 0.22, premium: 0.05 }
1113
+ };
1114
+ function likelyNeedsParallelToolCalls(prompt, needsTools, toolCount, toolNames) {
1115
+ if (!needsTools || toolCount === void 0 || toolCount < 1) return false;
1116
+ const text = prompt.trim();
1117
+ 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(
1118
+ text
1119
+ );
1120
+ if (explicitRepeat) return true;
1121
+ const sentenceClauses = text.split(/[.!?。!?]+/).map((part) => part.trim()).filter((part) => part.length >= 8);
1122
+ if (/\b(?:also|additionally|furthermore)\b|另外|此外|그리고/i.test(text) && sentenceClauses.length >= 2 || /\band\s+(?:also|for the)\b/i.test(text))
1123
+ return true;
1124
+ 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(
1125
+ text
1126
+ );
1127
+ if (pairedQuantity) return true;
1128
+ const operationTokens = /* @__PURE__ */ new Set([
1129
+ "add",
1130
+ "delete",
1131
+ "remove",
1132
+ "cancel",
1133
+ "return",
1134
+ "exchange",
1135
+ "modify",
1136
+ "book",
1137
+ "transfer",
1138
+ "send",
1139
+ "upload",
1140
+ "download",
1141
+ "create",
1142
+ "close"
1143
+ ]);
1144
+ const lowered = text.toLowerCase();
1145
+ const matchedOperationTokens = new Set(
1146
+ (toolNames ?? []).flatMap((name) => name.toLowerCase().split(/[^a-z0-9\u3400-\u9fff]+/)).filter((token) => operationTokens.has(token) && lowered.includes(token))
1147
+ );
1148
+ if (matchedOperationTokens.size >= 2) return true;
1149
+ const nonEmptyLines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1150
+ const quantityMentions = text.match(
1151
+ /\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
1152
+ ) ?? [];
1153
+ if (nonEmptyLines.length >= 2 && quantityMentions.length >= 2) return true;
1154
+ const repeatedLookup = /\b(?:weather|climate|clima|tiempo|temperature|snow|news|report)\b|天气|气象|温度|降雪|新闻|报告/i.test(
1155
+ text
1156
+ );
1157
+ const multiLocationConnector = /\b(?:and also|both|y|e)\b|还有|以及|和|、/i.test(text);
1158
+ const commaSeparatedLocations = (text.match(/[,,]/g) ?? []).length >= 2;
1159
+ if (repeatedLookup && (multiLocationConnector || commaSeparatedLocations)) return true;
1160
+ const distinctOrderParts = /\b(?:food|meal)\b[\s\S]*\bdrink\b|\bdrink\b[\s\S]*\b(?:food|meal)\b/i.test(text);
1161
+ const koreanParallelClauses = (text.match(/,/g) ?? []).length >= 3 && /하고|그리고/.test(text);
1162
+ return distinctOrderParts || koreanParallelClauses;
1163
+ }
1164
+ function classifyTask(prompt, systemPrompt, options) {
1165
+ const fullText = `${systemPrompt ?? ""} ${prompt}`;
1166
+ const estimatedInputTokens = Math.ceil(fullText.length / 4);
1167
+ const scanLimit = Math.max(1, Math.min(8e3, options.config.classifier.promptTruncationChars));
1168
+ const sample = (value) => {
1169
+ if (value.length <= scanLimit) return value;
1170
+ const prefixLength = Math.ceil(scanLimit / 2);
1171
+ return `${value.slice(0, prefixLength)}
1172
+ ${value.slice(-(scanLimit - prefixLength))}`;
1173
+ };
1174
+ const scannedPrompt = sample(prompt);
1175
+ const scannedSystemPrompt = sample(systemPrompt ?? "");
1176
+ const scannedFullText = `${scannedSystemPrompt} ${scannedPrompt}`;
1177
+ const text = scannedPrompt.toLowerCase();
1178
+ const explicitCodeSignal = /```|\b(?:typescript|javascript|python|rust|java|sql|stack trace|traceback|exception)\b|\.(?:ts|tsx|js|py|go|rs)\b/i.test(
1179
+ scannedPrompt
1180
+ );
1181
+ 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(
1182
+ scannedPrompt
1183
+ );
1184
+ const nativeCodeSignal = /\b(?:programmed|written|implemented?|code)\s+(?:in|using)\s+(?:c\+\+|c|rust|go)\b/i.test(
1185
+ scannedPrompt
1186
+ );
1187
+ const hasCode = explicitCodeSignal || codeConstructSignal || nativeCodeSignal;
1188
+ const toolsAvailable = options.hasTools ?? false;
1189
+ const needsTools = options.requiresTools ?? (toolsAvailable && inferToolRequirement(scannedPrompt, scannedSystemPrompt));
1190
+ const likelyParallelToolCalls = likelyNeedsParallelToolCalls(
1191
+ scannedPrompt,
1192
+ needsTools,
1193
+ options.toolCount,
1194
+ options.toolNames
1195
+ );
1196
+ const normalizedToolNames = (options.toolNames ?? []).map((name) => name.toLowerCase());
1197
+ const airlineToolSignal = normalizedToolNames.some(
1198
+ (name) => /(?:flight|reservation|airport|baggage|passenger)/.test(name)
1199
+ );
1200
+ const retailToolSignal = normalizedToolNames.some(
1201
+ (name) => /(?:order|product|item|return|exchange|address)/.test(name)
1202
+ );
1203
+ const webResearchToolSignal = normalizedToolNames.some(
1204
+ (name) => /^(?:web_?search|web_?fetch)$/.test(name)
1205
+ );
1206
+ const agentDomain = airlineToolSignal && !retailToolSignal ? "airline" : retailToolSignal && !airlineToolSignal ? "retail" : webResearchToolSignal ? "web_research" : "other";
1207
+ const clueConnectors = scannedFullText.match(
1208
+ /\b(?:after|before|while|where|whose|which|in \d{4}|as of|over \d+|another|also|furthermore)\b|(?:之后|之前|其中|截至|超过|另一个|此外)/gi
1209
+ ) ?? [];
1210
+ 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(
1211
+ scannedFullText
1212
+ );
1213
+ const exactAnswerSignal = /\b(?:exact answer|single best-supported answer|following clues|multiple public sources)\b|(?:精确答案|根据.*线索|多个公开来源)/i.test(
1214
+ scannedFullText
1215
+ );
1216
+ const deepWebResearch = agentDomain === "web_research" && (exactAnswerSignal || entityResolutionSignal && (clueConnectors.length >= 3 || prompt.length >= 320));
1217
+ 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(
1218
+ scannedPrompt
1219
+ );
1220
+ 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(
1221
+ scannedPrompt
1222
+ );
1223
+ const globalChoiceSignal = globalOptimizationSignal || globalScopeSignal;
1224
+ const crossRecordSignal = /\b(?:another|other|different|previous)\s+(?:order|reservation|booking|account|address)\b|另一(?:个)?(?:订单|预订|账户|地址)|其他(?:订单|预订|账户|地址)/i.test(
1225
+ scannedPrompt
1226
+ );
1227
+ const reservationIds = scannedPrompt.match(/\b[A-Z0-9]{6}\b/g) ?? [];
1228
+ 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(
1229
+ scannedPrompt
1230
+ ) || new Set(reservationIds).size >= 2);
1231
+ const conditionalGlobalWorkflowSignal = agentDomain === "airline" && globalScopeSignal && /\b(?:if|that (?:contain|have)|longer than|shorter than|under|over|at (?:most|least)|wherever possible)\b|如果|超过|少于|不超过|尽可能/i.test(
1232
+ scannedPrompt
1233
+ ) && /\b(?:cancel|change|upgrade|move|book)\b[\s\S]*\b(?:cancel|change|upgrade|move|book)\b|取消[\s\S]*(?:升级|更改)|升级[\s\S]*(?:取消|更改)/i.test(
1234
+ scannedPrompt
1235
+ );
1236
+ const policyExceptionSignal = agentDomain === "retail" && /\b(?:return|refund|send back|get (?:my |the )?money back)\b|退货|退款|退回/i.test(
1237
+ scannedPrompt
1238
+ ) && /\b(?:amex|american express|visa|mastercard|credit card|debit card|different card|another card|other card)\b|信用卡|借记卡|其他卡|另一张卡/i.test(
1239
+ scannedPrompt
1240
+ );
1241
+ const singleSelectedPolicyException = policyExceptionSignal && /\b(?:return|refund|send back)\b[^.!?。!?]{0,96}\b(?:the )?(?:pricier|cheaper|more expensive|less expensive|costlier|one)\b/i.test(
1242
+ scannedPrompt
1243
+ );
1244
+ const negotiatedWorkflowSignal = agentDomain === "retail" && /\b(?:return|exchange)\b|退货|退回|换货|交换/i.test(scannedPrompt);
1245
+ const numberedSteps = (scannedPrompt.match(/(?:^|\s)\d+(?:\.\d+)*[.)]\s+/g) ?? []).length;
1246
+ const complexMultiToolPlan = likelyParallelToolCalls && ((options.toolCount ?? 0) >= 6 || numberedSteps >= 3 || prompt.length > 1200);
1247
+ let agentRisk = needsTools && singleSelectedPolicyException ? "policy_exception_simple" : needsTools && policyExceptionSignal ? "policy_exception" : (
1248
+ // Airline prompts that require a global optimum (for example the
1249
+ // cheapest itinerary across several candidates) are materially harder
1250
+ // than applying one change to every passenger in a known reservation.
1251
+ // Full-session evidence supports Sonnet for the former, while upgrading
1252
+ // the latter merely because it says "all passengers" caused a large cost
1253
+ // increase without a quality gain.
1254
+ needsTools && agentDomain === "airline" && (globalOptimizationSignal || conditionalGlobalWorkflowSignal) ? "complex_high" : needsTools && (likelyParallelToolCalls || globalChoiceSignal || crossRecordSignal || crossReservationBatchSignal || negotiatedWorkflowSignal) ? "high" : "standard"
1255
+ );
1256
+ const needsVision = options.hasVision ?? false;
1257
+ const needsStructuredOutput = options.requiresStructuredOutput ?? false;
1258
+ const latencySensitive = /\b(?:urgent|asap|fast|quick|low latency|real[- ]time)\b|尽快|马上|快速|低延迟/i.test(
1259
+ scannedFullText
1260
+ );
1261
+ const highStakes = /\b(?:production|security|payment|legal|medical|financial|audit)\b|生产|安全|支付|法律|医疗|财务|审计/i.test(
1262
+ scannedFullText
1263
+ );
1264
+ const terminalToolSignal = normalizedToolNames.some(
1265
+ (name) => /^(?:terminalexec|terminalinspect|terminalsendkeys)$/.test(name)
1266
+ );
1267
+ 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(
1268
+ scannedPrompt
1269
+ );
1270
+ 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(
1271
+ scannedPrompt
1272
+ );
1273
+ const mentionedTerminalRuntimes = new Set(
1274
+ (scannedPrompt.match(/\b(?:gcc|clang|rustc|javac|go\s+build|node|python)\b/gi) ?? []).map(
1275
+ (name) => name.toLowerCase().replace(/\s+/g, " ")
1276
+ )
1277
+ );
1278
+ const terminalCrossRuntimeArtifact = terminalToolSignal && (/\bpolyglot\b/i.test(scannedPrompt) || /\b(?:both|each)\b[\s\S]{0,120}\b(?:compilers?|runtimes?|toolchains?)\b/i.test(
1279
+ scannedPrompt
1280
+ ) || mentionedTerminalRuntimes.size >= 2 && /\b(?:compile|build|run|execute)\b/i.test(scannedPrompt));
1281
+ const terminalFrameworkToNativeArtifact = terminalToolSignal && /\b(?:pytorch|tensorflow|jax|onnx|state[_ -]?dict|checkpoint|safetensors?)\b|\.(?:pth|pt|onnx)\b/i.test(
1282
+ scannedPrompt
1283
+ ) && /\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(
1284
+ scannedPrompt
1285
+ ) && /\b(?:inference|model|weights?|tensor|export|convert|load)\b/i.test(scannedPrompt);
1286
+ if (needsTools && (terminalComplexRepair || terminalCrossRuntimeArtifact || terminalFrameworkToNativeArtifact) && (agentRisk === "standard" || agentRisk === "high"))
1287
+ agentRisk = "complex_high";
1288
+ 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(
1289
+ scannedPrompt
1290
+ );
1291
+ const terminalCredentialSignal = /\b(?:ssh|nginx|certificate|authentication|credentials?|passwords?|api keys?|deploy|production|encrypt|gpg|shred|securely delete|decommission)\b/i.test(
1292
+ scannedPrompt
1293
+ ) || /\b(?:access|auth|authentication|bearer|secret|api)\s+tokens?\b|\btokens?\s+(?:secret|credential|authentication)\b/i.test(
1294
+ scannedPrompt
1295
+ );
1296
+ const terminalSafetySensitive = terminalToolSignal && (highStakes || terminalCredentialSignal);
1297
+ const implicitTerminalCode = needsTools && terminalToolSignal && agentRisk === "standard" && !highStakes && !complexTerminalOperation && numberedSteps < 3 && prompt.length <= 1e3 && simpleTerminalArtifact;
1298
+ const language = /[\u3400-\u9fff]/.test(scannedFullText) ? "zh" : "other";
1299
+ const multipleChoiceSignals = (scannedPrompt.match(/(?:^|\n)\s*[A-D][.)]\s+/gim) ?? []).length;
1300
+ const numericSignals = (scannedPrompt.match(/-?\d+(?:[.,]\d+)?/g) ?? []).length;
1301
+ const compactMathProblem = !hasCode && prompt.length < 2500 && numericSignals >= 2 && (/[+×÷=%$€£¥]|\b(?:total|each|per|times|half|twice|percent|how many|how much|calculate)\b/i.test(
1302
+ scannedPrompt
1303
+ ) || /[??]\s*$/.test(scannedPrompt.trim()) || numericSignals >= 3);
1304
+ let taskType = "chat";
1305
+ if (needsVision) taskType = "vision";
1306
+ else if (estimatedInputTokens > 8e4) taskType = "long_context";
1307
+ else if (needsTools && (hasCode || implicitTerminalCode)) taskType = "code_agent";
1308
+ else if (needsTools && likelyParallelToolCalls && !complexMultiToolPlan)
1309
+ taskType = "tool_agent_parallel";
1310
+ else if (needsTools) taskType = "tool_agent";
1311
+ else if (multipleChoiceSignals >= 3) taskType = "reasoning_mcq";
1312
+ else if (compactMathProblem) taskType = "reasoning_math";
1313
+ else if (/\b(?:bug|debug|error|failure|failing|regression|crash|修复|报错|错误|调试)\b/i.test(text))
1314
+ taskType = "debug";
1315
+ else if (hasCode || /\b(?:refactor|implement|patch|edit|rewrite|重构|实现|修改)\b/i.test(text))
1316
+ taskType = "code_edit";
1317
+ else if (needsStructuredOutput || /\b(?:extract|json|schema|csv|字段|提取)\b/i.test(text))
1318
+ taskType = "extraction";
1319
+ else if (/\b(?:prove|derive|theorem|formal|mathematical|reasoning|证明|推导|定理|数学)\b/i.test(text))
1320
+ taskType = "reasoning";
1321
+ return {
1322
+ taskType,
1323
+ estimatedInputTokens,
1324
+ hasCode,
1325
+ needsTools,
1326
+ toolsAvailable,
1327
+ needsVision,
1328
+ needsStructuredOutput,
1329
+ latencySensitive,
1330
+ highStakes,
1331
+ language,
1332
+ likelyParallelToolCalls,
1333
+ complexMultiToolPlan,
1334
+ agentDomain,
1335
+ deepWebResearch,
1336
+ agentRisk,
1337
+ terminalToolSignal,
1338
+ terminalSafetySensitive,
1339
+ implicitTerminalCode
1340
+ };
1341
+ }
1342
+ function affinity(modelId, task, language = "other", agentDomain = "other", deepWebResearch = false, agentRisk = "standard", terminalToolSignal = false, terminalSafetySensitive = false) {
1343
+ const id = modelId.toLowerCase();
1344
+ const modelName = id.slice(id.indexOf("/") + 1);
1345
+ const match = (values, score) => values.some((value) => modelName === value) ? score : 0;
1346
+ const base = 0.68;
1347
+ switch (task) {
1348
+ case "code_agent":
1349
+ if (terminalToolSignal && agentRisk === "complex_high") {
1350
+ return Math.max(
1351
+ base,
1352
+ match(["claude-sonnet-5"], 1),
1353
+ match(["gpt-5.3-codex"], 0.87),
1354
+ match(["gpt-5-mini"], 0.78),
1355
+ match(["gemini-3.5-flash"], 0.76)
1356
+ );
1357
+ }
1358
+ return Math.max(
1359
+ base,
1360
+ match(["gpt-5.3-codex"], 1),
1361
+ match(["claude-sonnet-5"], 0.98),
1362
+ match(["gpt-5-mini"], 0.96),
1363
+ match(["gemini-3.5-flash"], 0.92),
1364
+ match(["kimi-k3"], 0.9),
1365
+ match(["deepseek-v4-pro", "glm-5.2"], 0.88)
1366
+ );
1367
+ case "tool_agent":
1368
+ if (terminalToolSignal && agentRisk === "complex_high") {
1369
+ return Math.max(
1370
+ base,
1371
+ match(["claude-sonnet-5"], 1),
1372
+ match(["gpt-5.3-codex"], 0.87),
1373
+ match(["gpt-5-mini"], 0.78),
1374
+ match(["gemini-3.5-flash"], 0.76)
1375
+ );
1376
+ }
1377
+ if (terminalToolSignal && !terminalSafetySensitive) {
1378
+ return Math.max(
1379
+ base,
1380
+ match(["gpt-5-mini"], 1),
1381
+ match(["gpt-5.3-codex"], 0.98),
1382
+ match(["claude-sonnet-5"], 0.9),
1383
+ match(["gemini-3.5-flash"], 0.89)
1384
+ );
1385
+ }
1386
+ if (terminalToolSignal && terminalSafetySensitive) {
1387
+ return Math.max(
1388
+ base,
1389
+ match(["claude-sonnet-5"], 1),
1390
+ match(["claude-opus-4.8"], 0.9),
1391
+ match(["gpt-5.3-codex"], 0.84)
1392
+ );
1393
+ }
1394
+ if (agentDomain === "web_research") {
1395
+ return deepWebResearch ? Math.max(
1396
+ base,
1397
+ match(["claude-sonnet-5"], 1),
1398
+ match(["gpt-5-mini"], 0.88),
1399
+ match(["gemini-3.5-flash"], 0.84),
1400
+ match(["claude-opus-5"], 0.8),
1401
+ match(["claude-opus-4.8"], 0.78)
1402
+ ) : Math.max(
1403
+ base,
1404
+ match(["claude-sonnet-5"], 1),
1405
+ match(["gpt-5-mini"], 0.88),
1406
+ match(["gemini-3.5-flash"], 0.86),
1407
+ match(["claude-opus-5"], 0.84),
1408
+ match(["claude-opus-4.8"], 0.82)
1409
+ );
1410
+ }
1411
+ if (agentDomain === "retail") {
1412
+ if (agentRisk === "standard") {
1413
+ return Math.max(
1414
+ base,
1415
+ match(["gpt-5-mini"], 1),
1416
+ match(["claude-sonnet-5"], 0.88),
1417
+ match(["gemini-3.5-flash"], 0.82),
1418
+ match(["gpt-5.3-codex"], 0.81),
1419
+ match(["kimi-k3"], 0.78),
1420
+ match(["deepseek-v4-pro"], 0.76)
1421
+ );
1422
+ }
1423
+ if (agentRisk === "policy_exception") {
1424
+ return Math.max(
1425
+ base,
1426
+ match(["gpt-4.1"], 1),
1427
+ match(["claude-sonnet-5"], 0.9),
1428
+ match(["deepseek-v4-pro"], 0.82),
1429
+ match(["gpt-5-mini"], 0.8),
1430
+ match(["gpt-4o-mini"], 0.76)
1431
+ );
1432
+ }
1433
+ if (agentRisk === "policy_exception_simple") {
1434
+ return Math.max(
1435
+ base,
1436
+ match(["gpt-5-mini"], 1),
1437
+ match(["gpt-4.1"], 0.86),
1438
+ match(["deepseek-v4-pro"], 0.82),
1439
+ match(["gpt-4o-mini"], 0.8)
1440
+ );
1441
+ }
1442
+ return Math.max(
1443
+ base,
1444
+ match(["deepseek-v4-pro"], 1),
1445
+ match(["claude-sonnet-5"], 0.88),
1446
+ match(["gemini-3.5-flash"], 0.82),
1447
+ match(["gpt-5.3-codex"], 0.81),
1448
+ match(["kimi-k3"], 0.78),
1449
+ match(["gpt-5-mini"], 0.76)
1450
+ );
1451
+ }
1452
+ if (agentDomain === "airline") {
1453
+ if (agentRisk === "complex_high") {
1454
+ return Math.max(
1455
+ base,
1456
+ match(["claude-sonnet-5"], 1),
1457
+ match(["gpt-5-mini"], 0.78),
1458
+ match(["gemini-3.5-flash"], 0.76),
1459
+ match(["deepseek-v4-pro"], 0.74)
1460
+ );
1461
+ }
1462
+ return Math.max(
1463
+ base,
1464
+ match(["gpt-5-mini"], 1),
1465
+ match(["claude-sonnet-5"], 0.9),
1466
+ match(["gemini-3.5-flash"], 0.8),
1467
+ match(["deepseek-v4-pro"], 0.76)
1468
+ );
1469
+ }
1470
+ return Math.max(
1471
+ base,
1472
+ match(["claude-sonnet-5"], 1),
1473
+ match(["gemini-3.5-flash"], 0.88),
1474
+ match(["gpt-5.3-codex"], 0.87),
1475
+ match(["gpt-5-mini"], 0.84),
1476
+ match(["kimi-k3"], 0.85),
1477
+ match(["deepseek-v4-pro"], 0.82)
1478
+ );
1479
+ case "tool_agent_parallel":
1480
+ if (terminalToolSignal) {
1481
+ return terminalSafetySensitive ? Math.max(
1482
+ base,
1483
+ match(["claude-sonnet-5"], 1),
1484
+ match(["claude-opus-4.8"], 0.9),
1485
+ match(["gpt-5.3-codex"], 0.86)
1486
+ ) : Math.max(
1487
+ base,
1488
+ match(["gpt-5-mini"], 1),
1489
+ match(["gpt-5.3-codex"], 0.98),
1490
+ match(["claude-sonnet-5"], 0.92),
1491
+ match(["gemini-3.5-flash"], 0.88)
1492
+ );
1493
+ }
1494
+ if (agentDomain === "web_research") {
1495
+ return deepWebResearch ? Math.max(
1496
+ base,
1497
+ match(["claude-sonnet-5"], 1),
1498
+ match(["gpt-5-mini"], 0.88),
1499
+ match(["gemini-3.5-flash"], 0.84),
1500
+ match(["claude-opus-5"], 0.8),
1501
+ match(["claude-opus-4.8"], 0.78)
1502
+ ) : Math.max(
1503
+ base,
1504
+ match(["claude-sonnet-5"], 1),
1505
+ match(["gpt-5-mini"], 0.88),
1506
+ match(["gemini-3.5-flash"], 0.86),
1507
+ match(["claude-opus-5"], 0.84),
1508
+ match(["claude-opus-4.8"], 0.82)
1509
+ );
1510
+ }
1511
+ if (agentDomain === "retail") {
1512
+ if (agentRisk === "policy_exception") {
1513
+ return Math.max(
1514
+ base,
1515
+ match(["gpt-4.1"], 1),
1516
+ match(["claude-sonnet-5"], 0.9),
1517
+ match(["deepseek-v4-pro"], 0.82),
1518
+ match(["gpt-5-mini"], 0.8),
1519
+ match(["gpt-4o-mini"], 0.76)
1520
+ );
1521
+ }
1522
+ if (agentRisk === "policy_exception_simple") {
1523
+ return Math.max(
1524
+ base,
1525
+ match(["gpt-5-mini"], 1),
1526
+ match(["gpt-4.1"], 0.86),
1527
+ match(["deepseek-v4-pro"], 0.82),
1528
+ match(["gpt-4o-mini"], 0.8)
1529
+ );
1530
+ }
1531
+ return Math.max(
1532
+ base,
1533
+ match(["deepseek-v4-pro"], 1),
1534
+ match(["claude-sonnet-5"], 0.88),
1535
+ match(["claude-opus-4.8"], 0.84),
1536
+ match(["gpt-5-mini"], 0.78),
1537
+ match(["gemini-3.5-flash"], 0.76)
1538
+ );
1539
+ }
1540
+ if (agentDomain === "airline") {
1541
+ return agentRisk === "complex_high" ? Math.max(
1542
+ base,
1543
+ match(["claude-sonnet-5"], 1),
1544
+ match(["gpt-5-mini"], 0.78),
1545
+ match(["claude-opus-4.8"], 0.76),
1546
+ match(["gemini-3.5-flash"], 0.74)
1547
+ ) : Math.max(
1548
+ base,
1549
+ match(["gpt-5-mini"], 1),
1550
+ match(["claude-sonnet-5"], 0.9),
1551
+ match(["gemini-3.5-flash"], 0.8)
1552
+ );
1553
+ }
1554
+ return Math.max(
1555
+ base,
1556
+ match(["claude-opus-4.8"], 1),
1557
+ match(["claude-sonnet-5"], 0.84),
1558
+ match(["grok-4.5"], 0.82),
1559
+ match(["gemini-3.5-flash"], 0.8),
1560
+ match(["deepseek-v4-pro"], 0.78)
1561
+ );
1562
+ case "code_edit":
1563
+ case "debug":
1564
+ return Math.max(
1565
+ base,
1566
+ match(["gpt-5.3-codex"], 1),
1567
+ match(["claude-sonnet-4.6"], 0.94),
1568
+ match(["glm-5.2"], 0.9),
1569
+ match(["kimi-k2.7", "deepseek-v4-pro"], 0.86)
1570
+ );
1571
+ case "reasoning":
1572
+ return Math.max(
1573
+ base,
1574
+ match(["claude-sonnet-5", "claude-sonnet-4.6"], 0.98),
1575
+ match(["deepseek-v4-pro"], 0.95),
1576
+ match(["grok-4.5"], 0.94),
1577
+ match(["gemini-3.1-pro", "gemini-3.5-flash"], 0.92)
1578
+ );
1579
+ case "reasoning_mcq":
1580
+ return Math.max(
1581
+ base,
1582
+ match(["gemini-3-flash-preview"], 1),
1583
+ match(["gemini-3.5-flash"], 0.91),
1584
+ match(["grok-4.5"], 0.9),
1585
+ match(["claude-sonnet-5"], 0.88),
1586
+ match(["deepseek-v4-pro"], 0.84)
1587
+ );
1588
+ case "reasoning_math":
1589
+ return Math.max(
1590
+ base,
1591
+ match(["gemini-3.5-flash"], 1),
1592
+ match(["grok-4.5"], 0.93),
1593
+ match(["claude-sonnet-5", "deepseek-v4-pro", "kimi-k3"], 0.9),
1594
+ match(["kimi-k2.7"], 0.84)
1595
+ );
1596
+ case "vision":
1597
+ return Math.max(
1598
+ base,
1599
+ match(["gemini-3.1-pro"], 0.96),
1600
+ match(["qwen3.7-max", "claude-sonnet-4.6", "kimi-k2.7", "grok-4.3"], 0.9)
1601
+ );
1602
+ case "long_context":
1603
+ return Math.max(
1604
+ base,
1605
+ match(["gemini-3.1-pro"], 1),
1606
+ match(["qwen3.7-max", "glm-5.2"], 0.89),
1607
+ match(["gemini-3.5-flash"], 0.88),
1608
+ match(["deepseek-v4-pro"], 0.85)
1609
+ );
1610
+ case "extraction": {
1611
+ const kimiExtractionAffinity = language === "zh" ? 1 : 0.9;
1612
+ return Math.max(
1613
+ base,
1614
+ match(["gemini-3.5-flash", "gemini-2.5-flash", "gpt-4o-mini"], 0.9),
1615
+ match(["claude-sonnet-5", "claude-sonnet-4.6"], 0.9),
1616
+ match(["kimi-k3", "kimi-k2.7"], kimiExtractionAffinity)
1617
+ );
1618
+ }
1619
+ default:
1620
+ return Math.max(
1621
+ base,
1622
+ match(["gemini-3.5-flash", "gemini-2.5-flash", "kimi-k3", "kimi-k2.7"], 0.86)
1623
+ );
1624
+ }
1625
+ }
1626
+ function evidenceCandidates(task) {
1627
+ if (task === "code_agent") {
1628
+ return [
1629
+ "openai/gpt-5.3-codex",
1630
+ "anthropic/claude-sonnet-5",
1631
+ "openai/gpt-5-mini",
1632
+ "google/gemini-3.5-flash",
1633
+ "moonshot/kimi-k3",
1634
+ "deepseek/deepseek-v4-pro"
1635
+ ];
1636
+ }
1637
+ if (task === "tool_agent") {
1638
+ return [
1639
+ "anthropic/claude-sonnet-5",
1640
+ "anthropic/claude-opus-5",
1641
+ "openai/gpt-5-mini",
1642
+ "openai/gpt-4.1",
1643
+ "openai/gpt-4o-mini",
1644
+ "google/gemini-3.5-flash",
1645
+ "openai/gpt-5.3-codex",
1646
+ "moonshot/kimi-k3",
1647
+ "deepseek/deepseek-v4-pro"
1648
+ ];
1649
+ }
1650
+ if (task === "tool_agent_parallel") {
1651
+ return [
1652
+ "anthropic/claude-opus-5",
1653
+ "anthropic/claude-opus-4.8",
1654
+ "anthropic/claude-sonnet-5",
1655
+ "openai/gpt-5-mini",
1656
+ "openai/gpt-4.1",
1657
+ "openai/gpt-4o-mini",
1658
+ "xai/grok-4.5",
1659
+ "google/gemini-3.5-flash",
1660
+ "deepseek/deepseek-v4-pro"
1661
+ ];
1662
+ }
1663
+ if (task === "long_context") {
1664
+ return [
1665
+ "google/gemini-3.1-pro",
1666
+ "deepseek/deepseek-v4-pro",
1667
+ "qwen/qwen3.7-max",
1668
+ "zai/glm-5.2",
1669
+ "google/gemini-3.5-flash"
1670
+ ];
1671
+ }
1672
+ if (task === "reasoning_mcq") {
1673
+ return [
1674
+ "google/gemini-3-flash-preview",
1675
+ "google/gemini-3.5-flash",
1676
+ "xai/grok-4.5",
1677
+ "anthropic/claude-sonnet-5",
1678
+ "deepseek/deepseek-v4-pro"
1679
+ ];
1680
+ }
1681
+ if (task === "reasoning_math") {
1682
+ return [
1683
+ "google/gemini-3.5-flash",
1684
+ "xai/grok-4.5",
1685
+ "anthropic/claude-sonnet-5",
1686
+ "deepseek/deepseek-v4-pro",
1687
+ "moonshot/kimi-k3"
1688
+ ];
1689
+ }
1690
+ return [];
1691
+ }
1692
+ function isEligible(modelId, features, maxOutputTokens, options) {
1693
+ const model = options.modelCapabilities?.[modelId] ?? DEFAULT_MODEL_CAPABILITIES[modelId];
1694
+ if (!model) return true;
1695
+ if (features.needsTools && !model.supportsTools) return false;
1696
+ if (features.needsVision && !model.supportsVision) return false;
1697
+ if (features.needsStructuredOutput && !model.supportsTools) return false;
1698
+ if (model.maxOutputTokens < maxOutputTokens) return false;
1699
+ return model.contextWindow >= (features.estimatedInputTokens + maxOutputTokens) * 1.1;
1700
+ }
1701
+ function estimatedCost(modelId, options, inputTokens, outputTokens) {
1702
+ const price = options.modelPricing.get(modelId);
1703
+ if (!price) return Number.POSITIVE_INFINITY;
1704
+ if (price.flatPrice !== void 0) return price.flatPrice;
1705
+ return (inputTokens * price.inputPrice + outputTokens * price.outputPrice) / 1e6;
1706
+ }
1707
+ function profileScore(modelId, options, now) {
1708
+ const profile = options.modelPerformance?.[modelId] ?? LIVE_MODEL_PROFILES[modelId] ?? HISTORICAL_MODEL_PROFILES[modelId];
1709
+ if (!profile) return void 0;
1710
+ const measuredAt = Date.parse(profile.measuredAt);
1711
+ if (!Number.isFinite(measuredAt)) return void 0;
1712
+ const ageDays = Math.max(0, (now.getTime() - measuredAt) / 864e5);
1713
+ const sampleConfidence = profile.samples === void 0 ? 1 : Math.min(1, Math.max(0, profile.samples) / 10);
1714
+ const freshness = Math.pow(0.5, ageDays / 30) * sampleConfidence;
1715
+ const quality = profile.intelligenceIndex === void 0 ? void 0 : Math.min(1, profile.intelligenceIndex / 50);
1716
+ const speed = Math.min(
1717
+ 1,
1718
+ (2e3 / Math.max(500, profile.latencyMs) + profile.outputTokensPerSecond / 250) / 2
1719
+ );
1720
+ const tailSpeed = Math.min(1, 3e3 / Math.max(750, profile.p95LatencyMs ?? profile.latencyMs));
1721
+ const reliability = Math.max(0, 1 - (profile.errorRate ?? 0));
1722
+ return { quality, speed, tailSpeed, reliability, freshness };
1723
+ }
1724
+ var PortfolioStrategy = class {
1725
+ name = "portfolio";
1726
+ route(prompt, systemPrompt, maxOutputTokens, options) {
1727
+ const features = classifyTask(prompt, systemPrompt, options);
1728
+ const base = new RulesStrategy().route(prompt, systemPrompt, maxOutputTokens, {
1729
+ ...options,
1730
+ requiresTools: features.needsTools
1731
+ });
1732
+ const tierConfigs = base.tierConfigs;
1733
+ if (!tierConfigs) return base;
1734
+ const targetTier = (features.taskType === "reasoning_mcq" || features.taskType === "reasoning_math") && (base.tier === "SIMPLE" || base.tier === "MEDIUM") ? "REASONING" : base.tier;
1735
+ const tierConfig = tierConfigs[targetTier];
1736
+ const configuredCandidates = tierConfig ? getFallbackChain(targetTier, tierConfigs) : [];
1737
+ const chain = [
1738
+ .../* @__PURE__ */ new Set([...configuredCandidates, ...evidenceCandidates(features.taskType)])
1739
+ ].filter((model2) => typeof model2 === "string" && model2.length > 0);
1740
+ const eligible = chain.filter(
1741
+ (model2) => isEligible(model2, features, maxOutputTokens, options)
1742
+ );
1743
+ const eligibleCandidates = eligible.length > 0 ? eligible : chain;
1744
+ if (eligibleCandidates.length === 0) return base;
1745
+ const profileName = options.routingProfile === "eco" ? "eco" : options.routingProfile === "premium" ? "premium" : "auto";
1746
+ const portfolio = options.config.portfolio ?? DEFAULT_PORTFOLIO_WEIGHTS;
1747
+ const getAffinity = (model2) => affinity(
1748
+ model2,
1749
+ features.taskType,
1750
+ features.language,
1751
+ features.agentDomain,
1752
+ features.deepWebResearch,
1753
+ features.agentRisk,
1754
+ features.terminalToolSignal,
1755
+ features.terminalSafetySensitive
1756
+ );
1757
+ const bestAffinity = Math.max(...eligibleCandidates.map(getAffinity));
1758
+ const specificAffinity = eligibleCandidates.filter((model2) => getAffinity(model2) > 0.68);
1759
+ const affinityPool = specificAffinity.length > 0 ? specificAffinity : [eligibleCandidates[0]];
1760
+ const affinityFloorGap = features.terminalToolSignal ? Math.max(
1761
+ portfolio.affinityFloorGap[profileName],
1762
+ features.terminalSafetySensitive ? 0.15 : 0.12
1763
+ ) : portfolio.affinityFloorGap[profileName];
1764
+ const candidates = affinityPool.filter(
1765
+ (model2) => getAffinity(model2) >= bestAffinity - affinityFloorGap
1766
+ );
1767
+ const costs = candidates.map(
1768
+ (model2) => estimatedCost(model2, options, features.estimatedInputTokens, maxOutputTokens)
1769
+ );
1770
+ const finiteCosts = costs.filter(Number.isFinite);
1771
+ const minCost = finiteCosts.length > 0 ? Math.min(...finiteCosts) : 0;
1772
+ const maxCost = finiteCosts.length > 0 ? Math.max(...finiteCosts) : 1;
1773
+ const now = options.now ?? /* @__PURE__ */ new Date();
1774
+ const profileWeights = portfolio[profileName];
1775
+ const rankedEntries = candidates.map((model2, index) => {
1776
+ const cost = estimatedCost(model2, options, features.estimatedInputTokens, maxOutputTokens);
1777
+ const costScore = Number.isFinite(cost) && maxCost > minCost ? 1 - (cost - minCost) / (maxCost - minCost) : 0.5;
1778
+ const capabilityScore = isEligible(model2, features, maxOutputTokens, options) ? 1 : 0;
1779
+ const profile = profileScore(model2, options, now);
1780
+ const observedQuality = profile?.quality === void 0 ? getAffinity(model2) : getAffinity(model2) * (1 - profile.freshness) + profile.quality * profile.freshness;
1781
+ const observedSpeed = profile ? profile.speed * profile.freshness : 0.5;
1782
+ const observedTailSpeed = profile ? profile.tailSpeed * profile.freshness : 0.5;
1783
+ const observedReliability = profile ? profile.reliability * profile.freshness + (1 - profile.freshness) : 1;
1784
+ const legacyScore = 1 - index / Math.max(1, candidates.length - 1);
1785
+ const qualityWeight = profileWeights.quality + (features.highStakes ? portfolio.highStakesBoost.quality : 0);
1786
+ const speedScore = features.latencySensitive ? observedTailSpeed : observedSpeed;
1787
+ const speedWeight = profileWeights.speed + (features.latencySensitive ? portfolio.latencySensitiveSpeedBoost : 0);
1788
+ const reliabilityWeight = profileWeights.reliability + (features.highStakes ? portfolio.highStakesBoost.reliability : 0);
1789
+ const score = observedQuality * qualityWeight + capabilityScore * profileWeights.capability + costScore * profileWeights.cost + speedScore * speedWeight + observedReliability * reliabilityWeight + legacyScore * profileWeights.legacy;
1790
+ return {
1791
+ model: model2,
1792
+ score,
1793
+ quality: observedQuality,
1794
+ cost: costScore,
1795
+ speed: speedScore,
1796
+ reliability: observedReliability
1797
+ };
1798
+ }).sort((a, b) => b.score - a.score);
1799
+ const scoredModels = rankedEntries.map((item) => item.model);
1800
+ const webResearchFallbackOrder = [
1801
+ "anthropic/claude-sonnet-5",
1802
+ "openai/gpt-5-mini",
1803
+ "google/gemini-3.5-flash",
1804
+ "anthropic/claude-opus-5",
1805
+ "anthropic/claude-opus-4.8",
1806
+ "openai/gpt-5.3-codex"
1807
+ ];
1808
+ const ranked = features.agentDomain === "web_research" ? [
1809
+ ...scoredModels,
1810
+ ...webResearchFallbackOrder.filter(
1811
+ (model2) => eligibleCandidates.includes(model2) && !scoredModels.includes(model2)
1812
+ ),
1813
+ ...eligibleCandidates.filter(
1814
+ (model2) => !scoredModels.includes(model2) && !webResearchFallbackOrder.includes(model2)
1815
+ )
1816
+ ] : features.taskType === "tool_agent" || features.taskType === "tool_agent_parallel" && features.agentDomain !== "other" ? [
1817
+ ...scoredModels,
1818
+ ...eligibleCandidates.filter((model2) => !scoredModels.includes(model2))
1819
+ ] : [
1820
+ ...scoredModels,
1821
+ ...eligibleCandidates.filter((model2) => !scoredModels.includes(model2))
1822
+ ];
1823
+ const model = ranked[0] ?? base.model;
1824
+ const selectedTierConfigs = {
1825
+ ...tierConfigs,
1826
+ [targetTier]: { primary: model, fallback: ranked.slice(1) }
1827
+ };
1828
+ const decision = selectModel(
1829
+ targetTier,
1830
+ base.confidence,
1831
+ "portfolio",
1832
+ `${base.reasoning} | v3 task=${features.taskType} agentRisk=${features.agentRisk} deepWebResearch=${features.deepWebResearch} terminalCode=${features.implicitTerminalCode} terminalSafety=${features.terminalSafetySensitive} candidates=${ranked.length}`,
1833
+ selectedTierConfigs,
1834
+ options.modelPricing,
1835
+ features.estimatedInputTokens,
1836
+ maxOutputTokens,
1837
+ options.routingProfile,
1838
+ base.agenticScore
1839
+ );
1840
+ return {
1841
+ ...decision,
1842
+ tierConfigs: selectedTierConfigs,
1843
+ profile: base.profile,
1844
+ candidates: ranked,
1845
+ candidateScores: rankedEntries.map(({ model: model2, score, quality, cost, speed, reliability }) => ({
1846
+ model: model2,
1847
+ score,
1848
+ quality,
1849
+ cost,
1850
+ speed,
1851
+ reliability
1852
+ })),
1853
+ taskType: features.taskType,
1854
+ routerVersion: "v3-portfolio"
1855
+ };
1856
+ }
1857
+ };
1858
+ var DEFAULT_ROUTING_CONFIG = {
1859
+ version: "3.4",
1860
+ strategy: "portfolio",
1861
+ portfolio: {
1862
+ auto: {
1863
+ quality: 0.47,
1864
+ capability: 0.2,
1865
+ cost: 0.18,
1866
+ speed: 0.07,
1867
+ reliability: 0.03,
1868
+ legacy: 0.05
1869
+ },
1870
+ eco: {
1871
+ quality: 0.36,
1872
+ capability: 0.2,
1873
+ cost: 0.28,
1874
+ speed: 0.1,
1875
+ reliability: 0.04,
1876
+ legacy: 0.02
1877
+ },
1878
+ premium: {
1879
+ quality: 0.58,
1880
+ capability: 0.2,
1881
+ cost: 0.08,
1882
+ speed: 0.06,
1883
+ reliability: 0.06,
1884
+ legacy: 0.02
1885
+ },
1886
+ highStakesBoost: { quality: 0.08, reliability: 0.05 },
1887
+ latencySensitiveSpeedBoost: 0.08,
1888
+ affinityFloorGap: { auto: 0.1, eco: 0.22, premium: 0.05 }
1889
+ },
1890
+ classifier: {
1891
+ llmModel: "google/gemini-2.5-flash",
1892
+ llmMaxTokens: 10,
1893
+ llmTemperature: 0,
1894
+ promptTruncationChars: 500,
1895
+ cacheTtlMs: 36e5
1896
+ // 1 hour
1897
+ },
1898
+ scoring: {
1899
+ tokenCountThresholds: { simple: 50, complex: 500 },
1900
+ // Multilingual keywords: EN + ZH + JA + RU + DE + ES + PT + KO + AR
1901
+ codeKeywords: [
1902
+ // English
1903
+ "function",
1904
+ "class",
1905
+ "import",
1906
+ "def",
1907
+ "SELECT",
1908
+ "async",
1909
+ "await",
1910
+ "const",
1911
+ "let",
1912
+ "var",
1913
+ "return",
1914
+ "```",
1915
+ // Chinese
1916
+ "\u51FD\u6570",
1917
+ "\u7C7B",
1918
+ "\u5BFC\u5165",
1919
+ "\u5B9A\u4E49",
1920
+ "\u67E5\u8BE2",
1921
+ "\u5F02\u6B65",
1922
+ "\u7B49\u5F85",
1923
+ "\u5E38\u91CF",
1924
+ "\u53D8\u91CF",
1925
+ "\u8FD4\u56DE",
1926
+ // Japanese
1927
+ "\u95A2\u6570",
1928
+ "\u30AF\u30E9\u30B9",
1929
+ "\u30A4\u30F3\u30DD\u30FC\u30C8",
1930
+ "\u975E\u540C\u671F",
1931
+ "\u5B9A\u6570",
1932
+ "\u5909\u6570",
1933
+ // Russian
1934
+ "\u0444\u0443\u043D\u043A\u0446\u0438\u044F",
1935
+ "\u043A\u043B\u0430\u0441\u0441",
1936
+ "\u0438\u043C\u043F\u043E\u0440\u0442",
1937
+ "\u043E\u043F\u0440\u0435\u0434\u0435\u043B",
1938
+ "\u0437\u0430\u043F\u0440\u043E\u0441",
1939
+ "\u0430\u0441\u0438\u043D\u0445\u0440\u043E\u043D\u043D\u044B\u0439",
1940
+ "\u043E\u0436\u0438\u0434\u0430\u0442\u044C",
1941
+ "\u043A\u043E\u043D\u0441\u0442\u0430\u043D\u0442\u0430",
1942
+ "\u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u0430\u044F",
1943
+ "\u0432\u0435\u0440\u043D\u0443\u0442\u044C",
1944
+ // German
1945
+ "funktion",
1946
+ "klasse",
1947
+ "importieren",
1948
+ "definieren",
1949
+ "abfrage",
1950
+ "asynchron",
1951
+ "erwarten",
1952
+ "konstante",
1953
+ "variable",
1954
+ "zur\xFCckgeben",
1955
+ // Spanish
1956
+ "funci\xF3n",
1957
+ "clase",
1958
+ "importar",
1959
+ "definir",
1960
+ "consulta",
1961
+ "as\xEDncrono",
1962
+ "esperar",
1963
+ "constante",
1964
+ "variable",
1965
+ "retornar",
1966
+ // Portuguese
1967
+ "fun\xE7\xE3o",
1968
+ "classe",
1969
+ "importar",
1970
+ "definir",
1971
+ "consulta",
1972
+ "ass\xEDncrono",
1973
+ "aguardar",
1974
+ "constante",
1975
+ "vari\xE1vel",
1976
+ "retornar",
1977
+ // Korean
1978
+ "\uD568\uC218",
1979
+ "\uD074\uB798\uC2A4",
1980
+ "\uAC00\uC838\uC624\uAE30",
1981
+ "\uC815\uC758",
1982
+ "\uCFFC\uB9AC",
1983
+ "\uBE44\uB3D9\uAE30",
1984
+ "\uB300\uAE30",
1985
+ "\uC0C1\uC218",
1986
+ "\uBCC0\uC218",
1987
+ "\uBC18\uD658",
1988
+ // Arabic
1989
+ "\u062F\u0627\u0644\u0629",
1990
+ "\u0641\u0626\u0629",
1991
+ "\u0627\u0633\u062A\u064A\u0631\u0627\u062F",
1992
+ "\u062A\u0639\u0631\u064A\u0641",
1993
+ "\u0627\u0633\u062A\u0639\u0644\u0627\u0645",
1994
+ "\u063A\u064A\u0631 \u0645\u062A\u0632\u0627\u0645\u0646",
1995
+ "\u0627\u0646\u062A\u0638\u0627\u0631",
1996
+ "\u062B\u0627\u0628\u062A",
1997
+ "\u0645\u062A\u063A\u064A\u0631",
1998
+ "\u0625\u0631\u062C\u0627\u0639"
1999
+ ],
2000
+ reasoningKeywords: [
2001
+ // English
2002
+ "prove",
2003
+ "theorem",
2004
+ "derive",
2005
+ "step by step",
2006
+ "chain of thought",
2007
+ "formally",
2008
+ "mathematical",
2009
+ "proof",
2010
+ "logically",
2011
+ // Chinese
2012
+ "\u8BC1\u660E",
2013
+ "\u5B9A\u7406",
2014
+ "\u63A8\u5BFC",
2015
+ "\u9010\u6B65",
2016
+ "\u601D\u7EF4\u94FE",
2017
+ "\u5F62\u5F0F\u5316",
2018
+ "\u6570\u5B66",
2019
+ "\u903B\u8F91",
2020
+ // Japanese
2021
+ "\u8A3C\u660E",
2022
+ "\u5B9A\u7406",
2023
+ "\u5C0E\u51FA",
2024
+ "\u30B9\u30C6\u30C3\u30D7\u30D0\u30A4\u30B9\u30C6\u30C3\u30D7",
2025
+ "\u8AD6\u7406\u7684",
2026
+ // Russian
2027
+ "\u0434\u043E\u043A\u0430\u0437\u0430\u0442\u044C",
2028
+ "\u0434\u043E\u043A\u0430\u0436\u0438",
2029
+ "\u0434\u043E\u043A\u0430\u0437\u0430\u0442\u0435\u043B\u044C\u0441\u0442\u0432",
2030
+ "\u0442\u0435\u043E\u0440\u0435\u043C\u0430",
2031
+ "\u0432\u044B\u0432\u0435\u0441\u0442\u0438",
2032
+ "\u0448\u0430\u0433 \u0437\u0430 \u0448\u0430\u0433\u043E\u043C",
2033
+ "\u043F\u043E\u0448\u0430\u0433\u043E\u0432\u043E",
2034
+ "\u043F\u043E\u044D\u0442\u0430\u043F\u043D\u043E",
2035
+ "\u0446\u0435\u043F\u043E\u0447\u043A\u0430 \u0440\u0430\u0441\u0441\u0443\u0436\u0434\u0435\u043D\u0438\u0439",
2036
+ "\u0440\u0430\u0441\u0441\u0443\u0436\u0434\u0435\u043D\u0438",
2037
+ "\u0444\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u043E",
2038
+ "\u043C\u0430\u0442\u0435\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438",
2039
+ "\u043B\u043E\u0433\u0438\u0447\u0435\u0441\u043A\u0438",
2040
+ // German
2041
+ "beweisen",
2042
+ "beweis",
2043
+ "theorem",
2044
+ "ableiten",
2045
+ "schritt f\xFCr schritt",
2046
+ "gedankenkette",
2047
+ "formal",
2048
+ "mathematisch",
2049
+ "logisch",
2050
+ // Spanish
2051
+ "demostrar",
2052
+ "teorema",
2053
+ "derivar",
2054
+ "paso a paso",
2055
+ "cadena de pensamiento",
2056
+ "formalmente",
2057
+ "matem\xE1tico",
2058
+ "prueba",
2059
+ "l\xF3gicamente",
2060
+ // Portuguese
2061
+ "provar",
2062
+ "teorema",
2063
+ "derivar",
2064
+ "passo a passo",
2065
+ "cadeia de pensamento",
2066
+ "formalmente",
2067
+ "matem\xE1tico",
2068
+ "prova",
2069
+ "logicamente",
2070
+ // Korean
2071
+ "\uC99D\uBA85",
2072
+ "\uC815\uB9AC",
2073
+ "\uB3C4\uCD9C",
2074
+ "\uB2E8\uACC4\uBCC4",
2075
+ "\uC0AC\uACE0\uC758 \uC5F0\uC1C4",
2076
+ "\uD615\uC2DD\uC801",
2077
+ "\uC218\uD559\uC801",
2078
+ "\uB17C\uB9AC\uC801",
2079
+ // Arabic
2080
+ "\u0625\u062B\u0628\u0627\u062A",
2081
+ "\u0646\u0638\u0631\u064A\u0629",
2082
+ "\u0627\u0634\u062A\u0642\u0627\u0642",
2083
+ "\u062E\u0637\u0648\u0629 \u0628\u062E\u0637\u0648\u0629",
2084
+ "\u0633\u0644\u0633\u0644\u0629 \u0627\u0644\u062A\u0641\u0643\u064A\u0631",
2085
+ "\u0631\u0633\u0645\u064A\u0627\u064B",
2086
+ "\u0631\u064A\u0627\u0636\u064A",
2087
+ "\u0628\u0631\u0647\u0627\u0646",
2088
+ "\u0645\u0646\u0637\u0642\u064A\u0627\u064B"
2089
+ ],
2090
+ simpleKeywords: [
2091
+ // English
2092
+ "what is",
2093
+ "define",
2094
+ "translate",
2095
+ "hello",
2096
+ "yes or no",
2097
+ "capital of",
2098
+ "how old",
2099
+ "who is",
2100
+ "when was",
2101
+ // Chinese
2102
+ "\u4EC0\u4E48\u662F",
2103
+ "\u5B9A\u4E49",
2104
+ "\u7FFB\u8BD1",
2105
+ "\u4F60\u597D",
2106
+ "\u662F\u5426",
2107
+ "\u9996\u90FD",
2108
+ "\u591A\u5927",
2109
+ "\u8C01\u662F",
2110
+ "\u4F55\u65F6",
2111
+ // Japanese
2112
+ "\u3068\u306F",
2113
+ "\u5B9A\u7FA9",
2114
+ "\u7FFB\u8A33",
2115
+ "\u3053\u3093\u306B\u3061\u306F",
2116
+ "\u306F\u3044\u304B\u3044\u3044\u3048",
2117
+ "\u9996\u90FD",
2118
+ "\u8AB0",
2119
+ // Russian
2120
+ "\u0447\u0442\u043E \u0442\u0430\u043A\u043E\u0435",
2121
+ "\u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0435\u043D\u0438\u0435",
2122
+ "\u043F\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438",
2123
+ "\u043F\u0435\u0440\u0435\u0432\u0435\u0434\u0438",
2124
+ "\u043F\u0440\u0438\u0432\u0435\u0442",
2125
+ "\u0434\u0430 \u0438\u043B\u0438 \u043D\u0435\u0442",
2126
+ "\u0441\u0442\u043E\u043B\u0438\u0446\u0430",
2127
+ "\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u043B\u0435\u0442",
2128
+ "\u043A\u0442\u043E \u0442\u0430\u043A\u043E\u0439",
2129
+ "\u043A\u043E\u0433\u0434\u0430",
2130
+ "\u043E\u0431\u044A\u044F\u0441\u043D\u0438",
2131
+ // German
2132
+ "was ist",
2133
+ "definiere",
2134
+ "\xFCbersetze",
2135
+ "hallo",
2136
+ "ja oder nein",
2137
+ "hauptstadt",
2138
+ "wie alt",
2139
+ "wer ist",
2140
+ "wann",
2141
+ "erkl\xE4re",
2142
+ // Spanish
2143
+ "qu\xE9 es",
2144
+ "definir",
2145
+ "traducir",
2146
+ "hola",
2147
+ "s\xED o no",
2148
+ "capital de",
2149
+ "cu\xE1ntos a\xF1os",
2150
+ "qui\xE9n es",
2151
+ "cu\xE1ndo",
2152
+ // Portuguese
2153
+ "o que \xE9",
2154
+ "definir",
2155
+ "traduzir",
2156
+ "ol\xE1",
2157
+ "sim ou n\xE3o",
2158
+ "capital de",
2159
+ "quantos anos",
2160
+ "quem \xE9",
2161
+ "quando",
2162
+ // Korean
2163
+ "\uBB34\uC5C7",
2164
+ "\uC815\uC758",
2165
+ "\uBC88\uC5ED",
2166
+ "\uC548\uB155\uD558\uC138\uC694",
2167
+ "\uC608 \uB610\uB294 \uC544\uB2C8\uC624",
2168
+ "\uC218\uB3C4",
2169
+ "\uB204\uAD6C",
2170
+ "\uC5B8\uC81C",
2171
+ // Arabic
2172
+ "\u0645\u0627 \u0647\u0648",
2173
+ "\u062A\u0639\u0631\u064A\u0641",
2174
+ "\u062A\u0631\u062C\u0645",
2175
+ "\u0645\u0631\u062D\u0628\u0627",
2176
+ "\u0646\u0639\u0645 \u0623\u0648 \u0644\u0627",
2177
+ "\u0639\u0627\u0635\u0645\u0629",
2178
+ "\u0645\u0646 \u0647\u0648",
2179
+ "\u0645\u062A\u0649"
2180
+ ],
2181
+ technicalKeywords: [
2182
+ // English
2183
+ "algorithm",
2184
+ "optimize",
2185
+ "architecture",
2186
+ "distributed",
2187
+ "kubernetes",
2188
+ "microservice",
2189
+ "database",
2190
+ "infrastructure",
2191
+ // Chinese
2192
+ "\u7B97\u6CD5",
2193
+ "\u4F18\u5316",
2194
+ "\u67B6\u6784",
2195
+ "\u5206\u5E03\u5F0F",
2196
+ "\u5FAE\u670D\u52A1",
2197
+ "\u6570\u636E\u5E93",
2198
+ "\u57FA\u7840\u8BBE\u65BD",
2199
+ // Japanese
2200
+ "\u30A2\u30EB\u30B4\u30EA\u30BA\u30E0",
2201
+ "\u6700\u9069\u5316",
2202
+ "\u30A2\u30FC\u30AD\u30C6\u30AF\u30C1\u30E3",
2203
+ "\u5206\u6563",
2204
+ "\u30DE\u30A4\u30AF\u30ED\u30B5\u30FC\u30D3\u30B9",
2205
+ "\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9",
2206
+ // Russian
2207
+ "\u0430\u043B\u0433\u043E\u0440\u0438\u0442\u043C",
2208
+ "\u043E\u043F\u0442\u0438\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C",
2209
+ "\u043E\u043F\u0442\u0438\u043C\u0438\u0437\u0430\u0446\u0438",
2210
+ "\u043E\u043F\u0442\u0438\u043C\u0438\u0437\u0438\u0440\u0443\u0439",
2211
+ "\u0430\u0440\u0445\u0438\u0442\u0435\u043A\u0442\u0443\u0440\u0430",
2212
+ "\u0440\u0430\u0441\u043F\u0440\u0435\u0434\u0435\u043B\u0451\u043D\u043D\u044B\u0439",
2213
+ "\u043C\u0438\u043A\u0440\u043E\u0441\u0435\u0440\u0432\u0438\u0441",
2214
+ "\u0431\u0430\u0437\u0430 \u0434\u0430\u043D\u043D\u044B\u0445",
2215
+ "\u0438\u043D\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430",
2216
+ // German
2217
+ "algorithmus",
2218
+ "optimieren",
2219
+ "architektur",
2220
+ "verteilt",
2221
+ "kubernetes",
2222
+ "mikroservice",
2223
+ "datenbank",
2224
+ "infrastruktur",
2225
+ // Spanish
2226
+ "algoritmo",
2227
+ "optimizar",
2228
+ "arquitectura",
2229
+ "distribuido",
2230
+ "microservicio",
2231
+ "base de datos",
2232
+ "infraestructura",
2233
+ // Portuguese
2234
+ "algoritmo",
2235
+ "otimizar",
2236
+ "arquitetura",
2237
+ "distribu\xEDdo",
2238
+ "microsservi\xE7o",
2239
+ "banco de dados",
2240
+ "infraestrutura",
2241
+ // Korean
2242
+ "\uC54C\uACE0\uB9AC\uC998",
2243
+ "\uCD5C\uC801\uD654",
2244
+ "\uC544\uD0A4\uD14D\uCC98",
2245
+ "\uBD84\uC0B0",
2246
+ "\uB9C8\uC774\uD06C\uB85C\uC11C\uBE44\uC2A4",
2247
+ "\uB370\uC774\uD130\uBCA0\uC774\uC2A4",
2248
+ "\uC778\uD504\uB77C",
2249
+ // Arabic
2250
+ "\u062E\u0648\u0627\u0631\u0632\u0645\u064A\u0629",
2251
+ "\u062A\u062D\u0633\u064A\u0646",
2252
+ "\u0628\u0646\u064A\u0629",
2253
+ "\u0645\u0648\u0632\u0639",
2254
+ "\u062E\u062F\u0645\u0629 \u0645\u0635\u063A\u0631\u0629",
2255
+ "\u0642\u0627\u0639\u062F\u0629 \u0628\u064A\u0627\u0646\u0627\u062A",
2256
+ "\u0628\u0646\u064A\u0629 \u062A\u062D\u062A\u064A\u0629"
2257
+ ],
2258
+ creativeKeywords: [
2259
+ // English
2260
+ "story",
2261
+ "poem",
2262
+ "compose",
2263
+ "brainstorm",
2264
+ "creative",
2265
+ "imagine",
2266
+ "write a",
2267
+ // Chinese
2268
+ "\u6545\u4E8B",
2269
+ "\u8BD7",
2270
+ "\u521B\u4F5C",
2271
+ "\u5934\u8111\u98CE\u66B4",
2272
+ "\u521B\u610F",
2273
+ "\u60F3\u8C61",
2274
+ "\u5199\u4E00\u4E2A",
2275
+ // Japanese
2276
+ "\u7269\u8A9E",
2277
+ "\u8A69",
2278
+ "\u4F5C\u66F2",
2279
+ "\u30D6\u30EC\u30A4\u30F3\u30B9\u30C8\u30FC\u30E0",
2280
+ "\u5275\u9020\u7684",
2281
+ "\u60F3\u50CF",
2282
+ // Russian
2283
+ "\u0438\u0441\u0442\u043E\u0440\u0438\u044F",
2284
+ "\u0440\u0430\u0441\u0441\u043A\u0430\u0437",
2285
+ "\u0441\u0442\u0438\u0445\u043E\u0442\u0432\u043E\u0440\u0435\u043D\u0438\u0435",
2286
+ "\u0441\u043E\u0447\u0438\u043D\u0438\u0442\u044C",
2287
+ "\u0441\u043E\u0447\u0438\u043D\u0438",
2288
+ "\u043C\u043E\u0437\u0433\u043E\u0432\u043E\u0439 \u0448\u0442\u0443\u0440\u043C",
2289
+ "\u0442\u0432\u043E\u0440\u0447\u0435\u0441\u043A\u0438\u0439",
2290
+ "\u043F\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044C",
2291
+ "\u043F\u0440\u0438\u0434\u0443\u043C\u0430\u0439",
2292
+ "\u043D\u0430\u043F\u0438\u0448\u0438",
2293
+ // German
2294
+ "geschichte",
2295
+ "gedicht",
2296
+ "komponieren",
2297
+ "brainstorming",
2298
+ "kreativ",
2299
+ "vorstellen",
2300
+ "schreibe",
2301
+ "erz\xE4hlung",
2302
+ // Spanish
2303
+ "historia",
2304
+ "poema",
2305
+ "componer",
2306
+ "lluvia de ideas",
2307
+ "creativo",
2308
+ "imaginar",
2309
+ "escribe",
2310
+ // Portuguese
2311
+ "hist\xF3ria",
2312
+ "poema",
2313
+ "compor",
2314
+ "criativo",
2315
+ "imaginar",
2316
+ "escreva",
2317
+ // Korean
2318
+ "\uC774\uC57C\uAE30",
2319
+ "\uC2DC",
2320
+ "\uC791\uACE1",
2321
+ "\uBE0C\uB808\uC778\uC2A4\uD1A0\uBC0D",
2322
+ "\uCC3D\uC758\uC801",
2323
+ "\uC0C1\uC0C1",
2324
+ "\uC791\uC131",
2325
+ // Arabic
2326
+ "\u0642\u0635\u0629",
2327
+ "\u0642\u0635\u064A\u062F\u0629",
2328
+ "\u062A\u0623\u0644\u064A\u0641",
2329
+ "\u0639\u0635\u0641 \u0630\u0647\u0646\u064A",
2330
+ "\u0625\u0628\u062F\u0627\u0639\u064A",
2331
+ "\u062A\u062E\u064A\u0644",
2332
+ "\u0627\u0643\u062A\u0628"
2333
+ ],
2334
+ // New dimension keyword lists (multilingual)
2335
+ imperativeVerbs: [
2336
+ // English
2337
+ "build",
2338
+ "create",
2339
+ "implement",
2340
+ "design",
2341
+ "develop",
2342
+ "construct",
2343
+ "generate",
2344
+ "deploy",
2345
+ "configure",
2346
+ "set up",
2347
+ // Chinese
2348
+ "\u6784\u5EFA",
2349
+ "\u521B\u5EFA",
2350
+ "\u5B9E\u73B0",
2351
+ "\u8BBE\u8BA1",
2352
+ "\u5F00\u53D1",
2353
+ "\u751F\u6210",
2354
+ "\u90E8\u7F72",
2355
+ "\u914D\u7F6E",
2356
+ "\u8BBE\u7F6E",
2357
+ // Japanese
2358
+ "\u69CB\u7BC9",
2359
+ "\u4F5C\u6210",
2360
+ "\u5B9F\u88C5",
2361
+ "\u8A2D\u8A08",
2362
+ "\u958B\u767A",
2363
+ "\u751F\u6210",
2364
+ "\u30C7\u30D7\u30ED\u30A4",
2365
+ "\u8A2D\u5B9A",
2366
+ // Russian
2367
+ "\u043F\u043E\u0441\u0442\u0440\u043E\u0438\u0442\u044C",
2368
+ "\u043F\u043E\u0441\u0442\u0440\u043E\u0439",
2369
+ "\u0441\u043E\u0437\u0434\u0430\u0442\u044C",
2370
+ "\u0441\u043E\u0437\u0434\u0430\u0439",
2371
+ "\u0440\u0435\u0430\u043B\u0438\u0437\u043E\u0432\u0430\u0442\u044C",
2372
+ "\u0440\u0435\u0430\u043B\u0438\u0437\u0443\u0439",
2373
+ "\u0441\u043F\u0440\u043E\u0435\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C",
2374
+ "\u0440\u0430\u0437\u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C",
2375
+ "\u0440\u0430\u0437\u0440\u0430\u0431\u043E\u0442\u0430\u0439",
2376
+ "\u0441\u043A\u043E\u043D\u0441\u0442\u0440\u0443\u0438\u0440\u043E\u0432\u0430\u0442\u044C",
2377
+ "\u0441\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C",
2378
+ "\u0441\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0439",
2379
+ "\u0440\u0430\u0437\u0432\u0435\u0440\u043D\u0443\u0442\u044C",
2380
+ "\u0440\u0430\u0437\u0432\u0435\u0440\u043D\u0438",
2381
+ "\u043D\u0430\u0441\u0442\u0440\u043E\u0438\u0442\u044C",
2382
+ "\u043D\u0430\u0441\u0442\u0440\u043E\u0439",
2383
+ // German
2384
+ "erstellen",
2385
+ "bauen",
2386
+ "implementieren",
2387
+ "entwerfen",
2388
+ "entwickeln",
2389
+ "konstruieren",
2390
+ "generieren",
2391
+ "bereitstellen",
2392
+ "konfigurieren",
2393
+ "einrichten",
2394
+ // Spanish
2395
+ "construir",
2396
+ "crear",
2397
+ "implementar",
2398
+ "dise\xF1ar",
2399
+ "desarrollar",
2400
+ "generar",
2401
+ "desplegar",
2402
+ "configurar",
2403
+ // Portuguese
2404
+ "construir",
2405
+ "criar",
2406
+ "implementar",
2407
+ "projetar",
2408
+ "desenvolver",
2409
+ "gerar",
2410
+ "implantar",
2411
+ "configurar",
2412
+ // Korean
2413
+ "\uAD6C\uCD95",
2414
+ "\uC0DD\uC131",
2415
+ "\uAD6C\uD604",
2416
+ "\uC124\uACC4",
2417
+ "\uAC1C\uBC1C",
2418
+ "\uBC30\uD3EC",
2419
+ "\uC124\uC815",
2420
+ // Arabic
2421
+ "\u0628\u0646\u0627\u0621",
2422
+ "\u0625\u0646\u0634\u0627\u0621",
2423
+ "\u062A\u0646\u0641\u064A\u0630",
2424
+ "\u062A\u0635\u0645\u064A\u0645",
2425
+ "\u062A\u0637\u0648\u064A\u0631",
2426
+ "\u062A\u0648\u0644\u064A\u062F",
2427
+ "\u0646\u0634\u0631",
2428
+ "\u0625\u0639\u062F\u0627\u062F"
2429
+ ],
2430
+ constraintIndicators: [
2431
+ // English
2432
+ "under",
2433
+ "at most",
2434
+ "at least",
2435
+ "within",
2436
+ "no more than",
2437
+ "o(",
2438
+ "maximum",
2439
+ "minimum",
2440
+ "limit",
2441
+ "budget",
2442
+ // Chinese
2443
+ "\u4E0D\u8D85\u8FC7",
2444
+ "\u81F3\u5C11",
2445
+ "\u6700\u591A",
2446
+ "\u5728\u5185",
2447
+ "\u6700\u5927",
2448
+ "\u6700\u5C0F",
2449
+ "\u9650\u5236",
2450
+ "\u9884\u7B97",
2451
+ // Japanese
2452
+ "\u4EE5\u4E0B",
2453
+ "\u6700\u5927",
2454
+ "\u6700\u5C0F",
2455
+ "\u5236\u9650",
2456
+ "\u4E88\u7B97",
2457
+ // Russian
2458
+ "\u043D\u0435 \u0431\u043E\u043B\u0435\u0435",
2459
+ "\u043D\u0435 \u043C\u0435\u043D\u0435\u0435",
2460
+ "\u043A\u0430\u043A \u043C\u0438\u043D\u0438\u043C\u0443\u043C",
2461
+ "\u0432 \u043F\u0440\u0435\u0434\u0435\u043B\u0430\u0445",
2462
+ "\u043C\u0430\u043A\u0441\u0438\u043C\u0443\u043C",
2463
+ "\u043C\u0438\u043D\u0438\u043C\u0443\u043C",
2464
+ "\u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D\u0438\u0435",
2465
+ "\u0431\u044E\u0434\u0436\u0435\u0442",
2466
+ // German
2467
+ "h\xF6chstens",
2468
+ "mindestens",
2469
+ "innerhalb",
2470
+ "nicht mehr als",
2471
+ "maximal",
2472
+ "minimal",
2473
+ "grenze",
2474
+ "budget",
2475
+ // Spanish
2476
+ "como m\xE1ximo",
2477
+ "al menos",
2478
+ "dentro de",
2479
+ "no m\xE1s de",
2480
+ "m\xE1ximo",
2481
+ "m\xEDnimo",
2482
+ "l\xEDmite",
2483
+ "presupuesto",
2484
+ // Portuguese
2485
+ "no m\xE1ximo",
2486
+ "pelo menos",
2487
+ "dentro de",
2488
+ "n\xE3o mais que",
2489
+ "m\xE1ximo",
2490
+ "m\xEDnimo",
2491
+ "limite",
2492
+ "or\xE7amento",
2493
+ // Korean
2494
+ "\uC774\uD558",
2495
+ "\uC774\uC0C1",
2496
+ "\uCD5C\uB300",
2497
+ "\uCD5C\uC18C",
2498
+ "\uC81C\uD55C",
2499
+ "\uC608\uC0B0",
2500
+ // Arabic
2501
+ "\u0639\u0644\u0649 \u0627\u0644\u0623\u0643\u062B\u0631",
2502
+ "\u0639\u0644\u0649 \u0627\u0644\u0623\u0642\u0644",
2503
+ "\u0636\u0645\u0646",
2504
+ "\u0644\u0627 \u064A\u0632\u064A\u062F \u0639\u0646",
2505
+ "\u0623\u0642\u0635\u0649",
2506
+ "\u0623\u062F\u0646\u0649",
2507
+ "\u062D\u062F",
2508
+ "\u0645\u064A\u0632\u0627\u0646\u064A\u0629"
2509
+ ],
2510
+ outputFormatKeywords: [
2511
+ // English
2512
+ "json",
2513
+ "yaml",
2514
+ "xml",
2515
+ "table",
2516
+ "csv",
2517
+ "markdown",
2518
+ "schema",
2519
+ "format as",
2520
+ "structured",
2521
+ // Chinese
2522
+ "\u8868\u683C",
2523
+ "\u683C\u5F0F\u5316\u4E3A",
2524
+ "\u7ED3\u6784\u5316",
2525
+ // Japanese
2526
+ "\u30C6\u30FC\u30D6\u30EB",
2527
+ "\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8",
2528
+ "\u69CB\u9020\u5316",
2529
+ // Russian
2530
+ "\u0442\u0430\u0431\u043B\u0438\u0446\u0430",
2531
+ "\u0444\u043E\u0440\u043C\u0430\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043A\u0430\u043A",
2532
+ "\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0439",
2533
+ // German
2534
+ "tabelle",
2535
+ "formatieren als",
2536
+ "strukturiert",
2537
+ // Spanish
2538
+ "tabla",
2539
+ "formatear como",
2540
+ "estructurado",
2541
+ // Portuguese
2542
+ "tabela",
2543
+ "formatar como",
2544
+ "estruturado",
2545
+ // Korean
2546
+ "\uD14C\uC774\uBE14",
2547
+ "\uD615\uC2DD",
2548
+ "\uAD6C\uC870\uD654",
2549
+ // Arabic
2550
+ "\u062C\u062F\u0648\u0644",
2551
+ "\u062A\u0646\u0633\u064A\u0642",
2552
+ "\u0645\u0646\u0638\u0645"
2553
+ ],
2554
+ referenceKeywords: [
2555
+ // English
2556
+ "above",
2557
+ "below",
2558
+ "previous",
2559
+ "following",
2560
+ "the docs",
2561
+ "the api",
2562
+ "the code",
2563
+ "earlier",
2564
+ "attached",
2565
+ // Chinese
2566
+ "\u4E0A\u9762",
2567
+ "\u4E0B\u9762",
2568
+ "\u4E4B\u524D",
2569
+ "\u63A5\u4E0B\u6765",
2570
+ "\u6587\u6863",
2571
+ "\u4EE3\u7801",
2572
+ "\u9644\u4EF6",
2573
+ // Japanese
2574
+ "\u4E0A\u8A18",
2575
+ "\u4E0B\u8A18",
2576
+ "\u524D\u306E",
2577
+ "\u6B21\u306E",
2578
+ "\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8",
2579
+ "\u30B3\u30FC\u30C9",
2580
+ // Russian
2581
+ "\u0432\u044B\u0448\u0435",
2582
+ "\u043D\u0438\u0436\u0435",
2583
+ "\u043F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0438\u0439",
2584
+ "\u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439",
2585
+ "\u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u0446\u0438\u044F",
2586
+ "\u043A\u043E\u0434",
2587
+ "\u0440\u0430\u043D\u0435\u0435",
2588
+ "\u0432\u043B\u043E\u0436\u0435\u043D\u0438\u0435",
2589
+ // German
2590
+ "oben",
2591
+ "unten",
2592
+ "vorherige",
2593
+ "folgende",
2594
+ "dokumentation",
2595
+ "der code",
2596
+ "fr\xFCher",
2597
+ "anhang",
2598
+ // Spanish
2599
+ "arriba",
2600
+ "abajo",
2601
+ "anterior",
2602
+ "siguiente",
2603
+ "documentaci\xF3n",
2604
+ "el c\xF3digo",
2605
+ "adjunto",
2606
+ // Portuguese
2607
+ "acima",
2608
+ "abaixo",
2609
+ "anterior",
2610
+ "seguinte",
2611
+ "documenta\xE7\xE3o",
2612
+ "o c\xF3digo",
2613
+ "anexo",
2614
+ // Korean
2615
+ "\uC704",
2616
+ "\uC544\uB798",
2617
+ "\uC774\uC804",
2618
+ "\uB2E4\uC74C",
2619
+ "\uBB38\uC11C",
2620
+ "\uCF54\uB4DC",
2621
+ "\uCCA8\uBD80",
2622
+ // Arabic
2623
+ "\u0623\u0639\u0644\u0627\u0647",
2624
+ "\u0623\u062F\u0646\u0627\u0647",
2625
+ "\u0627\u0644\u0633\u0627\u0628\u0642",
2626
+ "\u0627\u0644\u062A\u0627\u0644\u064A",
2627
+ "\u0627\u0644\u0648\u062B\u0627\u0626\u0642",
2628
+ "\u0627\u0644\u0643\u0648\u062F",
2629
+ "\u0645\u0631\u0641\u0642"
2630
+ ],
2631
+ negationKeywords: [
2632
+ // English
2633
+ "don't",
2634
+ "do not",
2635
+ "avoid",
2636
+ "never",
2637
+ "without",
2638
+ "except",
2639
+ "exclude",
2640
+ "no longer",
2641
+ // Chinese
2642
+ "\u4E0D\u8981",
2643
+ "\u907F\u514D",
2644
+ "\u4ECE\u4E0D",
2645
+ "\u6CA1\u6709",
2646
+ "\u9664\u4E86",
2647
+ "\u6392\u9664",
2648
+ // Japanese
2649
+ "\u3057\u306A\u3044\u3067",
2650
+ "\u907F\u3051\u308B",
2651
+ "\u6C7A\u3057\u3066",
2652
+ "\u306A\u3057\u3067",
2653
+ "\u9664\u304F",
2654
+ // Russian
2655
+ "\u043D\u0435 \u0434\u0435\u043B\u0430\u0439",
2656
+ "\u043D\u0435 \u043D\u0430\u0434\u043E",
2657
+ "\u043D\u0435\u043B\u044C\u0437\u044F",
2658
+ "\u0438\u0437\u0431\u0435\u0433\u0430\u0442\u044C",
2659
+ "\u043D\u0438\u043A\u043E\u0433\u0434\u0430",
2660
+ "\u0431\u0435\u0437",
2661
+ "\u043A\u0440\u043E\u043C\u0435",
2662
+ "\u0438\u0441\u043A\u043B\u044E\u0447\u0438\u0442\u044C",
2663
+ "\u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435",
2664
+ // German
2665
+ "nicht",
2666
+ "vermeide",
2667
+ "niemals",
2668
+ "ohne",
2669
+ "au\xDFer",
2670
+ "ausschlie\xDFen",
2671
+ "nicht mehr",
2672
+ // Spanish
2673
+ "no hagas",
2674
+ "evitar",
2675
+ "nunca",
2676
+ "sin",
2677
+ "excepto",
2678
+ "excluir",
2679
+ // Portuguese
2680
+ "n\xE3o fa\xE7a",
2681
+ "evitar",
2682
+ "nunca",
2683
+ "sem",
2684
+ "exceto",
2685
+ "excluir",
2686
+ // Korean
2687
+ "\uD558\uC9C0 \uB9C8",
2688
+ "\uD53C\uD558\uB2E4",
2689
+ "\uC808\uB300",
2690
+ "\uC5C6\uC774",
2691
+ "\uC81C\uC678",
2692
+ // Arabic
2693
+ "\u0644\u0627 \u062A\u0641\u0639\u0644",
2694
+ "\u062A\u062C\u0646\u0628",
2695
+ "\u0623\u0628\u062F\u0627\u064B",
2696
+ "\u0628\u062F\u0648\u0646",
2697
+ "\u0628\u0627\u0633\u062A\u062B\u0646\u0627\u0621",
2698
+ "\u0627\u0633\u062A\u0628\u0639\u0627\u062F"
2699
+ ],
2700
+ domainSpecificKeywords: [
2701
+ // English
2702
+ "quantum",
2703
+ "fpga",
2704
+ "vlsi",
2705
+ "risc-v",
2706
+ "asic",
2707
+ "photonics",
2708
+ "genomics",
2709
+ "proteomics",
2710
+ "topological",
2711
+ "homomorphic",
2712
+ "zero-knowledge",
2713
+ "lattice-based",
2714
+ // Chinese
2715
+ "\u91CF\u5B50",
2716
+ "\u5149\u5B50\u5B66",
2717
+ "\u57FA\u56E0\u7EC4\u5B66",
2718
+ "\u86CB\u767D\u8D28\u7EC4\u5B66",
2719
+ "\u62D3\u6251",
2720
+ "\u540C\u6001",
2721
+ "\u96F6\u77E5\u8BC6",
2722
+ "\u683C\u5BC6\u7801",
2723
+ // Japanese
2724
+ "\u91CF\u5B50",
2725
+ "\u30D5\u30A9\u30C8\u30CB\u30AF\u30B9",
2726
+ "\u30B2\u30CE\u30DF\u30AF\u30B9",
2727
+ "\u30C8\u30DD\u30ED\u30B8\u30AB\u30EB",
2728
+ // Russian
2729
+ "\u043A\u0432\u0430\u043D\u0442\u043E\u0432\u044B\u0439",
2730
+ "\u0444\u043E\u0442\u043E\u043D\u0438\u043A\u0430",
2731
+ "\u0433\u0435\u043D\u043E\u043C\u0438\u043A\u0430",
2732
+ "\u043F\u0440\u043E\u0442\u0435\u043E\u043C\u0438\u043A\u0430",
2733
+ "\u0442\u043E\u043F\u043E\u043B\u043E\u0433\u0438\u0447\u0435\u0441\u043A\u0438\u0439",
2734
+ "\u0433\u043E\u043C\u043E\u043C\u043E\u0440\u0444\u043D\u044B\u0439",
2735
+ "\u0441 \u043D\u0443\u043B\u0435\u0432\u044B\u043C \u0440\u0430\u0437\u0433\u043B\u0430\u0448\u0435\u043D\u0438\u0435\u043C",
2736
+ "\u043D\u0430 \u043E\u0441\u043D\u043E\u0432\u0435 \u0440\u0435\u0448\u0451\u0442\u043E\u043A",
2737
+ // German
2738
+ "quanten",
2739
+ "photonik",
2740
+ "genomik",
2741
+ "proteomik",
2742
+ "topologisch",
2743
+ "homomorph",
2744
+ "zero-knowledge",
2745
+ "gitterbasiert",
2746
+ // Spanish
2747
+ "cu\xE1ntico",
2748
+ "fot\xF3nica",
2749
+ "gen\xF3mica",
2750
+ "prote\xF3mica",
2751
+ "topol\xF3gico",
2752
+ "homom\xF3rfico",
2753
+ // Portuguese
2754
+ "qu\xE2ntico",
2755
+ "fot\xF4nica",
2756
+ "gen\xF4mica",
2757
+ "prote\xF4mica",
2758
+ "topol\xF3gico",
2759
+ "homom\xF3rfico",
2760
+ // Korean
2761
+ "\uC591\uC790",
2762
+ "\uD3EC\uD1A0\uB2C9\uC2A4",
2763
+ "\uC720\uC804\uCCB4\uD559",
2764
+ "\uC704\uC0C1",
2765
+ "\uB3D9\uD615",
2766
+ // Arabic
2767
+ "\u0643\u0645\u064A",
2768
+ "\u0636\u0648\u0626\u064A\u0627\u062A",
2769
+ "\u062C\u064A\u0646\u0648\u0645\u064A\u0627\u062A",
2770
+ "\u0637\u0648\u0628\u0648\u0644\u0648\u062C\u064A",
2771
+ "\u062A\u0645\u0627\u062B\u0644\u064A"
2772
+ ],
2773
+ // Agentic task keywords - file ops, execution, multi-step, iterative work
2774
+ // Pruned: removed overly common words like "then", "first", "run", "test", "build"
2775
+ agenticTaskKeywords: [
2776
+ // English - File operations (clearly agentic)
2777
+ "read file",
2778
+ "read the file",
2779
+ "look at",
2780
+ "check the",
2781
+ "open the",
2782
+ "edit",
2783
+ "modify",
2784
+ "update the",
2785
+ "change the",
2786
+ "write to",
2787
+ "create file",
2788
+ // English - Execution (specific commands only)
2789
+ "execute",
2790
+ "deploy",
2791
+ "install",
2792
+ "npm",
2793
+ "pip",
2794
+ "compile",
2795
+ // English - Multi-step patterns (specific only)
2796
+ "after that",
2797
+ "and also",
2798
+ "once done",
2799
+ "step 1",
2800
+ "step 2",
2801
+ // English - Iterative work
2802
+ "fix",
2803
+ "debug",
2804
+ "until it works",
2805
+ "keep trying",
2806
+ "iterate",
2807
+ "make sure",
2808
+ "verify",
2809
+ "confirm",
2810
+ // Chinese (keep specific ones)
2811
+ "\u8BFB\u53D6\u6587\u4EF6",
2812
+ "\u67E5\u770B",
2813
+ "\u6253\u5F00",
2814
+ "\u7F16\u8F91",
2815
+ "\u4FEE\u6539",
2816
+ "\u66F4\u65B0",
2817
+ "\u521B\u5EFA",
2818
+ "\u6267\u884C",
2819
+ "\u90E8\u7F72",
2820
+ "\u5B89\u88C5",
2821
+ "\u7B2C\u4E00\u6B65",
2822
+ "\u7B2C\u4E8C\u6B65",
2823
+ "\u4FEE\u590D",
2824
+ "\u8C03\u8BD5",
2825
+ "\u76F4\u5230",
2826
+ "\u786E\u8BA4",
2827
+ "\u9A8C\u8BC1",
2828
+ // Spanish
2829
+ "leer archivo",
2830
+ "editar",
2831
+ "modificar",
2832
+ "actualizar",
2833
+ "ejecutar",
2834
+ "desplegar",
2835
+ "instalar",
2836
+ "paso 1",
2837
+ "paso 2",
2838
+ "arreglar",
2839
+ "depurar",
2840
+ "verificar",
2841
+ // Portuguese
2842
+ "ler arquivo",
2843
+ "editar",
2844
+ "modificar",
2845
+ "atualizar",
2846
+ "executar",
2847
+ "implantar",
2848
+ "instalar",
2849
+ "passo 1",
2850
+ "passo 2",
2851
+ "corrigir",
2852
+ "depurar",
2853
+ "verificar",
2854
+ // Korean
2855
+ "\uD30C\uC77C \uC77D\uAE30",
2856
+ "\uD3B8\uC9D1",
2857
+ "\uC218\uC815",
2858
+ "\uC5C5\uB370\uC774\uD2B8",
2859
+ "\uC2E4\uD589",
2860
+ "\uBC30\uD3EC",
2861
+ "\uC124\uCE58",
2862
+ "\uB2E8\uACC4 1",
2863
+ "\uB2E8\uACC4 2",
2864
+ "\uB514\uBC84\uADF8",
2865
+ "\uD655\uC778",
2866
+ // Arabic
2867
+ "\u0642\u0631\u0627\u0621\u0629 \u0645\u0644\u0641",
2868
+ "\u062A\u062D\u0631\u064A\u0631",
2869
+ "\u062A\u0639\u062F\u064A\u0644",
2870
+ "\u062A\u062D\u062F\u064A\u062B",
2871
+ "\u062A\u0646\u0641\u064A\u0630",
2872
+ "\u0646\u0634\u0631",
2873
+ "\u062A\u062B\u0628\u064A\u062A",
2874
+ "\u0627\u0644\u062E\u0637\u0648\u0629 1",
2875
+ "\u0627\u0644\u062E\u0637\u0648\u0629 2",
2876
+ "\u0625\u0635\u0644\u0627\u062D",
2877
+ "\u062A\u0635\u062D\u064A\u062D",
2878
+ "\u062A\u062D\u0642\u0642"
2879
+ ],
2880
+ // Dimension weights (sum to 1.0)
2881
+ dimensionWeights: {
2882
+ tokenCount: 0.08,
2883
+ codePresence: 0.15,
2884
+ reasoningMarkers: 0.18,
2885
+ technicalTerms: 0.1,
2886
+ creativeMarkers: 0.05,
2887
+ simpleIndicators: 0.02,
2888
+ // Reduced from 0.12 to make room for agenticTask
2889
+ multiStepPatterns: 0.12,
2890
+ questionComplexity: 0.05,
2891
+ imperativeVerbs: 0.03,
2892
+ constraintCount: 0.04,
2893
+ outputFormat: 0.03,
2894
+ referenceComplexity: 0.02,
2895
+ negationComplexity: 0.01,
2896
+ domainSpecificity: 0.02,
2897
+ agenticTask: 0.04
2898
+ // Reduced - agentic signals influence tier selection, not dominate it
2899
+ },
2900
+ // Tier boundaries on weighted score axis
2901
+ tierBoundaries: {
2902
+ simpleMedium: 0,
2903
+ mediumComplex: 0.3,
2904
+ // Raised from 0.18 - prevent simple tasks from reaching expensive COMPLEX tier
2905
+ complexReasoning: 0.5
2906
+ // Raised from 0.4 - reserve for true reasoning tasks
2907
+ },
2908
+ // Sigmoid steepness for confidence calibration
2909
+ confidenceSteepness: 12,
2910
+ // Below this confidence → ambiguous (null tier)
2911
+ confidenceThreshold: 0.7
2912
+ },
2913
+ // Auto (balanced) tier configs - current default smart routing
2914
+ // Benchmark-tuned 2026-03-16: balancing quality (retention) + latency
2915
+ tiers: {
2916
+ SIMPLE: {
2917
+ primary: "google/gemini-2.5-flash",
2918
+ // 1,238ms, IQ 20, 60% retention (best) — fast AND quality
2919
+ fallback: [
2920
+ "google/gemini-3-flash-preview",
2921
+ // 1,398ms, IQ 46 — smarter fallback
2922
+ "deepseek/deepseek-chat",
2923
+ // V4 Flash chat ($0.20/$0.40, 1M ctx) — repriced 2026-04-24
2924
+ "moonshot/kimi-k2.5",
2925
+ // 1,646ms, IQ 47, strong quality
2926
+ "google/gemini-3.1-flash-lite",
2927
+ // $0.25/$1.50, 1M context — newest flash-lite
2928
+ "google/gemini-2.5-flash-lite",
2929
+ // 1,353ms, $0.10/$0.40
2930
+ "openai/gpt-5.4-nano",
2931
+ // $0.20/$1.25, 1M context
2932
+ "xai/grok-4-fast-non-reasoning",
2933
+ // 1,143ms, $0.20/$0.50 — fast fallback
2934
+ "free/gpt-oss-120b"
2935
+ // 1,252ms, FREE fallback (hidden from /v1/models but direct calls work)
2936
+ ]
2937
+ },
2938
+ MEDIUM: {
2939
+ primary: "moonshot/kimi-k2.7",
2940
+ // $0.95/$4.00, 256K ctx, multi-modal + reasoning — Moonshot flagship; promoted from K2.6 (2026-06-14) after BlockRun added K2.7 + hid K2.6. Same price as K2.6.
2941
+ fallback: [
2942
+ "moonshot/kimi-k2.6",
2943
+ // identical-cost in-family hot swap (K2.6 still routable)
2944
+ "moonshot/kimi-k2.5",
2945
+ // $0.60/$3.00 — graceful-degradation backstop
2946
+ "google/gemini-3-flash-preview",
2947
+ // 1,398ms, IQ 46 — nearly same IQ, faster + cheaper
2948
+ "deepseek/deepseek-chat",
2949
+ // 1,431ms, IQ 32, 41% retention
2950
+ "google/gemini-2.5-flash",
2951
+ // 1,238ms, 60% retention
2952
+ "google/gemini-3.1-flash-lite",
2953
+ // $0.25/$1.50, 1M context
2954
+ "google/gemini-2.5-flash-lite",
2955
+ // 1,353ms, $0.10/$0.40
2956
+ "xai/grok-4-1-fast-non-reasoning",
2957
+ // 1,244ms, fast fallback
2958
+ "xai/grok-3-mini"
2959
+ // 1,202ms, $0.30/$0.50
2960
+ ]
2961
+ },
2962
+ COMPLEX: {
2963
+ primary: "google/gemini-3.1-pro",
2964
+ // 1,609ms, IQ 57 — fast flagship quality
2965
+ fallback: [
2966
+ "google/gemini-3-flash-preview",
2967
+ // 1,398ms, IQ 46 — fast + smart
2968
+ "xai/grok-4-0709",
2969
+ // 1,348ms, IQ 41
2970
+ "google/gemini-2.5-pro",
2971
+ // 1,294ms
2972
+ "anthropic/claude-sonnet-5",
2973
+ // near-Opus quality at Sonnet cost, 1M ctx
2974
+ "anthropic/claude-sonnet-4.6",
2975
+ // 2,110ms, IQ 52 — quality fallback
2976
+ "deepseek/deepseek-chat",
2977
+ // 1,431ms, IQ 32
2978
+ "google/gemini-2.5-flash",
2979
+ // 1,238ms, IQ 20 — cheap last resort
2980
+ "openai/gpt-5.6-terra",
2981
+ // GPT-5.6 balanced tier — newest generation, stable (Sol excluded: #202)
2982
+ "openai/gpt-5.5",
2983
+ // Prior OpenAI flagship — 1M+ ctx, native agent + computer use; benchmark TBD
2984
+ "openai/gpt-5.4"
2985
+ // 6,213ms, IQ 57 — previous flagship, benchmarked
2986
+ ]
2987
+ },
2988
+ REASONING: {
2989
+ primary: "xai/grok-4-1-fast-reasoning",
2990
+ // 1,454ms, $0.20/$0.50
2991
+ fallback: [
2992
+ "xai/grok-4-fast-reasoning",
2993
+ // 1,298ms, $0.20/$0.50
2994
+ "deepseek/deepseek-reasoner",
2995
+ // V4 Flash thinking ($0.20/$0.40, 1M ctx)
2996
+ "deepseek/deepseek-v4-pro",
2997
+ // V4 Pro flagship ($0.50/$1.00 promo through 2026-05-31, list $2/$4) — strongest open-weight reasoner
2998
+ "openai/o4-mini",
2999
+ // 2,328ms ($1.10/$4.40)
3000
+ "openai/o3"
3001
+ // 2,862ms
3002
+ ]
3003
+ }
3004
+ },
3005
+ // Eco tier configs - absolute cheapest (blockrun/eco)
3006
+ ecoTiers: {
3007
+ SIMPLE: {
3008
+ primary: "free/gpt-oss-120b",
3009
+ // FREE! $0.00/$0.00 — heavy user default
3010
+ fallback: [
3011
+ "free/gpt-oss-20b",
3012
+ // FREE — smaller, faster
3013
+ "free/deepseek-v4-flash",
3014
+ // FREE — 1M ctx; slow (~10 tok/s, 07-28 probe) but completes
3015
+ // seed-oss-36b sat here as the free coder until it EOL'd 2026-08-03 (HTTP 410).
3016
+ // gpt-oss-120b/20b already head this chain, so the rung is dropped, not retargeted.
3017
+ "google/gemini-3.1-flash-lite",
3018
+ // $0.25/$1.50 — newest flash-lite
3019
+ "openai/gpt-5.4-nano",
3020
+ // $0.20/$1.25 — fast nano
3021
+ "google/gemini-2.5-flash-lite",
3022
+ // $0.10/$0.40
3023
+ "xai/grok-4-fast-non-reasoning"
3024
+ // $0.20/$0.50
3025
+ ]
3026
+ },
3027
+ MEDIUM: {
3028
+ primary: "google/gemini-3.1-flash-lite",
3029
+ // $0.25/$1.50 — newest flash-lite
3030
+ fallback: [
3031
+ "openai/gpt-5.4-nano",
3032
+ // $0.20/$1.25
3033
+ "google/gemini-2.5-flash-lite",
3034
+ // $0.10/$0.40
3035
+ "xai/grok-4-fast-non-reasoning",
3036
+ "google/gemini-2.5-flash"
3037
+ ]
3038
+ },
3039
+ COMPLEX: {
3040
+ primary: "google/gemini-3.1-flash-lite",
3041
+ // $0.25/$1.50
3042
+ fallback: [
3043
+ "google/gemini-2.5-flash-lite",
3044
+ "xai/grok-4-0709",
3045
+ "google/gemini-2.5-flash",
3046
+ "deepseek/deepseek-chat"
3047
+ ]
3048
+ },
3049
+ REASONING: {
3050
+ primary: "xai/grok-4-1-fast-reasoning",
3051
+ // $0.20/$0.50
3052
+ fallback: [
3053
+ "xai/grok-4-fast-reasoning",
3054
+ "deepseek/deepseek-reasoner",
3055
+ // V4 Flash thinking — $0.20/$0.40
3056
+ "deepseek/deepseek-v4-pro"
3057
+ // V4 Pro flagship — $0.50/$1.00 promo, post-promo $2/$4
3058
+ ]
3059
+ }
3060
+ },
3061
+ // Premium tier configs - best quality (blockrun/premium)
3062
+ // codex=complex coding, kimi=simple coding, sonnet=reasoning/instructions, opus=architecture/PM/audits
3063
+ premiumTiers: {
3064
+ SIMPLE: {
3065
+ primary: "moonshot/kimi-k2.7",
3066
+ // $0.95/$4.00 - Moonshot flagship (256K ctx, multi-modal + reasoning); promoted from K2.6 (2026-06-14), same price
3067
+ fallback: [
3068
+ "moonshot/kimi-k2.6",
3069
+ // identical-cost in-family hot swap (K2.6 still routable)
3070
+ "moonshot/kimi-k2.5",
3071
+ // $0.60/$3.00 - proven reliable backstop when Moonshot direct API falters
3072
+ "google/gemini-2.5-flash",
3073
+ // 60% retention, fast growth
3074
+ "anthropic/claude-haiku-4.5",
3075
+ "google/gemini-2.5-flash-lite",
3076
+ "deepseek/deepseek-chat"
3077
+ ]
3078
+ },
3079
+ MEDIUM: {
3080
+ primary: "openai/gpt-5.3-codex",
3081
+ // $1.75/$14 - 400K context, 128K output, replaces 5.2
3082
+ fallback: [
3083
+ "moonshot/kimi-k2.7",
3084
+ // Moonshot flagship
3085
+ "moonshot/kimi-k2.6",
3086
+ "moonshot/kimi-k2.5",
3087
+ "google/gemini-2.5-flash",
3088
+ // 60% retention, good coding capability
3089
+ "google/gemini-2.5-pro",
3090
+ "xai/grok-4-0709",
3091
+ "anthropic/claude-sonnet-5",
3092
+ "anthropic/claude-sonnet-4.6"
3093
+ ]
3094
+ },
3095
+ COMPLEX: {
3096
+ // fable-5 was promoted here 2026-06-11, force-reverted 2026-06-13 when Anthropic
3097
+ // withdrew the offer, and restored 2026-07-14 now that BlockRun has relisted it.
3098
+ primary: "anthropic/claude-fable-5",
3099
+ // Best quality for complex tasks — Mythos-class flagship above Opus ($10/$50, 1M ctx, always-on thinking)
3100
+ // Fallback chain de-Gemini'd 2026-04-22: when Anthropic 503s, Gemini is
3101
+ // also prone to "high demand" 503s (correlated failure — everyone falls
3102
+ // back to Google at the same time). Prefer xAI Grok → Moonshot → OpenAI
3103
+ // flagship → DeepSeek → NVIDIA free instead.
3104
+ fallback: [
3105
+ "anthropic/claude-opus-5",
3106
+ // in-family hot swap first (half the price, 1M ctx + adaptive thinking)
3107
+ "anthropic/claude-opus-4.8",
3108
+ // in-family hot swap (identical cost to 5)
3109
+ "anthropic/claude-opus-4.7",
3110
+ // in-family hot swap (identical cost to 4.8)
3111
+ "anthropic/claude-opus-4.6",
3112
+ // in-family hot swap
3113
+ "anthropic/claude-sonnet-5",
3114
+ // Sonnet-tier drop-down, near-Opus quality
3115
+ "anthropic/claude-sonnet-4.6",
3116
+ "xai/grok-4.5",
3117
+ // xAI flagship — 503-resistant, direct-xAI SKU (added 2026-07-14)
3118
+ "xai/grok-4-0709",
3119
+ // 503-resistant flagship
3120
+ "moonshot/kimi-k2.7",
3121
+ // Moonshot flagship, independent infra
3122
+ "moonshot/kimi-k2.6",
3123
+ "moonshot/kimi-k2.5",
3124
+ "openai/gpt-5.6-terra",
3125
+ // GPT-5.6 balanced tier — newest generation, stable (Sol excluded: #202)
3126
+ "openai/gpt-5.5",
3127
+ // Prior OpenAI flagship — 1M+ ctx, native agent + computer use
3128
+ "openai/gpt-5.4",
3129
+ // Previous flagship (slow but stable, benchmarked at 6,213ms)
3130
+ "openai/gpt-5.3-codex",
3131
+ "deepseek/deepseek-chat",
3132
+ // Cheap, reliable
3133
+ "free/gpt-oss-120b"
3134
+ // NVIDIA free ultimate backstop (was seed-oss-36b; EOL'd 2026-08-03)
3135
+ ]
3136
+ },
3137
+ REASONING: {
3138
+ primary: "anthropic/claude-sonnet-4.6",
3139
+ // 2,110ms, $3/$15 - best for reasoning/instructions
3140
+ fallback: [
3141
+ "anthropic/claude-sonnet-5",
3142
+ // in-family hot swap — same cost, adaptive thinking, 1M ctx
3143
+ "anthropic/claude-opus-5",
3144
+ // Newest flagship Opus w/ adaptive thinking
3145
+ "anthropic/claude-opus-4.8",
3146
+ // Prior flagship Opus — identical cost to 5
3147
+ "anthropic/claude-opus-4.7",
3148
+ // Flagship Opus w/ adaptive thinking
3149
+ "anthropic/claude-opus-4.6",
3150
+ // 2,139ms
3151
+ "xai/grok-4-1-fast-reasoning",
3152
+ // 1,454ms, cheap fast reasoning
3153
+ "openai/o4-mini",
3154
+ // 2,328ms ($1.10/$4.40)
3155
+ "openai/o3"
3156
+ // 2,862ms
3157
+ ]
3158
+ }
3159
+ },
3160
+ // Agentic tier configs - models that excel at multi-step autonomous tasks
3161
+ agenticTiers: {
3162
+ SIMPLE: {
3163
+ primary: "openai/gpt-4o-mini",
3164
+ // $0.15/$0.60 - best tool compliance at lowest cost
3165
+ fallback: [
3166
+ "moonshot/kimi-k2.5",
3167
+ // 1,646ms, strong tool use quality
3168
+ "anthropic/claude-haiku-4.5",
3169
+ // 2,305ms
3170
+ "xai/grok-4-1-fast-non-reasoning"
3171
+ // 1,244ms, fast fallback
3172
+ ]
3173
+ },
3174
+ MEDIUM: {
3175
+ primary: "moonshot/kimi-k2.7",
3176
+ // $0.95/$4.00 — Moonshot flagship, strong tool use; promoted from K2.6 (2026-06-14) after BlockRun added K2.7 + hid K2.6. Same price.
3177
+ fallback: [
3178
+ "moonshot/kimi-k2.6",
3179
+ // identical-cost in-family hot swap (K2.6 still routable)
3180
+ "moonshot/kimi-k2.5",
3181
+ // $0.60/$3.00 — graceful-degradation backstop
3182
+ "xai/grok-4-1-fast-non-reasoning",
3183
+ // 1,244ms, fast fallback
3184
+ "openai/gpt-4o-mini",
3185
+ // 2,764ms, reliable tool calling
3186
+ "anthropic/claude-haiku-4.5",
3187
+ // 2,305ms
3188
+ "deepseek/deepseek-chat"
3189
+ // 1,431ms
3190
+ ]
3191
+ },
3192
+ COMPLEX: {
3193
+ primary: "anthropic/claude-sonnet-4.6",
3194
+ // 2,110ms — best agentic quality
3195
+ // Fallback chain de-Gemini'd 2026-04-22: Gemini's "high demand" 503s
3196
+ // correlate with Anthropic outages (everyone falls back together).
3197
+ // Prefer 503-resistant providers first.
3198
+ fallback: [
3199
+ "anthropic/claude-sonnet-5",
3200
+ // in-family hot swap — same cost, near-Opus agentic quality
3201
+ "anthropic/claude-opus-5",
3202
+ // Newest flagship Opus — in-family hot swap
3203
+ "anthropic/claude-opus-4.8",
3204
+ // Prior flagship Opus — identical cost to 5
3205
+ "anthropic/claude-opus-4.7",
3206
+ // Flagship Opus — in-family hot swap
3207
+ "anthropic/claude-opus-4.6",
3208
+ // 2,139ms
3209
+ "xai/grok-4-0709",
3210
+ // 1,348ms — strong tool use, independent infra
3211
+ "moonshot/kimi-k2.7",
3212
+ // Moonshot flagship — strong tool use, independent infra
3213
+ "moonshot/kimi-k2.5",
3214
+ // cost-stability backstop
3215
+ "openai/gpt-5.6-terra",
3216
+ // GPT-5.6 balanced tier — newest generation, stable (Sol excluded: #202)
3217
+ "openai/gpt-5.5",
3218
+ // Prior flagship — native agent + computer use (exactly the agentic-tier use case)
3219
+ "openai/gpt-5.4",
3220
+ // Previous flagship — 6,213ms, reliable
3221
+ "deepseek/deepseek-chat",
3222
+ // 1,431ms — cheap, reliable
3223
+ "free/gpt-oss-120b"
3224
+ // NVIDIA free ultimate backstop (was seed-oss-36b; EOL'd 2026-08-03)
3225
+ ]
3226
+ },
3227
+ REASONING: {
3228
+ primary: "anthropic/claude-sonnet-4.6",
3229
+ // 2,110ms — strong tool use + reasoning
3230
+ fallback: [
3231
+ "anthropic/claude-sonnet-5",
3232
+ // in-family hot swap — same cost, adaptive thinking
3233
+ "anthropic/claude-opus-5",
3234
+ // Newest flagship Opus w/ adaptive thinking
3235
+ "anthropic/claude-opus-4.8",
3236
+ // Prior flagship Opus — identical cost to 5
3237
+ "anthropic/claude-opus-4.7",
3238
+ // Flagship Opus w/ adaptive thinking
3239
+ "anthropic/claude-opus-4.6",
3240
+ // 2,139ms
3241
+ "xai/grok-4-1-fast-reasoning",
3242
+ // 1,454ms
3243
+ "deepseek/deepseek-reasoner"
3244
+ // 1,454ms
3245
+ ]
3246
+ }
3247
+ },
3248
+ // Time-windowed promotions — auto-applied when active, ignored when expired
3249
+ promotions: [
3250
+ {
3251
+ name: "GLM-5.1 Launch Promo ($0.001 flat)",
3252
+ startDate: "2026-04-01",
3253
+ endDate: "2026-05-01",
3254
+ tierOverrides: {
3255
+ SIMPLE: { primary: "zai/glm-5.1" }
3256
+ },
3257
+ profiles: ["auto"]
3258
+ // only auto profile — eco stays free, premium stays premium
3259
+ }
3260
+ ],
3261
+ overrides: {
3262
+ maxTokensForceComplex: 1e5,
3263
+ structuredOutputMinTier: "MEDIUM",
3264
+ ambiguousDefaultTier: "MEDIUM"
3265
+ // agenticMode left undefined → auto-detect via tools/agenticScore.
3266
+ // Set to `true` to force agentic tiers; `false` to disable them entirely.
3267
+ }
3268
+ };
3269
+ registerStrategy(new PortfolioStrategy());
3270
+ function route(prompt, systemPrompt, maxOutputTokens, options) {
3271
+ const strategy = getStrategy(options.config.strategy ?? "portfolio");
3272
+ return strategy.route(prompt, systemPrompt, maxOutputTokens, options);
3273
+ }
3274
+
3275
+ // src/router-adapter.ts
3276
+ var AUTO_ROUTING_PROFILES = {
3277
+ "blockrun/auto": "auto",
3278
+ "blockrun/eco": "eco",
3279
+ "blockrun/premium": "premium"
3280
+ };
3281
+ var BASE_MINIMUM_PAYMENT_USD = 2e-3;
3282
+ var SOLANA_MINIMUM_PAYMENT_USD = 1e-3;
3283
+ function isTransientError(err) {
3284
+ if (err instanceof PaymentError) return false;
3285
+ if (err instanceof APIError) {
3286
+ return [429, 502, 503, 504, 522, 524].includes(err.statusCode);
3287
+ }
3288
+ if (err instanceof Error) {
3289
+ if (err.name === "AbortError") return true;
3290
+ if (err.name === "TypeError" && /fetch|network/i.test(err.message)) return true;
3291
+ }
3292
+ return false;
3293
+ }
3294
+ function errSummary(err) {
3295
+ if (err instanceof APIError) return `APIError ${err.statusCode}`;
3296
+ if (err instanceof Error) {
3297
+ const msg = err.message.length > 80 ? err.message.slice(0, 80) : err.message;
3298
+ return `${err.name}: ${msg}`;
3299
+ }
3300
+ return String(err).slice(0, 100);
3301
+ }
3302
+ function routingProfileForModel(model) {
3303
+ return AUTO_ROUTING_PROFILES[model.toLowerCase()];
3304
+ }
3305
+ function routingText(messages) {
3306
+ const systemPrompt = messages.filter((message) => message.role === "system" && typeof message.content === "string").map((message) => message.content).join("\n") || void 0;
3307
+ const lastUser = [...messages].reverse().find((message) => message.role === "user" && typeof message.content === "string");
3308
+ const lastText = [...messages].reverse().find((message) => typeof message.content === "string");
3309
+ let conversationChars = 0;
3310
+ let hasVision = false;
3311
+ for (const message of messages) {
3312
+ if (typeof message.content === "string") {
3313
+ conversationChars += message.content.length;
3314
+ } else if (Array.isArray(message.content)) {
3315
+ for (const part of message.content) {
3316
+ if (part?.type === "image_url" || part?.type === "image") hasVision = true;
3317
+ if (typeof part?.text === "string") conversationChars += part.text.length;
3318
+ }
3319
+ }
3320
+ }
3321
+ return {
3322
+ prompt: lastUser?.content ?? lastText?.content ?? "",
3323
+ systemPrompt,
3324
+ conversationChars,
3325
+ hasVision
3326
+ };
3327
+ }
3328
+ function routeWithCatalog(prompt, systemPrompt, maxOutputTokens, modelPricing, options = {}) {
3329
+ const tools = options.tools ?? [];
3330
+ const requiresTools = options.toolChoice === "none" ? false : options.toolChoice === "required" || typeof options.toolChoice === "object" ? true : void 0;
3331
+ const decision = route(prompt, systemPrompt, maxOutputTokens, {
3332
+ config: DEFAULT_ROUTING_CONFIG,
3333
+ modelPricing,
3334
+ routingProfile: options.routingProfile,
3335
+ hasTools: tools.length > 0,
3336
+ toolCount: tools.length,
3337
+ toolNames: tools.map((tool) => tool.function.name),
3338
+ requiresTools,
3339
+ hasVision: options.hasVision,
3340
+ requiresStructuredOutput: options.requiresStructuredOutput
3341
+ });
3342
+ const tierConfigs = decision.tierConfigs ?? DEFAULT_ROUTING_CONFIG.tiers;
3343
+ const ranked = decision.candidates?.length ? decision.candidates : [decision.model, ...getFallbackChain(decision.tier, tierConfigs)];
3344
+ const callable = [];
3345
+ for (const id of ranked) {
3346
+ const resolved = !id.startsWith("free/") ? id : modelPricing.has(`nvidia/${id.slice(5)}`) ? `nvidia/${id.slice(5)}` : null;
3347
+ if (resolved && !callable.includes(resolved)) callable.push(resolved);
3348
+ }
3349
+ const estimatedInputTokens = Math.ceil(
3350
+ Math.max(options.conversationChars ?? 0, `${systemPrompt ?? ""} ${prompt}`.length) / 4
3351
+ );
3352
+ const fitting = filterCandidatesByCapacity(
3353
+ callable,
3354
+ estimatedInputTokens,
3355
+ maxOutputTokens,
3356
+ (id) => {
3357
+ const caps = DEFAULT_MODEL_CAPABILITIES[id];
3358
+ return caps ? { contextWindow: caps.contextWindow, maxOutput: caps.maxOutputTokens } : void 0;
3359
+ }
3360
+ );
3361
+ const availableCandidates = fitting.length > 0 ? fitting : callable;
3362
+ const model = availableCandidates[0] ?? decision.model;
3363
+ const costs = calculateModelCost(
3364
+ model,
3365
+ modelPricing,
3366
+ estimatedInputTokens,
3367
+ maxOutputTokens,
3368
+ options.routingProfile
3369
+ );
3370
+ const minimumPaymentUsd = options.minimumPaymentUsd ?? SOLANA_MINIMUM_PAYMENT_USD;
3371
+ const entry = modelPricing.get(model);
3372
+ const isFree = entry !== void 0 && entry.inputPrice === 0 && entry.outputPrice === 0 && !entry.flatPrice;
3373
+ const costEstimate = isFree ? 0 : Math.max(costs.costEstimate, minimumPaymentUsd);
3374
+ const savings = options.routingProfile === "premium" || costs.baselineCost <= 0 ? 0 : entry !== void 0 ? Math.max(0, (costs.baselineCost - costEstimate) / costs.baselineCost) : decision.savings;
3375
+ return {
3376
+ ...decision,
3377
+ ...costs,
3378
+ costEstimate,
3379
+ savings,
3380
+ model,
3381
+ reasoning: model === decision.model ? decision.reasoning : `${decision.reasoning} | catalog fallback: ${model}`,
3382
+ candidates: availableCandidates,
3383
+ candidateScores: decision.candidateScores?.filter(
3384
+ (score) => availableCandidates.includes(score.model)
3385
+ ),
3386
+ fallbacks: availableCandidates.slice(1)
3387
+ };
3388
+ }
3389
+
34
3390
  // src/x402.ts
35
3391
  import { signTypedData } from "viem/accounts";
36
3392
 
@@ -494,29 +3850,10 @@ function getCostSummary() {
494
3850
  }
495
3851
 
496
3852
  // src/version.ts
497
- var SDK_VERSION = "3.11.0";
3853
+ var SDK_VERSION = "3.12.0";
498
3854
  var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
499
3855
 
500
3856
  // src/client.ts
501
- function isTransientError(err) {
502
- if (err instanceof PaymentError) return false;
503
- if (err instanceof APIError) {
504
- return [429, 502, 503, 504, 522, 524].includes(err.statusCode);
505
- }
506
- if (err instanceof Error) {
507
- if (err.name === "AbortError") return true;
508
- if (err.name === "TypeError" && /fetch|network/i.test(err.message)) return true;
509
- }
510
- return false;
511
- }
512
- function errSummary(err) {
513
- if (err instanceof APIError) return `APIError ${err.statusCode}`;
514
- if (err instanceof Error) {
515
- const msg = err.message.length > 80 ? err.message.slice(0, 80) : err.message;
516
- return `${err.name}: ${msg}`;
517
- }
518
- return String(err).slice(0, 100);
519
- }
520
3857
  function mapRawToModel(m) {
521
3858
  return {
522
3859
  id: m.id,
@@ -571,7 +3908,7 @@ var LLMClient = class _LLMClient {
571
3908
  modelPricingPromise = null;
572
3909
  // Pre-auth cache: avoids the 402 round-trip on repeat requests to the same model.
573
3910
  // Key = "endpoint:model", value = cached payment header + timestamp.
574
- // TTL: 1 hour (mirrors ClawRouter's payment-preauth.ts approach).
3911
+ // TTL: 1 hour — pre-auth quotes are stable server-side on that horizon.
575
3912
  preAuthCache = /* @__PURE__ */ new Map();
576
3913
  static PRE_AUTH_TTL_MS = 36e5;
577
3914
  /**
@@ -625,11 +3962,35 @@ var LLMClient = class _LLMClient {
625
3962
  });
626
3963
  return result.choices[0].message.content || "";
627
3964
  }
3965
+ async makeRoutingDecision(prompt, systemPrompt, maxOutputTokens, options = {}) {
3966
+ return routeWithCatalog(
3967
+ prompt,
3968
+ systemPrompt,
3969
+ maxOutputTokens,
3970
+ await this.getModelPricing(),
3971
+ { ...options, minimumPaymentUsd: BASE_MINIMUM_PAYMENT_USD }
3972
+ );
3973
+ }
3974
+ /**
3975
+ * Inspect a local routing decision without making or paying for a model call.
3976
+ * The first invocation may fetch the public model catalog for current prices.
3977
+ */
3978
+ async route(prompt, options) {
3979
+ return this.makeRoutingDecision(
3980
+ prompt,
3981
+ options?.system,
3982
+ options?.maxOutputTokens ?? options?.maxTokens ?? DEFAULT_MAX_TOKENS,
3983
+ {
3984
+ routingProfile: options?.routingProfile,
3985
+ requiresStructuredOutput: options?.responseFormat !== void 0
3986
+ }
3987
+ );
3988
+ }
628
3989
  /**
629
3990
  * Smart chat with automatic model routing.
630
3991
  *
631
- * Uses ClawRouter's deterministic portfolio router (Router v3.4, the Auto
632
- * default since v0.12.242): it classifies the task shape locally (<1ms, no
3992
+ * Uses BlockRun's product-neutral Router Core portfolio strategy: it
3993
+ * classifies the task shape locally (no
633
3994
  * extra model call), enforces capability constraints as hard filters, and
634
3995
  * ranks an ordered candidate portfolio — the cheapest model that can handle
635
3996
  * the request wins, and the rest become the transient-error fallback chain.
@@ -657,33 +4018,8 @@ var LLMClient = class _LLMClient {
657
4018
  * ```
658
4019
  */
659
4020
  async smartChat(prompt, options) {
660
- const modelPricing = await this.getModelPricing();
661
- const maxOutputTokens = options?.maxOutputTokens || options?.maxTokens || 1024;
662
- let route;
663
- let DEFAULT_ROUTING_CONFIG;
664
- let getFallbackChain;
665
- try {
666
- ({ route, DEFAULT_ROUTING_CONFIG, getFallbackChain } = await import("@blockrun/clawrouter"));
667
- } catch (err) {
668
- throw new Error(
669
- `smartChat() requires the optional '@blockrun/clawrouter' routing engine, which is not installed or failed to load. Install it, or call chat() with an explicit model instead. Cause: ${err.message}`
670
- );
671
- }
672
- const decision = route(prompt, options?.system, maxOutputTokens, {
673
- config: DEFAULT_ROUTING_CONFIG,
674
- modelPricing,
675
- routingProfile: options?.routingProfile
676
- });
677
- const tierConfigs = decision.tierConfigs ?? DEFAULT_ROUTING_CONFIG.tiers;
678
- const ranked = decision.candidates?.length ? decision.candidates : [decision.model, ...getFallbackChain(decision.tier, tierConfigs)];
679
- const callable = [];
680
- for (const id of ranked) {
681
- const resolved = !id.startsWith("free/") ? id : modelPricing.has(`nvidia/${id.slice(5)}`) ? `nvidia/${id.slice(5)}` : null;
682
- if (resolved && !callable.includes(resolved)) callable.push(resolved);
683
- }
684
- const primary = callable[0] ?? decision.model;
685
- const fallbacks = callable.slice(1);
686
- const response = await this.chat(primary, prompt, {
4021
+ const decision = await this.route(prompt, options);
4022
+ const response = await this.chat(decision.model, prompt, {
687
4023
  system: options?.system,
688
4024
  maxTokens: options?.maxTokens,
689
4025
  temperature: options?.temperature,
@@ -692,14 +4028,47 @@ var LLMClient = class _LLMClient {
692
4028
  searchParameters: options?.searchParameters,
693
4029
  responseFormat: options?.responseFormat,
694
4030
  stop: options?.stop,
695
- fallbackModels: fallbacks
4031
+ // An explicit caller-supplied chain wins over the routed one.
4032
+ fallbackModels: options?.fallbackModels ?? decision.fallbacks
696
4033
  });
697
4034
  return {
698
4035
  response,
699
- model: primary,
700
- routing: { ...decision, model: primary, fallbacks }
4036
+ model: decision.model,
4037
+ routing: decision
701
4038
  };
702
4039
  }
4040
+ /** Route a full Agent/tool conversation while preserving its complete response. */
4041
+ async smartChatCompletion(messages, options = {}) {
4042
+ const { prompt, systemPrompt, conversationChars, hasVision } = routingText(messages);
4043
+ const decision = await this.makeRoutingDecision(
4044
+ prompt,
4045
+ systemPrompt,
4046
+ options.maxOutputTokens ?? options.maxTokens ?? DEFAULT_MAX_TOKENS,
4047
+ {
4048
+ routingProfile: options.routingProfile,
4049
+ requiresStructuredOutput: options.responseFormat !== void 0,
4050
+ tools: options.tools,
4051
+ toolChoice: options.toolChoice,
4052
+ conversationChars,
4053
+ hasVision
4054
+ }
4055
+ );
4056
+ const response = await this.chatCompletion(decision.model, messages, {
4057
+ maxTokens: options.maxTokens,
4058
+ temperature: options.temperature,
4059
+ topP: options.topP,
4060
+ search: options.search,
4061
+ searchParameters: options.searchParameters,
4062
+ tools: options.tools,
4063
+ toolChoice: options.toolChoice,
4064
+ responseFormat: options.responseFormat,
4065
+ stop: options.stop,
4066
+ // An explicit caller-supplied chain wins over the routed one.
4067
+ fallbackModels: options.fallbackModels ?? decision.fallbacks
4068
+ });
4069
+ response.routing = decision;
4070
+ return { response, model: decision.model, routing: decision };
4071
+ }
703
4072
  /**
704
4073
  * Get model pricing map (cached).
705
4074
  * Fetches from API on first call, then returns cached result.
@@ -719,31 +4088,18 @@ var LLMClient = class _LLMClient {
719
4088
  this.modelPricingPromise = null;
720
4089
  }
721
4090
  }
722
- /**
723
- * Fetch model pricing from API.
724
- *
725
- * For flat-billed models (e.g. ZAI GLM-5 family at $0.001/call) the
726
- * router still expects per-token rates, so we synthesise an equivalent
727
- * per-token price assuming ~1500 total tokens per call. Without this,
728
- * flat models would resolve to inputPrice=outputPrice=0 and the router
729
- * would treat them as free, biasing routing decisions and reporting
730
- * inflated savings %.
731
- */
4091
+ /** Fetch model pricing from the live catalog, preserving flat billing. */
732
4092
  async fetchModelPricing() {
733
4093
  const models = await this.listModels();
734
4094
  const pricing = /* @__PURE__ */ new Map();
735
4095
  for (const model of models) {
736
- if (model.billingMode === "flat" && model.flatPrice && model.flatPrice > 0) {
737
- const perDirection = model.flatPrice * 1e6 / 1500 / 2;
738
- pricing.set(model.id, {
739
- inputPrice: perDirection,
740
- outputPrice: perDirection
741
- });
4096
+ if (model.available === false) continue;
4097
+ const inputPrice = Number.isFinite(model.inputPrice) ? model.inputPrice : 0;
4098
+ const outputPrice = Number.isFinite(model.outputPrice) ? model.outputPrice : 0;
4099
+ if (model.billingMode === "flat" && Number.isFinite(model.flatPrice) && model.flatPrice > 0) {
4100
+ pricing.set(model.id, { inputPrice, outputPrice, flatPrice: model.flatPrice });
742
4101
  } else {
743
- pricing.set(model.id, {
744
- inputPrice: model.inputPrice,
745
- outputPrice: model.outputPrice
746
- });
4102
+ pricing.set(model.id, { inputPrice, outputPrice });
747
4103
  }
748
4104
  }
749
4105
  return pricing;
@@ -763,6 +4119,13 @@ var LLMClient = class _LLMClient {
763
4119
  * @returns ChatResponse object with choices and usage
764
4120
  */
765
4121
  async chatCompletion(model, messages, options) {
4122
+ const routingProfile = routingProfileForModel(model);
4123
+ if (routingProfile) {
4124
+ return (await this.smartChatCompletion(messages, {
4125
+ ...options,
4126
+ routingProfile
4127
+ })).response;
4128
+ }
766
4129
  validateMaxTokens(options?.maxTokens);
767
4130
  const buildBody = (m) => {
768
4131
  const body = {
@@ -1035,9 +4398,28 @@ var LLMClient = class _LLMClient {
1035
4398
  */
1036
4399
  async chatCompletionStream(model, messages, options) {
1037
4400
  validateMaxTokens(options?.maxTokens);
4401
+ let requestModel = model;
4402
+ const routingProfile = routingProfileForModel(model);
4403
+ if (routingProfile) {
4404
+ const { prompt, systemPrompt, conversationChars, hasVision } = routingText(messages);
4405
+ const decision = await this.makeRoutingDecision(
4406
+ prompt,
4407
+ systemPrompt,
4408
+ options?.maxTokens ?? DEFAULT_MAX_TOKENS,
4409
+ {
4410
+ routingProfile,
4411
+ requiresStructuredOutput: options?.responseFormat !== void 0,
4412
+ tools: options?.tools,
4413
+ toolChoice: options?.toolChoice,
4414
+ conversationChars,
4415
+ hasVision
4416
+ }
4417
+ );
4418
+ requestModel = decision.model;
4419
+ }
1038
4420
  const url = `${this.apiUrl}/v1/chat/completions`;
1039
4421
  const body = {
1040
- model,
4422
+ model: requestModel,
1041
4423
  messages,
1042
4424
  max_tokens: options?.maxTokens ?? DEFAULT_MAX_TOKENS,
1043
4425
  stream: true
@@ -1048,7 +4430,7 @@ var LLMClient = class _LLMClient {
1048
4430
  if (options?.toolChoice !== void 0) body.tool_choice = options.toolChoice;
1049
4431
  if (options?.responseFormat !== void 0) body.response_format = options.responseFormat;
1050
4432
  if (options?.stop !== void 0) body.stop = options.stop;
1051
- const cacheKey2 = `/v1/chat/completions:${model}`;
4433
+ const cacheKey2 = `/v1/chat/completions:${requestModel}`;
1052
4434
  const cached = this.preAuthCache.get(cacheKey2);
1053
4435
  const now = Date.now();
1054
4436
  if (cached && now - cached.cachedAt < _LLMClient.PRE_AUTH_TTL_MS) {
@@ -1658,7 +5040,13 @@ var LLMClient = class _LLMClient {
1658
5040
  */
1659
5041
  async getBalance() {
1660
5042
  const usdcContract = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
1661
- const rpcs = ["https://base.publicnode.com", "https://mainnet.base.org", "https://base.meowrpc.com"];
5043
+ const configuredRpc = typeof process !== "undefined" && process.env ? process.env.BASE_RPC_URL : void 0;
5044
+ const rpcs = [
5045
+ configuredRpc,
5046
+ "https://base-rpc.publicnode.com",
5047
+ "https://mainnet.base.org",
5048
+ "https://base.llamarpc.com"
5049
+ ].filter((rpc) => Boolean(rpc));
1662
5050
  const selector = "0x70a08231";
1663
5051
  const paddedAddress = this.account.address.slice(2).toLowerCase().padStart(64, "0");
1664
5052
  const data = selector + paddedAddress;
@@ -1676,7 +5064,9 @@ var LLMClient = class _LLMClient {
1676
5064
  headers: { "Content-Type": "application/json" },
1677
5065
  body: JSON.stringify(payload)
1678
5066
  });
5067
+ if (!response.ok) throw new Error(`Base RPC returned ${response.status}`);
1679
5068
  const result = await response.json();
5069
+ if (!result.result || result.error) throw new Error("Base RPC returned no balance result");
1680
5070
  const balanceRaw = parseInt(result.result || "0x0", 16);
1681
5071
  return balanceRaw / 1e6;
1682
5072
  } catch (e) {
@@ -5114,6 +8504,8 @@ var SolanaLLMClient = class {
5114
8504
  sessionTotalUsd = 0;
5115
8505
  sessionCalls = 0;
5116
8506
  addressCache = null;
8507
+ modelPricingCache = null;
8508
+ modelPricingPromise = null;
5117
8509
  constructor(options = {}) {
5118
8510
  const envKey = typeof process !== "undefined" && process.env ? process.env.SOLANA_WALLET_KEY : void 0;
5119
8511
  const privateKey = options.privateKey || envKey;
@@ -5148,25 +8540,141 @@ var SolanaLLMClient = class {
5148
8540
  temperature: options?.temperature,
5149
8541
  topP: options?.topP,
5150
8542
  search: options?.search,
5151
- searchParameters: options?.searchParameters
8543
+ searchParameters: options?.searchParameters,
8544
+ responseFormat: options?.responseFormat,
8545
+ stop: options?.stop,
8546
+ fallbackModels: options?.fallbackModels
5152
8547
  });
5153
8548
  return result.choices[0].message.content || "";
5154
8549
  }
5155
8550
  /** Full chat completion (OpenAI-compatible). */
5156
8551
  async chatCompletion(model, messages, options) {
8552
+ const routingProfile = routingProfileForModel(model);
8553
+ if (routingProfile) {
8554
+ return (await this.smartChatCompletion(messages, {
8555
+ ...options,
8556
+ routingProfile
8557
+ })).response;
8558
+ }
5157
8559
  validateMaxTokens(options?.maxTokens);
5158
- const body = {
5159
- model,
5160
- messages,
5161
- max_tokens: options?.maxTokens || DEFAULT_MAX_TOKENS2
8560
+ const buildBody = (candidate) => {
8561
+ const body = {
8562
+ model: candidate,
8563
+ messages,
8564
+ max_tokens: options?.maxTokens || DEFAULT_MAX_TOKENS2
8565
+ };
8566
+ if (options?.temperature !== void 0) body.temperature = options.temperature;
8567
+ if (options?.topP !== void 0) body.top_p = options.topP;
8568
+ if (options?.searchParameters !== void 0) body.search_parameters = options.searchParameters;
8569
+ else if (options?.search === true) body.search_parameters = { mode: "on" };
8570
+ if (options?.tools !== void 0) body.tools = options.tools;
8571
+ if (options?.toolChoice !== void 0) body.tool_choice = options.toolChoice;
8572
+ if (options?.responseFormat !== void 0) body.response_format = options.responseFormat;
8573
+ if (options?.stop !== void 0) body.stop = options.stop;
8574
+ return body;
5162
8575
  };
5163
- if (options?.temperature !== void 0) body.temperature = options.temperature;
5164
- if (options?.topP !== void 0) body.top_p = options.topP;
5165
- if (options?.searchParameters !== void 0) body.search_parameters = options.searchParameters;
5166
- else if (options?.search === true) body.search_parameters = { mode: "on" };
5167
- if (options?.tools !== void 0) body.tools = options.tools;
5168
- if (options?.toolChoice !== void 0) body.tool_choice = options.toolChoice;
5169
- return this.requestWithPayment("/v1/chat/completions", body);
8576
+ const chain = [model, ...options?.fallbackModels ?? []];
8577
+ let lastError;
8578
+ for (let index = 0; index < chain.length; index += 1) {
8579
+ const candidate = chain[index];
8580
+ try {
8581
+ return await this.requestWithPayment("/v1/chat/completions", buildBody(candidate));
8582
+ } catch (error) {
8583
+ lastError = error;
8584
+ if (!isTransientError(error) || index === chain.length - 1) throw error;
8585
+ console.error(`[@blockrun/llm] ${candidate} -> ${chain[index + 1]} (${errSummary(error)})`);
8586
+ }
8587
+ }
8588
+ throw lastError;
8589
+ }
8590
+ async getModelPricing() {
8591
+ if (this.modelPricingCache) return this.modelPricingCache;
8592
+ if (this.modelPricingPromise) return this.modelPricingPromise;
8593
+ this.modelPricingPromise = (async () => {
8594
+ const models = await this.listModels();
8595
+ const pricing = /* @__PURE__ */ new Map();
8596
+ for (const model of models) {
8597
+ const raw = model;
8598
+ if (model.available === false) continue;
8599
+ const num = (value) => {
8600
+ const n = Number(value ?? 0);
8601
+ return Number.isFinite(n) ? n : 0;
8602
+ };
8603
+ const inputPrice = num(model.inputPrice ?? raw.input_price ?? raw.pricing?.input);
8604
+ const outputPrice = num(model.outputPrice ?? raw.output_price ?? raw.pricing?.output);
8605
+ const flatPrice = num(model.flatPrice ?? raw.flat_price ?? raw.pricing?.flat);
8606
+ pricing.set(model.id, {
8607
+ inputPrice,
8608
+ outputPrice,
8609
+ ...(model.billingMode ?? raw.billing_mode) === "flat" && flatPrice > 0 ? { flatPrice } : {}
8610
+ });
8611
+ }
8612
+ return pricing;
8613
+ })();
8614
+ try {
8615
+ this.modelPricingCache = await this.modelPricingPromise;
8616
+ return this.modelPricingCache;
8617
+ } finally {
8618
+ this.modelPricingPromise = null;
8619
+ }
8620
+ }
8621
+ /** Inspect a Solana route without making or paying for a model call. */
8622
+ async route(prompt, options) {
8623
+ return routeWithCatalog(
8624
+ prompt,
8625
+ options?.system,
8626
+ options?.maxOutputTokens ?? options?.maxTokens ?? DEFAULT_MAX_TOKENS2,
8627
+ await this.getModelPricing(),
8628
+ {
8629
+ routingProfile: options?.routingProfile,
8630
+ requiresStructuredOutput: options?.responseFormat !== void 0,
8631
+ minimumPaymentUsd: SOLANA_MINIMUM_PAYMENT_USD
8632
+ }
8633
+ );
8634
+ }
8635
+ /** Smart one-line chat paid on Solana. */
8636
+ async smartChat(prompt, options) {
8637
+ const decision = await this.route(prompt, options);
8638
+ const response = await this.chat(decision.model, prompt, {
8639
+ ...options,
8640
+ // An explicit caller-supplied chain wins over the routed one.
8641
+ fallbackModels: options?.fallbackModels ?? decision.fallbacks
8642
+ });
8643
+ return { response, model: decision.model, routing: decision };
8644
+ }
8645
+ /** Smart full message/tool completion paid on Solana. */
8646
+ async smartChatCompletion(messages, options = {}) {
8647
+ const { prompt, systemPrompt, conversationChars, hasVision } = routingText(messages);
8648
+ const decision = routeWithCatalog(
8649
+ prompt,
8650
+ systemPrompt,
8651
+ options.maxOutputTokens ?? options.maxTokens ?? DEFAULT_MAX_TOKENS2,
8652
+ await this.getModelPricing(),
8653
+ {
8654
+ routingProfile: options.routingProfile,
8655
+ requiresStructuredOutput: options.responseFormat !== void 0,
8656
+ tools: options.tools,
8657
+ toolChoice: options.toolChoice,
8658
+ conversationChars,
8659
+ hasVision,
8660
+ minimumPaymentUsd: SOLANA_MINIMUM_PAYMENT_USD
8661
+ }
8662
+ );
8663
+ const response = await this.chatCompletion(decision.model, messages, {
8664
+ maxTokens: options.maxTokens,
8665
+ temperature: options.temperature,
8666
+ topP: options.topP,
8667
+ search: options.search,
8668
+ searchParameters: options.searchParameters,
8669
+ tools: options.tools,
8670
+ toolChoice: options.toolChoice,
8671
+ responseFormat: options.responseFormat,
8672
+ stop: options.stop,
8673
+ // An explicit caller-supplied chain wins over the routed one.
8674
+ fallbackModels: options.fallbackModels ?? decision.fallbacks
8675
+ });
8676
+ response.routing = decision;
8677
+ return { response, model: decision.model, routing: decision };
5170
8678
  }
5171
8679
  /** List available models. */
5172
8680
  async listModels() {
@@ -6047,7 +9555,8 @@ var ChatCompletions = class {
6047
9555
  },
6048
9556
  finish_reason: choice.finish_reason || "stop"
6049
9557
  })),
6050
- usage: response.usage
9558
+ usage: response.usage,
9559
+ routing: response.routing
6051
9560
  };
6052
9561
  }
6053
9562
  };