@blockrun/clawrouter 0.12.95 → 0.12.97
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/auth.d.ts +82 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +388 -0
- package/dist/auth.js.map +1 -0
- package/dist/balance.d.ts +97 -0
- package/dist/balance.d.ts.map +1 -0
- package/dist/balance.js +157 -0
- package/dist/balance.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +256 -54994
- package/dist/cli.js.map +1 -1
- package/dist/compression/codebook.d.ts +24 -0
- package/dist/compression/codebook.d.ts.map +1 -0
- package/dist/compression/codebook.js +119 -0
- package/dist/compression/codebook.js.map +1 -0
- package/dist/compression/index.d.ts +33 -0
- package/dist/compression/index.d.ts.map +1 -0
- package/dist/compression/index.js +246 -0
- package/dist/compression/index.js.map +1 -0
- package/dist/compression/layers/deduplication.d.ts +28 -0
- package/dist/compression/layers/deduplication.d.ts.map +1 -0
- package/dist/compression/layers/deduplication.js +101 -0
- package/dist/compression/layers/deduplication.js.map +1 -0
- package/dist/compression/layers/dictionary.d.ts +21 -0
- package/dist/compression/layers/dictionary.d.ts.map +1 -0
- package/dist/compression/layers/dictionary.js +73 -0
- package/dist/compression/layers/dictionary.js.map +1 -0
- package/dist/compression/layers/dynamic-codebook.d.ts +26 -0
- package/dist/compression/layers/dynamic-codebook.d.ts.map +1 -0
- package/dist/compression/layers/dynamic-codebook.js +149 -0
- package/dist/compression/layers/dynamic-codebook.js.map +1 -0
- package/dist/compression/layers/json-compact.d.ts +23 -0
- package/dist/compression/layers/json-compact.d.ts.map +1 -0
- package/dist/compression/layers/json-compact.js +79 -0
- package/dist/compression/layers/json-compact.js.map +1 -0
- package/dist/compression/layers/observation.d.ts +21 -0
- package/dist/compression/layers/observation.d.ts.map +1 -0
- package/dist/compression/layers/observation.js +130 -0
- package/dist/compression/layers/observation.js.map +1 -0
- package/dist/compression/layers/paths.d.ts +24 -0
- package/dist/compression/layers/paths.d.ts.map +1 -0
- package/dist/compression/layers/paths.js +110 -0
- package/dist/compression/layers/paths.js.map +1 -0
- package/dist/compression/layers/whitespace.d.ts +27 -0
- package/dist/compression/layers/whitespace.d.ts.map +1 -0
- package/dist/compression/layers/whitespace.js +60 -0
- package/dist/compression/layers/whitespace.js.map +1 -0
- package/dist/compression/types.d.ts +84 -0
- package/dist/compression/types.d.ts.map +1 -0
- package/dist/compression/types.js +27 -0
- package/dist/compression/types.js.map +1 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +22 -0
- package/dist/config.js.map +1 -0
- package/dist/dedup.d.ts +34 -0
- package/dist/dedup.d.ts.map +1 -0
- package/dist/dedup.js +150 -0
- package/dist/dedup.js.map +1 -0
- package/dist/doctor.d.ts +8 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +375 -0
- package/dist/doctor.js.map +1 -0
- package/dist/error-classification.test.d.ts +2 -0
- package/dist/error-classification.test.d.ts.map +1 -0
- package/dist/error-classification.test.js +59 -0
- package/dist/error-classification.test.js.map +1 -0
- package/dist/errors.d.ts +54 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +85 -0
- package/dist/errors.js.map +1 -0
- package/dist/exclude-models.d.ts +28 -0
- package/dist/exclude-models.d.ts.map +1 -0
- package/dist/exclude-models.integration.test.d.ts +2 -0
- package/dist/exclude-models.integration.test.d.ts.map +1 -0
- package/dist/exclude-models.integration.test.js +46 -0
- package/dist/exclude-models.integration.test.js.map +1 -0
- package/dist/exclude-models.js +70 -0
- package/dist/exclude-models.js.map +1 -0
- package/dist/exclude-models.test.d.ts +5 -0
- package/dist/exclude-models.test.d.ts.map +1 -0
- package/dist/exclude-models.test.js +95 -0
- package/dist/exclude-models.test.js.map +1 -0
- package/dist/fs-read.d.ts +11 -0
- package/dist/fs-read.d.ts.map +1 -0
- package/dist/fs-read.js +47 -0
- package/dist/fs-read.js.map +1 -0
- package/dist/index.d.ts +34 -1368
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1071 -56194
- package/dist/index.js.map +1 -1
- package/dist/journal.d.ts +67 -0
- package/dist/journal.d.ts.map +1 -0
- package/dist/journal.js +184 -0
- package/dist/journal.js.map +1 -0
- package/dist/journal.test.d.ts +2 -0
- package/dist/journal.test.d.ts.map +1 -0
- package/dist/journal.test.js +201 -0
- package/dist/journal.test.js.map +1 -0
- package/dist/logger.d.ts +33 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +35 -0
- package/dist/logger.js.map +1 -0
- package/dist/models.d.ts +107 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +941 -0
- package/dist/models.js.map +1 -0
- package/dist/models.test.d.ts +2 -0
- package/dist/models.test.d.ts.map +1 -0
- package/dist/models.test.js +38 -0
- package/dist/models.test.js.map +1 -0
- package/dist/partners/index.d.ts +8 -0
- package/dist/partners/index.d.ts.map +1 -0
- package/dist/partners/index.js +6 -0
- package/dist/partners/index.js.map +1 -0
- package/dist/partners/registry.d.ts +51 -0
- package/dist/partners/registry.d.ts.map +1 -0
- package/dist/partners/registry.js +256 -0
- package/dist/partners/registry.js.map +1 -0
- package/dist/partners/tools.d.ts +24 -0
- package/dist/partners/tools.d.ts.map +1 -0
- package/dist/partners/tools.js +97 -0
- package/dist/partners/tools.js.map +1 -0
- package/dist/payment-preauth.d.ts +17 -0
- package/dist/payment-preauth.d.ts.map +1 -0
- package/dist/payment-preauth.js +102 -0
- package/dist/payment-preauth.js.map +1 -0
- package/dist/provider.d.ts +19 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +45 -0
- package/dist/provider.js.map +1 -0
- package/dist/proxy.d.ts +210 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.debrand.test.d.ts +2 -0
- package/dist/proxy.debrand.test.d.ts.map +1 -0
- package/dist/proxy.debrand.test.js +101 -0
- package/dist/proxy.debrand.test.js.map +1 -0
- package/dist/proxy.degraded-response.test.d.ts +2 -0
- package/dist/proxy.degraded-response.test.d.ts.map +1 -0
- package/dist/proxy.degraded-response.test.js +105 -0
- package/dist/proxy.degraded-response.test.js.map +1 -0
- package/dist/proxy.image-download.test.d.ts +2 -0
- package/dist/proxy.image-download.test.d.ts.map +1 -0
- package/dist/proxy.image-download.test.js +92 -0
- package/dist/proxy.image-download.test.js.map +1 -0
- package/dist/proxy.js +3810 -0
- package/dist/proxy.js.map +1 -0
- package/dist/proxy.models-endpoint.test.d.ts +2 -0
- package/dist/proxy.models-endpoint.test.d.ts.map +1 -0
- package/dist/proxy.models-endpoint.test.js +19 -0
- package/dist/proxy.models-endpoint.test.js.map +1 -0
- package/dist/proxy.multiturn-thinking.test.d.ts +2 -0
- package/dist/proxy.multiturn-thinking.test.d.ts.map +1 -0
- package/dist/proxy.multiturn-thinking.test.js +82 -0
- package/dist/proxy.multiturn-thinking.test.js.map +1 -0
- package/dist/proxy.payment-chain-reuse.test.d.ts +2 -0
- package/dist/proxy.payment-chain-reuse.test.d.ts.map +1 -0
- package/dist/proxy.payment-chain-reuse.test.js +30 -0
- package/dist/proxy.payment-chain-reuse.test.js.map +1 -0
- package/dist/proxy.solana-resilience.test.d.ts +12 -0
- package/dist/proxy.solana-resilience.test.d.ts.map +1 -0
- package/dist/proxy.solana-resilience.test.js +39 -0
- package/dist/proxy.solana-resilience.test.js.map +1 -0
- package/dist/report.d.ts +6 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +44 -0
- package/dist/report.js.map +1 -0
- package/dist/response-cache.advanced.test.d.ts +2 -0
- package/dist/response-cache.advanced.test.d.ts.map +1 -0
- package/dist/response-cache.advanced.test.js +566 -0
- package/dist/response-cache.advanced.test.js.map +1 -0
- package/dist/response-cache.d.ts +85 -0
- package/dist/response-cache.d.ts.map +1 -0
- package/dist/response-cache.edge-cases.test.d.ts +2 -0
- package/dist/response-cache.edge-cases.test.d.ts.map +1 -0
- package/dist/response-cache.edge-cases.test.js +488 -0
- package/dist/response-cache.edge-cases.test.js.map +1 -0
- package/dist/response-cache.extreme.test.d.ts +2 -0
- package/dist/response-cache.extreme.test.d.ts.map +1 -0
- package/dist/response-cache.extreme.test.js +561 -0
- package/dist/response-cache.extreme.test.js.map +1 -0
- package/dist/response-cache.js +240 -0
- package/dist/response-cache.js.map +1 -0
- package/dist/response-cache.test.d.ts +2 -0
- package/dist/response-cache.test.d.ts.map +1 -0
- package/dist/response-cache.test.js +242 -0
- package/dist/response-cache.test.js.map +1 -0
- package/dist/retry.d.ts +42 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +99 -0
- package/dist/retry.js.map +1 -0
- package/dist/router/config.d.ts +11 -0
- package/dist/router/config.d.ts.map +1 -0
- package/dist/router/config.js +1215 -0
- package/dist/router/config.js.map +1 -0
- package/dist/router/index.d.ts +18 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +19 -0
- package/dist/router/index.js.map +1 -0
- package/dist/router/llm-classifier.d.ts +29 -0
- package/dist/router/llm-classifier.d.ts.map +1 -0
- package/dist/router/llm-classifier.js +99 -0
- package/dist/router/llm-classifier.js.map +1 -0
- package/dist/router/rules.d.ts +12 -0
- package/dist/router/rules.d.ts.map +1 -0
- package/dist/router/rules.js +202 -0
- package/dist/router/rules.js.map +1 -0
- package/dist/router/selector.d.ts +58 -0
- package/dist/router/selector.d.ts.map +1 -0
- package/dist/router/selector.js +165 -0
- package/dist/router/selector.js.map +1 -0
- package/dist/router/selector.test.d.ts +2 -0
- package/dist/router/selector.test.d.ts.map +1 -0
- package/dist/router/selector.test.js +67 -0
- package/dist/router/selector.test.js.map +1 -0
- package/dist/router/strategy.d.ts +19 -0
- package/dist/router/strategy.d.ts.map +1 -0
- package/dist/router/strategy.js +138 -0
- package/dist/router/strategy.js.map +1 -0
- package/dist/router/strategy.test.d.ts +2 -0
- package/dist/router/strategy.test.d.ts.map +1 -0
- package/dist/router/strategy.test.js +131 -0
- package/dist/router/strategy.test.js.map +1 -0
- package/dist/router/types.d.ts +127 -0
- package/dist/router/types.d.ts.map +1 -0
- package/dist/router/types.js +11 -0
- package/dist/router/types.js.map +1 -0
- package/dist/session.d.ts +124 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +256 -0
- package/dist/session.js.map +1 -0
- package/dist/session.test.d.ts +2 -0
- package/dist/session.test.d.ts.map +1 -0
- package/dist/session.test.js +163 -0
- package/dist/session.test.js.map +1 -0
- package/dist/solana-balance.d.ts +48 -0
- package/dist/solana-balance.d.ts.map +1 -0
- package/dist/solana-balance.js +142 -0
- package/dist/solana-balance.js.map +1 -0
- package/dist/spend-control.d.ts +112 -0
- package/dist/spend-control.d.ts.map +1 -0
- package/dist/spend-control.js +278 -0
- package/dist/spend-control.js.map +1 -0
- package/dist/spend-control.test.d.ts +5 -0
- package/dist/spend-control.test.d.ts.map +1 -0
- package/dist/spend-control.test.js +210 -0
- package/dist/spend-control.test.js.map +1 -0
- package/dist/stats.d.ts +64 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +295 -0
- package/dist/stats.js.map +1 -0
- package/dist/types.d.ts +150 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/update-hint.test.d.ts +9 -0
- package/dist/update-hint.test.d.ts.map +1 -0
- package/dist/update-hint.test.js +156 -0
- package/dist/update-hint.test.js.map +1 -0
- package/dist/updater.d.ts +10 -0
- package/dist/updater.d.ts.map +1 -0
- package/dist/updater.js +55 -0
- package/dist/updater.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +16 -0
- package/dist/version.js.map +1 -0
- package/dist/wallet.d.ts +52 -0
- package/dist/wallet.d.ts.map +1 -0
- package/dist/wallet.js +93 -0
- package/dist/wallet.js.map +1 -0
- package/dist/wallet.test.d.ts +6 -0
- package/dist/wallet.test.d.ts.map +1 -0
- package/dist/wallet.test.js +117 -0
- package/dist/wallet.test.js.map +1 -0
- package/dist/x402-sdk.test.d.ts +6 -0
- package/dist/x402-sdk.test.d.ts.map +1 -0
- package/dist/x402-sdk.test.js +161 -0
- package/dist/x402-sdk.test.js.map +1 -0
- package/package.json +1 -1
- package/scripts/e2e-multiturn.mjs +4 -1
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier → Model Selection
|
|
3
|
+
*
|
|
4
|
+
* Maps a classification tier to the cheapest capable model.
|
|
5
|
+
* Builds RoutingDecision metadata with cost estimates and savings.
|
|
6
|
+
*/
|
|
7
|
+
const BASELINE_MODEL_ID = "anthropic/claude-opus-4.6";
|
|
8
|
+
// Hardcoded fallback: Claude Opus 4.6 pricing (per 1M tokens)
|
|
9
|
+
// Used when baseline model not found in dynamic pricing map
|
|
10
|
+
const BASELINE_INPUT_PRICE = 5.0;
|
|
11
|
+
const BASELINE_OUTPUT_PRICE = 25.0;
|
|
12
|
+
/**
|
|
13
|
+
* Select the primary model for a tier and build the RoutingDecision.
|
|
14
|
+
*/
|
|
15
|
+
export function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPricing, estimatedInputTokens, maxOutputTokens, routingProfile, agenticScore) {
|
|
16
|
+
const tierConfig = tierConfigs[tier];
|
|
17
|
+
const model = tierConfig.primary;
|
|
18
|
+
const pricing = modelPricing.get(model);
|
|
19
|
+
let costEstimate;
|
|
20
|
+
if (pricing?.flatPrice !== undefined) {
|
|
21
|
+
costEstimate = pricing.flatPrice;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const inputPrice = pricing?.inputPrice ?? 0;
|
|
25
|
+
const outputPrice = pricing?.outputPrice ?? 0;
|
|
26
|
+
costEstimate =
|
|
27
|
+
(estimatedInputTokens / 1_000_000) * inputPrice +
|
|
28
|
+
(maxOutputTokens / 1_000_000) * outputPrice;
|
|
29
|
+
}
|
|
30
|
+
// Baseline: what Claude Opus 4.5 would cost (the premium reference)
|
|
31
|
+
const opusPricing = modelPricing.get(BASELINE_MODEL_ID);
|
|
32
|
+
const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE;
|
|
33
|
+
const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE;
|
|
34
|
+
const baselineInput = (estimatedInputTokens / 1_000_000) * opusInputPrice;
|
|
35
|
+
const baselineOutput = (maxOutputTokens / 1_000_000) * opusOutputPrice;
|
|
36
|
+
const baselineCost = baselineInput + baselineOutput;
|
|
37
|
+
// Premium profile doesn't calculate savings (it's about quality, not cost)
|
|
38
|
+
const savings = routingProfile === "premium"
|
|
39
|
+
? 0
|
|
40
|
+
: baselineCost > 0
|
|
41
|
+
? Math.max(0, (baselineCost - costEstimate) / baselineCost)
|
|
42
|
+
: 0;
|
|
43
|
+
return {
|
|
44
|
+
model,
|
|
45
|
+
tier,
|
|
46
|
+
confidence,
|
|
47
|
+
method,
|
|
48
|
+
reasoning,
|
|
49
|
+
costEstimate,
|
|
50
|
+
baselineCost,
|
|
51
|
+
savings,
|
|
52
|
+
...(agenticScore !== undefined && { agenticScore }),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get the ordered fallback chain for a tier: [primary, ...fallbacks].
|
|
57
|
+
*/
|
|
58
|
+
export function getFallbackChain(tier, tierConfigs) {
|
|
59
|
+
const config = tierConfigs[tier];
|
|
60
|
+
return [config.primary, ...config.fallback];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Calculate cost for a specific model (used when fallback model is used).
|
|
64
|
+
* Returns updated cost fields for RoutingDecision.
|
|
65
|
+
*/
|
|
66
|
+
// Server-side margin applied to all x402 payments (must match blockrun server's MARGIN_PERCENT)
|
|
67
|
+
const SERVER_MARGIN_PERCENT = 5;
|
|
68
|
+
// Minimum payment enforced by CDP Facilitator (must match blockrun server's MIN_PAYMENT_USD)
|
|
69
|
+
const MIN_PAYMENT_USD = 0.001;
|
|
70
|
+
export function calculateModelCost(model, modelPricing, estimatedInputTokens, maxOutputTokens, routingProfile) {
|
|
71
|
+
const pricing = modelPricing.get(model);
|
|
72
|
+
let costEstimate;
|
|
73
|
+
if (pricing?.flatPrice !== undefined) {
|
|
74
|
+
// Active promo: fixed cost per request
|
|
75
|
+
costEstimate = Math.max(pricing.flatPrice * (1 + SERVER_MARGIN_PERCENT / 100), MIN_PAYMENT_USD);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// Defensive: guard against undefined price fields (not just undefined pricing)
|
|
79
|
+
const inputPrice = pricing?.inputPrice ?? 0;
|
|
80
|
+
const outputPrice = pricing?.outputPrice ?? 0;
|
|
81
|
+
const inputCost = (estimatedInputTokens / 1_000_000) * inputPrice;
|
|
82
|
+
const outputCost = (maxOutputTokens / 1_000_000) * outputPrice;
|
|
83
|
+
// Include server margin + minimum payment to match actual x402 charge
|
|
84
|
+
costEstimate = Math.max((inputCost + outputCost) * (1 + SERVER_MARGIN_PERCENT / 100), MIN_PAYMENT_USD);
|
|
85
|
+
}
|
|
86
|
+
// Baseline: what Claude Opus 4.5 would cost (the premium reference)
|
|
87
|
+
const opusPricing = modelPricing.get(BASELINE_MODEL_ID);
|
|
88
|
+
const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE;
|
|
89
|
+
const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE;
|
|
90
|
+
const baselineInput = (estimatedInputTokens / 1_000_000) * opusInputPrice;
|
|
91
|
+
const baselineOutput = (maxOutputTokens / 1_000_000) * opusOutputPrice;
|
|
92
|
+
const baselineCost = baselineInput + baselineOutput;
|
|
93
|
+
// Premium profile doesn't calculate savings (it's about quality, not cost)
|
|
94
|
+
const savings = routingProfile === "premium"
|
|
95
|
+
? 0
|
|
96
|
+
: baselineCost > 0
|
|
97
|
+
? Math.max(0, (baselineCost - costEstimate) / baselineCost)
|
|
98
|
+
: 0;
|
|
99
|
+
return { costEstimate, baselineCost, savings };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Filter a model list to only those that support tool calling.
|
|
103
|
+
* When hasTools is false, returns the list unchanged.
|
|
104
|
+
* When all models lack tool calling support, returns the full list as a fallback
|
|
105
|
+
* (better to let the API error than produce an empty chain).
|
|
106
|
+
*/
|
|
107
|
+
export function filterByToolCalling(models, hasTools, supportsToolCalling) {
|
|
108
|
+
if (!hasTools)
|
|
109
|
+
return models;
|
|
110
|
+
const filtered = models.filter(supportsToolCalling);
|
|
111
|
+
return filtered.length > 0 ? filtered : models;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Filter a model list to only those that support vision (image inputs).
|
|
115
|
+
* When hasVision is false, returns the list unchanged.
|
|
116
|
+
* When all models lack vision support, returns the full list as a fallback
|
|
117
|
+
* (better to let the API error than produce an empty chain).
|
|
118
|
+
*/
|
|
119
|
+
export function filterByVision(models, hasVision, supportsVision) {
|
|
120
|
+
if (!hasVision)
|
|
121
|
+
return models;
|
|
122
|
+
const filtered = models.filter(supportsVision);
|
|
123
|
+
return filtered.length > 0 ? filtered : models;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Filter a model list to remove user-excluded models.
|
|
127
|
+
* When all models are excluded, returns the full list as a fallback
|
|
128
|
+
* (same safety pattern as filterByToolCalling/filterByVision).
|
|
129
|
+
*/
|
|
130
|
+
export function filterByExcludeList(models, excludeList) {
|
|
131
|
+
if (excludeList.size === 0)
|
|
132
|
+
return models;
|
|
133
|
+
const filtered = models.filter((m) => !excludeList.has(m));
|
|
134
|
+
return filtered.length > 0 ? filtered : models;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get the fallback chain filtered by context length.
|
|
138
|
+
* Only returns models that can handle the estimated total context.
|
|
139
|
+
*
|
|
140
|
+
* @param tier - The tier to get fallback chain for
|
|
141
|
+
* @param tierConfigs - Tier configurations
|
|
142
|
+
* @param estimatedTotalTokens - Estimated total context (input + output)
|
|
143
|
+
* @param getContextWindow - Function to get context window for a model ID
|
|
144
|
+
* @returns Filtered list of models that can handle the context
|
|
145
|
+
*/
|
|
146
|
+
export function getFallbackChainFiltered(tier, tierConfigs, estimatedTotalTokens, getContextWindow) {
|
|
147
|
+
const fullChain = getFallbackChain(tier, tierConfigs);
|
|
148
|
+
// Filter to models that can handle the context
|
|
149
|
+
const filtered = fullChain.filter((modelId) => {
|
|
150
|
+
const contextWindow = getContextWindow(modelId);
|
|
151
|
+
if (contextWindow === undefined) {
|
|
152
|
+
// Unknown model - include it (let API reject if needed)
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
// Add 10% buffer for safety
|
|
156
|
+
return contextWindow >= estimatedTotalTokens * 1.1;
|
|
157
|
+
});
|
|
158
|
+
// If all models filtered out, return the original chain
|
|
159
|
+
// (let the API error out - better than no options)
|
|
160
|
+
if (filtered.length === 0) {
|
|
161
|
+
return fullChain;
|
|
162
|
+
}
|
|
163
|
+
return filtered;
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=selector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selector.js","sourceRoot":"","sources":["../../src/router/selector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selector.test.d.ts","sourceRoot":"","sources":["../../src/router/selector.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { calculateModelCost, filterByExcludeList, filterByToolCalling, selectModel, } from "./selector.js";
|
|
3
|
+
const TIER_CONFIGS = {
|
|
4
|
+
SIMPLE: { primary: "moonshot/kimi-k2.5", fallback: [] },
|
|
5
|
+
MEDIUM: { primary: "moonshot/kimi-k2.5", fallback: [] },
|
|
6
|
+
COMPLEX: { primary: "moonshot/kimi-k2.5", fallback: [] },
|
|
7
|
+
REASONING: { primary: "moonshot/kimi-k2.5", fallback: [] },
|
|
8
|
+
};
|
|
9
|
+
const MODEL_PRICING = new Map([
|
|
10
|
+
["moonshot/kimi-k2.5", { inputPrice: 0.5, outputPrice: 2.4 }],
|
|
11
|
+
["anthropic/claude-opus-4.6", { inputPrice: 5, outputPrice: 25 }],
|
|
12
|
+
]);
|
|
13
|
+
describe("selectModel", () => {
|
|
14
|
+
it("uses claude-opus-4.6 as baseline ID when computing savings", () => {
|
|
15
|
+
const decision = selectModel("SIMPLE", 0.95, "rules", "test", TIER_CONFIGS, MODEL_PRICING, 1000, 1000);
|
|
16
|
+
expect(decision.baselineCost).toBeGreaterThan(0);
|
|
17
|
+
expect(decision.savings).toBeGreaterThan(0);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
describe("filterByToolCalling", () => {
|
|
21
|
+
const supportsToolCalling = (modelId) => !["minimax/minimax-m2.5", "nvidia/gpt-oss-120b"].includes(modelId);
|
|
22
|
+
it("removes models without tool calling support when request has tools", () => {
|
|
23
|
+
const models = ["moonshot/kimi-k2.5", "minimax/minimax-m2.5", "deepseek/deepseek-chat"];
|
|
24
|
+
const filtered = filterByToolCalling(models, true, supportsToolCalling);
|
|
25
|
+
expect(filtered).toEqual(["moonshot/kimi-k2.5", "deepseek/deepseek-chat"]);
|
|
26
|
+
});
|
|
27
|
+
it("keeps all models when request has no tools", () => {
|
|
28
|
+
const models = ["moonshot/kimi-k2.5", "minimax/minimax-m2.5", "nvidia/gpt-oss-120b"];
|
|
29
|
+
const filtered = filterByToolCalling(models, false, supportsToolCalling);
|
|
30
|
+
expect(filtered).toEqual(models);
|
|
31
|
+
});
|
|
32
|
+
it("returns original list unchanged when all models support tool calling", () => {
|
|
33
|
+
const models = ["moonshot/kimi-k2.5", "anthropic/claude-sonnet-4.6", "deepseek/deepseek-chat"];
|
|
34
|
+
const filtered = filterByToolCalling(models, true, supportsToolCalling);
|
|
35
|
+
expect(filtered).toEqual(models);
|
|
36
|
+
});
|
|
37
|
+
it("returns full list unchanged when no models support tool calling, to avoid empty chain", () => {
|
|
38
|
+
const models = ["minimax/minimax-m2.5", "nvidia/gpt-oss-120b"];
|
|
39
|
+
const filtered = filterByToolCalling(models, true, supportsToolCalling);
|
|
40
|
+
expect(filtered).toEqual(models);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe("filterByExcludeList", () => {
|
|
44
|
+
const chain = ["moonshot/kimi-k2.5", "deepseek/deepseek-chat", "anthropic/claude-sonnet-4.6"];
|
|
45
|
+
it("removes excluded models from chain", () => {
|
|
46
|
+
const excludeList = new Set(["deepseek/deepseek-chat"]);
|
|
47
|
+
const filtered = filterByExcludeList(chain, excludeList);
|
|
48
|
+
expect(filtered).toEqual(["moonshot/kimi-k2.5", "anthropic/claude-sonnet-4.6"]);
|
|
49
|
+
});
|
|
50
|
+
it("returns original chain if ALL models excluded (safety net)", () => {
|
|
51
|
+
const excludeList = new Set(chain);
|
|
52
|
+
const filtered = filterByExcludeList(chain, excludeList);
|
|
53
|
+
expect(filtered).toEqual(chain);
|
|
54
|
+
});
|
|
55
|
+
it("returns original chain for empty exclude set", () => {
|
|
56
|
+
const filtered = filterByExcludeList(chain, new Set());
|
|
57
|
+
expect(filtered).toEqual(chain);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe("calculateModelCost", () => {
|
|
61
|
+
it("uses claude-opus-4.6 as baseline ID when recomputing fallback costs", () => {
|
|
62
|
+
const costs = calculateModelCost("moonshot/kimi-k2.5", MODEL_PRICING, 1000, 1000);
|
|
63
|
+
expect(costs.baselineCost).toBeGreaterThan(0);
|
|
64
|
+
expect(costs.savings).toBeGreaterThan(0);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=selector.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selector.test.js","sourceRoot":"","sources":["../../src/router/selector.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Router Strategy Registry
|
|
3
|
+
*
|
|
4
|
+
* Pluggable strategy system for request routing.
|
|
5
|
+
* Default: RulesStrategy — identical to the original inline route() logic, <1ms.
|
|
6
|
+
*/
|
|
7
|
+
import type { RoutingDecision, RouterStrategy, RouterOptions } from "./types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Rules-based routing strategy.
|
|
10
|
+
* Extracted from the original route() in index.ts — logic is identical.
|
|
11
|
+
* Attaches tierConfigs and profile to the decision for downstream use.
|
|
12
|
+
*/
|
|
13
|
+
export declare class RulesStrategy implements RouterStrategy {
|
|
14
|
+
readonly name = "rules";
|
|
15
|
+
route(prompt: string, systemPrompt: string | undefined, maxOutputTokens: number, options: RouterOptions): RoutingDecision;
|
|
16
|
+
}
|
|
17
|
+
export declare function getStrategy(name: string): RouterStrategy;
|
|
18
|
+
export declare function registerStrategy(strategy: RouterStrategy): void;
|
|
19
|
+
//# sourceMappingURL=strategy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategy.d.ts","sourceRoot":"","sources":["../../src/router/strategy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAA+B,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AA8C9G;;;;GAIG;AACH,qBAAa,aAAc,YAAW,cAAc;IAClD,QAAQ,CAAC,IAAI,WAAW;IAExB,KAAK,CACH,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,aAAa,GACrB,eAAe;CAwGnB;AAOD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAMxD;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAE/D"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Router Strategy Registry
|
|
3
|
+
*
|
|
4
|
+
* Pluggable strategy system for request routing.
|
|
5
|
+
* Default: RulesStrategy — identical to the original inline route() logic, <1ms.
|
|
6
|
+
*/
|
|
7
|
+
import { classifyByRules } from "./rules.js";
|
|
8
|
+
import { selectModel } from "./selector.js";
|
|
9
|
+
/**
|
|
10
|
+
* Apply active time-windowed promotions to tier configs.
|
|
11
|
+
* Returns a new tierConfigs object with promotion overrides merged in.
|
|
12
|
+
* Expired or not-yet-active promotions are ignored.
|
|
13
|
+
*/
|
|
14
|
+
function applyPromotions(tierConfigs, promotions, profile, now = new Date()) {
|
|
15
|
+
if (!promotions || promotions.length === 0)
|
|
16
|
+
return tierConfigs;
|
|
17
|
+
let result = tierConfigs;
|
|
18
|
+
for (const promo of promotions) {
|
|
19
|
+
// Check time window
|
|
20
|
+
const start = new Date(promo.startDate);
|
|
21
|
+
const end = new Date(promo.endDate);
|
|
22
|
+
if (now < start || now >= end)
|
|
23
|
+
continue;
|
|
24
|
+
// Check profile filter
|
|
25
|
+
if (promo.profiles && !promo.profiles.includes(profile))
|
|
26
|
+
continue;
|
|
27
|
+
// Shallow-clone on first mutation
|
|
28
|
+
if (result === tierConfigs) {
|
|
29
|
+
result = { ...tierConfigs };
|
|
30
|
+
for (const t of Object.keys(result)) {
|
|
31
|
+
result[t] = { ...result[t] };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Merge overrides
|
|
35
|
+
for (const [tier, override] of Object.entries(promo.tierOverrides)) {
|
|
36
|
+
if (!result[tier])
|
|
37
|
+
continue;
|
|
38
|
+
if (override.primary)
|
|
39
|
+
result[tier].primary = override.primary;
|
|
40
|
+
if (override.fallback)
|
|
41
|
+
result[tier].fallback = override.fallback;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Rules-based routing strategy.
|
|
48
|
+
* Extracted from the original route() in index.ts — logic is identical.
|
|
49
|
+
* Attaches tierConfigs and profile to the decision for downstream use.
|
|
50
|
+
*/
|
|
51
|
+
export class RulesStrategy {
|
|
52
|
+
name = "rules";
|
|
53
|
+
route(prompt, systemPrompt, maxOutputTokens, options) {
|
|
54
|
+
const { config, modelPricing } = options;
|
|
55
|
+
// Estimate input tokens (~4 chars per token)
|
|
56
|
+
const fullText = `${systemPrompt ?? ""} ${prompt}`;
|
|
57
|
+
const estimatedTokens = Math.ceil(fullText.length / 4);
|
|
58
|
+
// --- Rule-based classification (runs first to get agenticScore) ---
|
|
59
|
+
const ruleResult = classifyByRules(prompt, systemPrompt, estimatedTokens, config.scoring);
|
|
60
|
+
// --- Select tier configs based on routing profile ---
|
|
61
|
+
const { routingProfile } = options;
|
|
62
|
+
let tierConfigs;
|
|
63
|
+
let profileSuffix;
|
|
64
|
+
let profile;
|
|
65
|
+
if (routingProfile === "eco" && config.ecoTiers) {
|
|
66
|
+
tierConfigs = config.ecoTiers;
|
|
67
|
+
profileSuffix = " | eco";
|
|
68
|
+
profile = "eco";
|
|
69
|
+
}
|
|
70
|
+
else if (routingProfile === "premium" && config.premiumTiers) {
|
|
71
|
+
tierConfigs = config.premiumTiers;
|
|
72
|
+
profileSuffix = " | premium";
|
|
73
|
+
profile = "premium";
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// Auto profile (or undefined): intelligent routing with agentic detection
|
|
77
|
+
const agenticScore = ruleResult.agenticScore ?? 0;
|
|
78
|
+
const isAutoAgentic = agenticScore >= 0.5;
|
|
79
|
+
const isExplicitAgentic = config.overrides.agenticMode ?? false;
|
|
80
|
+
const hasToolsInRequest = options.hasTools ?? false;
|
|
81
|
+
const useAgenticTiers = (hasToolsInRequest || isAutoAgentic || isExplicitAgentic) && config.agenticTiers != null;
|
|
82
|
+
tierConfigs = useAgenticTiers ? config.agenticTiers : config.tiers;
|
|
83
|
+
profileSuffix = useAgenticTiers ? ` | agentic${hasToolsInRequest ? " (tools)" : ""}` : "";
|
|
84
|
+
profile = useAgenticTiers ? "agentic" : "auto";
|
|
85
|
+
}
|
|
86
|
+
// Apply time-windowed promotions
|
|
87
|
+
tierConfigs = applyPromotions(tierConfigs, config.promotions, profile);
|
|
88
|
+
const agenticScoreValue = ruleResult.agenticScore;
|
|
89
|
+
// --- Override: large context → force COMPLEX ---
|
|
90
|
+
if (estimatedTokens > config.overrides.maxTokensForceComplex) {
|
|
91
|
+
const decision = selectModel("COMPLEX", 0.95, "rules", `Input exceeds ${config.overrides.maxTokensForceComplex} tokens${profileSuffix}`, tierConfigs, modelPricing, estimatedTokens, maxOutputTokens, routingProfile, agenticScoreValue);
|
|
92
|
+
return { ...decision, tierConfigs, profile };
|
|
93
|
+
}
|
|
94
|
+
// Structured output detection
|
|
95
|
+
const hasStructuredOutput = systemPrompt ? /json|structured|schema/i.test(systemPrompt) : false;
|
|
96
|
+
let tier;
|
|
97
|
+
let confidence;
|
|
98
|
+
const method = "rules";
|
|
99
|
+
let reasoning = `score=${ruleResult.score.toFixed(2)} | ${ruleResult.signals.join(", ")}`;
|
|
100
|
+
if (ruleResult.tier !== null) {
|
|
101
|
+
tier = ruleResult.tier;
|
|
102
|
+
confidence = ruleResult.confidence;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// Ambiguous — default to configurable tier (no external API call)
|
|
106
|
+
tier = config.overrides.ambiguousDefaultTier;
|
|
107
|
+
confidence = 0.5;
|
|
108
|
+
reasoning += ` | ambiguous -> default: ${tier}`;
|
|
109
|
+
}
|
|
110
|
+
// Apply structured output minimum tier
|
|
111
|
+
if (hasStructuredOutput) {
|
|
112
|
+
const tierRank = { SIMPLE: 0, MEDIUM: 1, COMPLEX: 2, REASONING: 3 };
|
|
113
|
+
const minTier = config.overrides.structuredOutputMinTier;
|
|
114
|
+
if (tierRank[tier] < tierRank[minTier]) {
|
|
115
|
+
reasoning += ` | upgraded to ${minTier} (structured output)`;
|
|
116
|
+
tier = minTier;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Add routing profile suffix to reasoning
|
|
120
|
+
reasoning += profileSuffix;
|
|
121
|
+
const decision = selectModel(tier, confidence, method, reasoning, tierConfigs, modelPricing, estimatedTokens, maxOutputTokens, routingProfile, agenticScoreValue);
|
|
122
|
+
return { ...decision, tierConfigs, profile };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// --- Strategy Registry ---
|
|
126
|
+
const registry = new Map();
|
|
127
|
+
registry.set("rules", new RulesStrategy());
|
|
128
|
+
export function getStrategy(name) {
|
|
129
|
+
const strategy = registry.get(name);
|
|
130
|
+
if (!strategy) {
|
|
131
|
+
throw new Error(`Unknown routing strategy: ${name}`);
|
|
132
|
+
}
|
|
133
|
+
return strategy;
|
|
134
|
+
}
|
|
135
|
+
export function registerStrategy(strategy) {
|
|
136
|
+
registry.set(strategy.name, strategy);
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=strategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategy.js","sourceRoot":"","sources":["../../src/router/strategy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategy.test.d.ts","sourceRoot":"","sources":["../../src/router/strategy.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { RulesStrategy, getStrategy, registerStrategy } from "./strategy.js";
|
|
3
|
+
import { DEFAULT_ROUTING_CONFIG } from "./config.js";
|
|
4
|
+
import { route } from "./index.js";
|
|
5
|
+
const MODEL_PRICING = new Map([
|
|
6
|
+
["moonshot/kimi-k2.5", { inputPrice: 0.5, outputPrice: 2.4 }],
|
|
7
|
+
["anthropic/claude-opus-4.6", { inputPrice: 5, outputPrice: 25 }],
|
|
8
|
+
["google/gemini-2.5-flash", { inputPrice: 0.15, outputPrice: 0.6 }],
|
|
9
|
+
["google/gemini-2.5-flash-lite", { inputPrice: 0.1, outputPrice: 0.4 }],
|
|
10
|
+
["deepseek/deepseek-chat", { inputPrice: 0.14, outputPrice: 0.28 }],
|
|
11
|
+
["anthropic/claude-sonnet-4.6", { inputPrice: 3, outputPrice: 15 }],
|
|
12
|
+
["google/gemini-3.1-pro", { inputPrice: 1.25, outputPrice: 10 }],
|
|
13
|
+
["xai/grok-4-1-fast-reasoning", { inputPrice: 0.2, outputPrice: 0.5 }],
|
|
14
|
+
["nvidia/gpt-oss-120b", { inputPrice: 0, outputPrice: 0 }],
|
|
15
|
+
["nvidia/gpt-oss-20b", { inputPrice: 0, outputPrice: 0 }],
|
|
16
|
+
["nvidia/nemotron-ultra-253b", { inputPrice: 0, outputPrice: 0 }],
|
|
17
|
+
["nvidia/nemotron-3-super-120b", { inputPrice: 0, outputPrice: 0 }],
|
|
18
|
+
["nvidia/nemotron-super-49b", { inputPrice: 0, outputPrice: 0 }],
|
|
19
|
+
["nvidia/deepseek-v3.2", { inputPrice: 0, outputPrice: 0 }],
|
|
20
|
+
["nvidia/mistral-large-3-675b", { inputPrice: 0, outputPrice: 0 }],
|
|
21
|
+
["nvidia/qwen3-coder-480b", { inputPrice: 0, outputPrice: 0 }],
|
|
22
|
+
["nvidia/devstral-2-123b", { inputPrice: 0, outputPrice: 0 }],
|
|
23
|
+
["nvidia/glm-4.7", { inputPrice: 0, outputPrice: 0 }],
|
|
24
|
+
["nvidia/llama-4-maverick", { inputPrice: 0, outputPrice: 0 }],
|
|
25
|
+
]);
|
|
26
|
+
const baseOptions = {
|
|
27
|
+
config: DEFAULT_ROUTING_CONFIG,
|
|
28
|
+
modelPricing: MODEL_PRICING,
|
|
29
|
+
};
|
|
30
|
+
describe("RulesStrategy", () => {
|
|
31
|
+
it("returns tierConfigs in the decision", () => {
|
|
32
|
+
const strategy = new RulesStrategy();
|
|
33
|
+
const decision = strategy.route("hello", undefined, 100, baseOptions);
|
|
34
|
+
expect(decision.tierConfigs).toBeDefined();
|
|
35
|
+
expect(decision.tierConfigs.SIMPLE).toBeDefined();
|
|
36
|
+
expect(decision.tierConfigs.MEDIUM).toBeDefined();
|
|
37
|
+
expect(decision.tierConfigs.COMPLEX).toBeDefined();
|
|
38
|
+
expect(decision.tierConfigs.REASONING).toBeDefined();
|
|
39
|
+
});
|
|
40
|
+
it("returns profile in the decision", () => {
|
|
41
|
+
const strategy = new RulesStrategy();
|
|
42
|
+
const decision = strategy.route("hello", undefined, 100, baseOptions);
|
|
43
|
+
expect(decision.profile).toBeDefined();
|
|
44
|
+
expect(["auto", "eco", "premium", "agentic"]).toContain(decision.profile);
|
|
45
|
+
});
|
|
46
|
+
it("sets eco profile when routingProfile is eco", () => {
|
|
47
|
+
const strategy = new RulesStrategy();
|
|
48
|
+
const decision = strategy.route("hello", undefined, 100, {
|
|
49
|
+
...baseOptions,
|
|
50
|
+
routingProfile: "eco",
|
|
51
|
+
});
|
|
52
|
+
expect(decision.profile).toBe("eco");
|
|
53
|
+
expect(decision.tierConfigs).toEqual(DEFAULT_ROUTING_CONFIG.ecoTiers);
|
|
54
|
+
});
|
|
55
|
+
it("sets premium profile when routingProfile is premium", () => {
|
|
56
|
+
const strategy = new RulesStrategy();
|
|
57
|
+
const decision = strategy.route("hello", undefined, 100, {
|
|
58
|
+
...baseOptions,
|
|
59
|
+
routingProfile: "premium",
|
|
60
|
+
});
|
|
61
|
+
expect(decision.profile).toBe("premium");
|
|
62
|
+
expect(decision.tierConfigs).toEqual(DEFAULT_ROUTING_CONFIG.premiumTiers);
|
|
63
|
+
});
|
|
64
|
+
it("sets agentic profile when tools are present", () => {
|
|
65
|
+
const strategy = new RulesStrategy();
|
|
66
|
+
const decision = strategy.route("hello", undefined, 100, {
|
|
67
|
+
...baseOptions,
|
|
68
|
+
hasTools: true,
|
|
69
|
+
});
|
|
70
|
+
expect(decision.profile).toBe("agentic");
|
|
71
|
+
expect(decision.tierConfigs).toEqual(DEFAULT_ROUTING_CONFIG.agenticTiers);
|
|
72
|
+
});
|
|
73
|
+
it("sets auto profile for default requests", () => {
|
|
74
|
+
const strategy = new RulesStrategy();
|
|
75
|
+
const decision = strategy.route("what is the capital of France", undefined, 100, baseOptions);
|
|
76
|
+
expect(decision.profile).toBe("auto");
|
|
77
|
+
expect(decision.tierConfigs).toEqual(DEFAULT_ROUTING_CONFIG.tiers);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe("Strategy Registry", () => {
|
|
81
|
+
it("retrieves the default rules strategy", () => {
|
|
82
|
+
const strategy = getStrategy("rules");
|
|
83
|
+
expect(strategy).toBeInstanceOf(RulesStrategy);
|
|
84
|
+
expect(strategy.name).toBe("rules");
|
|
85
|
+
});
|
|
86
|
+
it("throws for unknown strategy", () => {
|
|
87
|
+
expect(() => getStrategy("nonexistent")).toThrow("Unknown routing strategy: nonexistent");
|
|
88
|
+
});
|
|
89
|
+
it("registers and retrieves a custom strategy", () => {
|
|
90
|
+
const custom = {
|
|
91
|
+
name: "custom-test",
|
|
92
|
+
route: (_prompt, _sys, _max, options) => ({
|
|
93
|
+
model: "test/model",
|
|
94
|
+
tier: "SIMPLE",
|
|
95
|
+
confidence: 1,
|
|
96
|
+
method: "rules",
|
|
97
|
+
reasoning: "custom strategy",
|
|
98
|
+
costEstimate: 0,
|
|
99
|
+
baselineCost: 0,
|
|
100
|
+
savings: 0,
|
|
101
|
+
tierConfigs: options.config.tiers,
|
|
102
|
+
profile: "auto",
|
|
103
|
+
}),
|
|
104
|
+
};
|
|
105
|
+
registerStrategy(custom);
|
|
106
|
+
const retrieved = getStrategy("custom-test");
|
|
107
|
+
expect(retrieved.name).toBe("custom-test");
|
|
108
|
+
const decision = retrieved.route("test", undefined, 100, baseOptions);
|
|
109
|
+
expect(decision.model).toBe("test/model");
|
|
110
|
+
expect(decision.reasoning).toBe("custom strategy");
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
describe("Backward compatibility", () => {
|
|
114
|
+
it("route() produces same model/tier/method as before", () => {
|
|
115
|
+
// Simple prompt → SIMPLE tier
|
|
116
|
+
const simple = route("hello", undefined, 100, baseOptions);
|
|
117
|
+
expect(simple.tier).toBe("SIMPLE");
|
|
118
|
+
expect(simple.method).toBe("rules");
|
|
119
|
+
expect(simple.model).toBeDefined();
|
|
120
|
+
// Reasoning prompt → REASONING tier
|
|
121
|
+
const reasoning = route("prove the theorem step by step using mathematical induction", undefined, 4096, baseOptions);
|
|
122
|
+
expect(reasoning.tier).toBe("REASONING");
|
|
123
|
+
expect(reasoning.method).toBe("rules");
|
|
124
|
+
// New fields are present
|
|
125
|
+
expect(simple.tierConfigs).toBeDefined();
|
|
126
|
+
expect(simple.profile).toBeDefined();
|
|
127
|
+
expect(reasoning.tierConfigs).toBeDefined();
|
|
128
|
+
expect(reasoning.profile).toBeDefined();
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
//# sourceMappingURL=strategy.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategy.test.js","sourceRoot":"","sources":["../../src/router/strategy.test.ts"],"names":[],"mappings":"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"}
|