@blockrun/clawrouter 0.12.229 → 0.12.231
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 +1 -0
- package/dist/cli.js +109 -6
- package/dist/index.js +109 -6
- package/package.json +1 -1
- package/scripts/update.sh +122 -30
- package/skills/clawrouter/SKILL.md +1 -1
- package/src/top-models.json +1 -0
package/README.md
CHANGED
|
@@ -434,6 +434,7 @@ No Surf account, no API key — settles directly to Surf's Base treasury in USDC
|
|
|
434
434
|
| google/gemini-2.5-pro | $1.25 | $10.00 | $0.0056 | 1M | reasoning, vision, tools |
|
|
435
435
|
| zai/glm-5.2 | $1.40 | $4.40 | $0.0029 | 1M | reasoning, coding, tools (flagship) |
|
|
436
436
|
| zai/glm-5.1 | $1.40 | $4.40 | $0.0029 | 200K | reasoning, tools (promo ended 2026-06-05) |
|
|
437
|
+
| qwen/qwen3.7-max | $1.475 | $4.425 | $0.0030 | 1M | reasoning, agentic, tools (Qwen flagship) |
|
|
437
438
|
| xai/grok-4.3 | $1.50 | $4.00 | $0.0028 | 1M | reasoning, vision, agentic, tools |
|
|
438
439
|
| xai/grok-4.5 | $2.50 | $9.00 | $0.0058 | 500K | reasoning, vision, agentic, tools |
|
|
439
440
|
| xai/grok-build-0.1 | $1.50 | $3.00 | $0.0023 | 256K | agentic coding, tools |
|
package/dist/cli.js
CHANGED
|
@@ -34296,6 +34296,7 @@ var init_top_models = __esm({
|
|
|
34296
34296
|
"minimax/minimax-m3",
|
|
34297
34297
|
"minimax/minimax-m2.7",
|
|
34298
34298
|
"moonshot/kimi-k3",
|
|
34299
|
+
"qwen/qwen3.7-max",
|
|
34299
34300
|
"deepseek/deepseek-v4-pro",
|
|
34300
34301
|
"deepseek/deepseek-chat",
|
|
34301
34302
|
"deepseek/deepseek-reasoner",
|
|
@@ -34522,6 +34523,17 @@ var init_models = __esm({
|
|
|
34522
34523
|
"kimi-k2.6": "moonshot/kimi-k2.6",
|
|
34523
34524
|
"kimi-k2.5": "moonshot/kimi-k2.5",
|
|
34524
34525
|
"nvidia/kimi-k2.5": "moonshot/kimi-k2.5",
|
|
34526
|
+
// Qwen — Qwen3.7 Max is Alibaba's current flagship for reasoning, coding,
|
|
34527
|
+
// and agentic tool use. EXPLICIT PINS ONLY: bare `qwen` deliberately stays
|
|
34528
|
+
// unbound. Every other qwen* shorthand below points at a FREE model
|
|
34529
|
+
// (qwen-coder, qwen-thinking, qwen3-next, qwen3.5-122b), so binding the
|
|
34530
|
+
// shortest name to a $1.475/$4.425 flagship would silently charge callers
|
|
34531
|
+
// who typed it expecting the free tier — same rule that keeps generic
|
|
34532
|
+
// `kimi` on K2.7. (`grok` WAS promoted to 4.5, but only after the cost
|
|
34533
|
+
// tradeoff was argued explicitly; there's no such case for qwen yet.)
|
|
34534
|
+
"qwen3.7-max": "qwen/qwen3.7-max",
|
|
34535
|
+
"qwen-3.7-max": "qwen/qwen3.7-max",
|
|
34536
|
+
"qwen3-7-max": "qwen/qwen3.7-max",
|
|
34525
34537
|
// Google
|
|
34526
34538
|
// gemini-3-pro-preview delisted by Google 2026-06-06 — mirror the gateway
|
|
34527
34539
|
// redirect to its successor so pinned callers land on 3.1-pro, not an error.
|
|
@@ -35331,6 +35343,27 @@ var init_models = __esm({
|
|
|
35331
35343
|
agentic: true,
|
|
35332
35344
|
toolCalling: true
|
|
35333
35345
|
},
|
|
35346
|
+
// Qwen3.7 Max — BlockRun's paid Qwen flagship, served through its
|
|
35347
|
+
// OpenRouter credit pool. The gateway applies its standard 5% margin at
|
|
35348
|
+
// settlement, so keep the catalog rates at the upstream $1.475/$4.425 COGS.
|
|
35349
|
+
// NOTE: `reasoning: true` also enrolls this id in REASONING_MODEL_IDS
|
|
35350
|
+
// (proxy.ts), which raises its per-model timeout from 60s to 180s.
|
|
35351
|
+
// `toolCalling: true` is LIVE-VERIFIED (2026-07-20): a real request through
|
|
35352
|
+
// the gateway returned a structured tool_calls array (name + valid JSON
|
|
35353
|
+
// arguments, finish_reason "tool_calls"), not the textual leak that Kimi K3
|
|
35354
|
+
// (#213), Gemini (#189) and GPT (#193) produce. Don't downgrade on a hunch.
|
|
35355
|
+
{
|
|
35356
|
+
id: "qwen/qwen3.7-max",
|
|
35357
|
+
name: "Qwen3.7 Max",
|
|
35358
|
+
version: "3.7-max",
|
|
35359
|
+
inputPrice: 1.475,
|
|
35360
|
+
outputPrice: 4.425,
|
|
35361
|
+
contextWindow: 1e6,
|
|
35362
|
+
maxOutput: 65536,
|
|
35363
|
+
reasoning: true,
|
|
35364
|
+
agentic: true,
|
|
35365
|
+
toolCalling: true
|
|
35366
|
+
},
|
|
35334
35367
|
// Kimi K2.6 — superseded by K2.7 (2026-06-13), hidden on BlockRun but still routable.
|
|
35335
35368
|
{
|
|
35336
35369
|
id: "moonshot/kimi-k2.6",
|
|
@@ -82181,6 +82214,65 @@ function extractGptCalls(content) {
|
|
|
82181
82214
|
}
|
|
82182
82215
|
return { calls: [], matches: [] };
|
|
82183
82216
|
}
|
|
82217
|
+
function deriveRequestTools(tools) {
|
|
82218
|
+
if (!Array.isArray(tools)) return [];
|
|
82219
|
+
const derived = [];
|
|
82220
|
+
for (const raw of tools) {
|
|
82221
|
+
const fn = raw?.function;
|
|
82222
|
+
const name = fn?.name;
|
|
82223
|
+
if (typeof name !== "string" || name.trim() === "") continue;
|
|
82224
|
+
const params = fn?.parameters;
|
|
82225
|
+
const props = isPlainObject(params?.properties) ? Object.keys(params.properties) : [];
|
|
82226
|
+
const required = Array.isArray(params?.required) ? params.required.filter((r2) => typeof r2 === "string") : [];
|
|
82227
|
+
derived.push({ name: name.trim(), propKeys: new Set(props), required });
|
|
82228
|
+
}
|
|
82229
|
+
return derived;
|
|
82230
|
+
}
|
|
82231
|
+
function keyMatchesParam(key2, propKeys) {
|
|
82232
|
+
if (propKeys.has(key2)) return true;
|
|
82233
|
+
if (key2 === "cmd" && propKeys.has("command")) return true;
|
|
82234
|
+
if (key2 === "command" && propKeys.has("cmd")) return true;
|
|
82235
|
+
return false;
|
|
82236
|
+
}
|
|
82237
|
+
function requiredSatisfied(tool, blobKeys) {
|
|
82238
|
+
return tool.required.every(
|
|
82239
|
+
(r2) => blobKeys.has(r2) || r2 === "command" && blobKeys.has("cmd") || r2 === "cmd" && blobKeys.has("command")
|
|
82240
|
+
);
|
|
82241
|
+
}
|
|
82242
|
+
function resolveKimiTool(prose, blob, tools) {
|
|
82243
|
+
const blobKeys = Object.keys(blob);
|
|
82244
|
+
if (prose) {
|
|
82245
|
+
const named = tools.filter(
|
|
82246
|
+
(t) => new RegExp(`(?:^|[^A-Za-z0-9_])${escapeRegExp2(t.name)}(?:[^A-Za-z0-9_]|$)`).test(prose)
|
|
82247
|
+
);
|
|
82248
|
+
if (named.length === 1) return named[0].name;
|
|
82249
|
+
}
|
|
82250
|
+
if (blobKeys.length === 0) return null;
|
|
82251
|
+
const keySet = new Set(blobKeys);
|
|
82252
|
+
const sigMatches = tools.filter(
|
|
82253
|
+
(t) => blobKeys.every((k) => keyMatchesParam(k, t.propKeys)) && requiredSatisfied(t, keySet)
|
|
82254
|
+
);
|
|
82255
|
+
return sigMatches.length === 1 ? sigMatches[0].name : null;
|
|
82256
|
+
}
|
|
82257
|
+
function escapeRegExp2(s3) {
|
|
82258
|
+
return s3.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
82259
|
+
}
|
|
82260
|
+
function extractKimiCalls(content, tools) {
|
|
82261
|
+
if (tools.length === 0) return { calls: [], matches: [] };
|
|
82262
|
+
const trailing = findTrailingJsonObject(content);
|
|
82263
|
+
if (!trailing || !isPlainObject(trailing.parsed)) return { calls: [], matches: [] };
|
|
82264
|
+
if (typeof trailing.parsed.name === "string" && trailing.parsed.name.trim() !== "") {
|
|
82265
|
+
return { calls: [], matches: [] };
|
|
82266
|
+
}
|
|
82267
|
+
const prose = content.slice(0, trailing.start).trim();
|
|
82268
|
+
const name = resolveKimiTool(prose, trailing.parsed, tools);
|
|
82269
|
+
if (!name) return { calls: [], matches: [] };
|
|
82270
|
+
const start = prose === "" ? 0 : trailing.start;
|
|
82271
|
+
return {
|
|
82272
|
+
calls: [makeCall(name, normalizeGptArgs(trailing.parsed))],
|
|
82273
|
+
matches: [{ start, end: content.length }]
|
|
82274
|
+
};
|
|
82275
|
+
}
|
|
82184
82276
|
function stripRanges(content, ranges) {
|
|
82185
82277
|
if (ranges.length === 0) return content;
|
|
82186
82278
|
const sorted = [...ranges].sort((a, b) => a.start - b.start);
|
|
@@ -82195,15 +82287,23 @@ function stripRanges(content, ranges) {
|
|
|
82195
82287
|
cleaned += content.slice(cursor);
|
|
82196
82288
|
return cleaned;
|
|
82197
82289
|
}
|
|
82198
|
-
function extractTextualToolCalls(content) {
|
|
82290
|
+
function extractTextualToolCalls(content, options) {
|
|
82199
82291
|
if (!content) {
|
|
82200
82292
|
return { toolCalls: [], cleanedContent: "" };
|
|
82201
82293
|
}
|
|
82202
82294
|
const openClaw = extractOpenClawCalls(content);
|
|
82203
82295
|
const anthropic = extractAnthropicCalls(content);
|
|
82204
82296
|
const gemini = extractGeminiCalls(content);
|
|
82205
|
-
const
|
|
82206
|
-
const
|
|
82297
|
+
const tagBasedCount = openClaw.calls.length + anthropic.calls.length + gemini.calls.length;
|
|
82298
|
+
const gpt = tagBasedCount === 0 ? extractGptCalls(content) : { calls: [], matches: [] };
|
|
82299
|
+
const kimi = tagBasedCount + gpt.calls.length === 0 ? extractKimiCalls(content, deriveRequestTools(options?.tools)) : { calls: [], matches: [] };
|
|
82300
|
+
const toolCalls = [
|
|
82301
|
+
...openClaw.calls,
|
|
82302
|
+
...anthropic.calls,
|
|
82303
|
+
...gemini.calls,
|
|
82304
|
+
...gpt.calls,
|
|
82305
|
+
...kimi.calls
|
|
82306
|
+
];
|
|
82207
82307
|
if (toolCalls.length === 0) {
|
|
82208
82308
|
return { toolCalls: [], cleanedContent: content };
|
|
82209
82309
|
}
|
|
@@ -82211,7 +82311,8 @@ function extractTextualToolCalls(content) {
|
|
|
82211
82311
|
...openClaw.matches,
|
|
82212
82312
|
...anthropic.matches,
|
|
82213
82313
|
...gemini.matches,
|
|
82214
|
-
...gpt.matches
|
|
82314
|
+
...gpt.matches,
|
|
82315
|
+
...kimi.matches
|
|
82215
82316
|
]);
|
|
82216
82317
|
return { toolCalls, cleanedContent };
|
|
82217
82318
|
}
|
|
@@ -85986,6 +86087,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
85986
86087
|
const debugMode = debugHeadersEnabledFromEnv() && req.headers["x-clawrouter-debug"] !== "false";
|
|
85987
86088
|
let routingDecision;
|
|
85988
86089
|
let hasTools = false;
|
|
86090
|
+
let requestTools;
|
|
85989
86091
|
let hasVision = false;
|
|
85990
86092
|
let isStreaming = false;
|
|
85991
86093
|
let modelId = "";
|
|
@@ -86023,6 +86125,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
86023
86125
|
requestSummaryForStore = summarizeRequest(promptText);
|
|
86024
86126
|
}
|
|
86025
86127
|
hasTools = Array.isArray(parsed.tools) && parsed.tools.length > 0;
|
|
86128
|
+
requestTools = parsed.tools;
|
|
86026
86129
|
const rawLastContent = lastUserMsg?.content;
|
|
86027
86130
|
const lastContent = typeof rawLastContent === "string" ? rawLastContent : Array.isArray(rawLastContent) ? rawLastContent.filter((b) => b.type === "text").map((b) => b.text ?? "").join(" ") : "";
|
|
86028
86131
|
if (sessionId && parsedMessages.length > 0) {
|
|
@@ -87566,7 +87669,7 @@ data: [DONE]
|
|
|
87566
87669
|
let toolCalls = choice.message?.tool_calls ?? choice.delta?.tool_calls;
|
|
87567
87670
|
const rawContent = choice.message?.content ?? choice.delta?.content ?? "";
|
|
87568
87671
|
if (!endsWithToolCalls && (!toolCalls || toolCalls.length === 0) && rawContent) {
|
|
87569
|
-
const extracted = extractTextualToolCalls(rawContent);
|
|
87672
|
+
const extracted = extractTextualToolCalls(rawContent, { tools: requestTools });
|
|
87570
87673
|
if (extracted.toolCalls.length > 0) {
|
|
87571
87674
|
toolCalls = extracted.toolCalls;
|
|
87572
87675
|
}
|
|
@@ -87779,7 +87882,7 @@ data: [DONE]
|
|
|
87779
87882
|
}
|
|
87780
87883
|
continue;
|
|
87781
87884
|
}
|
|
87782
|
-
const extracted = extractTextualToolCalls(message.content);
|
|
87885
|
+
const extracted = extractTextualToolCalls(message.content, { tools: requestTools });
|
|
87783
87886
|
if (extracted.toolCalls.length > 0) {
|
|
87784
87887
|
message.tool_calls = extracted.toolCalls;
|
|
87785
87888
|
message.content = "";
|
package/dist/index.js
CHANGED
|
@@ -86,6 +86,7 @@ var init_top_models = __esm({
|
|
|
86
86
|
"minimax/minimax-m3",
|
|
87
87
|
"minimax/minimax-m2.7",
|
|
88
88
|
"moonshot/kimi-k3",
|
|
89
|
+
"qwen/qwen3.7-max",
|
|
89
90
|
"deepseek/deepseek-v4-pro",
|
|
90
91
|
"deepseek/deepseek-chat",
|
|
91
92
|
"deepseek/deepseek-reasoner",
|
|
@@ -312,6 +313,17 @@ var init_models = __esm({
|
|
|
312
313
|
"kimi-k2.6": "moonshot/kimi-k2.6",
|
|
313
314
|
"kimi-k2.5": "moonshot/kimi-k2.5",
|
|
314
315
|
"nvidia/kimi-k2.5": "moonshot/kimi-k2.5",
|
|
316
|
+
// Qwen — Qwen3.7 Max is Alibaba's current flagship for reasoning, coding,
|
|
317
|
+
// and agentic tool use. EXPLICIT PINS ONLY: bare `qwen` deliberately stays
|
|
318
|
+
// unbound. Every other qwen* shorthand below points at a FREE model
|
|
319
|
+
// (qwen-coder, qwen-thinking, qwen3-next, qwen3.5-122b), so binding the
|
|
320
|
+
// shortest name to a $1.475/$4.425 flagship would silently charge callers
|
|
321
|
+
// who typed it expecting the free tier — same rule that keeps generic
|
|
322
|
+
// `kimi` on K2.7. (`grok` WAS promoted to 4.5, but only after the cost
|
|
323
|
+
// tradeoff was argued explicitly; there's no such case for qwen yet.)
|
|
324
|
+
"qwen3.7-max": "qwen/qwen3.7-max",
|
|
325
|
+
"qwen-3.7-max": "qwen/qwen3.7-max",
|
|
326
|
+
"qwen3-7-max": "qwen/qwen3.7-max",
|
|
315
327
|
// Google
|
|
316
328
|
// gemini-3-pro-preview delisted by Google 2026-06-06 — mirror the gateway
|
|
317
329
|
// redirect to its successor so pinned callers land on 3.1-pro, not an error.
|
|
@@ -1121,6 +1133,27 @@ var init_models = __esm({
|
|
|
1121
1133
|
agentic: true,
|
|
1122
1134
|
toolCalling: true
|
|
1123
1135
|
},
|
|
1136
|
+
// Qwen3.7 Max — BlockRun's paid Qwen flagship, served through its
|
|
1137
|
+
// OpenRouter credit pool. The gateway applies its standard 5% margin at
|
|
1138
|
+
// settlement, so keep the catalog rates at the upstream $1.475/$4.425 COGS.
|
|
1139
|
+
// NOTE: `reasoning: true` also enrolls this id in REASONING_MODEL_IDS
|
|
1140
|
+
// (proxy.ts), which raises its per-model timeout from 60s to 180s.
|
|
1141
|
+
// `toolCalling: true` is LIVE-VERIFIED (2026-07-20): a real request through
|
|
1142
|
+
// the gateway returned a structured tool_calls array (name + valid JSON
|
|
1143
|
+
// arguments, finish_reason "tool_calls"), not the textual leak that Kimi K3
|
|
1144
|
+
// (#213), Gemini (#189) and GPT (#193) produce. Don't downgrade on a hunch.
|
|
1145
|
+
{
|
|
1146
|
+
id: "qwen/qwen3.7-max",
|
|
1147
|
+
name: "Qwen3.7 Max",
|
|
1148
|
+
version: "3.7-max",
|
|
1149
|
+
inputPrice: 1.475,
|
|
1150
|
+
outputPrice: 4.425,
|
|
1151
|
+
contextWindow: 1e6,
|
|
1152
|
+
maxOutput: 65536,
|
|
1153
|
+
reasoning: true,
|
|
1154
|
+
agentic: true,
|
|
1155
|
+
toolCalling: true
|
|
1156
|
+
},
|
|
1124
1157
|
// Kimi K2.6 — superseded by K2.7 (2026-06-13), hidden on BlockRun but still routable.
|
|
1125
1158
|
{
|
|
1126
1159
|
id: "moonshot/kimi-k2.6",
|
|
@@ -82001,6 +82034,65 @@ function extractGptCalls(content) {
|
|
|
82001
82034
|
}
|
|
82002
82035
|
return { calls: [], matches: [] };
|
|
82003
82036
|
}
|
|
82037
|
+
function deriveRequestTools(tools) {
|
|
82038
|
+
if (!Array.isArray(tools)) return [];
|
|
82039
|
+
const derived = [];
|
|
82040
|
+
for (const raw of tools) {
|
|
82041
|
+
const fn = raw?.function;
|
|
82042
|
+
const name = fn?.name;
|
|
82043
|
+
if (typeof name !== "string" || name.trim() === "") continue;
|
|
82044
|
+
const params = fn?.parameters;
|
|
82045
|
+
const props = isPlainObject(params?.properties) ? Object.keys(params.properties) : [];
|
|
82046
|
+
const required = Array.isArray(params?.required) ? params.required.filter((r2) => typeof r2 === "string") : [];
|
|
82047
|
+
derived.push({ name: name.trim(), propKeys: new Set(props), required });
|
|
82048
|
+
}
|
|
82049
|
+
return derived;
|
|
82050
|
+
}
|
|
82051
|
+
function keyMatchesParam(key2, propKeys) {
|
|
82052
|
+
if (propKeys.has(key2)) return true;
|
|
82053
|
+
if (key2 === "cmd" && propKeys.has("command")) return true;
|
|
82054
|
+
if (key2 === "command" && propKeys.has("cmd")) return true;
|
|
82055
|
+
return false;
|
|
82056
|
+
}
|
|
82057
|
+
function requiredSatisfied(tool, blobKeys) {
|
|
82058
|
+
return tool.required.every(
|
|
82059
|
+
(r2) => blobKeys.has(r2) || r2 === "command" && blobKeys.has("cmd") || r2 === "cmd" && blobKeys.has("command")
|
|
82060
|
+
);
|
|
82061
|
+
}
|
|
82062
|
+
function resolveKimiTool(prose, blob, tools) {
|
|
82063
|
+
const blobKeys = Object.keys(blob);
|
|
82064
|
+
if (prose) {
|
|
82065
|
+
const named = tools.filter(
|
|
82066
|
+
(t) => new RegExp(`(?:^|[^A-Za-z0-9_])${escapeRegExp2(t.name)}(?:[^A-Za-z0-9_]|$)`).test(prose)
|
|
82067
|
+
);
|
|
82068
|
+
if (named.length === 1) return named[0].name;
|
|
82069
|
+
}
|
|
82070
|
+
if (blobKeys.length === 0) return null;
|
|
82071
|
+
const keySet = new Set(blobKeys);
|
|
82072
|
+
const sigMatches = tools.filter(
|
|
82073
|
+
(t) => blobKeys.every((k) => keyMatchesParam(k, t.propKeys)) && requiredSatisfied(t, keySet)
|
|
82074
|
+
);
|
|
82075
|
+
return sigMatches.length === 1 ? sigMatches[0].name : null;
|
|
82076
|
+
}
|
|
82077
|
+
function escapeRegExp2(s3) {
|
|
82078
|
+
return s3.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
82079
|
+
}
|
|
82080
|
+
function extractKimiCalls(content, tools) {
|
|
82081
|
+
if (tools.length === 0) return { calls: [], matches: [] };
|
|
82082
|
+
const trailing = findTrailingJsonObject(content);
|
|
82083
|
+
if (!trailing || !isPlainObject(trailing.parsed)) return { calls: [], matches: [] };
|
|
82084
|
+
if (typeof trailing.parsed.name === "string" && trailing.parsed.name.trim() !== "") {
|
|
82085
|
+
return { calls: [], matches: [] };
|
|
82086
|
+
}
|
|
82087
|
+
const prose = content.slice(0, trailing.start).trim();
|
|
82088
|
+
const name = resolveKimiTool(prose, trailing.parsed, tools);
|
|
82089
|
+
if (!name) return { calls: [], matches: [] };
|
|
82090
|
+
const start = prose === "" ? 0 : trailing.start;
|
|
82091
|
+
return {
|
|
82092
|
+
calls: [makeCall(name, normalizeGptArgs(trailing.parsed))],
|
|
82093
|
+
matches: [{ start, end: content.length }]
|
|
82094
|
+
};
|
|
82095
|
+
}
|
|
82004
82096
|
function stripRanges(content, ranges) {
|
|
82005
82097
|
if (ranges.length === 0) return content;
|
|
82006
82098
|
const sorted = [...ranges].sort((a, b) => a.start - b.start);
|
|
@@ -82015,15 +82107,23 @@ function stripRanges(content, ranges) {
|
|
|
82015
82107
|
cleaned += content.slice(cursor);
|
|
82016
82108
|
return cleaned;
|
|
82017
82109
|
}
|
|
82018
|
-
function extractTextualToolCalls(content) {
|
|
82110
|
+
function extractTextualToolCalls(content, options) {
|
|
82019
82111
|
if (!content) {
|
|
82020
82112
|
return { toolCalls: [], cleanedContent: "" };
|
|
82021
82113
|
}
|
|
82022
82114
|
const openClaw = extractOpenClawCalls(content);
|
|
82023
82115
|
const anthropic = extractAnthropicCalls(content);
|
|
82024
82116
|
const gemini = extractGeminiCalls(content);
|
|
82025
|
-
const
|
|
82026
|
-
const
|
|
82117
|
+
const tagBasedCount = openClaw.calls.length + anthropic.calls.length + gemini.calls.length;
|
|
82118
|
+
const gpt = tagBasedCount === 0 ? extractGptCalls(content) : { calls: [], matches: [] };
|
|
82119
|
+
const kimi = tagBasedCount + gpt.calls.length === 0 ? extractKimiCalls(content, deriveRequestTools(options?.tools)) : { calls: [], matches: [] };
|
|
82120
|
+
const toolCalls = [
|
|
82121
|
+
...openClaw.calls,
|
|
82122
|
+
...anthropic.calls,
|
|
82123
|
+
...gemini.calls,
|
|
82124
|
+
...gpt.calls,
|
|
82125
|
+
...kimi.calls
|
|
82126
|
+
];
|
|
82027
82127
|
if (toolCalls.length === 0) {
|
|
82028
82128
|
return { toolCalls: [], cleanedContent: content };
|
|
82029
82129
|
}
|
|
@@ -82031,7 +82131,8 @@ function extractTextualToolCalls(content) {
|
|
|
82031
82131
|
...openClaw.matches,
|
|
82032
82132
|
...anthropic.matches,
|
|
82033
82133
|
...gemini.matches,
|
|
82034
|
-
...gpt.matches
|
|
82134
|
+
...gpt.matches,
|
|
82135
|
+
...kimi.matches
|
|
82035
82136
|
]);
|
|
82036
82137
|
return { toolCalls, cleanedContent };
|
|
82037
82138
|
}
|
|
@@ -85806,6 +85907,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
85806
85907
|
const debugMode = debugHeadersEnabledFromEnv() && req.headers["x-clawrouter-debug"] !== "false";
|
|
85807
85908
|
let routingDecision;
|
|
85808
85909
|
let hasTools = false;
|
|
85910
|
+
let requestTools;
|
|
85809
85911
|
let hasVision = false;
|
|
85810
85912
|
let isStreaming = false;
|
|
85811
85913
|
let modelId = "";
|
|
@@ -85843,6 +85945,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
85843
85945
|
requestSummaryForStore = summarizeRequest(promptText);
|
|
85844
85946
|
}
|
|
85845
85947
|
hasTools = Array.isArray(parsed.tools) && parsed.tools.length > 0;
|
|
85948
|
+
requestTools = parsed.tools;
|
|
85846
85949
|
const rawLastContent = lastUserMsg?.content;
|
|
85847
85950
|
const lastContent = typeof rawLastContent === "string" ? rawLastContent : Array.isArray(rawLastContent) ? rawLastContent.filter((b) => b.type === "text").map((b) => b.text ?? "").join(" ") : "";
|
|
85848
85951
|
if (sessionId && parsedMessages.length > 0) {
|
|
@@ -87386,7 +87489,7 @@ data: [DONE]
|
|
|
87386
87489
|
let toolCalls = choice.message?.tool_calls ?? choice.delta?.tool_calls;
|
|
87387
87490
|
const rawContent = choice.message?.content ?? choice.delta?.content ?? "";
|
|
87388
87491
|
if (!endsWithToolCalls && (!toolCalls || toolCalls.length === 0) && rawContent) {
|
|
87389
|
-
const extracted = extractTextualToolCalls(rawContent);
|
|
87492
|
+
const extracted = extractTextualToolCalls(rawContent, { tools: requestTools });
|
|
87390
87493
|
if (extracted.toolCalls.length > 0) {
|
|
87391
87494
|
toolCalls = extracted.toolCalls;
|
|
87392
87495
|
}
|
|
@@ -87599,7 +87702,7 @@ data: [DONE]
|
|
|
87599
87702
|
}
|
|
87600
87703
|
continue;
|
|
87601
87704
|
}
|
|
87602
|
-
const extracted = extractTextualToolCalls(message.content);
|
|
87705
|
+
const extracted = extractTextualToolCalls(message.content, { tools: requestTools });
|
|
87603
87706
|
if (extracted.toolCalls.length > 0) {
|
|
87604
87707
|
message.tool_calls = extracted.toolCalls;
|
|
87605
87708
|
message.content = "";
|
package/package.json
CHANGED
package/scripts/update.sh
CHANGED
|
@@ -16,6 +16,96 @@ WALLET_FILE="$HOME/.openclaw/blockrun/wallet.key"
|
|
|
16
16
|
WALLET_BACKUP=""
|
|
17
17
|
PLUGIN_BACKUP=""
|
|
18
18
|
CONFIG_BACKUP=""
|
|
19
|
+
ACTIVE_PACKAGE_DIR=""
|
|
20
|
+
|
|
21
|
+
NPM_PREFIX="$(npm config get prefix 2>/dev/null || echo "")"
|
|
22
|
+
if [ -n "$NPM_PREFIX" ] && [ -d "$NPM_PREFIX/bin" ]; then
|
|
23
|
+
PATH="$NPM_PREFIX/bin:$PATH"
|
|
24
|
+
export PATH
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
find_clawrouter_package_dir() {
|
|
28
|
+
node <<'NODE'
|
|
29
|
+
const fs = require('fs');
|
|
30
|
+
const os = require('os');
|
|
31
|
+
const path = require('path');
|
|
32
|
+
|
|
33
|
+
const home = os.homedir();
|
|
34
|
+
const candidates = [
|
|
35
|
+
{ packagePath: path.join(home, '.openclaw', 'extensions', 'clawrouter', 'package.json'), priority: 0 },
|
|
36
|
+
{ packagePath: path.join(home, '.openclaw', 'npm', 'node_modules', '@blockrun', 'clawrouter', 'package.json'), priority: 2 },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const projectsDir = path.join(home, '.openclaw', 'npm', 'projects');
|
|
40
|
+
try {
|
|
41
|
+
for (const name of fs.readdirSync(projectsDir)) {
|
|
42
|
+
candidates.push({
|
|
43
|
+
packagePath: path.join(projectsDir, name, 'node_modules', '@blockrun', 'clawrouter', 'package.json'),
|
|
44
|
+
priority: 1,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
} catch {}
|
|
48
|
+
|
|
49
|
+
function versionParts(version) {
|
|
50
|
+
return String(version || '').split(/[.-]/).slice(0, 3).map((part) => Number.parseInt(part, 10) || 0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function compareVersions(a, b) {
|
|
54
|
+
const left = versionParts(a);
|
|
55
|
+
const right = versionParts(b);
|
|
56
|
+
for (let i = 0; i < 3; i++) {
|
|
57
|
+
if (left[i] !== right[i]) return left[i] - right[i];
|
|
58
|
+
}
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let best = null;
|
|
63
|
+
for (const candidate of candidates) {
|
|
64
|
+
try {
|
|
65
|
+
const stat = fs.statSync(candidate.packagePath);
|
|
66
|
+
const pkg = JSON.parse(fs.readFileSync(candidate.packagePath, 'utf8'));
|
|
67
|
+
if (pkg?.name !== '@blockrun/clawrouter') continue;
|
|
68
|
+
const versionOrder = best ? compareVersions(pkg.version, best.pkg.version) : 1;
|
|
69
|
+
if (
|
|
70
|
+
!best ||
|
|
71
|
+
versionOrder > 0 ||
|
|
72
|
+
(versionOrder === 0 &&
|
|
73
|
+
(candidate.priority < best.priority ||
|
|
74
|
+
(candidate.priority === best.priority && stat.mtimeMs > best.mtimeMs)))
|
|
75
|
+
) {
|
|
76
|
+
best = { ...candidate, pkg, mtimeMs: stat.mtimeMs };
|
|
77
|
+
}
|
|
78
|
+
} catch {}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (best) console.log(path.dirname(best.packagePath));
|
|
82
|
+
NODE
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
sync_cli_shim() {
|
|
86
|
+
local package_dir="$1"
|
|
87
|
+
if [ -z "$package_dir" ] || [ ! -f "$package_dir/dist/cli.js" ]; then
|
|
88
|
+
echo " ⚠ Could not update CLI shim: ClawRouter CLI not found"
|
|
89
|
+
return 1
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
local prefix
|
|
93
|
+
prefix=$(npm config get prefix 2>/dev/null || echo "")
|
|
94
|
+
if [ -z "$prefix" ]; then
|
|
95
|
+
echo " ⚠ Could not update CLI shim: npm prefix unavailable"
|
|
96
|
+
return 1
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
local bin_dir="$prefix/bin"
|
|
100
|
+
mkdir -p "$bin_dir"
|
|
101
|
+
rm -f "$bin_dir/clawrouter"
|
|
102
|
+
cat >"$bin_dir/clawrouter" <<EOF
|
|
103
|
+
#!/bin/sh
|
|
104
|
+
exec node "$package_dir/dist/cli.js" "\$@"
|
|
105
|
+
EOF
|
|
106
|
+
chmod +x "$bin_dir/clawrouter"
|
|
107
|
+
echo " ✓ CLI shim updated: $bin_dir/clawrouter → $package_dir/dist/cli.js"
|
|
108
|
+
}
|
|
19
109
|
|
|
20
110
|
cleanup_backups() {
|
|
21
111
|
if [ -n "$PLUGIN_BACKUP" ] && [ -d "$PLUGIN_BACKUP" ]; then
|
|
@@ -93,6 +183,7 @@ echo ""
|
|
|
93
183
|
|
|
94
184
|
# Pre-flight: fail fast if config is corrupt
|
|
95
185
|
validate_config
|
|
186
|
+
ACTIVE_PACKAGE_DIR=$(find_clawrouter_package_dir)
|
|
96
187
|
|
|
97
188
|
echo "→ Checking wallet..."
|
|
98
189
|
|
|
@@ -105,7 +196,7 @@ if [ -f "$WALLET_FILE" ]; then
|
|
|
105
196
|
# Derive wallet address via node (viem is available post-install)
|
|
106
197
|
WALLET_ADDRESS=$(node -e "
|
|
107
198
|
try {
|
|
108
|
-
const { privateKeyToAccount } = require('
|
|
199
|
+
const { privateKeyToAccount } = require(require.resolve('viem/accounts', { paths: ['$ACTIVE_PACKAGE_DIR'] }));
|
|
109
200
|
const acct = privateKeyToAccount('$WALLET_KEY');
|
|
110
201
|
console.log(acct.address);
|
|
111
202
|
} catch {
|
|
@@ -551,41 +642,42 @@ force_install_from_npm() {
|
|
|
551
642
|
return 1
|
|
552
643
|
}
|
|
553
644
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
645
|
+
LATEST_VER=$(npm view @blockrun/clawrouter@latest version 2>/dev/null || echo "")
|
|
646
|
+
if [ -z "$LATEST_VER" ]; then
|
|
647
|
+
echo " ✗ Could not resolve latest ClawRouter version from npm"
|
|
648
|
+
exit 1
|
|
649
|
+
fi
|
|
650
|
+
|
|
651
|
+
ACTIVE_PACKAGE_DIR=$(find_clawrouter_package_dir)
|
|
652
|
+
INSTALLED_VER=""
|
|
653
|
+
if [ -n "$ACTIVE_PACKAGE_DIR" ] && [ -f "$ACTIVE_PACKAGE_DIR/package.json" ]; then
|
|
654
|
+
INSTALLED_VER=$(node -e "try{const p=require('$ACTIVE_PACKAGE_DIR/package.json');console.log(p.version);}catch{console.log('');}" 2>/dev/null || echo "")
|
|
655
|
+
fi
|
|
656
|
+
|
|
657
|
+
if [ "$OPENCLAW_INSTALL_RECOVERABLE" = "1" ] || [ -z "$INSTALLED_VER" ] || [ "$INSTALLED_VER" != "$LATEST_VER" ]; then
|
|
658
|
+
if [ -n "$INSTALLED_VER" ] && [ "$INSTALLED_VER" != "$LATEST_VER" ]; then
|
|
659
|
+
echo " ⚠️ OpenClaw installed v${INSTALLED_VER} but latest is v${LATEST_VER}"
|
|
559
660
|
fi
|
|
560
661
|
force_install_from_npm "$LATEST_VER"
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
exit 1
|
|
564
|
-
fi
|
|
565
|
-
INSTALLED_VER=$(node -e "try{const p=require('$PLUGIN_DIR/package.json');console.log(p.version);}catch{console.log('');}" 2>/dev/null || echo "")
|
|
566
|
-
if [ -z "$INSTALLED_VER" ]; then
|
|
567
|
-
echo " ✗ Could not verify ClawRouter version after npm fallback"
|
|
568
|
-
exit 1
|
|
569
|
-
fi
|
|
570
|
-
echo " ✓ ClawRouter v${INSTALLED_VER} installed"
|
|
571
|
-
trap - EXIT INT TERM
|
|
572
|
-
elif [ -d "$PLUGIN_DIR" ] && [ -f "$PLUGIN_DIR/package.json" ]; then
|
|
573
|
-
INSTALLED_VER=$(node -e "try{const p=require('$PLUGIN_DIR/package.json');console.log(p.version);}catch{console.log('');}" 2>/dev/null || echo "")
|
|
574
|
-
LATEST_VER=$(npm view @blockrun/clawrouter@latest version 2>/dev/null || echo "")
|
|
575
|
-
if [ -n "$LATEST_VER" ] && [ -n "$INSTALLED_VER" ] && [ "$INSTALLED_VER" != "$LATEST_VER" ]; then
|
|
576
|
-
echo " ⚠️ openclaw installed v${INSTALLED_VER} (cached) but latest is v${LATEST_VER}"
|
|
577
|
-
force_install_from_npm "$LATEST_VER" || true
|
|
578
|
-
fi
|
|
579
|
-
INSTALLED_VER=$(node -e "try{const p=require('$PLUGIN_DIR/package.json');console.log(p.version);}catch{console.log('?');}" 2>/dev/null || echo "?")
|
|
580
|
-
echo " ✓ ClawRouter v${INSTALLED_VER} installed"
|
|
662
|
+
ACTIVE_PACKAGE_DIR=$(find_clawrouter_package_dir)
|
|
663
|
+
INSTALLED_VER=$(node -e "try{const p=require('$ACTIVE_PACKAGE_DIR/package.json');console.log(p.version);}catch{console.log('');}" 2>/dev/null || echo "")
|
|
581
664
|
fi
|
|
582
665
|
|
|
666
|
+
if [ -z "$ACTIVE_PACKAGE_DIR" ] || [ -z "$INSTALLED_VER" ]; then
|
|
667
|
+
echo " ✗ Could not verify ClawRouter install"
|
|
668
|
+
exit 1
|
|
669
|
+
fi
|
|
670
|
+
|
|
671
|
+
echo " ✓ ClawRouter v${INSTALLED_VER} installed at $ACTIVE_PACKAGE_DIR"
|
|
672
|
+
sync_cli_shim "$ACTIVE_PACKAGE_DIR"
|
|
673
|
+
trap - EXIT INT TERM
|
|
674
|
+
|
|
583
675
|
# ── Step 4c: Ensure all dependencies are installed ────────────
|
|
584
676
|
# openclaw's plugin installer may skip native/optional deps like @solana/kit.
|
|
585
677
|
# Run npm install in the plugin directory to fill any gaps.
|
|
586
|
-
if [ -
|
|
678
|
+
if [ -n "$ACTIVE_PACKAGE_DIR" ] && [ -f "$ACTIVE_PACKAGE_DIR/package.json" ]; then
|
|
587
679
|
echo "→ Installing dependencies (Solana, x402, etc.)..."
|
|
588
|
-
run_dependency_install "$
|
|
680
|
+
run_dependency_install "$ACTIVE_PACKAGE_DIR"
|
|
589
681
|
fi
|
|
590
682
|
|
|
591
683
|
# ── Step 5: Verify wallet survived ─────────────────────────────
|
|
@@ -724,7 +816,7 @@ const os = require('os');
|
|
|
724
816
|
const fs = require('fs');
|
|
725
817
|
const path = require('path');
|
|
726
818
|
const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
|
|
727
|
-
const topModelsPath = '$
|
|
819
|
+
const topModelsPath = path.join('$ACTIVE_PACKAGE_DIR', 'src', 'top-models.json');
|
|
728
820
|
|
|
729
821
|
if (!fs.existsSync(configPath)) {
|
|
730
822
|
console.log(' No config file found, skipping');
|
|
@@ -1025,7 +1117,7 @@ if [ -f "$WALLET_FILE" ]; then
|
|
|
1025
1117
|
FINAL_KEY=$(cat "$WALLET_FILE" | tr -d '[:space:]')
|
|
1026
1118
|
FINAL_ADDRESS=$(node -e "
|
|
1027
1119
|
try {
|
|
1028
|
-
const { privateKeyToAccount } = require('
|
|
1120
|
+
const { privateKeyToAccount } = require(require.resolve('viem/accounts', { paths: ['$ACTIVE_PACKAGE_DIR'] }));
|
|
1029
1121
|
console.log(privateKeyToAccount('$FINAL_KEY').address);
|
|
1030
1122
|
} catch { console.log('(run /wallet in OpenClaw to see your address)'); }
|
|
1031
1123
|
" 2>/dev/null || echo "(run /wallet in OpenClaw to see your address)")
|
|
@@ -135,7 +135,7 @@ Rules handle ~~80% of requests in <1ms. Only ambiguous queries hit the LLM class
|
|
|
135
135
|
|
|
136
136
|
## Available Models
|
|
137
137
|
|
|
138
|
-
55+ models including: gpt-5.6-terra [balanced, stable default], gpt-5.6-sol [flagship], gpt-5.6-luna [cost-efficient], gpt-5.5, gpt-5.4, gpt-4o, o3, claude-fable-5, claude-opus-4.8, claude-opus-4.7, claude-opus-4.5, claude-sonnet-5, claude-sonnet-4.6, gemini-3.1-pro, gemini-2.5-flash, deepseek-v4-pro, deepseek-chat, grok-4.5, grok-4.3, grok-build-0.1, kimi-k3 [1M ctx flagship], kimi-k2.7, and 8 free NVIDIA models (mistral-large-3-675b [default, 675B flagship], deepseek-v4-flash [1M context], seed-oss-36b [coding], nemotron-3-nano-omni-30b-a3b-reasoning [vision], mistral-nemotron, step-3.7-flash, nemotron-nano-9b-v2, nemotron-nano-12b-v2-vl [vision]).
|
|
138
|
+
55+ models including: gpt-5.6-terra [balanced, stable default], gpt-5.6-sol [flagship], gpt-5.6-luna [cost-efficient], gpt-5.5, gpt-5.4, gpt-4o, o3, claude-fable-5, claude-opus-4.8, claude-opus-4.7, claude-opus-4.5, claude-sonnet-5, claude-sonnet-4.6, gemini-3.1-pro, gemini-2.5-flash, deepseek-v4-pro, deepseek-chat, grok-4.5, grok-4.3, grok-build-0.1, kimi-k3 [1M ctx flagship], kimi-k2.7, qwen3.7-max [Qwen flagship, 1M ctx], and 8 free NVIDIA models (mistral-large-3-675b [default, 675B flagship], deepseek-v4-flash [1M context], seed-oss-36b [coding], nemotron-3-nano-omni-30b-a3b-reasoning [vision], mistral-nemotron, step-3.7-flash, nemotron-nano-9b-v2, nemotron-nano-12b-v2-vl [vision]).
|
|
139
139
|
|
|
140
140
|
## Built-in Agent Tools
|
|
141
141
|
|