@blockrun/clawrouter 0.10.13 → 0.10.15

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.d.ts CHANGED
@@ -694,6 +694,13 @@ type BlockRunModel = {
694
694
  vision?: boolean;
695
695
  /** Models optimized for agentic workflows (multi-step autonomous tasks) */
696
696
  agentic?: boolean;
697
+ /**
698
+ * Model supports OpenAI-compatible structured function/tool calling.
699
+ * Models without this flag output tool invocations as plain text JSON,
700
+ * which leaks raw {"command":"..."} into visible chat messages.
701
+ * Default: false (must opt-in to prevent silent regressions on new models).
702
+ */
703
+ toolCalling?: boolean;
697
704
  };
698
705
  declare const BLOCKRUN_MODELS: BlockRunModel[];
699
706
  /**
package/dist/index.js CHANGED
@@ -115,7 +115,8 @@ var BLOCKRUN_MODELS = [
115
115
  maxOutput: 128e3,
116
116
  reasoning: true,
117
117
  vision: true,
118
- agentic: true
118
+ agentic: true,
119
+ toolCalling: true
119
120
  },
120
121
  {
121
122
  id: "openai/gpt-5-mini",
@@ -124,7 +125,8 @@ var BLOCKRUN_MODELS = [
124
125
  inputPrice: 0.25,
125
126
  outputPrice: 2,
126
127
  contextWindow: 2e5,
127
- maxOutput: 65536
128
+ maxOutput: 65536,
129
+ toolCalling: true
128
130
  },
129
131
  {
130
132
  id: "openai/gpt-5-nano",
@@ -133,7 +135,8 @@ var BLOCKRUN_MODELS = [
133
135
  inputPrice: 0.05,
134
136
  outputPrice: 0.4,
135
137
  contextWindow: 128e3,
136
- maxOutput: 32768
138
+ maxOutput: 32768,
139
+ toolCalling: true
137
140
  },
138
141
  {
139
142
  id: "openai/gpt-5.2-pro",
@@ -143,7 +146,8 @@ var BLOCKRUN_MODELS = [
143
146
  outputPrice: 168,
144
147
  contextWindow: 4e5,
145
148
  maxOutput: 128e3,
146
- reasoning: true
149
+ reasoning: true,
150
+ toolCalling: true
147
151
  },
148
152
  // OpenAI Codex Family
149
153
  {
@@ -154,7 +158,8 @@ var BLOCKRUN_MODELS = [
154
158
  outputPrice: 14,
155
159
  contextWindow: 128e3,
156
160
  maxOutput: 32e3,
157
- agentic: true
161
+ agentic: true,
162
+ toolCalling: true
158
163
  },
159
164
  // OpenAI GPT-4 Family
160
165
  {
@@ -165,7 +170,8 @@ var BLOCKRUN_MODELS = [
165
170
  outputPrice: 8,
166
171
  contextWindow: 128e3,
167
172
  maxOutput: 16384,
168
- vision: true
173
+ vision: true,
174
+ toolCalling: true
169
175
  },
170
176
  {
171
177
  id: "openai/gpt-4.1-mini",
@@ -174,7 +180,8 @@ var BLOCKRUN_MODELS = [
174
180
  inputPrice: 0.4,
175
181
  outputPrice: 1.6,
176
182
  contextWindow: 128e3,
177
- maxOutput: 16384
183
+ maxOutput: 16384,
184
+ toolCalling: true
178
185
  },
179
186
  {
180
187
  id: "openai/gpt-4.1-nano",
@@ -183,7 +190,8 @@ var BLOCKRUN_MODELS = [
183
190
  inputPrice: 0.1,
184
191
  outputPrice: 0.4,
185
192
  contextWindow: 128e3,
186
- maxOutput: 16384
193
+ maxOutput: 16384,
194
+ toolCalling: true
187
195
  },
188
196
  {
189
197
  id: "openai/gpt-4o",
@@ -194,7 +202,8 @@ var BLOCKRUN_MODELS = [
194
202
  contextWindow: 128e3,
195
203
  maxOutput: 16384,
196
204
  vision: true,
197
- agentic: true
205
+ agentic: true,
206
+ toolCalling: true
198
207
  },
199
208
  {
200
209
  id: "openai/gpt-4o-mini",
@@ -203,7 +212,8 @@ var BLOCKRUN_MODELS = [
203
212
  inputPrice: 0.15,
204
213
  outputPrice: 0.6,
205
214
  contextWindow: 128e3,
206
- maxOutput: 16384
215
+ maxOutput: 16384,
216
+ toolCalling: true
207
217
  },
208
218
  // OpenAI O-series (Reasoning)
209
219
  {
@@ -214,7 +224,8 @@ var BLOCKRUN_MODELS = [
214
224
  outputPrice: 60,
215
225
  contextWindow: 2e5,
216
226
  maxOutput: 1e5,
217
- reasoning: true
227
+ reasoning: true,
228
+ toolCalling: true
218
229
  },
219
230
  {
220
231
  id: "openai/o1-mini",
@@ -224,7 +235,8 @@ var BLOCKRUN_MODELS = [
224
235
  outputPrice: 4.4,
225
236
  contextWindow: 128e3,
226
237
  maxOutput: 65536,
227
- reasoning: true
238
+ reasoning: true,
239
+ toolCalling: true
228
240
  },
229
241
  {
230
242
  id: "openai/o3",
@@ -234,7 +246,8 @@ var BLOCKRUN_MODELS = [
234
246
  outputPrice: 8,
235
247
  contextWindow: 2e5,
236
248
  maxOutput: 1e5,
237
- reasoning: true
249
+ reasoning: true,
250
+ toolCalling: true
238
251
  },
239
252
  {
240
253
  id: "openai/o3-mini",
@@ -244,7 +257,8 @@ var BLOCKRUN_MODELS = [
244
257
  outputPrice: 4.4,
245
258
  contextWindow: 128e3,
246
259
  maxOutput: 65536,
247
- reasoning: true
260
+ reasoning: true,
261
+ toolCalling: true
248
262
  },
249
263
  {
250
264
  id: "openai/o4-mini",
@@ -254,7 +268,8 @@ var BLOCKRUN_MODELS = [
254
268
  outputPrice: 4.4,
255
269
  contextWindow: 128e3,
256
270
  maxOutput: 65536,
257
- reasoning: true
271
+ reasoning: true,
272
+ toolCalling: true
258
273
  },
259
274
  // Anthropic - all Claude models excel at agentic workflows
260
275
  // Use newest versions (4.6) with full provider prefix
@@ -266,7 +281,8 @@ var BLOCKRUN_MODELS = [
266
281
  outputPrice: 5,
267
282
  contextWindow: 2e5,
268
283
  maxOutput: 8192,
269
- agentic: true
284
+ agentic: true,
285
+ toolCalling: true
270
286
  },
271
287
  {
272
288
  id: "anthropic/claude-sonnet-4.6",
@@ -277,7 +293,8 @@ var BLOCKRUN_MODELS = [
277
293
  contextWindow: 2e5,
278
294
  maxOutput: 64e3,
279
295
  reasoning: true,
280
- agentic: true
296
+ agentic: true,
297
+ toolCalling: true
281
298
  },
282
299
  {
283
300
  id: "anthropic/claude-opus-4.6",
@@ -288,7 +305,8 @@ var BLOCKRUN_MODELS = [
288
305
  contextWindow: 2e5,
289
306
  maxOutput: 32e3,
290
307
  reasoning: true,
291
- agentic: true
308
+ agentic: true,
309
+ toolCalling: true
292
310
  },
293
311
  // Google
294
312
  {
@@ -300,7 +318,8 @@ var BLOCKRUN_MODELS = [
300
318
  contextWindow: 105e4,
301
319
  maxOutput: 65536,
302
320
  reasoning: true,
303
- vision: true
321
+ vision: true,
322
+ toolCalling: true
304
323
  },
305
324
  {
306
325
  id: "google/gemini-3-pro-preview",
@@ -311,7 +330,8 @@ var BLOCKRUN_MODELS = [
311
330
  contextWindow: 105e4,
312
331
  maxOutput: 65536,
313
332
  reasoning: true,
314
- vision: true
333
+ vision: true,
334
+ toolCalling: true
315
335
  },
316
336
  {
317
337
  id: "google/gemini-3-flash-preview",
@@ -321,7 +341,8 @@ var BLOCKRUN_MODELS = [
321
341
  outputPrice: 3,
322
342
  contextWindow: 1e6,
323
343
  maxOutput: 65536,
324
- vision: true
344
+ vision: true,
345
+ toolCalling: true
325
346
  },
326
347
  {
327
348
  id: "google/gemini-2.5-pro",
@@ -332,7 +353,8 @@ var BLOCKRUN_MODELS = [
332
353
  contextWindow: 105e4,
333
354
  maxOutput: 65536,
334
355
  reasoning: true,
335
- vision: true
356
+ vision: true,
357
+ toolCalling: true
336
358
  },
337
359
  {
338
360
  id: "google/gemini-2.5-flash",
@@ -341,7 +363,8 @@ var BLOCKRUN_MODELS = [
341
363
  inputPrice: 0.3,
342
364
  outputPrice: 2.5,
343
365
  contextWindow: 1e6,
344
- maxOutput: 65536
366
+ maxOutput: 65536,
367
+ toolCalling: true
345
368
  },
346
369
  {
347
370
  id: "google/gemini-2.5-flash-lite",
@@ -350,7 +373,8 @@ var BLOCKRUN_MODELS = [
350
373
  inputPrice: 0.1,
351
374
  outputPrice: 0.4,
352
375
  contextWindow: 1e6,
353
- maxOutput: 65536
376
+ maxOutput: 65536,
377
+ toolCalling: true
354
378
  },
355
379
  // DeepSeek
356
380
  {
@@ -360,7 +384,8 @@ var BLOCKRUN_MODELS = [
360
384
  inputPrice: 0.28,
361
385
  outputPrice: 0.42,
362
386
  contextWindow: 128e3,
363
- maxOutput: 8192
387
+ maxOutput: 8192,
388
+ toolCalling: true
364
389
  },
365
390
  {
366
391
  id: "deepseek/deepseek-reasoner",
@@ -370,7 +395,8 @@ var BLOCKRUN_MODELS = [
370
395
  outputPrice: 0.42,
371
396
  contextWindow: 128e3,
372
397
  maxOutput: 8192,
373
- reasoning: true
398
+ reasoning: true,
399
+ toolCalling: true
374
400
  },
375
401
  // Moonshot / Kimi - optimized for agentic workflows
376
402
  {
@@ -383,7 +409,8 @@ var BLOCKRUN_MODELS = [
383
409
  maxOutput: 8192,
384
410
  reasoning: true,
385
411
  vision: true,
386
- agentic: true
412
+ agentic: true,
413
+ toolCalling: true
387
414
  },
388
415
  // xAI / Grok
389
416
  {
@@ -394,7 +421,8 @@ var BLOCKRUN_MODELS = [
394
421
  outputPrice: 15,
395
422
  contextWindow: 131072,
396
423
  maxOutput: 16384,
397
- reasoning: true
424
+ reasoning: true,
425
+ toolCalling: true
398
426
  },
399
427
  // grok-3-fast removed - too expensive ($5/$25), use grok-4-fast instead
400
428
  {
@@ -404,7 +432,8 @@ var BLOCKRUN_MODELS = [
404
432
  inputPrice: 0.3,
405
433
  outputPrice: 0.5,
406
434
  contextWindow: 131072,
407
- maxOutput: 16384
435
+ maxOutput: 16384,
436
+ toolCalling: true
408
437
  },
409
438
  // xAI Grok 4 Family - Ultra-cheap fast models
410
439
  {
@@ -415,7 +444,8 @@ var BLOCKRUN_MODELS = [
415
444
  outputPrice: 0.5,
416
445
  contextWindow: 131072,
417
446
  maxOutput: 16384,
418
- reasoning: true
447
+ reasoning: true,
448
+ toolCalling: true
419
449
  },
420
450
  {
421
451
  id: "xai/grok-4-fast-non-reasoning",
@@ -424,7 +454,8 @@ var BLOCKRUN_MODELS = [
424
454
  inputPrice: 0.2,
425
455
  outputPrice: 0.5,
426
456
  contextWindow: 131072,
427
- maxOutput: 16384
457
+ maxOutput: 16384,
458
+ toolCalling: true
428
459
  },
429
460
  {
430
461
  id: "xai/grok-4-1-fast-reasoning",
@@ -434,7 +465,8 @@ var BLOCKRUN_MODELS = [
434
465
  outputPrice: 0.5,
435
466
  contextWindow: 131072,
436
467
  maxOutput: 16384,
437
- reasoning: true
468
+ reasoning: true,
469
+ toolCalling: true
438
470
  },
439
471
  {
440
472
  id: "xai/grok-4-1-fast-non-reasoning",
@@ -443,7 +475,8 @@ var BLOCKRUN_MODELS = [
443
475
  inputPrice: 0.2,
444
476
  outputPrice: 0.5,
445
477
  contextWindow: 131072,
446
- maxOutput: 16384
478
+ maxOutput: 16384,
479
+ toolCalling: true
447
480
  },
448
481
  {
449
482
  id: "xai/grok-code-fast-1",
@@ -452,9 +485,10 @@ var BLOCKRUN_MODELS = [
452
485
  inputPrice: 0.2,
453
486
  outputPrice: 1.5,
454
487
  contextWindow: 131072,
455
- maxOutput: 16384,
456
- agentic: true
457
- // Good for coding tasks
488
+ maxOutput: 16384
489
+ // toolCalling intentionally omitted: outputs tool calls as plain text JSON,
490
+ // not OpenAI-compatible structured function calls. Will be skipped when
491
+ // request has tools to prevent the "talking to itself" bug.
458
492
  },
459
493
  {
460
494
  id: "xai/grok-4-0709",
@@ -464,7 +498,8 @@ var BLOCKRUN_MODELS = [
464
498
  outputPrice: 1.5,
465
499
  contextWindow: 131072,
466
500
  maxOutput: 16384,
467
- reasoning: true
501
+ reasoning: true,
502
+ toolCalling: true
468
503
  },
469
504
  {
470
505
  id: "xai/grok-2-vision",
@@ -474,7 +509,8 @@ var BLOCKRUN_MODELS = [
474
509
  outputPrice: 10,
475
510
  contextWindow: 131072,
476
511
  maxOutput: 16384,
477
- vision: true
512
+ vision: true,
513
+ toolCalling: true
478
514
  },
479
515
  // MiniMax
480
516
  {
@@ -486,7 +522,8 @@ var BLOCKRUN_MODELS = [
486
522
  contextWindow: 204800,
487
523
  maxOutput: 16384,
488
524
  reasoning: true,
489
- agentic: true
525
+ agentic: true,
526
+ toolCalling: true
490
527
  },
491
528
  // NVIDIA - Free/cheap models
492
529
  {
@@ -497,6 +534,8 @@ var BLOCKRUN_MODELS = [
497
534
  outputPrice: 0,
498
535
  contextWindow: 128e3,
499
536
  maxOutput: 16384
537
+ // toolCalling intentionally omitted: free model, structured function
538
+ // calling support unverified. Excluded from tool-heavy routing paths.
500
539
  },
501
540
  {
502
541
  id: "nvidia/kimi-k2.5",
@@ -505,7 +544,8 @@ var BLOCKRUN_MODELS = [
505
544
  inputPrice: 0.55,
506
545
  outputPrice: 2.5,
507
546
  contextWindow: 262144,
508
- maxOutput: 16384
547
+ maxOutput: 16384,
548
+ toolCalling: true
509
549
  }
510
550
  ];
511
551
  function toOpenClawModel(m) {
@@ -550,6 +590,11 @@ function isAgenticModel(modelId) {
550
590
  function getAgenticModels() {
551
591
  return BLOCKRUN_MODELS.filter((m) => m.agentic).map((m) => m.id);
552
592
  }
593
+ function supportsToolCalling(modelId) {
594
+ const normalized = modelId.replace("blockrun/", "");
595
+ const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
596
+ return model?.toolCalling ?? false;
597
+ }
553
598
  function getModelContextWindow(modelId) {
554
599
  const normalized = modelId.replace("blockrun/", "");
555
600
  const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
@@ -1061,7 +1106,8 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1061
1106
  tier: "REASONING",
1062
1107
  confidence: Math.max(confidence2, 0.85),
1063
1108
  signals,
1064
- agenticScore
1109
+ agenticScore,
1110
+ dimensions
1065
1111
  };
1066
1112
  }
1067
1113
  const { simpleMedium, mediumComplex, complexReasoning } = config.tierBoundaries;
@@ -1085,9 +1131,9 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1085
1131
  }
1086
1132
  const confidence = calibrateConfidence(distanceFromBoundary, config.confidenceSteepness);
1087
1133
  if (confidence < config.confidenceThreshold) {
1088
- return { score: weightedScore, tier: null, confidence, signals, agenticScore };
1134
+ return { score: weightedScore, tier: null, confidence, signals, agenticScore, dimensions };
1089
1135
  }
1090
- return { score: weightedScore, tier, confidence, signals, agenticScore };
1136
+ return { score: weightedScore, tier, confidence, signals, agenticScore, dimensions };
1091
1137
  }
1092
1138
  function calibrateConfidence(distance, steepness) {
1093
1139
  return 1 / (1 + Math.exp(-steepness * distance));
@@ -1143,6 +1189,11 @@ function calculateModelCost(model, modelPricing, estimatedInputTokens, maxOutput
1143
1189
  const savings = routingProfile === "premium" ? 0 : baselineCost > 0 ? Math.max(0, (baselineCost - costEstimate) / baselineCost) : 0;
1144
1190
  return { costEstimate, baselineCost, savings };
1145
1191
  }
1192
+ function filterByToolCalling(models, hasTools, supportsToolCalling2) {
1193
+ if (!hasTools) return models;
1194
+ const filtered = models.filter(supportsToolCalling2);
1195
+ return filtered.length > 0 ? filtered : models;
1196
+ }
1146
1197
  function getFallbackChainFiltered(tier, tierConfigs, estimatedTotalTokens, getContextWindow) {
1147
1198
  const fullChain = getFallbackChain(tier, tierConfigs);
1148
1199
  const filtered = fullChain.filter((modelId) => {
@@ -2198,12 +2249,12 @@ var DEFAULT_ROUTING_CONFIG = {
2198
2249
  ]
2199
2250
  },
2200
2251
  MEDIUM: {
2201
- primary: "xai/grok-code-fast-1",
2202
- // Code specialist, $0.20/$1.50
2252
+ primary: "moonshot/kimi-k2.5",
2253
+ // $0.50/$2.40 - strong tool use, proper function call format
2203
2254
  fallback: [
2255
+ "deepseek/deepseek-chat",
2204
2256
  "google/gemini-2.5-flash-lite",
2205
2257
  // 1M context, ultra cheap ($0.10/$0.40)
2206
- "deepseek/deepseek-chat",
2207
2258
  "xai/grok-4-1-fast-non-reasoning"
2208
2259
  // Upgraded Grok 4.1
2209
2260
  ]
@@ -2269,7 +2320,7 @@ var DEFAULT_ROUTING_CONFIG = {
2269
2320
  fallback: [
2270
2321
  "anthropic/claude-haiku-4.5",
2271
2322
  "google/gemini-2.5-flash-lite",
2272
- "xai/grok-code-fast-1"
2323
+ "deepseek/deepseek-chat"
2273
2324
  ]
2274
2325
  },
2275
2326
  MEDIUM: {
@@ -2320,9 +2371,9 @@ var DEFAULT_ROUTING_CONFIG = {
2320
2371
  ]
2321
2372
  },
2322
2373
  MEDIUM: {
2323
- primary: "xai/grok-code-fast-1",
2324
- // Code specialist for agentic coding
2325
- fallback: ["moonshot/kimi-k2.5", "anthropic/claude-haiku-4.5", "claude-sonnet-4"]
2374
+ primary: "moonshot/kimi-k2.5",
2375
+ // $0.50/$2.40 - strong tool use, handles function calls correctly
2376
+ fallback: ["anthropic/claude-haiku-4.5", "deepseek/deepseek-chat", "xai/grok-4-1-fast-non-reasoning"]
2326
2377
  },
2327
2378
  COMPLEX: {
2328
2379
  primary: "anthropic/claude-sonnet-4.6",
@@ -5182,6 +5233,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5182
5233
  const originalContextSizeKB = Math.ceil(body.length / 1024);
5183
5234
  const debugMode = req.headers["x-clawrouter-debug"] !== "false";
5184
5235
  let routingDecision;
5236
+ let hasTools = false;
5185
5237
  let isStreaming = false;
5186
5238
  let modelId = "";
5187
5239
  let maxTokens = 4096;
@@ -5196,10 +5248,11 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5196
5248
  modelId = parsed.model || "";
5197
5249
  maxTokens = parsed.max_tokens || 4096;
5198
5250
  let bodyModified = false;
5199
- if (sessionId && Array.isArray(parsed.messages)) {
5200
- const messages = parsed.messages;
5201
- const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
5202
- const lastContent = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
5251
+ const parsedMessages = Array.isArray(parsed.messages) ? parsed.messages : [];
5252
+ const lastUserMsg = [...parsedMessages].reverse().find((m) => m.role === "user");
5253
+ const lastContent = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
5254
+ if (sessionId && parsedMessages.length > 0) {
5255
+ const messages = parsedMessages;
5203
5256
  if (sessionJournal.needsContext(lastContent)) {
5204
5257
  const journalText = sessionJournal.format(sessionId);
5205
5258
  if (journalText) {
@@ -5220,6 +5273,106 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5220
5273
  }
5221
5274
  }
5222
5275
  }
5276
+ if (lastContent.startsWith("/debug")) {
5277
+ const debugPrompt = lastContent.slice("/debug".length).trim() || "hello";
5278
+ const messages = parsed.messages;
5279
+ const systemMsg = messages?.find((m) => m.role === "system");
5280
+ const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
5281
+ const fullText = `${systemPrompt ?? ""} ${debugPrompt}`;
5282
+ const estimatedTokens = Math.ceil(fullText.length / 4);
5283
+ const normalizedModel2 = typeof parsed.model === "string" ? parsed.model.trim().toLowerCase() : "";
5284
+ const profileName = normalizedModel2.replace("blockrun/", "");
5285
+ const debugProfile = ["free", "eco", "auto", "premium"].includes(profileName) ? profileName : "auto";
5286
+ const scoring = classifyByRules(
5287
+ debugPrompt,
5288
+ systemPrompt,
5289
+ estimatedTokens,
5290
+ DEFAULT_ROUTING_CONFIG.scoring
5291
+ );
5292
+ const debugRouting = route(debugPrompt, systemPrompt, maxTokens, {
5293
+ ...routerOpts,
5294
+ routingProfile: debugProfile
5295
+ });
5296
+ const dimLines = (scoring.dimensions ?? []).map((d) => {
5297
+ const nameStr = (d.name + ":").padEnd(24);
5298
+ const scoreStr = d.score.toFixed(2).padStart(6);
5299
+ const sigStr = d.signal ? ` [${d.signal}]` : "";
5300
+ return ` ${nameStr}${scoreStr}${sigStr}`;
5301
+ }).join("\n");
5302
+ const sess = sessionId ? sessionStore.getSession(sessionId) : void 0;
5303
+ 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";
5304
+ const { simpleMedium, mediumComplex, complexReasoning } = DEFAULT_ROUTING_CONFIG.scoring.tierBoundaries;
5305
+ const debugText = [
5306
+ "ClawRouter Debug",
5307
+ "",
5308
+ `Profile: ${debugProfile} | Tier: ${debugRouting.tier} | Model: ${debugRouting.model}`,
5309
+ `Confidence: ${debugRouting.confidence.toFixed(2)} | Cost: $${debugRouting.costEstimate.toFixed(4)} | Savings: ${(debugRouting.savings * 100).toFixed(0)}%`,
5310
+ `Reasoning: ${debugRouting.reasoning}`,
5311
+ "",
5312
+ `Scoring (weighted: ${scoring.score.toFixed(3)})`,
5313
+ dimLines,
5314
+ "",
5315
+ `Tier Boundaries: SIMPLE <${simpleMedium.toFixed(2)} | MEDIUM <${mediumComplex.toFixed(2)} | COMPLEX <${complexReasoning.toFixed(2)} | REASONING >=${complexReasoning.toFixed(2)}`,
5316
+ "",
5317
+ sessLine
5318
+ ].join("\n");
5319
+ const completionId = `chatcmpl-debug-${Date.now()}`;
5320
+ const timestamp = Math.floor(Date.now() / 1e3);
5321
+ const syntheticResponse = {
5322
+ id: completionId,
5323
+ object: "chat.completion",
5324
+ created: timestamp,
5325
+ model: "clawrouter/debug",
5326
+ choices: [
5327
+ {
5328
+ index: 0,
5329
+ message: { role: "assistant", content: debugText },
5330
+ finish_reason: "stop"
5331
+ }
5332
+ ],
5333
+ usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
5334
+ };
5335
+ if (isStreaming) {
5336
+ res.writeHead(200, {
5337
+ "Content-Type": "text/event-stream",
5338
+ "Cache-Control": "no-cache",
5339
+ Connection: "keep-alive"
5340
+ });
5341
+ const sseChunk = {
5342
+ id: completionId,
5343
+ object: "chat.completion.chunk",
5344
+ created: timestamp,
5345
+ model: "clawrouter/debug",
5346
+ choices: [
5347
+ {
5348
+ index: 0,
5349
+ delta: { role: "assistant", content: debugText },
5350
+ finish_reason: null
5351
+ }
5352
+ ]
5353
+ };
5354
+ const sseDone = {
5355
+ id: completionId,
5356
+ object: "chat.completion.chunk",
5357
+ created: timestamp,
5358
+ model: "clawrouter/debug",
5359
+ choices: [{ index: 0, delta: {}, finish_reason: "stop" }]
5360
+ };
5361
+ res.write(`data: ${JSON.stringify(sseChunk)}
5362
+
5363
+ `);
5364
+ res.write(`data: ${JSON.stringify(sseDone)}
5365
+
5366
+ `);
5367
+ res.write("data: [DONE]\n\n");
5368
+ res.end();
5369
+ } else {
5370
+ res.writeHead(200, { "Content-Type": "application/json" });
5371
+ res.end(JSON.stringify(syntheticResponse));
5372
+ }
5373
+ console.log(`[ClawRouter] /debug command \u2192 ${debugRouting.tier} | ${debugRouting.model}`);
5374
+ return;
5375
+ }
5223
5376
  if (parsed.stream === true) {
5224
5377
  parsed.stream = false;
5225
5378
  bodyModified = true;
@@ -5274,20 +5427,20 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5274
5427
  sessionStore.touchSession(sessionId2);
5275
5428
  } else {
5276
5429
  const messages = parsed.messages;
5277
- let lastUserMsg;
5430
+ let lastUserMsg2;
5278
5431
  if (messages) {
5279
5432
  for (let i = messages.length - 1; i >= 0; i--) {
5280
5433
  if (messages[i].role === "user") {
5281
- lastUserMsg = messages[i];
5434
+ lastUserMsg2 = messages[i];
5282
5435
  break;
5283
5436
  }
5284
5437
  }
5285
5438
  }
5286
5439
  const systemMsg = messages?.find((m) => m.role === "system");
5287
- const prompt = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
5440
+ const prompt = typeof lastUserMsg2?.content === "string" ? lastUserMsg2.content : "";
5288
5441
  const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
5289
5442
  const tools = parsed.tools;
5290
- const hasTools = Array.isArray(tools) && tools.length > 0;
5443
+ hasTools = Array.isArray(tools) && tools.length > 0;
5291
5444
  if (hasTools && tools) {
5292
5445
  console.log(
5293
5446
  `[ClawRouter] Tools detected (${tools.length}), agentic mode via keywords`
@@ -5504,7 +5657,14 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5504
5657
  `[ClawRouter] Context filter (~${estimatedTotalTokens} tokens): excluded ${contextExcluded.join(", ")}`
5505
5658
  );
5506
5659
  }
5507
- modelsToTry = contextFiltered.slice(0, MAX_FALLBACK_ATTEMPTS);
5660
+ const toolFiltered = filterByToolCalling(contextFiltered, hasTools, supportsToolCalling);
5661
+ const toolExcluded = contextFiltered.filter((m) => !toolFiltered.includes(m));
5662
+ if (toolExcluded.length > 0) {
5663
+ console.log(
5664
+ `[ClawRouter] Tool-calling filter: excluded ${toolExcluded.join(", ")} (no structured function call support)`
5665
+ );
5666
+ }
5667
+ modelsToTry = toolFiltered.slice(0, MAX_FALLBACK_ATTEMPTS);
5508
5668
  modelsToTry = prioritizeNonRateLimited(modelsToTry);
5509
5669
  } else {
5510
5670
  if (modelId && modelId !== FREE_MODEL) {