@blockrun/clawrouter 0.8.31 → 0.9.1
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 +14 -12
- package/dist/cli.js +834 -8
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +18 -0
- package/dist/index.js +835 -14
- 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,739 @@ 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
|
+
const 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((a, b) => b.length - a.length);
|
|
2713
|
+
let charsSaved = 0;
|
|
2714
|
+
let substitutions = 0;
|
|
2715
|
+
const result = messages.map((msg) => {
|
|
2716
|
+
if (!msg.content) return msg;
|
|
2717
|
+
let content = msg.content;
|
|
2718
|
+
for (const phrase of sortedPhrases) {
|
|
2719
|
+
const code = phraseToCode[phrase];
|
|
2720
|
+
const regex = new RegExp(escapeRegex2(phrase), "g");
|
|
2721
|
+
const matches = content.match(regex);
|
|
2722
|
+
if (matches) {
|
|
2723
|
+
content = content.replace(regex, code);
|
|
2724
|
+
charsSaved += (phrase.length - code.length) * matches.length;
|
|
2725
|
+
substitutions += matches.length;
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
return { ...msg, content };
|
|
2729
|
+
});
|
|
2730
|
+
return {
|
|
2731
|
+
messages: result,
|
|
2732
|
+
charsSaved,
|
|
2733
|
+
dynamicCodes: codebook,
|
|
2734
|
+
substitutions
|
|
2735
|
+
};
|
|
2736
|
+
}
|
|
2737
|
+
function generateDynamicCodebookHeader(codebook) {
|
|
2738
|
+
if (Object.keys(codebook).length === 0) return "";
|
|
2739
|
+
const entries = Object.entries(codebook).slice(0, 20).map(([code, phrase]) => {
|
|
2740
|
+
const displayPhrase = phrase.length > 40 ? phrase.slice(0, 37) + "..." : phrase;
|
|
2741
|
+
return `${code}=${displayPhrase}`;
|
|
2742
|
+
}).join(", ");
|
|
2743
|
+
return `[DynDict: ${entries}]`;
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
// src/compression/index.ts
|
|
2747
|
+
function calculateTotalChars(messages) {
|
|
2748
|
+
return messages.reduce((total, msg) => {
|
|
2749
|
+
let chars = msg.content?.length || 0;
|
|
2750
|
+
if (msg.tool_calls) {
|
|
2751
|
+
chars += JSON.stringify(msg.tool_calls).length;
|
|
2752
|
+
}
|
|
2753
|
+
return total + chars;
|
|
2754
|
+
}, 0);
|
|
2755
|
+
}
|
|
2756
|
+
function cloneMessages(messages) {
|
|
2757
|
+
return JSON.parse(JSON.stringify(messages));
|
|
2758
|
+
}
|
|
2759
|
+
function prependCodebookHeader(messages, usedCodes, pathMap) {
|
|
2760
|
+
const header = generateCodebookHeader(usedCodes, pathMap);
|
|
2761
|
+
if (!header) return messages;
|
|
2762
|
+
const userIndex = messages.findIndex((m) => m.role === "user");
|
|
2763
|
+
if (userIndex === -1) {
|
|
2764
|
+
return [{ role: "system", content: header }, ...messages];
|
|
2765
|
+
}
|
|
2766
|
+
return messages.map((msg, i) => {
|
|
2767
|
+
if (i === userIndex) {
|
|
2768
|
+
return {
|
|
2769
|
+
...msg,
|
|
2770
|
+
content: `${header}
|
|
2771
|
+
|
|
2772
|
+
${msg.content || ""}`
|
|
2773
|
+
};
|
|
2774
|
+
}
|
|
2775
|
+
return msg;
|
|
2776
|
+
});
|
|
2777
|
+
}
|
|
2778
|
+
async function compressContext(messages, config = {}) {
|
|
2779
|
+
const fullConfig = {
|
|
2780
|
+
...DEFAULT_COMPRESSION_CONFIG,
|
|
2781
|
+
...config,
|
|
2782
|
+
layers: {
|
|
2783
|
+
...DEFAULT_COMPRESSION_CONFIG.layers,
|
|
2784
|
+
...config.layers
|
|
2785
|
+
},
|
|
2786
|
+
dictionary: {
|
|
2787
|
+
...DEFAULT_COMPRESSION_CONFIG.dictionary,
|
|
2788
|
+
...config.dictionary
|
|
2789
|
+
}
|
|
2790
|
+
};
|
|
2791
|
+
if (!fullConfig.enabled) {
|
|
2792
|
+
const originalChars2 = calculateTotalChars(messages);
|
|
2793
|
+
return {
|
|
2794
|
+
messages,
|
|
2795
|
+
originalMessages: messages,
|
|
2796
|
+
originalChars: originalChars2,
|
|
2797
|
+
compressedChars: originalChars2,
|
|
2798
|
+
compressionRatio: 1,
|
|
2799
|
+
stats: {
|
|
2800
|
+
duplicatesRemoved: 0,
|
|
2801
|
+
whitespaceSavedChars: 0,
|
|
2802
|
+
dictionarySubstitutions: 0,
|
|
2803
|
+
pathsShortened: 0,
|
|
2804
|
+
jsonCompactedChars: 0,
|
|
2805
|
+
observationsCompressed: 0,
|
|
2806
|
+
observationCharsSaved: 0,
|
|
2807
|
+
dynamicSubstitutions: 0,
|
|
2808
|
+
dynamicCharsSaved: 0
|
|
2809
|
+
},
|
|
2810
|
+
codebook: {},
|
|
2811
|
+
pathMap: {},
|
|
2812
|
+
dynamicCodes: {}
|
|
2813
|
+
};
|
|
2814
|
+
}
|
|
2815
|
+
const originalMessages = fullConfig.preserveRaw ? cloneMessages(messages) : messages;
|
|
2816
|
+
const originalChars = calculateTotalChars(messages);
|
|
2817
|
+
const stats = {
|
|
2818
|
+
duplicatesRemoved: 0,
|
|
2819
|
+
whitespaceSavedChars: 0,
|
|
2820
|
+
dictionarySubstitutions: 0,
|
|
2821
|
+
pathsShortened: 0,
|
|
2822
|
+
jsonCompactedChars: 0,
|
|
2823
|
+
observationsCompressed: 0,
|
|
2824
|
+
observationCharsSaved: 0,
|
|
2825
|
+
dynamicSubstitutions: 0,
|
|
2826
|
+
dynamicCharsSaved: 0
|
|
2827
|
+
};
|
|
2828
|
+
let result = cloneMessages(messages);
|
|
2829
|
+
let usedCodes = /* @__PURE__ */ new Set();
|
|
2830
|
+
let pathMap = {};
|
|
2831
|
+
let dynamicCodes = {};
|
|
2832
|
+
if (fullConfig.layers.deduplication) {
|
|
2833
|
+
const dedupResult = deduplicateMessages(result);
|
|
2834
|
+
result = dedupResult.messages;
|
|
2835
|
+
stats.duplicatesRemoved = dedupResult.duplicatesRemoved;
|
|
2836
|
+
}
|
|
2837
|
+
if (fullConfig.layers.whitespace) {
|
|
2838
|
+
const wsResult = normalizeMessagesWhitespace(result);
|
|
2839
|
+
result = wsResult.messages;
|
|
2840
|
+
stats.whitespaceSavedChars = wsResult.charsSaved;
|
|
2841
|
+
}
|
|
2842
|
+
if (fullConfig.layers.dictionary) {
|
|
2843
|
+
const dictResult = encodeMessages(result);
|
|
2844
|
+
result = dictResult.messages;
|
|
2845
|
+
stats.dictionarySubstitutions = dictResult.substitutionCount;
|
|
2846
|
+
usedCodes = dictResult.usedCodes;
|
|
2847
|
+
}
|
|
2848
|
+
if (fullConfig.layers.paths) {
|
|
2849
|
+
const pathResult = shortenPaths(result);
|
|
2850
|
+
result = pathResult.messages;
|
|
2851
|
+
pathMap = pathResult.pathMap;
|
|
2852
|
+
stats.pathsShortened = Object.keys(pathMap).length;
|
|
2853
|
+
}
|
|
2854
|
+
if (fullConfig.layers.jsonCompact) {
|
|
2855
|
+
const jsonResult = compactMessagesJson(result);
|
|
2856
|
+
result = jsonResult.messages;
|
|
2857
|
+
stats.jsonCompactedChars = jsonResult.charsSaved;
|
|
2858
|
+
}
|
|
2859
|
+
if (fullConfig.layers.observation) {
|
|
2860
|
+
const obsResult = compressObservations(result);
|
|
2861
|
+
result = obsResult.messages;
|
|
2862
|
+
stats.observationsCompressed = obsResult.observationsCompressed;
|
|
2863
|
+
stats.observationCharsSaved = obsResult.charsSaved;
|
|
2864
|
+
}
|
|
2865
|
+
if (fullConfig.layers.dynamicCodebook) {
|
|
2866
|
+
const dynResult = applyDynamicCodebook(result);
|
|
2867
|
+
result = dynResult.messages;
|
|
2868
|
+
stats.dynamicSubstitutions = dynResult.substitutions;
|
|
2869
|
+
stats.dynamicCharsSaved = dynResult.charsSaved;
|
|
2870
|
+
dynamicCodes = dynResult.dynamicCodes;
|
|
2871
|
+
}
|
|
2872
|
+
if (fullConfig.dictionary.includeCodebookHeader && (usedCodes.size > 0 || Object.keys(pathMap).length > 0 || Object.keys(dynamicCodes).length > 0)) {
|
|
2873
|
+
result = prependCodebookHeader(result, usedCodes, pathMap);
|
|
2874
|
+
if (Object.keys(dynamicCodes).length > 0) {
|
|
2875
|
+
const dynHeader = generateDynamicCodebookHeader(dynamicCodes);
|
|
2876
|
+
if (dynHeader) {
|
|
2877
|
+
const systemIndex = result.findIndex((m) => m.role === "system");
|
|
2878
|
+
if (systemIndex >= 0) {
|
|
2879
|
+
result[systemIndex] = {
|
|
2880
|
+
...result[systemIndex],
|
|
2881
|
+
content: `${dynHeader}
|
|
2882
|
+
${result[systemIndex].content || ""}`
|
|
2883
|
+
};
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
const compressedChars = calculateTotalChars(result);
|
|
2889
|
+
const compressionRatio = compressedChars / originalChars;
|
|
2890
|
+
const usedCodebook = {};
|
|
2891
|
+
usedCodes.forEach((code) => {
|
|
2892
|
+
usedCodebook[code] = STATIC_CODEBOOK[code];
|
|
2893
|
+
});
|
|
2894
|
+
return {
|
|
2895
|
+
messages: result,
|
|
2896
|
+
originalMessages,
|
|
2897
|
+
originalChars,
|
|
2898
|
+
compressedChars,
|
|
2899
|
+
compressionRatio,
|
|
2900
|
+
stats,
|
|
2901
|
+
codebook: usedCodebook,
|
|
2902
|
+
pathMap,
|
|
2903
|
+
dynamicCodes
|
|
2904
|
+
};
|
|
2905
|
+
}
|
|
2906
|
+
function shouldCompress(messages) {
|
|
2907
|
+
const chars = calculateTotalChars(messages);
|
|
2908
|
+
return chars > 5e3;
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2171
2911
|
// src/session.ts
|
|
2172
2912
|
var DEFAULT_SESSION_CONFIG = {
|
|
2173
2913
|
enabled: false,
|
|
@@ -2363,7 +3103,7 @@ var ROUTING_PROFILES = /* @__PURE__ */ new Set([
|
|
|
2363
3103
|
var FREE_MODEL = "nvidia/gpt-oss-120b";
|
|
2364
3104
|
var HEARTBEAT_INTERVAL_MS = 2e3;
|
|
2365
3105
|
var DEFAULT_REQUEST_TIMEOUT_MS = 18e4;
|
|
2366
|
-
var MAX_FALLBACK_ATTEMPTS =
|
|
3106
|
+
var MAX_FALLBACK_ATTEMPTS = 5;
|
|
2367
3107
|
var HEALTH_CHECK_TIMEOUT_MS = 2e3;
|
|
2368
3108
|
var RATE_LIMIT_COOLDOWN_MS = 6e4;
|
|
2369
3109
|
var PORT_RETRY_ATTEMPTS = 5;
|
|
@@ -2497,7 +3237,10 @@ var PROVIDER_ERROR_PATTERNS = [
|
|
|
2497
3237
|
/overloaded/i,
|
|
2498
3238
|
/temporarily.*unavailable/i,
|
|
2499
3239
|
/api.*key.*invalid/i,
|
|
2500
|
-
/authentication.*failed/i
|
|
3240
|
+
/authentication.*failed/i,
|
|
3241
|
+
/request too large/i,
|
|
3242
|
+
/request.*size.*exceeds/i,
|
|
3243
|
+
/payload too large/i
|
|
2501
3244
|
];
|
|
2502
3245
|
var FALLBACK_STATUS_CODES = [
|
|
2503
3246
|
400,
|
|
@@ -3109,7 +3852,9 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
3109
3852
|
const tools = parsed.tools;
|
|
3110
3853
|
const hasTools = Array.isArray(tools) && tools.length > 0;
|
|
3111
3854
|
if (hasTools) {
|
|
3112
|
-
console.log(
|
|
3855
|
+
console.log(
|
|
3856
|
+
`[ClawRouter] Tools detected (${tools.length}), agentic mode via keywords`
|
|
3857
|
+
);
|
|
3113
3858
|
}
|
|
3114
3859
|
routingDecision = route(prompt, systemPrompt, maxTokens, {
|
|
3115
3860
|
...routerOpts,
|
|
@@ -3137,6 +3882,87 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
3137
3882
|
options.onError?.(new Error(`Routing failed: ${errorMsg}`));
|
|
3138
3883
|
}
|
|
3139
3884
|
}
|
|
3885
|
+
const autoCompress = options.autoCompressRequests ?? true;
|
|
3886
|
+
const compressionThreshold = options.compressionThresholdKB ?? 180;
|
|
3887
|
+
const sizeLimit = options.maxRequestSizeKB ?? 200;
|
|
3888
|
+
const requestSizeKB = Math.ceil(body.length / 1024);
|
|
3889
|
+
if (autoCompress && requestSizeKB > compressionThreshold) {
|
|
3890
|
+
try {
|
|
3891
|
+
console.log(
|
|
3892
|
+
`[ClawRouter] Request size ${requestSizeKB}KB exceeds threshold ${compressionThreshold}KB, applying compression...`
|
|
3893
|
+
);
|
|
3894
|
+
const parsed = JSON.parse(body.toString());
|
|
3895
|
+
if (parsed.messages && parsed.messages.length > 0 && shouldCompress(parsed.messages)) {
|
|
3896
|
+
const compressionResult = await compressContext(parsed.messages, {
|
|
3897
|
+
enabled: true,
|
|
3898
|
+
preserveRaw: false,
|
|
3899
|
+
// Don't need originals in proxy
|
|
3900
|
+
layers: {
|
|
3901
|
+
deduplication: true,
|
|
3902
|
+
// Safe: removes duplicate messages
|
|
3903
|
+
whitespace: true,
|
|
3904
|
+
// Safe: normalizes whitespace
|
|
3905
|
+
dictionary: false,
|
|
3906
|
+
// Disabled: requires model to understand codebook
|
|
3907
|
+
paths: false,
|
|
3908
|
+
// Disabled: requires model to understand path codes
|
|
3909
|
+
jsonCompact: true,
|
|
3910
|
+
// Safe: just removes JSON whitespace
|
|
3911
|
+
observation: false,
|
|
3912
|
+
// Disabled: may lose important context
|
|
3913
|
+
dynamicCodebook: false
|
|
3914
|
+
// Disabled: requires model to understand codes
|
|
3915
|
+
},
|
|
3916
|
+
dictionary: {
|
|
3917
|
+
maxEntries: 50,
|
|
3918
|
+
minPhraseLength: 15,
|
|
3919
|
+
includeCodebookHeader: false
|
|
3920
|
+
}
|
|
3921
|
+
});
|
|
3922
|
+
const compressedSizeKB = Math.ceil(compressionResult.compressedChars / 1024);
|
|
3923
|
+
const savings = ((requestSizeKB - compressedSizeKB) / requestSizeKB * 100).toFixed(1);
|
|
3924
|
+
console.log(
|
|
3925
|
+
`[ClawRouter] Compressed ${requestSizeKB}KB \u2192 ${compressedSizeKB}KB (${savings}% reduction)`
|
|
3926
|
+
);
|
|
3927
|
+
parsed.messages = compressionResult.messages;
|
|
3928
|
+
body = Buffer.from(JSON.stringify(parsed));
|
|
3929
|
+
if (compressedSizeKB > sizeLimit) {
|
|
3930
|
+
const errorMsg = {
|
|
3931
|
+
error: {
|
|
3932
|
+
message: `Request size ${compressedSizeKB}KB still exceeds limit after compression (original: ${requestSizeKB}KB). Please reduce context size.`,
|
|
3933
|
+
type: "request_too_large",
|
|
3934
|
+
original_size_kb: requestSizeKB,
|
|
3935
|
+
compressed_size_kb: compressedSizeKB,
|
|
3936
|
+
limit_kb: sizeLimit,
|
|
3937
|
+
help: "Try: 1) Remove old messages from history, 2) Summarize large tool results, 3) Use direct API for very large contexts"
|
|
3938
|
+
}
|
|
3939
|
+
};
|
|
3940
|
+
res.writeHead(413, { "Content-Type": "application/json" });
|
|
3941
|
+
res.end(JSON.stringify(errorMsg));
|
|
3942
|
+
return;
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
} catch (err) {
|
|
3946
|
+
console.warn(
|
|
3947
|
+
`[ClawRouter] Compression failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3948
|
+
);
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
const finalSizeKB = Math.ceil(body.length / 1024);
|
|
3952
|
+
if (finalSizeKB > sizeLimit) {
|
|
3953
|
+
const errorMsg = {
|
|
3954
|
+
error: {
|
|
3955
|
+
message: `Request size ${finalSizeKB}KB exceeds limit ${sizeLimit}KB. Please reduce context size.`,
|
|
3956
|
+
type: "request_too_large",
|
|
3957
|
+
size_kb: finalSizeKB,
|
|
3958
|
+
limit_kb: sizeLimit,
|
|
3959
|
+
help: "Try: 1) Remove old messages from history, 2) Summarize large tool results, 3) Enable compression (autoCompressRequests: true)"
|
|
3960
|
+
}
|
|
3961
|
+
};
|
|
3962
|
+
res.writeHead(413, { "Content-Type": "application/json" });
|
|
3963
|
+
res.end(JSON.stringify(errorMsg));
|
|
3964
|
+
return;
|
|
3965
|
+
}
|
|
3140
3966
|
const dedupKey = RequestDeduplicator.hash(body);
|
|
3141
3967
|
const cached = deduplicator.getCached(dedupKey);
|
|
3142
3968
|
if (cached) {
|