@blockrun/clawrouter 0.8.30 → 0.9.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 +854 -9
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +18 -0
- package/dist/index.js +854 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1128,9 +1128,10 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
1128
1128
|
primary: "nvidia/kimi-k2.5",
|
|
1129
1129
|
// $0.55/$2.5 - best quality/price for simple tasks
|
|
1130
1130
|
fallback: [
|
|
1131
|
+
"google/gemini-2.5-flash",
|
|
1132
|
+
// 1M context, cost-effective
|
|
1131
1133
|
"nvidia/gpt-oss-120b",
|
|
1132
1134
|
// FREE fallback
|
|
1133
|
-
"google/gemini-2.5-flash",
|
|
1134
1135
|
"deepseek/deepseek-chat"
|
|
1135
1136
|
]
|
|
1136
1137
|
},
|
|
@@ -1138,17 +1139,22 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
1138
1139
|
primary: "xai/grok-code-fast-1",
|
|
1139
1140
|
// Code specialist, $0.20/$1.50
|
|
1140
1141
|
fallback: [
|
|
1141
|
-
"
|
|
1142
|
-
//
|
|
1142
|
+
"google/gemini-2.5-flash",
|
|
1143
|
+
// 1M context, cost-effective
|
|
1143
1144
|
"deepseek/deepseek-chat",
|
|
1144
|
-
"
|
|
1145
|
+
"xai/grok-4-1-fast-non-reasoning"
|
|
1146
|
+
// Upgraded Grok 4.1
|
|
1145
1147
|
]
|
|
1146
1148
|
},
|
|
1147
1149
|
COMPLEX: {
|
|
1148
1150
|
primary: "google/gemini-3-pro-preview",
|
|
1149
1151
|
// Latest Gemini - upgraded from 2.5
|
|
1150
1152
|
fallback: [
|
|
1153
|
+
"google/gemini-2.5-flash",
|
|
1154
|
+
// CRITICAL: 1M context, cheap failsafe before expensive models
|
|
1151
1155
|
"google/gemini-2.5-pro",
|
|
1156
|
+
"deepseek/deepseek-chat",
|
|
1157
|
+
// Another cheap option
|
|
1152
1158
|
"xai/grok-4-0709",
|
|
1153
1159
|
"openai/gpt-4o",
|
|
1154
1160
|
"openai/gpt-5.2",
|
|
@@ -1159,11 +1165,12 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
1159
1165
|
primary: "xai/grok-4-1-fast-reasoning",
|
|
1160
1166
|
// Upgraded Grok 4.1 reasoning $0.20/$0.50
|
|
1161
1167
|
fallback: [
|
|
1168
|
+
"deepseek/deepseek-reasoner",
|
|
1169
|
+
// Cheap reasoning model as first fallback
|
|
1162
1170
|
"xai/grok-4-fast-reasoning",
|
|
1163
1171
|
"openai/o3",
|
|
1164
1172
|
"openai/o4-mini",
|
|
1165
1173
|
// Latest o-series mini
|
|
1166
|
-
"deepseek/deepseek-reasoner",
|
|
1167
1174
|
"moonshot/kimi-k2.5"
|
|
1168
1175
|
]
|
|
1169
1176
|
}
|
|
@@ -2168,6 +2175,744 @@ var BalanceMonitor = class {
|
|
|
2168
2175
|
}
|
|
2169
2176
|
};
|
|
2170
2177
|
|
|
2178
|
+
// src/compression/types.ts
|
|
2179
|
+
var DEFAULT_COMPRESSION_CONFIG = {
|
|
2180
|
+
enabled: true,
|
|
2181
|
+
preserveRaw: true,
|
|
2182
|
+
layers: {
|
|
2183
|
+
deduplication: true,
|
|
2184
|
+
// Safe: removes duplicate messages
|
|
2185
|
+
whitespace: true,
|
|
2186
|
+
// Safe: normalizes whitespace
|
|
2187
|
+
dictionary: false,
|
|
2188
|
+
// DISABLED: requires model to understand codebook
|
|
2189
|
+
paths: false,
|
|
2190
|
+
// DISABLED: requires model to understand path codes
|
|
2191
|
+
jsonCompact: true,
|
|
2192
|
+
// Safe: just removes JSON whitespace
|
|
2193
|
+
observation: false,
|
|
2194
|
+
// DISABLED: may lose important context
|
|
2195
|
+
dynamicCodebook: false
|
|
2196
|
+
// DISABLED: requires model to understand codes
|
|
2197
|
+
},
|
|
2198
|
+
dictionary: {
|
|
2199
|
+
maxEntries: 50,
|
|
2200
|
+
minPhraseLength: 15,
|
|
2201
|
+
includeCodebookHeader: false
|
|
2202
|
+
// No codebook header needed
|
|
2203
|
+
}
|
|
2204
|
+
};
|
|
2205
|
+
|
|
2206
|
+
// src/compression/layers/deduplication.ts
|
|
2207
|
+
import crypto2 from "crypto";
|
|
2208
|
+
function hashMessage(message) {
|
|
2209
|
+
const parts = [
|
|
2210
|
+
message.role,
|
|
2211
|
+
message.content || "",
|
|
2212
|
+
message.tool_call_id || "",
|
|
2213
|
+
message.name || ""
|
|
2214
|
+
];
|
|
2215
|
+
if (message.tool_calls) {
|
|
2216
|
+
parts.push(
|
|
2217
|
+
JSON.stringify(
|
|
2218
|
+
message.tool_calls.map((tc) => ({
|
|
2219
|
+
name: tc.function.name,
|
|
2220
|
+
args: tc.function.arguments
|
|
2221
|
+
}))
|
|
2222
|
+
)
|
|
2223
|
+
);
|
|
2224
|
+
}
|
|
2225
|
+
const content = parts.join("|");
|
|
2226
|
+
return crypto2.createHash("md5").update(content).digest("hex");
|
|
2227
|
+
}
|
|
2228
|
+
function deduplicateMessages(messages) {
|
|
2229
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2230
|
+
const result = [];
|
|
2231
|
+
let duplicatesRemoved = 0;
|
|
2232
|
+
const referencedToolCallIds = /* @__PURE__ */ new Set();
|
|
2233
|
+
for (const message of messages) {
|
|
2234
|
+
if (message.role === "tool" && message.tool_call_id) {
|
|
2235
|
+
referencedToolCallIds.add(message.tool_call_id);
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
for (const message of messages) {
|
|
2239
|
+
if (message.role === "system") {
|
|
2240
|
+
result.push(message);
|
|
2241
|
+
continue;
|
|
2242
|
+
}
|
|
2243
|
+
if (message.role === "user") {
|
|
2244
|
+
result.push(message);
|
|
2245
|
+
continue;
|
|
2246
|
+
}
|
|
2247
|
+
if (message.role === "tool") {
|
|
2248
|
+
result.push(message);
|
|
2249
|
+
continue;
|
|
2250
|
+
}
|
|
2251
|
+
if (message.role === "assistant" && message.tool_calls) {
|
|
2252
|
+
const hasReferencedToolCall = message.tool_calls.some(
|
|
2253
|
+
(tc) => referencedToolCallIds.has(tc.id)
|
|
2254
|
+
);
|
|
2255
|
+
if (hasReferencedToolCall) {
|
|
2256
|
+
result.push(message);
|
|
2257
|
+
continue;
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
const hash = hashMessage(message);
|
|
2261
|
+
if (!seen.has(hash)) {
|
|
2262
|
+
seen.add(hash);
|
|
2263
|
+
result.push(message);
|
|
2264
|
+
} else {
|
|
2265
|
+
duplicatesRemoved++;
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
return {
|
|
2269
|
+
messages: result,
|
|
2270
|
+
duplicatesRemoved,
|
|
2271
|
+
originalCount: messages.length
|
|
2272
|
+
};
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
// src/compression/layers/whitespace.ts
|
|
2276
|
+
function normalizeWhitespace(content) {
|
|
2277
|
+
if (!content) return content;
|
|
2278
|
+
return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n{3,}/g, "\n\n").replace(/[ \t]+$/gm, "").replace(/([^\n]) {2,}/g, "$1 ").replace(/^[ ]{8,}/gm, (match) => " ".repeat(Math.ceil(match.length / 4))).replace(/\t/g, " ").trim();
|
|
2279
|
+
}
|
|
2280
|
+
function normalizeMessagesWhitespace(messages) {
|
|
2281
|
+
let charsSaved = 0;
|
|
2282
|
+
const result = messages.map((message) => {
|
|
2283
|
+
if (!message.content) return message;
|
|
2284
|
+
const originalLength = message.content.length;
|
|
2285
|
+
const normalizedContent = normalizeWhitespace(message.content);
|
|
2286
|
+
charsSaved += originalLength - normalizedContent.length;
|
|
2287
|
+
return {
|
|
2288
|
+
...message,
|
|
2289
|
+
content: normalizedContent
|
|
2290
|
+
};
|
|
2291
|
+
});
|
|
2292
|
+
return {
|
|
2293
|
+
messages: result,
|
|
2294
|
+
charsSaved
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
// src/compression/codebook.ts
|
|
2299
|
+
var STATIC_CODEBOOK = {
|
|
2300
|
+
// High-impact: OpenClaw/Agent system prompt patterns (very common)
|
|
2301
|
+
"$OC01": "unbrowse_",
|
|
2302
|
+
// Common prefix in tool names
|
|
2303
|
+
"$OC02": "<location>",
|
|
2304
|
+
"$OC03": "</location>",
|
|
2305
|
+
"$OC04": "<name>",
|
|
2306
|
+
"$OC05": "</name>",
|
|
2307
|
+
"$OC06": "<description>",
|
|
2308
|
+
"$OC07": "</description>",
|
|
2309
|
+
"$OC08": "(may need login)",
|
|
2310
|
+
"$OC09": "API skill for OpenClaw",
|
|
2311
|
+
"$OC10": "endpoints",
|
|
2312
|
+
// Skill/tool markers
|
|
2313
|
+
"$SK01": "<available_skills>",
|
|
2314
|
+
"$SK02": "</available_skills>",
|
|
2315
|
+
"$SK03": "<skill>",
|
|
2316
|
+
"$SK04": "</skill>",
|
|
2317
|
+
// Schema patterns (very common in tool definitions)
|
|
2318
|
+
"$T01": 'type: "function"',
|
|
2319
|
+
"$T02": '"type": "function"',
|
|
2320
|
+
"$T03": '"type": "string"',
|
|
2321
|
+
"$T04": '"type": "object"',
|
|
2322
|
+
"$T05": '"type": "array"',
|
|
2323
|
+
"$T06": '"type": "boolean"',
|
|
2324
|
+
"$T07": '"type": "number"',
|
|
2325
|
+
// Common descriptions
|
|
2326
|
+
"$D01": "description:",
|
|
2327
|
+
"$D02": '"description":',
|
|
2328
|
+
// Common instructions
|
|
2329
|
+
"$I01": "You are a personal assistant",
|
|
2330
|
+
"$I02": "Tool names are case-sensitive",
|
|
2331
|
+
"$I03": "Call tools exactly as listed",
|
|
2332
|
+
"$I04": "Use when",
|
|
2333
|
+
"$I05": "without asking",
|
|
2334
|
+
// Safety phrases
|
|
2335
|
+
"$S01": "Do not manipulate or persuade",
|
|
2336
|
+
"$S02": "Prioritize safety and human oversight",
|
|
2337
|
+
"$S03": "unless explicitly requested",
|
|
2338
|
+
// JSON patterns
|
|
2339
|
+
"$J01": '"required": ["',
|
|
2340
|
+
"$J02": '"properties": {',
|
|
2341
|
+
"$J03": '"additionalProperties": false',
|
|
2342
|
+
// Heartbeat patterns
|
|
2343
|
+
"$H01": "HEARTBEAT_OK",
|
|
2344
|
+
"$H02": "Read HEARTBEAT.md if it exists",
|
|
2345
|
+
// Role markers
|
|
2346
|
+
"$R01": '"role": "system"',
|
|
2347
|
+
"$R02": '"role": "user"',
|
|
2348
|
+
"$R03": '"role": "assistant"',
|
|
2349
|
+
"$R04": '"role": "tool"',
|
|
2350
|
+
// Common endings/phrases
|
|
2351
|
+
"$E01": "would you like to",
|
|
2352
|
+
"$E02": "Let me know if you",
|
|
2353
|
+
"$E03": "internal APIs",
|
|
2354
|
+
"$E04": "session cookies",
|
|
2355
|
+
// BlockRun model aliases (common in prompts)
|
|
2356
|
+
"$M01": "blockrun/",
|
|
2357
|
+
"$M02": "openai/",
|
|
2358
|
+
"$M03": "anthropic/",
|
|
2359
|
+
"$M04": "google/",
|
|
2360
|
+
"$M05": "xai/"
|
|
2361
|
+
};
|
|
2362
|
+
function getInverseCodebook() {
|
|
2363
|
+
const inverse = {};
|
|
2364
|
+
for (const [code, phrase] of Object.entries(STATIC_CODEBOOK)) {
|
|
2365
|
+
inverse[phrase] = code;
|
|
2366
|
+
}
|
|
2367
|
+
return inverse;
|
|
2368
|
+
}
|
|
2369
|
+
function generateCodebookHeader(usedCodes, pathMap = {}) {
|
|
2370
|
+
if (usedCodes.size === 0 && Object.keys(pathMap).length === 0) {
|
|
2371
|
+
return "";
|
|
2372
|
+
}
|
|
2373
|
+
const parts = [];
|
|
2374
|
+
if (usedCodes.size > 0) {
|
|
2375
|
+
const codeEntries = Array.from(usedCodes).map((code) => `${code}=${STATIC_CODEBOOK[code]}`).join(", ");
|
|
2376
|
+
parts.push(`[Dict: ${codeEntries}]`);
|
|
2377
|
+
}
|
|
2378
|
+
if (Object.keys(pathMap).length > 0) {
|
|
2379
|
+
const pathEntries = Object.entries(pathMap).map(([code, path]) => `${code}=${path}`).join(", ");
|
|
2380
|
+
parts.push(`[Paths: ${pathEntries}]`);
|
|
2381
|
+
}
|
|
2382
|
+
return parts.join("\n");
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
// src/compression/layers/dictionary.ts
|
|
2386
|
+
function encodeContent(content, inverseCodebook) {
|
|
2387
|
+
let encoded = content;
|
|
2388
|
+
let substitutions = 0;
|
|
2389
|
+
let charsSaved = 0;
|
|
2390
|
+
const codes = /* @__PURE__ */ new Set();
|
|
2391
|
+
const phrases = Object.keys(inverseCodebook).sort((a, b) => b.length - a.length);
|
|
2392
|
+
for (const phrase of phrases) {
|
|
2393
|
+
const code = inverseCodebook[phrase];
|
|
2394
|
+
const regex = new RegExp(escapeRegex(phrase), "g");
|
|
2395
|
+
const matches = encoded.match(regex);
|
|
2396
|
+
if (matches && matches.length > 0) {
|
|
2397
|
+
encoded = encoded.replace(regex, code);
|
|
2398
|
+
substitutions += matches.length;
|
|
2399
|
+
charsSaved += matches.length * (phrase.length - code.length);
|
|
2400
|
+
codes.add(code);
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
return { encoded, substitutions, codes, charsSaved };
|
|
2404
|
+
}
|
|
2405
|
+
function escapeRegex(str) {
|
|
2406
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2407
|
+
}
|
|
2408
|
+
function encodeMessages(messages) {
|
|
2409
|
+
const inverseCodebook = getInverseCodebook();
|
|
2410
|
+
let totalSubstitutions = 0;
|
|
2411
|
+
let totalCharsSaved = 0;
|
|
2412
|
+
const allUsedCodes = /* @__PURE__ */ new Set();
|
|
2413
|
+
const result = messages.map((message) => {
|
|
2414
|
+
if (!message.content) return message;
|
|
2415
|
+
const { encoded, substitutions, codes, charsSaved } = encodeContent(
|
|
2416
|
+
message.content,
|
|
2417
|
+
inverseCodebook
|
|
2418
|
+
);
|
|
2419
|
+
totalSubstitutions += substitutions;
|
|
2420
|
+
totalCharsSaved += charsSaved;
|
|
2421
|
+
codes.forEach((code) => allUsedCodes.add(code));
|
|
2422
|
+
return {
|
|
2423
|
+
...message,
|
|
2424
|
+
content: encoded
|
|
2425
|
+
};
|
|
2426
|
+
});
|
|
2427
|
+
return {
|
|
2428
|
+
messages: result,
|
|
2429
|
+
substitutionCount: totalSubstitutions,
|
|
2430
|
+
usedCodes: allUsedCodes,
|
|
2431
|
+
charsSaved: totalCharsSaved
|
|
2432
|
+
};
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
// src/compression/layers/paths.ts
|
|
2436
|
+
var PATH_REGEX = /(?:\/[\w.-]+){3,}/g;
|
|
2437
|
+
function extractPaths(messages) {
|
|
2438
|
+
const paths = [];
|
|
2439
|
+
for (const message of messages) {
|
|
2440
|
+
if (!message.content) continue;
|
|
2441
|
+
const matches = message.content.match(PATH_REGEX);
|
|
2442
|
+
if (matches) {
|
|
2443
|
+
paths.push(...matches);
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
return paths;
|
|
2447
|
+
}
|
|
2448
|
+
function findFrequentPrefixes(paths) {
|
|
2449
|
+
const prefixCounts = /* @__PURE__ */ new Map();
|
|
2450
|
+
for (const path of paths) {
|
|
2451
|
+
const parts = path.split("/").filter(Boolean);
|
|
2452
|
+
for (let i = 2; i < parts.length; i++) {
|
|
2453
|
+
const prefix = "/" + parts.slice(0, i).join("/") + "/";
|
|
2454
|
+
prefixCounts.set(prefix, (prefixCounts.get(prefix) || 0) + 1);
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
return Array.from(prefixCounts.entries()).filter(([_, count]) => count >= 3).sort((a, b) => b[0].length - a[0].length).slice(0, 5).map(([prefix]) => prefix);
|
|
2458
|
+
}
|
|
2459
|
+
function shortenPaths(messages) {
|
|
2460
|
+
const allPaths = extractPaths(messages);
|
|
2461
|
+
if (allPaths.length < 5) {
|
|
2462
|
+
return {
|
|
2463
|
+
messages,
|
|
2464
|
+
pathMap: {},
|
|
2465
|
+
charsSaved: 0
|
|
2466
|
+
};
|
|
2467
|
+
}
|
|
2468
|
+
const prefixes = findFrequentPrefixes(allPaths);
|
|
2469
|
+
if (prefixes.length === 0) {
|
|
2470
|
+
return {
|
|
2471
|
+
messages,
|
|
2472
|
+
pathMap: {},
|
|
2473
|
+
charsSaved: 0
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
const pathMap = {};
|
|
2477
|
+
prefixes.forEach((prefix, i) => {
|
|
2478
|
+
pathMap[`$P${i + 1}`] = prefix;
|
|
2479
|
+
});
|
|
2480
|
+
let charsSaved = 0;
|
|
2481
|
+
const result = messages.map((message) => {
|
|
2482
|
+
if (!message.content) return message;
|
|
2483
|
+
let content = message.content;
|
|
2484
|
+
const originalLength = content.length;
|
|
2485
|
+
for (const [code, prefix] of Object.entries(pathMap)) {
|
|
2486
|
+
content = content.split(prefix).join(code + "/");
|
|
2487
|
+
}
|
|
2488
|
+
charsSaved += originalLength - content.length;
|
|
2489
|
+
return {
|
|
2490
|
+
...message,
|
|
2491
|
+
content
|
|
2492
|
+
};
|
|
2493
|
+
});
|
|
2494
|
+
return {
|
|
2495
|
+
messages: result,
|
|
2496
|
+
pathMap,
|
|
2497
|
+
charsSaved
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
// src/compression/layers/json-compact.ts
|
|
2502
|
+
function compactJson(jsonString) {
|
|
2503
|
+
try {
|
|
2504
|
+
const parsed = JSON.parse(jsonString);
|
|
2505
|
+
return JSON.stringify(parsed);
|
|
2506
|
+
} catch {
|
|
2507
|
+
return jsonString;
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
function looksLikeJson(str) {
|
|
2511
|
+
const trimmed = str.trim();
|
|
2512
|
+
return trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]");
|
|
2513
|
+
}
|
|
2514
|
+
function compactToolCalls(toolCalls) {
|
|
2515
|
+
return toolCalls.map((tc) => ({
|
|
2516
|
+
...tc,
|
|
2517
|
+
function: {
|
|
2518
|
+
...tc.function,
|
|
2519
|
+
arguments: compactJson(tc.function.arguments)
|
|
2520
|
+
}
|
|
2521
|
+
}));
|
|
2522
|
+
}
|
|
2523
|
+
function compactMessagesJson(messages) {
|
|
2524
|
+
let charsSaved = 0;
|
|
2525
|
+
const result = messages.map((message) => {
|
|
2526
|
+
let newMessage = { ...message };
|
|
2527
|
+
if (message.tool_calls && message.tool_calls.length > 0) {
|
|
2528
|
+
const originalLength = JSON.stringify(message.tool_calls).length;
|
|
2529
|
+
newMessage.tool_calls = compactToolCalls(message.tool_calls);
|
|
2530
|
+
const newLength = JSON.stringify(newMessage.tool_calls).length;
|
|
2531
|
+
charsSaved += originalLength - newLength;
|
|
2532
|
+
}
|
|
2533
|
+
if (message.role === "tool" && message.content && looksLikeJson(message.content)) {
|
|
2534
|
+
const originalLength = message.content.length;
|
|
2535
|
+
const compacted = compactJson(message.content);
|
|
2536
|
+
charsSaved += originalLength - compacted.length;
|
|
2537
|
+
newMessage.content = compacted;
|
|
2538
|
+
}
|
|
2539
|
+
return newMessage;
|
|
2540
|
+
});
|
|
2541
|
+
return {
|
|
2542
|
+
messages: result,
|
|
2543
|
+
charsSaved
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
// src/compression/layers/observation.ts
|
|
2548
|
+
var TOOL_RESULT_THRESHOLD = 500;
|
|
2549
|
+
var COMPRESSED_RESULT_MAX = 300;
|
|
2550
|
+
function compressToolResult(content) {
|
|
2551
|
+
if (!content || content.length <= TOOL_RESULT_THRESHOLD) {
|
|
2552
|
+
return content;
|
|
2553
|
+
}
|
|
2554
|
+
const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
2555
|
+
const errorLines = lines.filter(
|
|
2556
|
+
(l) => /error|exception|failed|denied|refused|timeout|invalid/i.test(l) && l.length < 200
|
|
2557
|
+
);
|
|
2558
|
+
const statusLines = lines.filter(
|
|
2559
|
+
(l) => /success|complete|created|updated|found|result|status|total|count/i.test(l) && l.length < 150
|
|
2560
|
+
);
|
|
2561
|
+
const jsonMatches = [];
|
|
2562
|
+
const jsonPattern = /"(id|name|status|error|message|count|total|url|path)":\s*"?([^",}\n]+)"?/gi;
|
|
2563
|
+
let match;
|
|
2564
|
+
while ((match = jsonPattern.exec(content)) !== null) {
|
|
2565
|
+
jsonMatches.push(`${match[1]}: ${match[2].slice(0, 50)}`);
|
|
2566
|
+
}
|
|
2567
|
+
const firstLine = lines[0]?.slice(0, 100);
|
|
2568
|
+
const lastLine = lines.length > 1 ? lines[lines.length - 1]?.slice(0, 100) : "";
|
|
2569
|
+
const parts = [];
|
|
2570
|
+
if (errorLines.length > 0) {
|
|
2571
|
+
parts.push("[ERR] " + errorLines.slice(0, 3).join(" | "));
|
|
2572
|
+
}
|
|
2573
|
+
if (statusLines.length > 0) {
|
|
2574
|
+
parts.push(statusLines.slice(0, 3).join(" | "));
|
|
2575
|
+
}
|
|
2576
|
+
if (jsonMatches.length > 0) {
|
|
2577
|
+
parts.push(jsonMatches.slice(0, 5).join(", "));
|
|
2578
|
+
}
|
|
2579
|
+
if (parts.length === 0) {
|
|
2580
|
+
parts.push(firstLine || "");
|
|
2581
|
+
if (lines.length > 2) {
|
|
2582
|
+
parts.push(`[...${lines.length - 2} lines...]`);
|
|
2583
|
+
}
|
|
2584
|
+
if (lastLine && lastLine !== firstLine) {
|
|
2585
|
+
parts.push(lastLine);
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
let result = parts.join("\n");
|
|
2589
|
+
if (result.length > COMPRESSED_RESULT_MAX) {
|
|
2590
|
+
result = result.slice(0, COMPRESSED_RESULT_MAX - 20) + "\n[...truncated]";
|
|
2591
|
+
}
|
|
2592
|
+
return result;
|
|
2593
|
+
}
|
|
2594
|
+
function deduplicateLargeBlocks(messages) {
|
|
2595
|
+
const blockHashes = /* @__PURE__ */ new Map();
|
|
2596
|
+
let charsSaved = 0;
|
|
2597
|
+
const result = messages.map((msg, idx) => {
|
|
2598
|
+
if (!msg.content || msg.content.length < 500) {
|
|
2599
|
+
return msg;
|
|
2600
|
+
}
|
|
2601
|
+
const blockKey = msg.content.slice(0, 200);
|
|
2602
|
+
if (blockHashes.has(blockKey)) {
|
|
2603
|
+
const firstIdx = blockHashes.get(blockKey);
|
|
2604
|
+
const original = msg.content;
|
|
2605
|
+
const compressed = `[See message #${firstIdx + 1} - same content]`;
|
|
2606
|
+
charsSaved += original.length - compressed.length;
|
|
2607
|
+
return { ...msg, content: compressed };
|
|
2608
|
+
}
|
|
2609
|
+
blockHashes.set(blockKey, idx);
|
|
2610
|
+
return msg;
|
|
2611
|
+
});
|
|
2612
|
+
return { messages: result, charsSaved };
|
|
2613
|
+
}
|
|
2614
|
+
function compressObservations(messages) {
|
|
2615
|
+
let charsSaved = 0;
|
|
2616
|
+
let observationsCompressed = 0;
|
|
2617
|
+
let result = messages.map((msg) => {
|
|
2618
|
+
if (msg.role !== "tool" || !msg.content) {
|
|
2619
|
+
return msg;
|
|
2620
|
+
}
|
|
2621
|
+
const original = msg.content;
|
|
2622
|
+
if (original.length <= TOOL_RESULT_THRESHOLD) {
|
|
2623
|
+
return msg;
|
|
2624
|
+
}
|
|
2625
|
+
const compressed = compressToolResult(original);
|
|
2626
|
+
const saved = original.length - compressed.length;
|
|
2627
|
+
if (saved > 50) {
|
|
2628
|
+
charsSaved += saved;
|
|
2629
|
+
observationsCompressed++;
|
|
2630
|
+
return { ...msg, content: compressed };
|
|
2631
|
+
}
|
|
2632
|
+
return msg;
|
|
2633
|
+
});
|
|
2634
|
+
const dedupResult = deduplicateLargeBlocks(result);
|
|
2635
|
+
result = dedupResult.messages;
|
|
2636
|
+
charsSaved += dedupResult.charsSaved;
|
|
2637
|
+
return {
|
|
2638
|
+
messages: result,
|
|
2639
|
+
charsSaved,
|
|
2640
|
+
observationsCompressed
|
|
2641
|
+
};
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
// src/compression/layers/dynamic-codebook.ts
|
|
2645
|
+
var MIN_PHRASE_LENGTH = 20;
|
|
2646
|
+
var MAX_PHRASE_LENGTH = 200;
|
|
2647
|
+
var MIN_FREQUENCY = 3;
|
|
2648
|
+
var MAX_ENTRIES = 100;
|
|
2649
|
+
var CODE_PREFIX = "$D";
|
|
2650
|
+
function findRepeatedPhrases(allContent) {
|
|
2651
|
+
const phrases = /* @__PURE__ */ new Map();
|
|
2652
|
+
const segments = allContent.split(/(?<=[.!?\n])\s+/);
|
|
2653
|
+
for (const segment of segments) {
|
|
2654
|
+
const trimmed = segment.trim();
|
|
2655
|
+
if (trimmed.length >= MIN_PHRASE_LENGTH && trimmed.length <= MAX_PHRASE_LENGTH) {
|
|
2656
|
+
phrases.set(trimmed, (phrases.get(trimmed) || 0) + 1);
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
const lines = allContent.split("\n");
|
|
2660
|
+
for (const line of lines) {
|
|
2661
|
+
const trimmed = line.trim();
|
|
2662
|
+
if (trimmed.length >= MIN_PHRASE_LENGTH && trimmed.length <= MAX_PHRASE_LENGTH) {
|
|
2663
|
+
phrases.set(trimmed, (phrases.get(trimmed) || 0) + 1);
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
return phrases;
|
|
2667
|
+
}
|
|
2668
|
+
function buildDynamicCodebook(messages) {
|
|
2669
|
+
let allContent = "";
|
|
2670
|
+
for (const msg of messages) {
|
|
2671
|
+
if (msg.content) {
|
|
2672
|
+
allContent += msg.content + "\n";
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
const phrases = findRepeatedPhrases(allContent);
|
|
2676
|
+
const candidates = [];
|
|
2677
|
+
for (const [phrase, count] of phrases.entries()) {
|
|
2678
|
+
if (count >= MIN_FREQUENCY) {
|
|
2679
|
+
const codeLength = 4;
|
|
2680
|
+
const savings = (phrase.length - codeLength) * count;
|
|
2681
|
+
if (savings > 50) {
|
|
2682
|
+
candidates.push({ phrase, count, savings });
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
candidates.sort((a, b) => b.savings - a.savings);
|
|
2687
|
+
const topCandidates = candidates.slice(0, MAX_ENTRIES);
|
|
2688
|
+
const codebook = {};
|
|
2689
|
+
topCandidates.forEach((c, i) => {
|
|
2690
|
+
const code = `${CODE_PREFIX}${String(i + 1).padStart(2, "0")}`;
|
|
2691
|
+
codebook[code] = c.phrase;
|
|
2692
|
+
});
|
|
2693
|
+
return codebook;
|
|
2694
|
+
}
|
|
2695
|
+
function escapeRegex2(str) {
|
|
2696
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2697
|
+
}
|
|
2698
|
+
function applyDynamicCodebook(messages) {
|
|
2699
|
+
const codebook = buildDynamicCodebook(messages);
|
|
2700
|
+
if (Object.keys(codebook).length === 0) {
|
|
2701
|
+
return {
|
|
2702
|
+
messages,
|
|
2703
|
+
charsSaved: 0,
|
|
2704
|
+
dynamicCodes: {},
|
|
2705
|
+
substitutions: 0
|
|
2706
|
+
};
|
|
2707
|
+
}
|
|
2708
|
+
const phraseToCode = {};
|
|
2709
|
+
for (const [code, phrase] of Object.entries(codebook)) {
|
|
2710
|
+
phraseToCode[phrase] = code;
|
|
2711
|
+
}
|
|
2712
|
+
const sortedPhrases = Object.keys(phraseToCode).sort(
|
|
2713
|
+
(a, b) => b.length - a.length
|
|
2714
|
+
);
|
|
2715
|
+
let charsSaved = 0;
|
|
2716
|
+
let substitutions = 0;
|
|
2717
|
+
const result = messages.map((msg) => {
|
|
2718
|
+
if (!msg.content) return msg;
|
|
2719
|
+
let content = msg.content;
|
|
2720
|
+
for (const phrase of sortedPhrases) {
|
|
2721
|
+
const code = phraseToCode[phrase];
|
|
2722
|
+
const regex = new RegExp(escapeRegex2(phrase), "g");
|
|
2723
|
+
const matches = content.match(regex);
|
|
2724
|
+
if (matches) {
|
|
2725
|
+
content = content.replace(regex, code);
|
|
2726
|
+
charsSaved += (phrase.length - code.length) * matches.length;
|
|
2727
|
+
substitutions += matches.length;
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
return { ...msg, content };
|
|
2731
|
+
});
|
|
2732
|
+
return {
|
|
2733
|
+
messages: result,
|
|
2734
|
+
charsSaved,
|
|
2735
|
+
dynamicCodes: codebook,
|
|
2736
|
+
substitutions
|
|
2737
|
+
};
|
|
2738
|
+
}
|
|
2739
|
+
function generateDynamicCodebookHeader(codebook) {
|
|
2740
|
+
if (Object.keys(codebook).length === 0) return "";
|
|
2741
|
+
const entries = Object.entries(codebook).slice(0, 20).map(([code, phrase]) => {
|
|
2742
|
+
const displayPhrase = phrase.length > 40 ? phrase.slice(0, 37) + "..." : phrase;
|
|
2743
|
+
return `${code}=${displayPhrase}`;
|
|
2744
|
+
}).join(", ");
|
|
2745
|
+
return `[DynDict: ${entries}]`;
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
// src/compression/index.ts
|
|
2749
|
+
function calculateTotalChars(messages) {
|
|
2750
|
+
return messages.reduce((total, msg) => {
|
|
2751
|
+
let chars = msg.content?.length || 0;
|
|
2752
|
+
if (msg.tool_calls) {
|
|
2753
|
+
chars += JSON.stringify(msg.tool_calls).length;
|
|
2754
|
+
}
|
|
2755
|
+
return total + chars;
|
|
2756
|
+
}, 0);
|
|
2757
|
+
}
|
|
2758
|
+
function cloneMessages(messages) {
|
|
2759
|
+
return JSON.parse(JSON.stringify(messages));
|
|
2760
|
+
}
|
|
2761
|
+
function prependCodebookHeader(messages, usedCodes, pathMap) {
|
|
2762
|
+
const header = generateCodebookHeader(usedCodes, pathMap);
|
|
2763
|
+
if (!header) return messages;
|
|
2764
|
+
const userIndex = messages.findIndex((m) => m.role === "user");
|
|
2765
|
+
if (userIndex === -1) {
|
|
2766
|
+
return [
|
|
2767
|
+
{ role: "system", content: header },
|
|
2768
|
+
...messages
|
|
2769
|
+
];
|
|
2770
|
+
}
|
|
2771
|
+
return messages.map((msg, i) => {
|
|
2772
|
+
if (i === userIndex) {
|
|
2773
|
+
return {
|
|
2774
|
+
...msg,
|
|
2775
|
+
content: `${header}
|
|
2776
|
+
|
|
2777
|
+
${msg.content || ""}`
|
|
2778
|
+
};
|
|
2779
|
+
}
|
|
2780
|
+
return msg;
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2783
|
+
async function compressContext(messages, config = {}) {
|
|
2784
|
+
const fullConfig = {
|
|
2785
|
+
...DEFAULT_COMPRESSION_CONFIG,
|
|
2786
|
+
...config,
|
|
2787
|
+
layers: {
|
|
2788
|
+
...DEFAULT_COMPRESSION_CONFIG.layers,
|
|
2789
|
+
...config.layers
|
|
2790
|
+
},
|
|
2791
|
+
dictionary: {
|
|
2792
|
+
...DEFAULT_COMPRESSION_CONFIG.dictionary,
|
|
2793
|
+
...config.dictionary
|
|
2794
|
+
}
|
|
2795
|
+
};
|
|
2796
|
+
if (!fullConfig.enabled) {
|
|
2797
|
+
const originalChars2 = calculateTotalChars(messages);
|
|
2798
|
+
return {
|
|
2799
|
+
messages,
|
|
2800
|
+
originalMessages: messages,
|
|
2801
|
+
originalChars: originalChars2,
|
|
2802
|
+
compressedChars: originalChars2,
|
|
2803
|
+
compressionRatio: 1,
|
|
2804
|
+
stats: {
|
|
2805
|
+
duplicatesRemoved: 0,
|
|
2806
|
+
whitespaceSavedChars: 0,
|
|
2807
|
+
dictionarySubstitutions: 0,
|
|
2808
|
+
pathsShortened: 0,
|
|
2809
|
+
jsonCompactedChars: 0,
|
|
2810
|
+
observationsCompressed: 0,
|
|
2811
|
+
observationCharsSaved: 0,
|
|
2812
|
+
dynamicSubstitutions: 0,
|
|
2813
|
+
dynamicCharsSaved: 0
|
|
2814
|
+
},
|
|
2815
|
+
codebook: {},
|
|
2816
|
+
pathMap: {},
|
|
2817
|
+
dynamicCodes: {}
|
|
2818
|
+
};
|
|
2819
|
+
}
|
|
2820
|
+
const originalMessages = fullConfig.preserveRaw ? cloneMessages(messages) : messages;
|
|
2821
|
+
const originalChars = calculateTotalChars(messages);
|
|
2822
|
+
const stats = {
|
|
2823
|
+
duplicatesRemoved: 0,
|
|
2824
|
+
whitespaceSavedChars: 0,
|
|
2825
|
+
dictionarySubstitutions: 0,
|
|
2826
|
+
pathsShortened: 0,
|
|
2827
|
+
jsonCompactedChars: 0,
|
|
2828
|
+
observationsCompressed: 0,
|
|
2829
|
+
observationCharsSaved: 0,
|
|
2830
|
+
dynamicSubstitutions: 0,
|
|
2831
|
+
dynamicCharsSaved: 0
|
|
2832
|
+
};
|
|
2833
|
+
let result = cloneMessages(messages);
|
|
2834
|
+
let usedCodes = /* @__PURE__ */ new Set();
|
|
2835
|
+
let pathMap = {};
|
|
2836
|
+
let dynamicCodes = {};
|
|
2837
|
+
if (fullConfig.layers.deduplication) {
|
|
2838
|
+
const dedupResult = deduplicateMessages(result);
|
|
2839
|
+
result = dedupResult.messages;
|
|
2840
|
+
stats.duplicatesRemoved = dedupResult.duplicatesRemoved;
|
|
2841
|
+
}
|
|
2842
|
+
if (fullConfig.layers.whitespace) {
|
|
2843
|
+
const wsResult = normalizeMessagesWhitespace(result);
|
|
2844
|
+
result = wsResult.messages;
|
|
2845
|
+
stats.whitespaceSavedChars = wsResult.charsSaved;
|
|
2846
|
+
}
|
|
2847
|
+
if (fullConfig.layers.dictionary) {
|
|
2848
|
+
const dictResult = encodeMessages(result);
|
|
2849
|
+
result = dictResult.messages;
|
|
2850
|
+
stats.dictionarySubstitutions = dictResult.substitutionCount;
|
|
2851
|
+
usedCodes = dictResult.usedCodes;
|
|
2852
|
+
}
|
|
2853
|
+
if (fullConfig.layers.paths) {
|
|
2854
|
+
const pathResult = shortenPaths(result);
|
|
2855
|
+
result = pathResult.messages;
|
|
2856
|
+
pathMap = pathResult.pathMap;
|
|
2857
|
+
stats.pathsShortened = Object.keys(pathMap).length;
|
|
2858
|
+
}
|
|
2859
|
+
if (fullConfig.layers.jsonCompact) {
|
|
2860
|
+
const jsonResult = compactMessagesJson(result);
|
|
2861
|
+
result = jsonResult.messages;
|
|
2862
|
+
stats.jsonCompactedChars = jsonResult.charsSaved;
|
|
2863
|
+
}
|
|
2864
|
+
if (fullConfig.layers.observation) {
|
|
2865
|
+
const obsResult = compressObservations(result);
|
|
2866
|
+
result = obsResult.messages;
|
|
2867
|
+
stats.observationsCompressed = obsResult.observationsCompressed;
|
|
2868
|
+
stats.observationCharsSaved = obsResult.charsSaved;
|
|
2869
|
+
}
|
|
2870
|
+
if (fullConfig.layers.dynamicCodebook) {
|
|
2871
|
+
const dynResult = applyDynamicCodebook(result);
|
|
2872
|
+
result = dynResult.messages;
|
|
2873
|
+
stats.dynamicSubstitutions = dynResult.substitutions;
|
|
2874
|
+
stats.dynamicCharsSaved = dynResult.charsSaved;
|
|
2875
|
+
dynamicCodes = dynResult.dynamicCodes;
|
|
2876
|
+
}
|
|
2877
|
+
if (fullConfig.dictionary.includeCodebookHeader && (usedCodes.size > 0 || Object.keys(pathMap).length > 0 || Object.keys(dynamicCodes).length > 0)) {
|
|
2878
|
+
result = prependCodebookHeader(result, usedCodes, pathMap);
|
|
2879
|
+
if (Object.keys(dynamicCodes).length > 0) {
|
|
2880
|
+
const dynHeader = generateDynamicCodebookHeader(dynamicCodes);
|
|
2881
|
+
if (dynHeader) {
|
|
2882
|
+
const systemIndex = result.findIndex((m) => m.role === "system");
|
|
2883
|
+
if (systemIndex >= 0) {
|
|
2884
|
+
result[systemIndex] = {
|
|
2885
|
+
...result[systemIndex],
|
|
2886
|
+
content: `${dynHeader}
|
|
2887
|
+
${result[systemIndex].content || ""}`
|
|
2888
|
+
};
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
const compressedChars = calculateTotalChars(result);
|
|
2894
|
+
const compressionRatio = compressedChars / originalChars;
|
|
2895
|
+
const usedCodebook = {};
|
|
2896
|
+
usedCodes.forEach((code) => {
|
|
2897
|
+
usedCodebook[code] = STATIC_CODEBOOK[code];
|
|
2898
|
+
});
|
|
2899
|
+
return {
|
|
2900
|
+
messages: result,
|
|
2901
|
+
originalMessages,
|
|
2902
|
+
originalChars,
|
|
2903
|
+
compressedChars,
|
|
2904
|
+
compressionRatio,
|
|
2905
|
+
stats,
|
|
2906
|
+
codebook: usedCodebook,
|
|
2907
|
+
pathMap,
|
|
2908
|
+
dynamicCodes
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2911
|
+
function shouldCompress(messages) {
|
|
2912
|
+
const chars = calculateTotalChars(messages);
|
|
2913
|
+
return chars > 5e3;
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2171
2916
|
// src/session.ts
|
|
2172
2917
|
var DEFAULT_SESSION_CONFIG = {
|
|
2173
2918
|
enabled: false,
|
|
@@ -2363,7 +3108,7 @@ var ROUTING_PROFILES = /* @__PURE__ */ new Set([
|
|
|
2363
3108
|
var FREE_MODEL = "nvidia/gpt-oss-120b";
|
|
2364
3109
|
var HEARTBEAT_INTERVAL_MS = 2e3;
|
|
2365
3110
|
var DEFAULT_REQUEST_TIMEOUT_MS = 18e4;
|
|
2366
|
-
var MAX_FALLBACK_ATTEMPTS =
|
|
3111
|
+
var MAX_FALLBACK_ATTEMPTS = 5;
|
|
2367
3112
|
var HEALTH_CHECK_TIMEOUT_MS = 2e3;
|
|
2368
3113
|
var RATE_LIMIT_COOLDOWN_MS = 6e4;
|
|
2369
3114
|
var PORT_RETRY_ATTEMPTS = 5;
|
|
@@ -2497,7 +3242,10 @@ var PROVIDER_ERROR_PATTERNS = [
|
|
|
2497
3242
|
/overloaded/i,
|
|
2498
3243
|
/temporarily.*unavailable/i,
|
|
2499
3244
|
/api.*key.*invalid/i,
|
|
2500
|
-
/authentication.*failed/i
|
|
3245
|
+
/authentication.*failed/i,
|
|
3246
|
+
/request too large/i,
|
|
3247
|
+
/request.*size.*exceeds/i,
|
|
3248
|
+
/payload too large/i
|
|
2501
3249
|
];
|
|
2502
3250
|
var FALLBACK_STATUS_CODES = [
|
|
2503
3251
|
400,
|
|
@@ -3137,6 +3885,83 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
3137
3885
|
options.onError?.(new Error(`Routing failed: ${errorMsg}`));
|
|
3138
3886
|
}
|
|
3139
3887
|
}
|
|
3888
|
+
const autoCompress = options.autoCompressRequests ?? true;
|
|
3889
|
+
const compressionThreshold = options.compressionThresholdKB ?? 180;
|
|
3890
|
+
const sizeLimit = options.maxRequestSizeKB ?? 200;
|
|
3891
|
+
const requestSizeKB = Math.ceil(body.length / 1024);
|
|
3892
|
+
if (autoCompress && requestSizeKB > compressionThreshold) {
|
|
3893
|
+
try {
|
|
3894
|
+
console.log(`[ClawRouter] Request size ${requestSizeKB}KB exceeds threshold ${compressionThreshold}KB, applying compression...`);
|
|
3895
|
+
const parsed = JSON.parse(body.toString());
|
|
3896
|
+
if (parsed.messages && parsed.messages.length > 0 && shouldCompress(parsed.messages)) {
|
|
3897
|
+
const compressionResult = await compressContext(parsed.messages, {
|
|
3898
|
+
enabled: true,
|
|
3899
|
+
preserveRaw: false,
|
|
3900
|
+
// Don't need originals in proxy
|
|
3901
|
+
layers: {
|
|
3902
|
+
deduplication: true,
|
|
3903
|
+
// Safe: removes duplicate messages
|
|
3904
|
+
whitespace: true,
|
|
3905
|
+
// Safe: normalizes whitespace
|
|
3906
|
+
dictionary: false,
|
|
3907
|
+
// Disabled: requires model to understand codebook
|
|
3908
|
+
paths: false,
|
|
3909
|
+
// Disabled: requires model to understand path codes
|
|
3910
|
+
jsonCompact: true,
|
|
3911
|
+
// Safe: just removes JSON whitespace
|
|
3912
|
+
observation: false,
|
|
3913
|
+
// Disabled: may lose important context
|
|
3914
|
+
dynamicCodebook: false
|
|
3915
|
+
// Disabled: requires model to understand codes
|
|
3916
|
+
},
|
|
3917
|
+
dictionary: {
|
|
3918
|
+
maxEntries: 50,
|
|
3919
|
+
minPhraseLength: 15,
|
|
3920
|
+
includeCodebookHeader: false
|
|
3921
|
+
}
|
|
3922
|
+
});
|
|
3923
|
+
const compressedSizeKB = Math.ceil(compressionResult.compressedChars / 1024);
|
|
3924
|
+
const savings = ((requestSizeKB - compressedSizeKB) / requestSizeKB * 100).toFixed(1);
|
|
3925
|
+
console.log(
|
|
3926
|
+
`[ClawRouter] Compressed ${requestSizeKB}KB \u2192 ${compressedSizeKB}KB (${savings}% reduction)`
|
|
3927
|
+
);
|
|
3928
|
+
parsed.messages = compressionResult.messages;
|
|
3929
|
+
body = Buffer.from(JSON.stringify(parsed));
|
|
3930
|
+
if (compressedSizeKB > sizeLimit) {
|
|
3931
|
+
const errorMsg = {
|
|
3932
|
+
error: {
|
|
3933
|
+
message: `Request size ${compressedSizeKB}KB still exceeds limit after compression (original: ${requestSizeKB}KB). Please reduce context size.`,
|
|
3934
|
+
type: "request_too_large",
|
|
3935
|
+
original_size_kb: requestSizeKB,
|
|
3936
|
+
compressed_size_kb: compressedSizeKB,
|
|
3937
|
+
limit_kb: sizeLimit,
|
|
3938
|
+
help: "Try: 1) Remove old messages from history, 2) Summarize large tool results, 3) Use direct API for very large contexts"
|
|
3939
|
+
}
|
|
3940
|
+
};
|
|
3941
|
+
res.writeHead(413, { "Content-Type": "application/json" });
|
|
3942
|
+
res.end(JSON.stringify(errorMsg));
|
|
3943
|
+
return;
|
|
3944
|
+
}
|
|
3945
|
+
}
|
|
3946
|
+
} catch (err) {
|
|
3947
|
+
console.warn(`[ClawRouter] Compression failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
const finalSizeKB = Math.ceil(body.length / 1024);
|
|
3951
|
+
if (finalSizeKB > sizeLimit) {
|
|
3952
|
+
const errorMsg = {
|
|
3953
|
+
error: {
|
|
3954
|
+
message: `Request size ${finalSizeKB}KB exceeds limit ${sizeLimit}KB. Please reduce context size.`,
|
|
3955
|
+
type: "request_too_large",
|
|
3956
|
+
size_kb: finalSizeKB,
|
|
3957
|
+
limit_kb: sizeLimit,
|
|
3958
|
+
help: "Try: 1) Remove old messages from history, 2) Summarize large tool results, 3) Enable compression (autoCompressRequests: true)"
|
|
3959
|
+
}
|
|
3960
|
+
};
|
|
3961
|
+
res.writeHead(413, { "Content-Type": "application/json" });
|
|
3962
|
+
res.end(JSON.stringify(errorMsg));
|
|
3963
|
+
return;
|
|
3964
|
+
}
|
|
3140
3965
|
const dedupKey = RequestDeduplicator.hash(body);
|
|
3141
3966
|
const cached = deduplicator.getCached(dedupKey);
|
|
3142
3967
|
if (cached) {
|
|
@@ -3548,8 +4373,17 @@ var WALLET_FILE = join4(WALLET_DIR, "wallet.key");
|
|
|
3548
4373
|
async function loadSavedWallet() {
|
|
3549
4374
|
try {
|
|
3550
4375
|
const key = (await readFile2(WALLET_FILE, "utf-8")).trim();
|
|
3551
|
-
if (key.startsWith("0x") && key.length === 66)
|
|
3552
|
-
|
|
4376
|
+
if (key.startsWith("0x") && key.length === 66) {
|
|
4377
|
+
console.log(`[ClawRouter] \u2713 Loaded existing wallet from ${WALLET_FILE}`);
|
|
4378
|
+
return key;
|
|
4379
|
+
}
|
|
4380
|
+
console.warn(`[ClawRouter] \u26A0 Wallet file exists but is invalid (wrong format)`);
|
|
4381
|
+
} catch (err) {
|
|
4382
|
+
if (err.code !== "ENOENT") {
|
|
4383
|
+
console.error(
|
|
4384
|
+
`[ClawRouter] \u2717 Failed to read wallet file: ${err instanceof Error ? err.message : String(err)}`
|
|
4385
|
+
);
|
|
4386
|
+
}
|
|
3553
4387
|
}
|
|
3554
4388
|
return void 0;
|
|
3555
4389
|
}
|
|
@@ -3558,6 +4392,17 @@ async function generateAndSaveWallet() {
|
|
|
3558
4392
|
const account = privateKeyToAccount3(key);
|
|
3559
4393
|
await mkdir2(WALLET_DIR, { recursive: true });
|
|
3560
4394
|
await writeFile(WALLET_FILE, key + "\n", { mode: 384 });
|
|
4395
|
+
try {
|
|
4396
|
+
const verification = (await readFile2(WALLET_FILE, "utf-8")).trim();
|
|
4397
|
+
if (verification !== key) {
|
|
4398
|
+
throw new Error("Wallet file verification failed - content mismatch");
|
|
4399
|
+
}
|
|
4400
|
+
console.log(`[ClawRouter] \u2713 Wallet saved and verified at ${WALLET_FILE}`);
|
|
4401
|
+
} catch (err) {
|
|
4402
|
+
throw new Error(
|
|
4403
|
+
`Failed to verify wallet file after creation: ${err instanceof Error ? err.message : String(err)}`
|
|
4404
|
+
);
|
|
4405
|
+
}
|
|
3561
4406
|
return { key, address: account.address };
|
|
3562
4407
|
}
|
|
3563
4408
|
async function resolveOrGenerateWalletKey() {
|