@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/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: ["google/gemini-2.5-flash-lite", "google/gemini-2.5-flash", "deepseek/deepseek-chat"]
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 { config, modelPricing } = options;
3243
- const fullText = `${systemPrompt ?? ""} ${prompt}`;
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 = { model: freeModel, tier: "SIMPLE", confidence: 1, method: "rules", reasoning: "free profile" };
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 {