@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 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
- "xai/grok-4-1-fast-non-reasoning",
1142
- // Upgraded Grok 4.1
1142
+ "google/gemini-2.5-flash",
1143
+ // 1M context, cost-effective
1143
1144
  "deepseek/deepseek-chat",
1144
- "google/gemini-2.5-flash"
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 = 3;
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) return key;
3552
- } catch {
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() {