@blockrun/clawrouter 0.10.21 → 0.11.0
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 +266 -5
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +25 -1
- package/dist/index.js +267 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/update.sh +27 -0
package/dist/cli.js
CHANGED
|
@@ -566,6 +566,11 @@ function filterByToolCalling(models, hasTools, supportsToolCalling2) {
|
|
|
566
566
|
const filtered = models.filter(supportsToolCalling2);
|
|
567
567
|
return filtered.length > 0 ? filtered : models;
|
|
568
568
|
}
|
|
569
|
+
function filterByVision(models, hasVision, supportsVision2) {
|
|
570
|
+
if (!hasVision) return models;
|
|
571
|
+
const filtered = models.filter(supportsVision2);
|
|
572
|
+
return filtered.length > 0 ? filtered : models;
|
|
573
|
+
}
|
|
569
574
|
function getFallbackChainFiltered(tier, tierConfigs, estimatedTotalTokens, getContextWindow) {
|
|
570
575
|
const fullChain = getFallbackChain(tier, tierConfigs);
|
|
571
576
|
const filtered = fullChain.filter((modelId) => {
|
|
@@ -1899,6 +1904,8 @@ var MODEL_ALIASES = {
|
|
|
1899
1904
|
// Google
|
|
1900
1905
|
gemini: "google/gemini-2.5-pro",
|
|
1901
1906
|
flash: "google/gemini-2.5-flash",
|
|
1907
|
+
"gemini-3.1-pro-preview": "google/gemini-3.1-pro",
|
|
1908
|
+
"google/gemini-3.1-pro-preview": "google/gemini-3.1-pro",
|
|
1902
1909
|
// xAI
|
|
1903
1910
|
grok: "xai/grok-3",
|
|
1904
1911
|
"grok-fast": "xai/grok-4-fast-reasoning",
|
|
@@ -2138,6 +2145,7 @@ var BLOCKRUN_MODELS = [
|
|
|
2138
2145
|
outputPrice: 5,
|
|
2139
2146
|
contextWindow: 2e5,
|
|
2140
2147
|
maxOutput: 8192,
|
|
2148
|
+
vision: true,
|
|
2141
2149
|
agentic: true,
|
|
2142
2150
|
toolCalling: true
|
|
2143
2151
|
},
|
|
@@ -2150,6 +2158,7 @@ var BLOCKRUN_MODELS = [
|
|
|
2150
2158
|
contextWindow: 2e5,
|
|
2151
2159
|
maxOutput: 64e3,
|
|
2152
2160
|
reasoning: true,
|
|
2161
|
+
vision: true,
|
|
2153
2162
|
agentic: true,
|
|
2154
2163
|
toolCalling: true
|
|
2155
2164
|
},
|
|
@@ -2162,6 +2171,7 @@ var BLOCKRUN_MODELS = [
|
|
|
2162
2171
|
contextWindow: 2e5,
|
|
2163
2172
|
maxOutput: 32e3,
|
|
2164
2173
|
reasoning: true,
|
|
2174
|
+
vision: true,
|
|
2165
2175
|
agentic: true,
|
|
2166
2176
|
toolCalling: true
|
|
2167
2177
|
},
|
|
@@ -2221,6 +2231,7 @@ var BLOCKRUN_MODELS = [
|
|
|
2221
2231
|
outputPrice: 2.5,
|
|
2222
2232
|
contextWindow: 1e6,
|
|
2223
2233
|
maxOutput: 65536,
|
|
2234
|
+
vision: true,
|
|
2224
2235
|
toolCalling: true
|
|
2225
2236
|
},
|
|
2226
2237
|
{
|
|
@@ -2436,6 +2447,11 @@ function supportsToolCalling(modelId) {
|
|
|
2436
2447
|
const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
|
|
2437
2448
|
return model?.toolCalling ?? false;
|
|
2438
2449
|
}
|
|
2450
|
+
function supportsVision(modelId) {
|
|
2451
|
+
const normalized = modelId.replace("blockrun/", "");
|
|
2452
|
+
const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
|
|
2453
|
+
return model?.vision ?? false;
|
|
2454
|
+
}
|
|
2439
2455
|
function getModelContextWindow(modelId) {
|
|
2440
2456
|
const normalized = modelId.replace("blockrun/", "");
|
|
2441
2457
|
const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
|
|
@@ -3889,7 +3905,10 @@ var SessionStore = class {
|
|
|
3889
3905
|
tier,
|
|
3890
3906
|
createdAt: now,
|
|
3891
3907
|
lastUsedAt: now,
|
|
3892
|
-
requestCount: 1
|
|
3908
|
+
requestCount: 1,
|
|
3909
|
+
recentHashes: [],
|
|
3910
|
+
strikes: 0,
|
|
3911
|
+
escalated: false
|
|
3893
3912
|
});
|
|
3894
3913
|
}
|
|
3895
3914
|
}
|
|
@@ -3941,6 +3960,43 @@ var SessionStore = class {
|
|
|
3941
3960
|
}
|
|
3942
3961
|
}
|
|
3943
3962
|
}
|
|
3963
|
+
/**
|
|
3964
|
+
* Record a request content hash and detect repetitive patterns.
|
|
3965
|
+
* Returns true if escalation should be triggered (3+ consecutive similar requests).
|
|
3966
|
+
*/
|
|
3967
|
+
recordRequestHash(sessionId, hash) {
|
|
3968
|
+
const entry = this.sessions.get(sessionId);
|
|
3969
|
+
if (!entry) return false;
|
|
3970
|
+
const prev = entry.recentHashes;
|
|
3971
|
+
if (prev.length > 0 && prev[prev.length - 1] === hash) {
|
|
3972
|
+
entry.strikes++;
|
|
3973
|
+
} else {
|
|
3974
|
+
entry.strikes = 0;
|
|
3975
|
+
}
|
|
3976
|
+
entry.recentHashes.push(hash);
|
|
3977
|
+
if (entry.recentHashes.length > 3) {
|
|
3978
|
+
entry.recentHashes.shift();
|
|
3979
|
+
}
|
|
3980
|
+
return entry.strikes >= 2 && !entry.escalated;
|
|
3981
|
+
}
|
|
3982
|
+
/**
|
|
3983
|
+
* Escalate session to next tier. Returns the new model/tier or null if already at max.
|
|
3984
|
+
*/
|
|
3985
|
+
escalateSession(sessionId, tierConfigs) {
|
|
3986
|
+
const entry = this.sessions.get(sessionId);
|
|
3987
|
+
if (!entry) return null;
|
|
3988
|
+
const TIER_ORDER = ["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"];
|
|
3989
|
+
const currentIdx = TIER_ORDER.indexOf(entry.tier);
|
|
3990
|
+
if (currentIdx < 0 || currentIdx >= TIER_ORDER.length - 1) return null;
|
|
3991
|
+
const nextTier = TIER_ORDER[currentIdx + 1];
|
|
3992
|
+
const nextConfig = tierConfigs[nextTier];
|
|
3993
|
+
if (!nextConfig) return null;
|
|
3994
|
+
entry.model = nextConfig.primary;
|
|
3995
|
+
entry.tier = nextTier;
|
|
3996
|
+
entry.strikes = 0;
|
|
3997
|
+
entry.escalated = true;
|
|
3998
|
+
return { model: nextConfig.primary, tier: nextTier };
|
|
3999
|
+
}
|
|
3944
4000
|
/**
|
|
3945
4001
|
* Stop the cleanup interval.
|
|
3946
4002
|
*/
|
|
@@ -3967,6 +4023,11 @@ function deriveSessionId(messages) {
|
|
|
3967
4023
|
const content = typeof firstUser.content === "string" ? firstUser.content : JSON.stringify(firstUser.content);
|
|
3968
4024
|
return createHash3("sha256").update(content).digest("hex").slice(0, 8);
|
|
3969
4025
|
}
|
|
4026
|
+
function hashRequestContent(lastUserContent, toolCallNames) {
|
|
4027
|
+
const normalized = lastUserContent.replace(/\s+/g, " ").trim().slice(0, 500);
|
|
4028
|
+
const toolSuffix = toolCallNames?.length ? `|tools:${toolCallNames.sort().join(",")}` : "";
|
|
4029
|
+
return createHash3("sha256").update(normalized + toolSuffix).digest("hex").slice(0, 12);
|
|
4030
|
+
}
|
|
3970
4031
|
|
|
3971
4032
|
// src/updater.ts
|
|
3972
4033
|
var NPM_REGISTRY = "https://registry.npmjs.org/@blockrun/clawrouter/latest";
|
|
@@ -5097,6 +5158,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5097
5158
|
const debugMode = req.headers["x-clawrouter-debug"] !== "false";
|
|
5098
5159
|
let routingDecision;
|
|
5099
5160
|
let hasTools = false;
|
|
5161
|
+
let hasVision = false;
|
|
5100
5162
|
let isStreaming = false;
|
|
5101
5163
|
let modelId = "";
|
|
5102
5164
|
let maxTokens = 4096;
|
|
@@ -5238,6 +5300,154 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5238
5300
|
console.log(`[ClawRouter] /debug command \u2192 ${debugRouting.tier} | ${debugRouting.model}`);
|
|
5239
5301
|
return;
|
|
5240
5302
|
}
|
|
5303
|
+
if (lastContent.startsWith("/imagegen")) {
|
|
5304
|
+
const imageArgs = lastContent.slice("/imagegen".length).trim();
|
|
5305
|
+
let imageModel = "google/nano-banana";
|
|
5306
|
+
let imageSize = "1024x1024";
|
|
5307
|
+
let imagePrompt = imageArgs;
|
|
5308
|
+
const modelMatch = imageArgs.match(/--model\s+(\S+)/);
|
|
5309
|
+
if (modelMatch) {
|
|
5310
|
+
const raw = modelMatch[1];
|
|
5311
|
+
const IMAGE_MODEL_ALIASES = {
|
|
5312
|
+
"dall-e-3": "openai/dall-e-3",
|
|
5313
|
+
"dalle3": "openai/dall-e-3",
|
|
5314
|
+
"dalle": "openai/dall-e-3",
|
|
5315
|
+
"gpt-image": "openai/gpt-image-1",
|
|
5316
|
+
"gpt-image-1": "openai/gpt-image-1",
|
|
5317
|
+
"flux": "black-forest/flux-1.1-pro",
|
|
5318
|
+
"flux-pro": "black-forest/flux-1.1-pro",
|
|
5319
|
+
"banana": "google/nano-banana",
|
|
5320
|
+
"nano-banana": "google/nano-banana",
|
|
5321
|
+
"banana-pro": "google/nano-banana-pro",
|
|
5322
|
+
"nano-banana-pro": "google/nano-banana-pro"
|
|
5323
|
+
};
|
|
5324
|
+
imageModel = IMAGE_MODEL_ALIASES[raw] ?? raw;
|
|
5325
|
+
imagePrompt = imagePrompt.replace(/--model\s+\S+/, "").trim();
|
|
5326
|
+
}
|
|
5327
|
+
const sizeMatch = imageArgs.match(/--size\s+(\d+x\d+)/);
|
|
5328
|
+
if (sizeMatch) {
|
|
5329
|
+
imageSize = sizeMatch[1];
|
|
5330
|
+
imagePrompt = imagePrompt.replace(/--size\s+\d+x\d+/, "").trim();
|
|
5331
|
+
}
|
|
5332
|
+
if (!imagePrompt) {
|
|
5333
|
+
const errorText = [
|
|
5334
|
+
"Usage: /imagegen <prompt>",
|
|
5335
|
+
"",
|
|
5336
|
+
"Options:",
|
|
5337
|
+
" --model <model> Model to use (default: nano-banana)",
|
|
5338
|
+
" --size <WxH> Image size (default: 1024x1024)",
|
|
5339
|
+
"",
|
|
5340
|
+
"Models:",
|
|
5341
|
+
" nano-banana Google Gemini Flash \u2014 $0.05/image",
|
|
5342
|
+
" banana-pro Google Gemini Pro \u2014 $0.10/image (up to 4K)",
|
|
5343
|
+
" dall-e-3 OpenAI DALL-E 3 \u2014 $0.04/image",
|
|
5344
|
+
" gpt-image OpenAI GPT Image 1 \u2014 $0.02/image",
|
|
5345
|
+
" flux Black Forest Flux 1.1 Pro \u2014 $0.04/image",
|
|
5346
|
+
"",
|
|
5347
|
+
"Examples:",
|
|
5348
|
+
" /imagegen a cat wearing sunglasses",
|
|
5349
|
+
" /imagegen --model dall-e-3 a futuristic city at sunset",
|
|
5350
|
+
" /imagegen --model banana-pro --size 2048x2048 mountain landscape"
|
|
5351
|
+
].join("\n");
|
|
5352
|
+
const completionId = `chatcmpl-image-${Date.now()}`;
|
|
5353
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
5354
|
+
if (isStreaming) {
|
|
5355
|
+
res.writeHead(200, {
|
|
5356
|
+
"Content-Type": "text/event-stream",
|
|
5357
|
+
"Cache-Control": "no-cache",
|
|
5358
|
+
Connection: "keep-alive"
|
|
5359
|
+
});
|
|
5360
|
+
res.write(`data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: { role: "assistant", content: errorText }, finish_reason: null }] })}
|
|
5361
|
+
|
|
5362
|
+
`);
|
|
5363
|
+
res.write(`data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}
|
|
5364
|
+
|
|
5365
|
+
`);
|
|
5366
|
+
res.write("data: [DONE]\n\n");
|
|
5367
|
+
res.end();
|
|
5368
|
+
} else {
|
|
5369
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
5370
|
+
res.end(JSON.stringify({
|
|
5371
|
+
id: completionId,
|
|
5372
|
+
object: "chat.completion",
|
|
5373
|
+
created: timestamp,
|
|
5374
|
+
model: "clawrouter/image",
|
|
5375
|
+
choices: [{ index: 0, message: { role: "assistant", content: errorText }, finish_reason: "stop" }],
|
|
5376
|
+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
5377
|
+
}));
|
|
5378
|
+
}
|
|
5379
|
+
console.log(`[ClawRouter] /imagegen command \u2192 showing usage help`);
|
|
5380
|
+
return;
|
|
5381
|
+
}
|
|
5382
|
+
console.log(`[ClawRouter] /imagegen command \u2192 ${imageModel} (${imageSize}): ${imagePrompt.slice(0, 80)}...`);
|
|
5383
|
+
try {
|
|
5384
|
+
const imageUpstreamUrl = `${apiBase}/v1/images/generations`;
|
|
5385
|
+
const imageBody = JSON.stringify({ model: imageModel, prompt: imagePrompt, size: imageSize, n: 1 });
|
|
5386
|
+
const imageResponse = await payFetch(imageUpstreamUrl, {
|
|
5387
|
+
method: "POST",
|
|
5388
|
+
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
5389
|
+
body: imageBody
|
|
5390
|
+
});
|
|
5391
|
+
const imageResult = await imageResponse.json();
|
|
5392
|
+
let responseText;
|
|
5393
|
+
if (!imageResponse.ok || imageResult.error) {
|
|
5394
|
+
const errMsg = typeof imageResult.error === "string" ? imageResult.error : imageResult.error?.message ?? `HTTP ${imageResponse.status}`;
|
|
5395
|
+
responseText = `Image generation failed: ${errMsg}`;
|
|
5396
|
+
console.log(`[ClawRouter] /imagegen error: ${errMsg}`);
|
|
5397
|
+
} else {
|
|
5398
|
+
const images = imageResult.data ?? [];
|
|
5399
|
+
if (images.length === 0) {
|
|
5400
|
+
responseText = "Image generation returned no results.";
|
|
5401
|
+
} else {
|
|
5402
|
+
const lines = [];
|
|
5403
|
+
for (const img of images) {
|
|
5404
|
+
if (img.url) lines.push(``);
|
|
5405
|
+
if (img.revised_prompt) lines.push(`*Revised prompt: ${img.revised_prompt}*`);
|
|
5406
|
+
}
|
|
5407
|
+
lines.push("", `Model: ${imageModel} | Size: ${imageSize}`);
|
|
5408
|
+
responseText = lines.join("\n");
|
|
5409
|
+
}
|
|
5410
|
+
console.log(`[ClawRouter] /imagegen success: ${images.length} image(s) generated`);
|
|
5411
|
+
}
|
|
5412
|
+
const completionId = `chatcmpl-image-${Date.now()}`;
|
|
5413
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
5414
|
+
if (isStreaming) {
|
|
5415
|
+
res.writeHead(200, {
|
|
5416
|
+
"Content-Type": "text/event-stream",
|
|
5417
|
+
"Cache-Control": "no-cache",
|
|
5418
|
+
Connection: "keep-alive"
|
|
5419
|
+
});
|
|
5420
|
+
res.write(`data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: { role: "assistant", content: responseText }, finish_reason: null }] })}
|
|
5421
|
+
|
|
5422
|
+
`);
|
|
5423
|
+
res.write(`data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}
|
|
5424
|
+
|
|
5425
|
+
`);
|
|
5426
|
+
res.write("data: [DONE]\n\n");
|
|
5427
|
+
res.end();
|
|
5428
|
+
} else {
|
|
5429
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
5430
|
+
res.end(JSON.stringify({
|
|
5431
|
+
id: completionId,
|
|
5432
|
+
object: "chat.completion",
|
|
5433
|
+
created: timestamp,
|
|
5434
|
+
model: "clawrouter/image",
|
|
5435
|
+
choices: [{ index: 0, message: { role: "assistant", content: responseText }, finish_reason: "stop" }],
|
|
5436
|
+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
5437
|
+
}));
|
|
5438
|
+
}
|
|
5439
|
+
} catch (err) {
|
|
5440
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
5441
|
+
console.error(`[ClawRouter] /imagegen error: ${errMsg}`);
|
|
5442
|
+
if (!res.headersSent) {
|
|
5443
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
5444
|
+
res.end(JSON.stringify({
|
|
5445
|
+
error: { message: `Image generation failed: ${errMsg}`, type: "image_error" }
|
|
5446
|
+
}));
|
|
5447
|
+
}
|
|
5448
|
+
}
|
|
5449
|
+
return;
|
|
5450
|
+
}
|
|
5241
5451
|
if (parsed.stream === true) {
|
|
5242
5452
|
parsed.stream = false;
|
|
5243
5453
|
bodyModified = true;
|
|
@@ -5289,6 +5499,15 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5289
5499
|
if (hasTools && tools) {
|
|
5290
5500
|
console.log(`[ClawRouter] Tools detected (${tools.length}), agentic mode via keywords`);
|
|
5291
5501
|
}
|
|
5502
|
+
hasVision = parsedMessages.some((m) => {
|
|
5503
|
+
if (Array.isArray(m.content)) {
|
|
5504
|
+
return m.content.some((p) => p.type === "image_url");
|
|
5505
|
+
}
|
|
5506
|
+
return false;
|
|
5507
|
+
});
|
|
5508
|
+
if (hasVision) {
|
|
5509
|
+
console.log(`[ClawRouter] Vision content detected, filtering to vision-capable models`);
|
|
5510
|
+
}
|
|
5292
5511
|
routingDecision = route(prompt, systemPrompt, maxTokens, {
|
|
5293
5512
|
...routerOpts,
|
|
5294
5513
|
routingProfile: routingProfile ?? void 0
|
|
@@ -5330,6 +5549,43 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5330
5549
|
tier: existingSession.tier
|
|
5331
5550
|
};
|
|
5332
5551
|
}
|
|
5552
|
+
const lastAssistantMsg = [...parsedMessages].reverse().find((m) => m.role === "assistant");
|
|
5553
|
+
const toolCallNames = Array.isArray(lastAssistantMsg?.tool_calls) ? lastAssistantMsg.tool_calls.map((tc) => tc.function?.name).filter(Boolean) : void 0;
|
|
5554
|
+
const contentHash = hashRequestContent(prompt, toolCallNames);
|
|
5555
|
+
const shouldEscalate = sessionStore.recordRequestHash(
|
|
5556
|
+
effectiveSessionId,
|
|
5557
|
+
contentHash
|
|
5558
|
+
);
|
|
5559
|
+
if (shouldEscalate) {
|
|
5560
|
+
const activeTierConfigs = (() => {
|
|
5561
|
+
if (routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers) {
|
|
5562
|
+
return routerOpts.config.agenticTiers;
|
|
5563
|
+
}
|
|
5564
|
+
if (routingProfile === "eco" && routerOpts.config.ecoTiers) {
|
|
5565
|
+
return routerOpts.config.ecoTiers;
|
|
5566
|
+
}
|
|
5567
|
+
if (routingProfile === "premium" && routerOpts.config.premiumTiers) {
|
|
5568
|
+
return routerOpts.config.premiumTiers;
|
|
5569
|
+
}
|
|
5570
|
+
return routerOpts.config.tiers;
|
|
5571
|
+
})();
|
|
5572
|
+
const escalation = sessionStore.escalateSession(
|
|
5573
|
+
effectiveSessionId,
|
|
5574
|
+
activeTierConfigs
|
|
5575
|
+
);
|
|
5576
|
+
if (escalation) {
|
|
5577
|
+
console.log(
|
|
5578
|
+
`[ClawRouter] \u26A1 3-strike escalation: ${existingSession.model} \u2192 ${escalation.model} (${existingSession.tier} \u2192 ${escalation.tier})`
|
|
5579
|
+
);
|
|
5580
|
+
parsed.model = escalation.model;
|
|
5581
|
+
modelId = escalation.model;
|
|
5582
|
+
routingDecision = {
|
|
5583
|
+
...routingDecision,
|
|
5584
|
+
model: escalation.model,
|
|
5585
|
+
tier: escalation.tier
|
|
5586
|
+
};
|
|
5587
|
+
}
|
|
5588
|
+
}
|
|
5333
5589
|
} else {
|
|
5334
5590
|
parsed.model = routingDecision.model;
|
|
5335
5591
|
modelId = routingDecision.model;
|
|
@@ -5549,7 +5805,14 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5549
5805
|
`[ClawRouter] Tool-calling filter: excluded ${toolExcluded.join(", ")} (no structured function call support)`
|
|
5550
5806
|
);
|
|
5551
5807
|
}
|
|
5552
|
-
|
|
5808
|
+
const visionFiltered = filterByVision(toolFiltered, hasVision, supportsVision);
|
|
5809
|
+
const visionExcluded = toolFiltered.filter((m) => !visionFiltered.includes(m));
|
|
5810
|
+
if (visionExcluded.length > 0) {
|
|
5811
|
+
console.log(
|
|
5812
|
+
`[ClawRouter] Vision filter: excluded ${visionExcluded.join(", ")} (no vision support)`
|
|
5813
|
+
);
|
|
5814
|
+
}
|
|
5815
|
+
modelsToTry = visionFiltered.slice(0, MAX_FALLBACK_ATTEMPTS);
|
|
5553
5816
|
modelsToTry = prioritizeNonRateLimited(modelsToTry);
|
|
5554
5817
|
} else {
|
|
5555
5818
|
modelsToTry = modelId ? [modelId] : [];
|
|
@@ -5595,9 +5858,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5595
5858
|
if (isPaymentErr && tryModel !== FREE_MODEL) {
|
|
5596
5859
|
const freeIdx = modelsToTry.indexOf(FREE_MODEL);
|
|
5597
5860
|
if (freeIdx > i + 1) {
|
|
5598
|
-
console.log(
|
|
5599
|
-
`[ClawRouter] Payment error \u2014 skipping to free model: ${FREE_MODEL}`
|
|
5600
|
-
);
|
|
5861
|
+
console.log(`[ClawRouter] Payment error \u2014 skipping to free model: ${FREE_MODEL}`);
|
|
5601
5862
|
i = freeIdx - 1;
|
|
5602
5863
|
continue;
|
|
5603
5864
|
}
|