@blockrun/clawrouter 0.10.14 → 0.10.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -476,7 +476,8 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
476
476
  tier: "REASONING",
477
477
  confidence: Math.max(confidence2, 0.85),
478
478
  signals,
479
- agenticScore
479
+ agenticScore,
480
+ dimensions
480
481
  };
481
482
  }
482
483
  const { simpleMedium, mediumComplex, complexReasoning } = config.tierBoundaries;
@@ -500,9 +501,9 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
500
501
  }
501
502
  const confidence = calibrateConfidence(distanceFromBoundary, config.confidenceSteepness);
502
503
  if (confidence < config.confidenceThreshold) {
503
- return { score: weightedScore, tier: null, confidence, signals, agenticScore };
504
+ return { score: weightedScore, tier: null, confidence, signals, agenticScore, dimensions };
504
505
  }
505
- return { score: weightedScore, tier, confidence, signals, agenticScore };
506
+ return { score: weightedScore, tier, confidence, signals, agenticScore, dimensions };
506
507
  }
507
508
  function calibrateConfidence(distance, steepness) {
508
509
  return 1 / (1 + Math.exp(-steepness * distance));
@@ -558,6 +559,11 @@ function calculateModelCost(model, modelPricing, estimatedInputTokens, maxOutput
558
559
  const savings = routingProfile === "premium" ? 0 : baselineCost > 0 ? Math.max(0, (baselineCost - costEstimate) / baselineCost) : 0;
559
560
  return { costEstimate, baselineCost, savings };
560
561
  }
562
+ function filterByToolCalling(models, hasTools, supportsToolCalling2) {
563
+ if (!hasTools) return models;
564
+ const filtered = models.filter(supportsToolCalling2);
565
+ return filtered.length > 0 ? filtered : models;
566
+ }
561
567
  function getFallbackChainFiltered(tier, tierConfigs, estimatedTotalTokens, getContextWindow) {
562
568
  const fullChain = getFallbackChain(tier, tierConfigs);
563
569
  const filtered = fullChain.filter((modelId) => {
@@ -1737,7 +1743,11 @@ var DEFAULT_ROUTING_CONFIG = {
1737
1743
  MEDIUM: {
1738
1744
  primary: "moonshot/kimi-k2.5",
1739
1745
  // $0.50/$2.40 - strong tool use, handles function calls correctly
1740
- fallback: ["anthropic/claude-haiku-4.5", "deepseek/deepseek-chat", "xai/grok-4-1-fast-non-reasoning"]
1746
+ fallback: [
1747
+ "anthropic/claude-haiku-4.5",
1748
+ "deepseek/deepseek-chat",
1749
+ "xai/grok-4-1-fast-non-reasoning"
1750
+ ]
1741
1751
  },
1742
1752
  COMPLEX: {
1743
1753
  primary: "anthropic/claude-sonnet-4.6",
@@ -1960,7 +1970,8 @@ var BLOCKRUN_MODELS = [
1960
1970
  maxOutput: 128e3,
1961
1971
  reasoning: true,
1962
1972
  vision: true,
1963
- agentic: true
1973
+ agentic: true,
1974
+ toolCalling: true
1964
1975
  },
1965
1976
  {
1966
1977
  id: "openai/gpt-5-mini",
@@ -1969,7 +1980,8 @@ var BLOCKRUN_MODELS = [
1969
1980
  inputPrice: 0.25,
1970
1981
  outputPrice: 2,
1971
1982
  contextWindow: 2e5,
1972
- maxOutput: 65536
1983
+ maxOutput: 65536,
1984
+ toolCalling: true
1973
1985
  },
1974
1986
  {
1975
1987
  id: "openai/gpt-5-nano",
@@ -1978,7 +1990,8 @@ var BLOCKRUN_MODELS = [
1978
1990
  inputPrice: 0.05,
1979
1991
  outputPrice: 0.4,
1980
1992
  contextWindow: 128e3,
1981
- maxOutput: 32768
1993
+ maxOutput: 32768,
1994
+ toolCalling: true
1982
1995
  },
1983
1996
  {
1984
1997
  id: "openai/gpt-5.2-pro",
@@ -1988,7 +2001,8 @@ var BLOCKRUN_MODELS = [
1988
2001
  outputPrice: 168,
1989
2002
  contextWindow: 4e5,
1990
2003
  maxOutput: 128e3,
1991
- reasoning: true
2004
+ reasoning: true,
2005
+ toolCalling: true
1992
2006
  },
1993
2007
  // OpenAI Codex Family
1994
2008
  {
@@ -1999,7 +2013,8 @@ var BLOCKRUN_MODELS = [
1999
2013
  outputPrice: 14,
2000
2014
  contextWindow: 128e3,
2001
2015
  maxOutput: 32e3,
2002
- agentic: true
2016
+ agentic: true,
2017
+ toolCalling: true
2003
2018
  },
2004
2019
  // OpenAI GPT-4 Family
2005
2020
  {
@@ -2010,7 +2025,8 @@ var BLOCKRUN_MODELS = [
2010
2025
  outputPrice: 8,
2011
2026
  contextWindow: 128e3,
2012
2027
  maxOutput: 16384,
2013
- vision: true
2028
+ vision: true,
2029
+ toolCalling: true
2014
2030
  },
2015
2031
  {
2016
2032
  id: "openai/gpt-4.1-mini",
@@ -2019,7 +2035,8 @@ var BLOCKRUN_MODELS = [
2019
2035
  inputPrice: 0.4,
2020
2036
  outputPrice: 1.6,
2021
2037
  contextWindow: 128e3,
2022
- maxOutput: 16384
2038
+ maxOutput: 16384,
2039
+ toolCalling: true
2023
2040
  },
2024
2041
  {
2025
2042
  id: "openai/gpt-4.1-nano",
@@ -2028,7 +2045,8 @@ var BLOCKRUN_MODELS = [
2028
2045
  inputPrice: 0.1,
2029
2046
  outputPrice: 0.4,
2030
2047
  contextWindow: 128e3,
2031
- maxOutput: 16384
2048
+ maxOutput: 16384,
2049
+ toolCalling: true
2032
2050
  },
2033
2051
  {
2034
2052
  id: "openai/gpt-4o",
@@ -2039,7 +2057,8 @@ var BLOCKRUN_MODELS = [
2039
2057
  contextWindow: 128e3,
2040
2058
  maxOutput: 16384,
2041
2059
  vision: true,
2042
- agentic: true
2060
+ agentic: true,
2061
+ toolCalling: true
2043
2062
  },
2044
2063
  {
2045
2064
  id: "openai/gpt-4o-mini",
@@ -2048,7 +2067,8 @@ var BLOCKRUN_MODELS = [
2048
2067
  inputPrice: 0.15,
2049
2068
  outputPrice: 0.6,
2050
2069
  contextWindow: 128e3,
2051
- maxOutput: 16384
2070
+ maxOutput: 16384,
2071
+ toolCalling: true
2052
2072
  },
2053
2073
  // OpenAI O-series (Reasoning)
2054
2074
  {
@@ -2059,7 +2079,8 @@ var BLOCKRUN_MODELS = [
2059
2079
  outputPrice: 60,
2060
2080
  contextWindow: 2e5,
2061
2081
  maxOutput: 1e5,
2062
- reasoning: true
2082
+ reasoning: true,
2083
+ toolCalling: true
2063
2084
  },
2064
2085
  {
2065
2086
  id: "openai/o1-mini",
@@ -2069,7 +2090,8 @@ var BLOCKRUN_MODELS = [
2069
2090
  outputPrice: 4.4,
2070
2091
  contextWindow: 128e3,
2071
2092
  maxOutput: 65536,
2072
- reasoning: true
2093
+ reasoning: true,
2094
+ toolCalling: true
2073
2095
  },
2074
2096
  {
2075
2097
  id: "openai/o3",
@@ -2079,7 +2101,8 @@ var BLOCKRUN_MODELS = [
2079
2101
  outputPrice: 8,
2080
2102
  contextWindow: 2e5,
2081
2103
  maxOutput: 1e5,
2082
- reasoning: true
2104
+ reasoning: true,
2105
+ toolCalling: true
2083
2106
  },
2084
2107
  {
2085
2108
  id: "openai/o3-mini",
@@ -2089,7 +2112,8 @@ var BLOCKRUN_MODELS = [
2089
2112
  outputPrice: 4.4,
2090
2113
  contextWindow: 128e3,
2091
2114
  maxOutput: 65536,
2092
- reasoning: true
2115
+ reasoning: true,
2116
+ toolCalling: true
2093
2117
  },
2094
2118
  {
2095
2119
  id: "openai/o4-mini",
@@ -2099,7 +2123,8 @@ var BLOCKRUN_MODELS = [
2099
2123
  outputPrice: 4.4,
2100
2124
  contextWindow: 128e3,
2101
2125
  maxOutput: 65536,
2102
- reasoning: true
2126
+ reasoning: true,
2127
+ toolCalling: true
2103
2128
  },
2104
2129
  // Anthropic - all Claude models excel at agentic workflows
2105
2130
  // Use newest versions (4.6) with full provider prefix
@@ -2111,7 +2136,8 @@ var BLOCKRUN_MODELS = [
2111
2136
  outputPrice: 5,
2112
2137
  contextWindow: 2e5,
2113
2138
  maxOutput: 8192,
2114
- agentic: true
2139
+ agentic: true,
2140
+ toolCalling: true
2115
2141
  },
2116
2142
  {
2117
2143
  id: "anthropic/claude-sonnet-4.6",
@@ -2122,7 +2148,8 @@ var BLOCKRUN_MODELS = [
2122
2148
  contextWindow: 2e5,
2123
2149
  maxOutput: 64e3,
2124
2150
  reasoning: true,
2125
- agentic: true
2151
+ agentic: true,
2152
+ toolCalling: true
2126
2153
  },
2127
2154
  {
2128
2155
  id: "anthropic/claude-opus-4.6",
@@ -2133,7 +2160,8 @@ var BLOCKRUN_MODELS = [
2133
2160
  contextWindow: 2e5,
2134
2161
  maxOutput: 32e3,
2135
2162
  reasoning: true,
2136
- agentic: true
2163
+ agentic: true,
2164
+ toolCalling: true
2137
2165
  },
2138
2166
  // Google
2139
2167
  {
@@ -2145,7 +2173,8 @@ var BLOCKRUN_MODELS = [
2145
2173
  contextWindow: 105e4,
2146
2174
  maxOutput: 65536,
2147
2175
  reasoning: true,
2148
- vision: true
2176
+ vision: true,
2177
+ toolCalling: true
2149
2178
  },
2150
2179
  {
2151
2180
  id: "google/gemini-3-pro-preview",
@@ -2156,7 +2185,8 @@ var BLOCKRUN_MODELS = [
2156
2185
  contextWindow: 105e4,
2157
2186
  maxOutput: 65536,
2158
2187
  reasoning: true,
2159
- vision: true
2188
+ vision: true,
2189
+ toolCalling: true
2160
2190
  },
2161
2191
  {
2162
2192
  id: "google/gemini-3-flash-preview",
@@ -2166,7 +2196,8 @@ var BLOCKRUN_MODELS = [
2166
2196
  outputPrice: 3,
2167
2197
  contextWindow: 1e6,
2168
2198
  maxOutput: 65536,
2169
- vision: true
2199
+ vision: true,
2200
+ toolCalling: true
2170
2201
  },
2171
2202
  {
2172
2203
  id: "google/gemini-2.5-pro",
@@ -2177,7 +2208,8 @@ var BLOCKRUN_MODELS = [
2177
2208
  contextWindow: 105e4,
2178
2209
  maxOutput: 65536,
2179
2210
  reasoning: true,
2180
- vision: true
2211
+ vision: true,
2212
+ toolCalling: true
2181
2213
  },
2182
2214
  {
2183
2215
  id: "google/gemini-2.5-flash",
@@ -2186,7 +2218,8 @@ var BLOCKRUN_MODELS = [
2186
2218
  inputPrice: 0.3,
2187
2219
  outputPrice: 2.5,
2188
2220
  contextWindow: 1e6,
2189
- maxOutput: 65536
2221
+ maxOutput: 65536,
2222
+ toolCalling: true
2190
2223
  },
2191
2224
  {
2192
2225
  id: "google/gemini-2.5-flash-lite",
@@ -2195,7 +2228,8 @@ var BLOCKRUN_MODELS = [
2195
2228
  inputPrice: 0.1,
2196
2229
  outputPrice: 0.4,
2197
2230
  contextWindow: 1e6,
2198
- maxOutput: 65536
2231
+ maxOutput: 65536,
2232
+ toolCalling: true
2199
2233
  },
2200
2234
  // DeepSeek
2201
2235
  {
@@ -2205,7 +2239,8 @@ var BLOCKRUN_MODELS = [
2205
2239
  inputPrice: 0.28,
2206
2240
  outputPrice: 0.42,
2207
2241
  contextWindow: 128e3,
2208
- maxOutput: 8192
2242
+ maxOutput: 8192,
2243
+ toolCalling: true
2209
2244
  },
2210
2245
  {
2211
2246
  id: "deepseek/deepseek-reasoner",
@@ -2215,7 +2250,8 @@ var BLOCKRUN_MODELS = [
2215
2250
  outputPrice: 0.42,
2216
2251
  contextWindow: 128e3,
2217
2252
  maxOutput: 8192,
2218
- reasoning: true
2253
+ reasoning: true,
2254
+ toolCalling: true
2219
2255
  },
2220
2256
  // Moonshot / Kimi - optimized for agentic workflows
2221
2257
  {
@@ -2228,7 +2264,8 @@ var BLOCKRUN_MODELS = [
2228
2264
  maxOutput: 8192,
2229
2265
  reasoning: true,
2230
2266
  vision: true,
2231
- agentic: true
2267
+ agentic: true,
2268
+ toolCalling: true
2232
2269
  },
2233
2270
  // xAI / Grok
2234
2271
  {
@@ -2239,7 +2276,8 @@ var BLOCKRUN_MODELS = [
2239
2276
  outputPrice: 15,
2240
2277
  contextWindow: 131072,
2241
2278
  maxOutput: 16384,
2242
- reasoning: true
2279
+ reasoning: true,
2280
+ toolCalling: true
2243
2281
  },
2244
2282
  // grok-3-fast removed - too expensive ($5/$25), use grok-4-fast instead
2245
2283
  {
@@ -2249,7 +2287,8 @@ var BLOCKRUN_MODELS = [
2249
2287
  inputPrice: 0.3,
2250
2288
  outputPrice: 0.5,
2251
2289
  contextWindow: 131072,
2252
- maxOutput: 16384
2290
+ maxOutput: 16384,
2291
+ toolCalling: true
2253
2292
  },
2254
2293
  // xAI Grok 4 Family - Ultra-cheap fast models
2255
2294
  {
@@ -2260,7 +2299,8 @@ var BLOCKRUN_MODELS = [
2260
2299
  outputPrice: 0.5,
2261
2300
  contextWindow: 131072,
2262
2301
  maxOutput: 16384,
2263
- reasoning: true
2302
+ reasoning: true,
2303
+ toolCalling: true
2264
2304
  },
2265
2305
  {
2266
2306
  id: "xai/grok-4-fast-non-reasoning",
@@ -2269,7 +2309,8 @@ var BLOCKRUN_MODELS = [
2269
2309
  inputPrice: 0.2,
2270
2310
  outputPrice: 0.5,
2271
2311
  contextWindow: 131072,
2272
- maxOutput: 16384
2312
+ maxOutput: 16384,
2313
+ toolCalling: true
2273
2314
  },
2274
2315
  {
2275
2316
  id: "xai/grok-4-1-fast-reasoning",
@@ -2279,7 +2320,8 @@ var BLOCKRUN_MODELS = [
2279
2320
  outputPrice: 0.5,
2280
2321
  contextWindow: 131072,
2281
2322
  maxOutput: 16384,
2282
- reasoning: true
2323
+ reasoning: true,
2324
+ toolCalling: true
2283
2325
  },
2284
2326
  {
2285
2327
  id: "xai/grok-4-1-fast-non-reasoning",
@@ -2288,7 +2330,8 @@ var BLOCKRUN_MODELS = [
2288
2330
  inputPrice: 0.2,
2289
2331
  outputPrice: 0.5,
2290
2332
  contextWindow: 131072,
2291
- maxOutput: 16384
2333
+ maxOutput: 16384,
2334
+ toolCalling: true
2292
2335
  },
2293
2336
  {
2294
2337
  id: "xai/grok-code-fast-1",
@@ -2297,9 +2340,10 @@ var BLOCKRUN_MODELS = [
2297
2340
  inputPrice: 0.2,
2298
2341
  outputPrice: 1.5,
2299
2342
  contextWindow: 131072,
2300
- maxOutput: 16384,
2301
- agentic: true
2302
- // Good for coding tasks
2343
+ maxOutput: 16384
2344
+ // toolCalling intentionally omitted: outputs tool calls as plain text JSON,
2345
+ // not OpenAI-compatible structured function calls. Will be skipped when
2346
+ // request has tools to prevent the "talking to itself" bug.
2303
2347
  },
2304
2348
  {
2305
2349
  id: "xai/grok-4-0709",
@@ -2309,7 +2353,8 @@ var BLOCKRUN_MODELS = [
2309
2353
  outputPrice: 1.5,
2310
2354
  contextWindow: 131072,
2311
2355
  maxOutput: 16384,
2312
- reasoning: true
2356
+ reasoning: true,
2357
+ toolCalling: true
2313
2358
  },
2314
2359
  {
2315
2360
  id: "xai/grok-2-vision",
@@ -2319,7 +2364,8 @@ var BLOCKRUN_MODELS = [
2319
2364
  outputPrice: 10,
2320
2365
  contextWindow: 131072,
2321
2366
  maxOutput: 16384,
2322
- vision: true
2367
+ vision: true,
2368
+ toolCalling: true
2323
2369
  },
2324
2370
  // MiniMax
2325
2371
  {
@@ -2331,7 +2377,8 @@ var BLOCKRUN_MODELS = [
2331
2377
  contextWindow: 204800,
2332
2378
  maxOutput: 16384,
2333
2379
  reasoning: true,
2334
- agentic: true
2380
+ agentic: true,
2381
+ toolCalling: true
2335
2382
  },
2336
2383
  // NVIDIA - Free/cheap models
2337
2384
  {
@@ -2342,6 +2389,8 @@ var BLOCKRUN_MODELS = [
2342
2389
  outputPrice: 0,
2343
2390
  contextWindow: 128e3,
2344
2391
  maxOutput: 16384
2392
+ // toolCalling intentionally omitted: free model, structured function
2393
+ // calling support unverified. Excluded from tool-heavy routing paths.
2345
2394
  },
2346
2395
  {
2347
2396
  id: "nvidia/kimi-k2.5",
@@ -2350,7 +2399,8 @@ var BLOCKRUN_MODELS = [
2350
2399
  inputPrice: 0.55,
2351
2400
  outputPrice: 2.5,
2352
2401
  contextWindow: 262144,
2353
- maxOutput: 16384
2402
+ maxOutput: 16384,
2403
+ toolCalling: true
2354
2404
  }
2355
2405
  ];
2356
2406
  function toOpenClawModel(m) {
@@ -2379,6 +2429,11 @@ var OPENCLAW_MODELS = [
2379
2429
  ...BLOCKRUN_MODELS.map(toOpenClawModel),
2380
2430
  ...ALIAS_MODELS
2381
2431
  ];
2432
+ function supportsToolCalling(modelId) {
2433
+ const normalized = modelId.replace("blockrun/", "");
2434
+ const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
2435
+ return model?.toolCalling ?? false;
2436
+ }
2382
2437
  function getModelContextWindow(modelId) {
2383
2438
  const normalized = modelId.replace("blockrun/", "");
2384
2439
  const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
@@ -5032,6 +5087,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5032
5087
  const originalContextSizeKB = Math.ceil(body.length / 1024);
5033
5088
  const debugMode = req.headers["x-clawrouter-debug"] !== "false";
5034
5089
  let routingDecision;
5090
+ let hasTools = false;
5035
5091
  let isStreaming = false;
5036
5092
  let modelId = "";
5037
5093
  let maxTokens = 4096;
@@ -5046,10 +5102,11 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5046
5102
  modelId = parsed.model || "";
5047
5103
  maxTokens = parsed.max_tokens || 4096;
5048
5104
  let bodyModified = false;
5049
- if (sessionId && Array.isArray(parsed.messages)) {
5050
- const messages = parsed.messages;
5051
- const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
5052
- const lastContent = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
5105
+ const parsedMessages = Array.isArray(parsed.messages) ? parsed.messages : [];
5106
+ const lastUserMsg = [...parsedMessages].reverse().find((m) => m.role === "user");
5107
+ const lastContent = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
5108
+ if (sessionId && parsedMessages.length > 0) {
5109
+ const messages = parsedMessages;
5053
5110
  if (sessionJournal.needsContext(lastContent)) {
5054
5111
  const journalText = sessionJournal.format(sessionId);
5055
5112
  if (journalText) {
@@ -5070,6 +5127,106 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5070
5127
  }
5071
5128
  }
5072
5129
  }
5130
+ if (lastContent.startsWith("/debug")) {
5131
+ const debugPrompt = lastContent.slice("/debug".length).trim() || "hello";
5132
+ const messages = parsed.messages;
5133
+ const systemMsg = messages?.find((m) => m.role === "system");
5134
+ const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
5135
+ const fullText = `${systemPrompt ?? ""} ${debugPrompt}`;
5136
+ const estimatedTokens = Math.ceil(fullText.length / 4);
5137
+ const normalizedModel2 = typeof parsed.model === "string" ? parsed.model.trim().toLowerCase() : "";
5138
+ const profileName = normalizedModel2.replace("blockrun/", "");
5139
+ const debugProfile = ["free", "eco", "auto", "premium"].includes(profileName) ? profileName : "auto";
5140
+ const scoring = classifyByRules(
5141
+ debugPrompt,
5142
+ systemPrompt,
5143
+ estimatedTokens,
5144
+ DEFAULT_ROUTING_CONFIG.scoring
5145
+ );
5146
+ const debugRouting = route(debugPrompt, systemPrompt, maxTokens, {
5147
+ ...routerOpts,
5148
+ routingProfile: debugProfile
5149
+ });
5150
+ const dimLines = (scoring.dimensions ?? []).map((d) => {
5151
+ const nameStr = (d.name + ":").padEnd(24);
5152
+ const scoreStr = d.score.toFixed(2).padStart(6);
5153
+ const sigStr = d.signal ? ` [${d.signal}]` : "";
5154
+ return ` ${nameStr}${scoreStr}${sigStr}`;
5155
+ }).join("\n");
5156
+ const sess = sessionId ? sessionStore.getSession(sessionId) : void 0;
5157
+ const sessLine = sess ? `Session: ${sessionId.slice(0, 8)}... \u2192 pinned: ${sess.model} (${sess.requestCount} requests)` : sessionId ? `Session: ${sessionId.slice(0, 8)}... \u2192 no pinned model` : "Session: none";
5158
+ const { simpleMedium, mediumComplex, complexReasoning } = DEFAULT_ROUTING_CONFIG.scoring.tierBoundaries;
5159
+ const debugText = [
5160
+ "ClawRouter Debug",
5161
+ "",
5162
+ `Profile: ${debugProfile} | Tier: ${debugRouting.tier} | Model: ${debugRouting.model}`,
5163
+ `Confidence: ${debugRouting.confidence.toFixed(2)} | Cost: $${debugRouting.costEstimate.toFixed(4)} | Savings: ${(debugRouting.savings * 100).toFixed(0)}%`,
5164
+ `Reasoning: ${debugRouting.reasoning}`,
5165
+ "",
5166
+ `Scoring (weighted: ${scoring.score.toFixed(3)})`,
5167
+ dimLines,
5168
+ "",
5169
+ `Tier Boundaries: SIMPLE <${simpleMedium.toFixed(2)} | MEDIUM <${mediumComplex.toFixed(2)} | COMPLEX <${complexReasoning.toFixed(2)} | REASONING >=${complexReasoning.toFixed(2)}`,
5170
+ "",
5171
+ sessLine
5172
+ ].join("\n");
5173
+ const completionId = `chatcmpl-debug-${Date.now()}`;
5174
+ const timestamp = Math.floor(Date.now() / 1e3);
5175
+ const syntheticResponse = {
5176
+ id: completionId,
5177
+ object: "chat.completion",
5178
+ created: timestamp,
5179
+ model: "clawrouter/debug",
5180
+ choices: [
5181
+ {
5182
+ index: 0,
5183
+ message: { role: "assistant", content: debugText },
5184
+ finish_reason: "stop"
5185
+ }
5186
+ ],
5187
+ usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
5188
+ };
5189
+ if (isStreaming) {
5190
+ res.writeHead(200, {
5191
+ "Content-Type": "text/event-stream",
5192
+ "Cache-Control": "no-cache",
5193
+ Connection: "keep-alive"
5194
+ });
5195
+ const sseChunk = {
5196
+ id: completionId,
5197
+ object: "chat.completion.chunk",
5198
+ created: timestamp,
5199
+ model: "clawrouter/debug",
5200
+ choices: [
5201
+ {
5202
+ index: 0,
5203
+ delta: { role: "assistant", content: debugText },
5204
+ finish_reason: null
5205
+ }
5206
+ ]
5207
+ };
5208
+ const sseDone = {
5209
+ id: completionId,
5210
+ object: "chat.completion.chunk",
5211
+ created: timestamp,
5212
+ model: "clawrouter/debug",
5213
+ choices: [{ index: 0, delta: {}, finish_reason: "stop" }]
5214
+ };
5215
+ res.write(`data: ${JSON.stringify(sseChunk)}
5216
+
5217
+ `);
5218
+ res.write(`data: ${JSON.stringify(sseDone)}
5219
+
5220
+ `);
5221
+ res.write("data: [DONE]\n\n");
5222
+ res.end();
5223
+ } else {
5224
+ res.writeHead(200, { "Content-Type": "application/json" });
5225
+ res.end(JSON.stringify(syntheticResponse));
5226
+ }
5227
+ console.log(`[ClawRouter] /debug command \u2192 ${debugRouting.tier} | ${debugRouting.model}`);
5228
+ return;
5229
+ }
5073
5230
  if (parsed.stream === true) {
5074
5231
  parsed.stream = false;
5075
5232
  bodyModified = true;
@@ -5124,20 +5281,20 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5124
5281
  sessionStore.touchSession(sessionId2);
5125
5282
  } else {
5126
5283
  const messages = parsed.messages;
5127
- let lastUserMsg;
5284
+ let lastUserMsg2;
5128
5285
  if (messages) {
5129
5286
  for (let i = messages.length - 1; i >= 0; i--) {
5130
5287
  if (messages[i].role === "user") {
5131
- lastUserMsg = messages[i];
5288
+ lastUserMsg2 = messages[i];
5132
5289
  break;
5133
5290
  }
5134
5291
  }
5135
5292
  }
5136
5293
  const systemMsg = messages?.find((m) => m.role === "system");
5137
- const prompt = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
5294
+ const prompt = typeof lastUserMsg2?.content === "string" ? lastUserMsg2.content : "";
5138
5295
  const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
5139
5296
  const tools = parsed.tools;
5140
- const hasTools = Array.isArray(tools) && tools.length > 0;
5297
+ hasTools = Array.isArray(tools) && tools.length > 0;
5141
5298
  if (hasTools && tools) {
5142
5299
  console.log(
5143
5300
  `[ClawRouter] Tools detected (${tools.length}), agentic mode via keywords`
@@ -5354,7 +5511,14 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5354
5511
  `[ClawRouter] Context filter (~${estimatedTotalTokens} tokens): excluded ${contextExcluded.join(", ")}`
5355
5512
  );
5356
5513
  }
5357
- modelsToTry = contextFiltered.slice(0, MAX_FALLBACK_ATTEMPTS);
5514
+ const toolFiltered = filterByToolCalling(contextFiltered, hasTools, supportsToolCalling);
5515
+ const toolExcluded = contextFiltered.filter((m) => !toolFiltered.includes(m));
5516
+ if (toolExcluded.length > 0) {
5517
+ console.log(
5518
+ `[ClawRouter] Tool-calling filter: excluded ${toolExcluded.join(", ")} (no structured function call support)`
5519
+ );
5520
+ }
5521
+ modelsToTry = toolFiltered.slice(0, MAX_FALLBACK_ATTEMPTS);
5358
5522
  modelsToTry = prioritizeNonRateLimited(modelsToTry);
5359
5523
  } else {
5360
5524
  if (modelId && modelId !== FREE_MODEL) {