@blockrun/clawrouter 0.12.39 → 0.12.40
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/README.md +129 -115
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +130 -100
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1387 -0
- package/dist/index.js +127 -97
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2022,6 +2022,99 @@ function getFallbackChainFiltered(tier, tierConfigs, estimatedTotalTokens, getCo
|
|
|
2022
2022
|
return filtered;
|
|
2023
2023
|
}
|
|
2024
2024
|
|
|
2025
|
+
// src/router/strategy.ts
|
|
2026
|
+
var RulesStrategy = class {
|
|
2027
|
+
name = "rules";
|
|
2028
|
+
route(prompt, systemPrompt, maxOutputTokens, options) {
|
|
2029
|
+
const { config, modelPricing } = options;
|
|
2030
|
+
const fullText = `${systemPrompt ?? ""} ${prompt}`;
|
|
2031
|
+
const estimatedTokens = Math.ceil(fullText.length / 4);
|
|
2032
|
+
const ruleResult = classifyByRules(prompt, systemPrompt, estimatedTokens, config.scoring);
|
|
2033
|
+
const { routingProfile } = options;
|
|
2034
|
+
let tierConfigs;
|
|
2035
|
+
let profileSuffix;
|
|
2036
|
+
let profile;
|
|
2037
|
+
if (routingProfile === "eco" && config.ecoTiers) {
|
|
2038
|
+
tierConfigs = config.ecoTiers;
|
|
2039
|
+
profileSuffix = " | eco";
|
|
2040
|
+
profile = "eco";
|
|
2041
|
+
} else if (routingProfile === "premium" && config.premiumTiers) {
|
|
2042
|
+
tierConfigs = config.premiumTiers;
|
|
2043
|
+
profileSuffix = " | premium";
|
|
2044
|
+
profile = "premium";
|
|
2045
|
+
} else {
|
|
2046
|
+
const agenticScore = ruleResult.agenticScore ?? 0;
|
|
2047
|
+
const isAutoAgentic = agenticScore >= 0.5;
|
|
2048
|
+
const isExplicitAgentic = config.overrides.agenticMode ?? false;
|
|
2049
|
+
const hasToolsInRequest = options.hasTools ?? false;
|
|
2050
|
+
const useAgenticTiers = (hasToolsInRequest || isAutoAgentic || isExplicitAgentic) && config.agenticTiers != null;
|
|
2051
|
+
tierConfigs = useAgenticTiers ? config.agenticTiers : config.tiers;
|
|
2052
|
+
profileSuffix = useAgenticTiers ? ` | agentic${hasToolsInRequest ? " (tools)" : ""}` : "";
|
|
2053
|
+
profile = useAgenticTiers ? "agentic" : "auto";
|
|
2054
|
+
}
|
|
2055
|
+
const agenticScoreValue = ruleResult.agenticScore;
|
|
2056
|
+
if (estimatedTokens > config.overrides.maxTokensForceComplex) {
|
|
2057
|
+
const decision2 = selectModel(
|
|
2058
|
+
"COMPLEX",
|
|
2059
|
+
0.95,
|
|
2060
|
+
"rules",
|
|
2061
|
+
`Input exceeds ${config.overrides.maxTokensForceComplex} tokens${profileSuffix}`,
|
|
2062
|
+
tierConfigs,
|
|
2063
|
+
modelPricing,
|
|
2064
|
+
estimatedTokens,
|
|
2065
|
+
maxOutputTokens,
|
|
2066
|
+
routingProfile,
|
|
2067
|
+
agenticScoreValue
|
|
2068
|
+
);
|
|
2069
|
+
return { ...decision2, tierConfigs, profile };
|
|
2070
|
+
}
|
|
2071
|
+
const hasStructuredOutput = systemPrompt ? /json|structured|schema/i.test(systemPrompt) : false;
|
|
2072
|
+
let tier;
|
|
2073
|
+
let confidence;
|
|
2074
|
+
const method = "rules";
|
|
2075
|
+
let reasoning = `score=${ruleResult.score.toFixed(2)} | ${ruleResult.signals.join(", ")}`;
|
|
2076
|
+
if (ruleResult.tier !== null) {
|
|
2077
|
+
tier = ruleResult.tier;
|
|
2078
|
+
confidence = ruleResult.confidence;
|
|
2079
|
+
} else {
|
|
2080
|
+
tier = config.overrides.ambiguousDefaultTier;
|
|
2081
|
+
confidence = 0.5;
|
|
2082
|
+
reasoning += ` | ambiguous -> default: ${tier}`;
|
|
2083
|
+
}
|
|
2084
|
+
if (hasStructuredOutput) {
|
|
2085
|
+
const tierRank = { SIMPLE: 0, MEDIUM: 1, COMPLEX: 2, REASONING: 3 };
|
|
2086
|
+
const minTier = config.overrides.structuredOutputMinTier;
|
|
2087
|
+
if (tierRank[tier] < tierRank[minTier]) {
|
|
2088
|
+
reasoning += ` | upgraded to ${minTier} (structured output)`;
|
|
2089
|
+
tier = minTier;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
reasoning += profileSuffix;
|
|
2093
|
+
const decision = selectModel(
|
|
2094
|
+
tier,
|
|
2095
|
+
confidence,
|
|
2096
|
+
method,
|
|
2097
|
+
reasoning,
|
|
2098
|
+
tierConfigs,
|
|
2099
|
+
modelPricing,
|
|
2100
|
+
estimatedTokens,
|
|
2101
|
+
maxOutputTokens,
|
|
2102
|
+
routingProfile,
|
|
2103
|
+
agenticScoreValue
|
|
2104
|
+
);
|
|
2105
|
+
return { ...decision, tierConfigs, profile };
|
|
2106
|
+
}
|
|
2107
|
+
};
|
|
2108
|
+
var registry = /* @__PURE__ */ new Map();
|
|
2109
|
+
registry.set("rules", new RulesStrategy());
|
|
2110
|
+
function getStrategy(name) {
|
|
2111
|
+
const strategy = registry.get(name);
|
|
2112
|
+
if (!strategy) {
|
|
2113
|
+
throw new Error(`Unknown routing strategy: ${name}`);
|
|
2114
|
+
}
|
|
2115
|
+
return strategy;
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2025
2118
|
// src/router/config.ts
|
|
2026
2119
|
var DEFAULT_ROUTING_CONFIG = {
|
|
2027
2120
|
version: "2.0",
|
|
@@ -3114,7 +3207,11 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
3114
3207
|
SIMPLE: {
|
|
3115
3208
|
primary: "nvidia/gpt-oss-120b",
|
|
3116
3209
|
// FREE! $0.00/$0.00
|
|
3117
|
-
fallback: [
|
|
3210
|
+
fallback: [
|
|
3211
|
+
"google/gemini-2.5-flash-lite",
|
|
3212
|
+
"google/gemini-2.5-flash",
|
|
3213
|
+
"deepseek/deepseek-chat"
|
|
3214
|
+
]
|
|
3118
3215
|
},
|
|
3119
3216
|
MEDIUM: {
|
|
3120
3217
|
primary: "google/gemini-2.5-flash-lite",
|
|
@@ -3239,77 +3336,8 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
3239
3336
|
|
|
3240
3337
|
// src/router/index.ts
|
|
3241
3338
|
function route(prompt, systemPrompt, maxOutputTokens, options) {
|
|
3242
|
-
const
|
|
3243
|
-
|
|
3244
|
-
const estimatedTokens = Math.ceil(fullText.length / 4);
|
|
3245
|
-
const ruleResult = classifyByRules(prompt, systemPrompt, estimatedTokens, config.scoring);
|
|
3246
|
-
const { routingProfile } = options;
|
|
3247
|
-
let tierConfigs;
|
|
3248
|
-
let profileSuffix;
|
|
3249
|
-
if (routingProfile === "eco" && config.ecoTiers) {
|
|
3250
|
-
tierConfigs = config.ecoTiers;
|
|
3251
|
-
profileSuffix = " | eco";
|
|
3252
|
-
} else if (routingProfile === "premium" && config.premiumTiers) {
|
|
3253
|
-
tierConfigs = config.premiumTiers;
|
|
3254
|
-
profileSuffix = " | premium";
|
|
3255
|
-
} else {
|
|
3256
|
-
const agenticScore = ruleResult.agenticScore ?? 0;
|
|
3257
|
-
const isAutoAgentic = agenticScore >= 0.5;
|
|
3258
|
-
const isExplicitAgentic = config.overrides.agenticMode ?? false;
|
|
3259
|
-
const hasToolsInRequest = options.hasTools ?? false;
|
|
3260
|
-
const useAgenticTiers = (hasToolsInRequest || isAutoAgentic || isExplicitAgentic) && config.agenticTiers != null;
|
|
3261
|
-
tierConfigs = useAgenticTiers ? config.agenticTiers : config.tiers;
|
|
3262
|
-
profileSuffix = useAgenticTiers ? ` | agentic${hasToolsInRequest ? " (tools)" : ""}` : "";
|
|
3263
|
-
}
|
|
3264
|
-
const agenticScoreValue = ruleResult.agenticScore;
|
|
3265
|
-
if (estimatedTokens > config.overrides.maxTokensForceComplex) {
|
|
3266
|
-
return selectModel(
|
|
3267
|
-
"COMPLEX",
|
|
3268
|
-
0.95,
|
|
3269
|
-
"rules",
|
|
3270
|
-
`Input exceeds ${config.overrides.maxTokensForceComplex} tokens${profileSuffix}`,
|
|
3271
|
-
tierConfigs,
|
|
3272
|
-
modelPricing,
|
|
3273
|
-
estimatedTokens,
|
|
3274
|
-
maxOutputTokens,
|
|
3275
|
-
routingProfile,
|
|
3276
|
-
agenticScoreValue
|
|
3277
|
-
);
|
|
3278
|
-
}
|
|
3279
|
-
const hasStructuredOutput = systemPrompt ? /json|structured|schema/i.test(systemPrompt) : false;
|
|
3280
|
-
let tier;
|
|
3281
|
-
let confidence;
|
|
3282
|
-
const method = "rules";
|
|
3283
|
-
let reasoning = `score=${ruleResult.score.toFixed(2)} | ${ruleResult.signals.join(", ")}`;
|
|
3284
|
-
if (ruleResult.tier !== null) {
|
|
3285
|
-
tier = ruleResult.tier;
|
|
3286
|
-
confidence = ruleResult.confidence;
|
|
3287
|
-
} else {
|
|
3288
|
-
tier = config.overrides.ambiguousDefaultTier;
|
|
3289
|
-
confidence = 0.5;
|
|
3290
|
-
reasoning += ` | ambiguous -> default: ${tier}`;
|
|
3291
|
-
}
|
|
3292
|
-
if (hasStructuredOutput) {
|
|
3293
|
-
const tierRank = { SIMPLE: 0, MEDIUM: 1, COMPLEX: 2, REASONING: 3 };
|
|
3294
|
-
const minTier = config.overrides.structuredOutputMinTier;
|
|
3295
|
-
if (tierRank[tier] < tierRank[minTier]) {
|
|
3296
|
-
reasoning += ` | upgraded to ${minTier} (structured output)`;
|
|
3297
|
-
tier = minTier;
|
|
3298
|
-
}
|
|
3299
|
-
}
|
|
3300
|
-
reasoning += profileSuffix;
|
|
3301
|
-
return selectModel(
|
|
3302
|
-
tier,
|
|
3303
|
-
confidence,
|
|
3304
|
-
method,
|
|
3305
|
-
reasoning,
|
|
3306
|
-
tierConfigs,
|
|
3307
|
-
modelPricing,
|
|
3308
|
-
estimatedTokens,
|
|
3309
|
-
maxOutputTokens,
|
|
3310
|
-
routingProfile,
|
|
3311
|
-
agenticScoreValue
|
|
3312
|
-
);
|
|
3339
|
+
const strategy = getStrategy("rules");
|
|
3340
|
+
return strategy.route(prompt, systemPrompt, maxOutputTokens, options);
|
|
3313
3341
|
}
|
|
3314
3342
|
|
|
3315
3343
|
// src/logger.ts
|
|
@@ -5454,6 +5482,12 @@ var ROUTING_PROFILES = /* @__PURE__ */ new Set([
|
|
|
5454
5482
|
"premium"
|
|
5455
5483
|
]);
|
|
5456
5484
|
var FREE_MODEL = "nvidia/gpt-oss-120b";
|
|
5485
|
+
var FREE_TIER_CONFIGS = {
|
|
5486
|
+
SIMPLE: { primary: FREE_MODEL, fallback: [] },
|
|
5487
|
+
MEDIUM: { primary: FREE_MODEL, fallback: [] },
|
|
5488
|
+
COMPLEX: { primary: FREE_MODEL, fallback: [] },
|
|
5489
|
+
REASONING: { primary: FREE_MODEL, fallback: [] }
|
|
5490
|
+
};
|
|
5457
5491
|
var freeRequestCount = 0;
|
|
5458
5492
|
var MAX_MESSAGES = 200;
|
|
5459
5493
|
var CONTEXT_LIMIT_KB = 5120;
|
|
@@ -7328,7 +7362,17 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
7328
7362
|
|
|
7329
7363
|
`;
|
|
7330
7364
|
}
|
|
7331
|
-
routingDecision = {
|
|
7365
|
+
routingDecision = {
|
|
7366
|
+
model: freeModel,
|
|
7367
|
+
tier: "SIMPLE",
|
|
7368
|
+
confidence: 1,
|
|
7369
|
+
method: "rules",
|
|
7370
|
+
reasoning: "free profile",
|
|
7371
|
+
costEstimate: 0,
|
|
7372
|
+
baselineCost: 0,
|
|
7373
|
+
savings: 1,
|
|
7374
|
+
tierConfigs: FREE_TIER_CONFIGS
|
|
7375
|
+
};
|
|
7332
7376
|
} else {
|
|
7333
7377
|
effectiveSessionId = getSessionId(req.headers) ?? deriveSessionId(parsedMessages);
|
|
7334
7378
|
const existingSession = effectiveSessionId ? sessionStore.getSession(effectiveSessionId) : void 0;
|
|
@@ -7419,18 +7463,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
7419
7463
|
const contentHash = hashRequestContent(prompt, toolCallNames);
|
|
7420
7464
|
const shouldEscalate = sessionStore.recordRequestHash(effectiveSessionId, contentHash);
|
|
7421
7465
|
if (shouldEscalate) {
|
|
7422
|
-
const activeTierConfigs =
|
|
7423
|
-
if (routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers) {
|
|
7424
|
-
return routerOpts.config.agenticTiers;
|
|
7425
|
-
}
|
|
7426
|
-
if (routingProfile === "eco" && routerOpts.config.ecoTiers) {
|
|
7427
|
-
return routerOpts.config.ecoTiers;
|
|
7428
|
-
}
|
|
7429
|
-
if (routingProfile === "premium" && routerOpts.config.premiumTiers) {
|
|
7430
|
-
return routerOpts.config.premiumTiers;
|
|
7431
|
-
}
|
|
7432
|
-
return routerOpts.config.tiers;
|
|
7433
|
-
})();
|
|
7466
|
+
const activeTierConfigs = routingDecision.tierConfigs ?? routerOpts.config.tiers;
|
|
7434
7467
|
const escalation = sessionStore.escalateSession(
|
|
7435
7468
|
effectiveSessionId,
|
|
7436
7469
|
activeTierConfigs
|
|
@@ -7646,18 +7679,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
7646
7679
|
if (routingDecision) {
|
|
7647
7680
|
const estimatedInputTokens = Math.ceil(body.length / 4);
|
|
7648
7681
|
const estimatedTotalTokens = estimatedInputTokens + maxTokens;
|
|
7649
|
-
const tierConfigs =
|
|
7650
|
-
if (routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers) {
|
|
7651
|
-
return routerOpts.config.agenticTiers;
|
|
7652
|
-
}
|
|
7653
|
-
if (routingProfile === "eco" && routerOpts.config.ecoTiers) {
|
|
7654
|
-
return routerOpts.config.ecoTiers;
|
|
7655
|
-
}
|
|
7656
|
-
if (routingProfile === "premium" && routerOpts.config.premiumTiers) {
|
|
7657
|
-
return routerOpts.config.premiumTiers;
|
|
7658
|
-
}
|
|
7659
|
-
return routerOpts.config.tiers;
|
|
7660
|
-
})();
|
|
7682
|
+
const tierConfigs = routingDecision.tierConfigs ?? routerOpts.config.tiers;
|
|
7661
7683
|
const fullChain = getFallbackChain(routingDecision.tier, tierConfigs);
|
|
7662
7684
|
const contextFiltered = getFallbackChainFiltered(
|
|
7663
7685
|
routingDecision.tier,
|
|
@@ -7737,6 +7759,14 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
7737
7759
|
status: result.errorStatus || 500
|
|
7738
7760
|
};
|
|
7739
7761
|
if (result.isProviderError && !isLastAttempt) {
|
|
7762
|
+
const isExplicitModelError = !routingDecision;
|
|
7763
|
+
const isUnknownExplicitModel = isExplicitModelError && /unknown.*model|invalid.*model/i.test(result.errorBody || "");
|
|
7764
|
+
if (isUnknownExplicitModel) {
|
|
7765
|
+
console.log(
|
|
7766
|
+
`[ClawRouter] Explicit model error from ${tryModel}, not falling back: ${result.errorBody?.slice(0, 100)}`
|
|
7767
|
+
);
|
|
7768
|
+
break;
|
|
7769
|
+
}
|
|
7740
7770
|
if (result.errorStatus === 429) {
|
|
7741
7771
|
markRateLimited(tryModel);
|
|
7742
7772
|
try {
|