@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/index.js CHANGED
@@ -1573,9 +1573,10 @@ var DEFAULT_ROUTING_CONFIG = {
1573
1573
  primary: "nvidia/kimi-k2.5",
1574
1574
  // $0.55/$2.5 - best quality/price for simple tasks
1575
1575
  fallback: [
1576
+ "google/gemini-2.5-flash",
1577
+ // 1M context, cost-effective
1576
1578
  "nvidia/gpt-oss-120b",
1577
1579
  // FREE fallback
1578
- "google/gemini-2.5-flash",
1579
1580
  "deepseek/deepseek-chat"
1580
1581
  ]
1581
1582
  },
@@ -1583,17 +1584,22 @@ var DEFAULT_ROUTING_CONFIG = {
1583
1584
  primary: "xai/grok-code-fast-1",
1584
1585
  // Code specialist, $0.20/$1.50
1585
1586
  fallback: [
1586
- "xai/grok-4-1-fast-non-reasoning",
1587
- // Upgraded Grok 4.1
1587
+ "google/gemini-2.5-flash",
1588
+ // 1M context, cost-effective
1588
1589
  "deepseek/deepseek-chat",
1589
- "google/gemini-2.5-flash"
1590
+ "xai/grok-4-1-fast-non-reasoning"
1591
+ // Upgraded Grok 4.1
1590
1592
  ]
1591
1593
  },
1592
1594
  COMPLEX: {
1593
1595
  primary: "google/gemini-3-pro-preview",
1594
1596
  // Latest Gemini - upgraded from 2.5
1595
1597
  fallback: [
1598
+ "google/gemini-2.5-flash",
1599
+ // CRITICAL: 1M context, cheap failsafe before expensive models
1596
1600
  "google/gemini-2.5-pro",
1601
+ "deepseek/deepseek-chat",
1602
+ // Another cheap option
1597
1603
  "xai/grok-4-0709",
1598
1604
  "openai/gpt-4o",
1599
1605
  "openai/gpt-5.2",
@@ -1604,11 +1610,12 @@ var DEFAULT_ROUTING_CONFIG = {
1604
1610
  primary: "xai/grok-4-1-fast-reasoning",
1605
1611
  // Upgraded Grok 4.1 reasoning $0.20/$0.50
1606
1612
  fallback: [
1613
+ "deepseek/deepseek-reasoner",
1614
+ // Cheap reasoning model as first fallback
1607
1615
  "xai/grok-4-fast-reasoning",
1608
1616
  "openai/o3",
1609
1617
  "openai/o4-mini",
1610
1618
  // Latest o-series mini
1611
- "deepseek/deepseek-reasoner",
1612
1619
  "moonshot/kimi-k2.5"
1613
1620
  ]
1614
1621
  }
@@ -2308,6 +2315,744 @@ var BalanceMonitor = class {
2308
2315
  }
2309
2316
  };
2310
2317
 
2318
+ // src/compression/types.ts
2319
+ var DEFAULT_COMPRESSION_CONFIG = {
2320
+ enabled: true,
2321
+ preserveRaw: true,
2322
+ layers: {
2323
+ deduplication: true,
2324
+ // Safe: removes duplicate messages
2325
+ whitespace: true,
2326
+ // Safe: normalizes whitespace
2327
+ dictionary: false,
2328
+ // DISABLED: requires model to understand codebook
2329
+ paths: false,
2330
+ // DISABLED: requires model to understand path codes
2331
+ jsonCompact: true,
2332
+ // Safe: just removes JSON whitespace
2333
+ observation: false,
2334
+ // DISABLED: may lose important context
2335
+ dynamicCodebook: false
2336
+ // DISABLED: requires model to understand codes
2337
+ },
2338
+ dictionary: {
2339
+ maxEntries: 50,
2340
+ minPhraseLength: 15,
2341
+ includeCodebookHeader: false
2342
+ // No codebook header needed
2343
+ }
2344
+ };
2345
+
2346
+ // src/compression/layers/deduplication.ts
2347
+ import crypto2 from "crypto";
2348
+ function hashMessage(message) {
2349
+ const parts = [
2350
+ message.role,
2351
+ message.content || "",
2352
+ message.tool_call_id || "",
2353
+ message.name || ""
2354
+ ];
2355
+ if (message.tool_calls) {
2356
+ parts.push(
2357
+ JSON.stringify(
2358
+ message.tool_calls.map((tc) => ({
2359
+ name: tc.function.name,
2360
+ args: tc.function.arguments
2361
+ }))
2362
+ )
2363
+ );
2364
+ }
2365
+ const content = parts.join("|");
2366
+ return crypto2.createHash("md5").update(content).digest("hex");
2367
+ }
2368
+ function deduplicateMessages(messages) {
2369
+ const seen = /* @__PURE__ */ new Set();
2370
+ const result = [];
2371
+ let duplicatesRemoved = 0;
2372
+ const referencedToolCallIds = /* @__PURE__ */ new Set();
2373
+ for (const message of messages) {
2374
+ if (message.role === "tool" && message.tool_call_id) {
2375
+ referencedToolCallIds.add(message.tool_call_id);
2376
+ }
2377
+ }
2378
+ for (const message of messages) {
2379
+ if (message.role === "system") {
2380
+ result.push(message);
2381
+ continue;
2382
+ }
2383
+ if (message.role === "user") {
2384
+ result.push(message);
2385
+ continue;
2386
+ }
2387
+ if (message.role === "tool") {
2388
+ result.push(message);
2389
+ continue;
2390
+ }
2391
+ if (message.role === "assistant" && message.tool_calls) {
2392
+ const hasReferencedToolCall = message.tool_calls.some(
2393
+ (tc) => referencedToolCallIds.has(tc.id)
2394
+ );
2395
+ if (hasReferencedToolCall) {
2396
+ result.push(message);
2397
+ continue;
2398
+ }
2399
+ }
2400
+ const hash = hashMessage(message);
2401
+ if (!seen.has(hash)) {
2402
+ seen.add(hash);
2403
+ result.push(message);
2404
+ } else {
2405
+ duplicatesRemoved++;
2406
+ }
2407
+ }
2408
+ return {
2409
+ messages: result,
2410
+ duplicatesRemoved,
2411
+ originalCount: messages.length
2412
+ };
2413
+ }
2414
+
2415
+ // src/compression/layers/whitespace.ts
2416
+ function normalizeWhitespace(content) {
2417
+ if (!content) return content;
2418
+ 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();
2419
+ }
2420
+ function normalizeMessagesWhitespace(messages) {
2421
+ let charsSaved = 0;
2422
+ const result = messages.map((message) => {
2423
+ if (!message.content) return message;
2424
+ const originalLength = message.content.length;
2425
+ const normalizedContent = normalizeWhitespace(message.content);
2426
+ charsSaved += originalLength - normalizedContent.length;
2427
+ return {
2428
+ ...message,
2429
+ content: normalizedContent
2430
+ };
2431
+ });
2432
+ return {
2433
+ messages: result,
2434
+ charsSaved
2435
+ };
2436
+ }
2437
+
2438
+ // src/compression/codebook.ts
2439
+ var STATIC_CODEBOOK = {
2440
+ // High-impact: OpenClaw/Agent system prompt patterns (very common)
2441
+ "$OC01": "unbrowse_",
2442
+ // Common prefix in tool names
2443
+ "$OC02": "<location>",
2444
+ "$OC03": "</location>",
2445
+ "$OC04": "<name>",
2446
+ "$OC05": "</name>",
2447
+ "$OC06": "<description>",
2448
+ "$OC07": "</description>",
2449
+ "$OC08": "(may need login)",
2450
+ "$OC09": "API skill for OpenClaw",
2451
+ "$OC10": "endpoints",
2452
+ // Skill/tool markers
2453
+ "$SK01": "<available_skills>",
2454
+ "$SK02": "</available_skills>",
2455
+ "$SK03": "<skill>",
2456
+ "$SK04": "</skill>",
2457
+ // Schema patterns (very common in tool definitions)
2458
+ "$T01": 'type: "function"',
2459
+ "$T02": '"type": "function"',
2460
+ "$T03": '"type": "string"',
2461
+ "$T04": '"type": "object"',
2462
+ "$T05": '"type": "array"',
2463
+ "$T06": '"type": "boolean"',
2464
+ "$T07": '"type": "number"',
2465
+ // Common descriptions
2466
+ "$D01": "description:",
2467
+ "$D02": '"description":',
2468
+ // Common instructions
2469
+ "$I01": "You are a personal assistant",
2470
+ "$I02": "Tool names are case-sensitive",
2471
+ "$I03": "Call tools exactly as listed",
2472
+ "$I04": "Use when",
2473
+ "$I05": "without asking",
2474
+ // Safety phrases
2475
+ "$S01": "Do not manipulate or persuade",
2476
+ "$S02": "Prioritize safety and human oversight",
2477
+ "$S03": "unless explicitly requested",
2478
+ // JSON patterns
2479
+ "$J01": '"required": ["',
2480
+ "$J02": '"properties": {',
2481
+ "$J03": '"additionalProperties": false',
2482
+ // Heartbeat patterns
2483
+ "$H01": "HEARTBEAT_OK",
2484
+ "$H02": "Read HEARTBEAT.md if it exists",
2485
+ // Role markers
2486
+ "$R01": '"role": "system"',
2487
+ "$R02": '"role": "user"',
2488
+ "$R03": '"role": "assistant"',
2489
+ "$R04": '"role": "tool"',
2490
+ // Common endings/phrases
2491
+ "$E01": "would you like to",
2492
+ "$E02": "Let me know if you",
2493
+ "$E03": "internal APIs",
2494
+ "$E04": "session cookies",
2495
+ // BlockRun model aliases (common in prompts)
2496
+ "$M01": "blockrun/",
2497
+ "$M02": "openai/",
2498
+ "$M03": "anthropic/",
2499
+ "$M04": "google/",
2500
+ "$M05": "xai/"
2501
+ };
2502
+ function getInverseCodebook() {
2503
+ const inverse = {};
2504
+ for (const [code, phrase] of Object.entries(STATIC_CODEBOOK)) {
2505
+ inverse[phrase] = code;
2506
+ }
2507
+ return inverse;
2508
+ }
2509
+ function generateCodebookHeader(usedCodes, pathMap = {}) {
2510
+ if (usedCodes.size === 0 && Object.keys(pathMap).length === 0) {
2511
+ return "";
2512
+ }
2513
+ const parts = [];
2514
+ if (usedCodes.size > 0) {
2515
+ const codeEntries = Array.from(usedCodes).map((code) => `${code}=${STATIC_CODEBOOK[code]}`).join(", ");
2516
+ parts.push(`[Dict: ${codeEntries}]`);
2517
+ }
2518
+ if (Object.keys(pathMap).length > 0) {
2519
+ const pathEntries = Object.entries(pathMap).map(([code, path]) => `${code}=${path}`).join(", ");
2520
+ parts.push(`[Paths: ${pathEntries}]`);
2521
+ }
2522
+ return parts.join("\n");
2523
+ }
2524
+
2525
+ // src/compression/layers/dictionary.ts
2526
+ function encodeContent(content, inverseCodebook) {
2527
+ let encoded = content;
2528
+ let substitutions = 0;
2529
+ let charsSaved = 0;
2530
+ const codes = /* @__PURE__ */ new Set();
2531
+ const phrases = Object.keys(inverseCodebook).sort((a, b) => b.length - a.length);
2532
+ for (const phrase of phrases) {
2533
+ const code = inverseCodebook[phrase];
2534
+ const regex = new RegExp(escapeRegex(phrase), "g");
2535
+ const matches = encoded.match(regex);
2536
+ if (matches && matches.length > 0) {
2537
+ encoded = encoded.replace(regex, code);
2538
+ substitutions += matches.length;
2539
+ charsSaved += matches.length * (phrase.length - code.length);
2540
+ codes.add(code);
2541
+ }
2542
+ }
2543
+ return { encoded, substitutions, codes, charsSaved };
2544
+ }
2545
+ function escapeRegex(str) {
2546
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2547
+ }
2548
+ function encodeMessages(messages) {
2549
+ const inverseCodebook = getInverseCodebook();
2550
+ let totalSubstitutions = 0;
2551
+ let totalCharsSaved = 0;
2552
+ const allUsedCodes = /* @__PURE__ */ new Set();
2553
+ const result = messages.map((message) => {
2554
+ if (!message.content) return message;
2555
+ const { encoded, substitutions, codes, charsSaved } = encodeContent(
2556
+ message.content,
2557
+ inverseCodebook
2558
+ );
2559
+ totalSubstitutions += substitutions;
2560
+ totalCharsSaved += charsSaved;
2561
+ codes.forEach((code) => allUsedCodes.add(code));
2562
+ return {
2563
+ ...message,
2564
+ content: encoded
2565
+ };
2566
+ });
2567
+ return {
2568
+ messages: result,
2569
+ substitutionCount: totalSubstitutions,
2570
+ usedCodes: allUsedCodes,
2571
+ charsSaved: totalCharsSaved
2572
+ };
2573
+ }
2574
+
2575
+ // src/compression/layers/paths.ts
2576
+ var PATH_REGEX = /(?:\/[\w.-]+){3,}/g;
2577
+ function extractPaths(messages) {
2578
+ const paths = [];
2579
+ for (const message of messages) {
2580
+ if (!message.content) continue;
2581
+ const matches = message.content.match(PATH_REGEX);
2582
+ if (matches) {
2583
+ paths.push(...matches);
2584
+ }
2585
+ }
2586
+ return paths;
2587
+ }
2588
+ function findFrequentPrefixes(paths) {
2589
+ const prefixCounts = /* @__PURE__ */ new Map();
2590
+ for (const path of paths) {
2591
+ const parts = path.split("/").filter(Boolean);
2592
+ for (let i = 2; i < parts.length; i++) {
2593
+ const prefix = "/" + parts.slice(0, i).join("/") + "/";
2594
+ prefixCounts.set(prefix, (prefixCounts.get(prefix) || 0) + 1);
2595
+ }
2596
+ }
2597
+ return Array.from(prefixCounts.entries()).filter(([_, count]) => count >= 3).sort((a, b) => b[0].length - a[0].length).slice(0, 5).map(([prefix]) => prefix);
2598
+ }
2599
+ function shortenPaths(messages) {
2600
+ const allPaths = extractPaths(messages);
2601
+ if (allPaths.length < 5) {
2602
+ return {
2603
+ messages,
2604
+ pathMap: {},
2605
+ charsSaved: 0
2606
+ };
2607
+ }
2608
+ const prefixes = findFrequentPrefixes(allPaths);
2609
+ if (prefixes.length === 0) {
2610
+ return {
2611
+ messages,
2612
+ pathMap: {},
2613
+ charsSaved: 0
2614
+ };
2615
+ }
2616
+ const pathMap = {};
2617
+ prefixes.forEach((prefix, i) => {
2618
+ pathMap[`$P${i + 1}`] = prefix;
2619
+ });
2620
+ let charsSaved = 0;
2621
+ const result = messages.map((message) => {
2622
+ if (!message.content) return message;
2623
+ let content = message.content;
2624
+ const originalLength = content.length;
2625
+ for (const [code, prefix] of Object.entries(pathMap)) {
2626
+ content = content.split(prefix).join(code + "/");
2627
+ }
2628
+ charsSaved += originalLength - content.length;
2629
+ return {
2630
+ ...message,
2631
+ content
2632
+ };
2633
+ });
2634
+ return {
2635
+ messages: result,
2636
+ pathMap,
2637
+ charsSaved
2638
+ };
2639
+ }
2640
+
2641
+ // src/compression/layers/json-compact.ts
2642
+ function compactJson(jsonString) {
2643
+ try {
2644
+ const parsed = JSON.parse(jsonString);
2645
+ return JSON.stringify(parsed);
2646
+ } catch {
2647
+ return jsonString;
2648
+ }
2649
+ }
2650
+ function looksLikeJson(str) {
2651
+ const trimmed = str.trim();
2652
+ return trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]");
2653
+ }
2654
+ function compactToolCalls(toolCalls) {
2655
+ return toolCalls.map((tc) => ({
2656
+ ...tc,
2657
+ function: {
2658
+ ...tc.function,
2659
+ arguments: compactJson(tc.function.arguments)
2660
+ }
2661
+ }));
2662
+ }
2663
+ function compactMessagesJson(messages) {
2664
+ let charsSaved = 0;
2665
+ const result = messages.map((message) => {
2666
+ let newMessage = { ...message };
2667
+ if (message.tool_calls && message.tool_calls.length > 0) {
2668
+ const originalLength = JSON.stringify(message.tool_calls).length;
2669
+ newMessage.tool_calls = compactToolCalls(message.tool_calls);
2670
+ const newLength = JSON.stringify(newMessage.tool_calls).length;
2671
+ charsSaved += originalLength - newLength;
2672
+ }
2673
+ if (message.role === "tool" && message.content && looksLikeJson(message.content)) {
2674
+ const originalLength = message.content.length;
2675
+ const compacted = compactJson(message.content);
2676
+ charsSaved += originalLength - compacted.length;
2677
+ newMessage.content = compacted;
2678
+ }
2679
+ return newMessage;
2680
+ });
2681
+ return {
2682
+ messages: result,
2683
+ charsSaved
2684
+ };
2685
+ }
2686
+
2687
+ // src/compression/layers/observation.ts
2688
+ var TOOL_RESULT_THRESHOLD = 500;
2689
+ var COMPRESSED_RESULT_MAX = 300;
2690
+ function compressToolResult(content) {
2691
+ if (!content || content.length <= TOOL_RESULT_THRESHOLD) {
2692
+ return content;
2693
+ }
2694
+ const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
2695
+ const errorLines = lines.filter(
2696
+ (l) => /error|exception|failed|denied|refused|timeout|invalid/i.test(l) && l.length < 200
2697
+ );
2698
+ const statusLines = lines.filter(
2699
+ (l) => /success|complete|created|updated|found|result|status|total|count/i.test(l) && l.length < 150
2700
+ );
2701
+ const jsonMatches = [];
2702
+ const jsonPattern = /"(id|name|status|error|message|count|total|url|path)":\s*"?([^",}\n]+)"?/gi;
2703
+ let match;
2704
+ while ((match = jsonPattern.exec(content)) !== null) {
2705
+ jsonMatches.push(`${match[1]}: ${match[2].slice(0, 50)}`);
2706
+ }
2707
+ const firstLine = lines[0]?.slice(0, 100);
2708
+ const lastLine = lines.length > 1 ? lines[lines.length - 1]?.slice(0, 100) : "";
2709
+ const parts = [];
2710
+ if (errorLines.length > 0) {
2711
+ parts.push("[ERR] " + errorLines.slice(0, 3).join(" | "));
2712
+ }
2713
+ if (statusLines.length > 0) {
2714
+ parts.push(statusLines.slice(0, 3).join(" | "));
2715
+ }
2716
+ if (jsonMatches.length > 0) {
2717
+ parts.push(jsonMatches.slice(0, 5).join(", "));
2718
+ }
2719
+ if (parts.length === 0) {
2720
+ parts.push(firstLine || "");
2721
+ if (lines.length > 2) {
2722
+ parts.push(`[...${lines.length - 2} lines...]`);
2723
+ }
2724
+ if (lastLine && lastLine !== firstLine) {
2725
+ parts.push(lastLine);
2726
+ }
2727
+ }
2728
+ let result = parts.join("\n");
2729
+ if (result.length > COMPRESSED_RESULT_MAX) {
2730
+ result = result.slice(0, COMPRESSED_RESULT_MAX - 20) + "\n[...truncated]";
2731
+ }
2732
+ return result;
2733
+ }
2734
+ function deduplicateLargeBlocks(messages) {
2735
+ const blockHashes = /* @__PURE__ */ new Map();
2736
+ let charsSaved = 0;
2737
+ const result = messages.map((msg, idx) => {
2738
+ if (!msg.content || msg.content.length < 500) {
2739
+ return msg;
2740
+ }
2741
+ const blockKey = msg.content.slice(0, 200);
2742
+ if (blockHashes.has(blockKey)) {
2743
+ const firstIdx = blockHashes.get(blockKey);
2744
+ const original = msg.content;
2745
+ const compressed = `[See message #${firstIdx + 1} - same content]`;
2746
+ charsSaved += original.length - compressed.length;
2747
+ return { ...msg, content: compressed };
2748
+ }
2749
+ blockHashes.set(blockKey, idx);
2750
+ return msg;
2751
+ });
2752
+ return { messages: result, charsSaved };
2753
+ }
2754
+ function compressObservations(messages) {
2755
+ let charsSaved = 0;
2756
+ let observationsCompressed = 0;
2757
+ let result = messages.map((msg) => {
2758
+ if (msg.role !== "tool" || !msg.content) {
2759
+ return msg;
2760
+ }
2761
+ const original = msg.content;
2762
+ if (original.length <= TOOL_RESULT_THRESHOLD) {
2763
+ return msg;
2764
+ }
2765
+ const compressed = compressToolResult(original);
2766
+ const saved = original.length - compressed.length;
2767
+ if (saved > 50) {
2768
+ charsSaved += saved;
2769
+ observationsCompressed++;
2770
+ return { ...msg, content: compressed };
2771
+ }
2772
+ return msg;
2773
+ });
2774
+ const dedupResult = deduplicateLargeBlocks(result);
2775
+ result = dedupResult.messages;
2776
+ charsSaved += dedupResult.charsSaved;
2777
+ return {
2778
+ messages: result,
2779
+ charsSaved,
2780
+ observationsCompressed
2781
+ };
2782
+ }
2783
+
2784
+ // src/compression/layers/dynamic-codebook.ts
2785
+ var MIN_PHRASE_LENGTH = 20;
2786
+ var MAX_PHRASE_LENGTH = 200;
2787
+ var MIN_FREQUENCY = 3;
2788
+ var MAX_ENTRIES = 100;
2789
+ var CODE_PREFIX = "$D";
2790
+ function findRepeatedPhrases(allContent) {
2791
+ const phrases = /* @__PURE__ */ new Map();
2792
+ const segments = allContent.split(/(?<=[.!?\n])\s+/);
2793
+ for (const segment of segments) {
2794
+ const trimmed = segment.trim();
2795
+ if (trimmed.length >= MIN_PHRASE_LENGTH && trimmed.length <= MAX_PHRASE_LENGTH) {
2796
+ phrases.set(trimmed, (phrases.get(trimmed) || 0) + 1);
2797
+ }
2798
+ }
2799
+ const lines = allContent.split("\n");
2800
+ for (const line of lines) {
2801
+ const trimmed = line.trim();
2802
+ if (trimmed.length >= MIN_PHRASE_LENGTH && trimmed.length <= MAX_PHRASE_LENGTH) {
2803
+ phrases.set(trimmed, (phrases.get(trimmed) || 0) + 1);
2804
+ }
2805
+ }
2806
+ return phrases;
2807
+ }
2808
+ function buildDynamicCodebook(messages) {
2809
+ let allContent = "";
2810
+ for (const msg of messages) {
2811
+ if (msg.content) {
2812
+ allContent += msg.content + "\n";
2813
+ }
2814
+ }
2815
+ const phrases = findRepeatedPhrases(allContent);
2816
+ const candidates = [];
2817
+ for (const [phrase, count] of phrases.entries()) {
2818
+ if (count >= MIN_FREQUENCY) {
2819
+ const codeLength = 4;
2820
+ const savings = (phrase.length - codeLength) * count;
2821
+ if (savings > 50) {
2822
+ candidates.push({ phrase, count, savings });
2823
+ }
2824
+ }
2825
+ }
2826
+ candidates.sort((a, b) => b.savings - a.savings);
2827
+ const topCandidates = candidates.slice(0, MAX_ENTRIES);
2828
+ const codebook = {};
2829
+ topCandidates.forEach((c, i) => {
2830
+ const code = `${CODE_PREFIX}${String(i + 1).padStart(2, "0")}`;
2831
+ codebook[code] = c.phrase;
2832
+ });
2833
+ return codebook;
2834
+ }
2835
+ function escapeRegex2(str) {
2836
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2837
+ }
2838
+ function applyDynamicCodebook(messages) {
2839
+ const codebook = buildDynamicCodebook(messages);
2840
+ if (Object.keys(codebook).length === 0) {
2841
+ return {
2842
+ messages,
2843
+ charsSaved: 0,
2844
+ dynamicCodes: {},
2845
+ substitutions: 0
2846
+ };
2847
+ }
2848
+ const phraseToCode = {};
2849
+ for (const [code, phrase] of Object.entries(codebook)) {
2850
+ phraseToCode[phrase] = code;
2851
+ }
2852
+ const sortedPhrases = Object.keys(phraseToCode).sort(
2853
+ (a, b) => b.length - a.length
2854
+ );
2855
+ let charsSaved = 0;
2856
+ let substitutions = 0;
2857
+ const result = messages.map((msg) => {
2858
+ if (!msg.content) return msg;
2859
+ let content = msg.content;
2860
+ for (const phrase of sortedPhrases) {
2861
+ const code = phraseToCode[phrase];
2862
+ const regex = new RegExp(escapeRegex2(phrase), "g");
2863
+ const matches = content.match(regex);
2864
+ if (matches) {
2865
+ content = content.replace(regex, code);
2866
+ charsSaved += (phrase.length - code.length) * matches.length;
2867
+ substitutions += matches.length;
2868
+ }
2869
+ }
2870
+ return { ...msg, content };
2871
+ });
2872
+ return {
2873
+ messages: result,
2874
+ charsSaved,
2875
+ dynamicCodes: codebook,
2876
+ substitutions
2877
+ };
2878
+ }
2879
+ function generateDynamicCodebookHeader(codebook) {
2880
+ if (Object.keys(codebook).length === 0) return "";
2881
+ const entries = Object.entries(codebook).slice(0, 20).map(([code, phrase]) => {
2882
+ const displayPhrase = phrase.length > 40 ? phrase.slice(0, 37) + "..." : phrase;
2883
+ return `${code}=${displayPhrase}`;
2884
+ }).join(", ");
2885
+ return `[DynDict: ${entries}]`;
2886
+ }
2887
+
2888
+ // src/compression/index.ts
2889
+ function calculateTotalChars(messages) {
2890
+ return messages.reduce((total, msg) => {
2891
+ let chars = msg.content?.length || 0;
2892
+ if (msg.tool_calls) {
2893
+ chars += JSON.stringify(msg.tool_calls).length;
2894
+ }
2895
+ return total + chars;
2896
+ }, 0);
2897
+ }
2898
+ function cloneMessages(messages) {
2899
+ return JSON.parse(JSON.stringify(messages));
2900
+ }
2901
+ function prependCodebookHeader(messages, usedCodes, pathMap) {
2902
+ const header = generateCodebookHeader(usedCodes, pathMap);
2903
+ if (!header) return messages;
2904
+ const userIndex = messages.findIndex((m) => m.role === "user");
2905
+ if (userIndex === -1) {
2906
+ return [
2907
+ { role: "system", content: header },
2908
+ ...messages
2909
+ ];
2910
+ }
2911
+ return messages.map((msg, i) => {
2912
+ if (i === userIndex) {
2913
+ return {
2914
+ ...msg,
2915
+ content: `${header}
2916
+
2917
+ ${msg.content || ""}`
2918
+ };
2919
+ }
2920
+ return msg;
2921
+ });
2922
+ }
2923
+ async function compressContext(messages, config = {}) {
2924
+ const fullConfig = {
2925
+ ...DEFAULT_COMPRESSION_CONFIG,
2926
+ ...config,
2927
+ layers: {
2928
+ ...DEFAULT_COMPRESSION_CONFIG.layers,
2929
+ ...config.layers
2930
+ },
2931
+ dictionary: {
2932
+ ...DEFAULT_COMPRESSION_CONFIG.dictionary,
2933
+ ...config.dictionary
2934
+ }
2935
+ };
2936
+ if (!fullConfig.enabled) {
2937
+ const originalChars2 = calculateTotalChars(messages);
2938
+ return {
2939
+ messages,
2940
+ originalMessages: messages,
2941
+ originalChars: originalChars2,
2942
+ compressedChars: originalChars2,
2943
+ compressionRatio: 1,
2944
+ stats: {
2945
+ duplicatesRemoved: 0,
2946
+ whitespaceSavedChars: 0,
2947
+ dictionarySubstitutions: 0,
2948
+ pathsShortened: 0,
2949
+ jsonCompactedChars: 0,
2950
+ observationsCompressed: 0,
2951
+ observationCharsSaved: 0,
2952
+ dynamicSubstitutions: 0,
2953
+ dynamicCharsSaved: 0
2954
+ },
2955
+ codebook: {},
2956
+ pathMap: {},
2957
+ dynamicCodes: {}
2958
+ };
2959
+ }
2960
+ const originalMessages = fullConfig.preserveRaw ? cloneMessages(messages) : messages;
2961
+ const originalChars = calculateTotalChars(messages);
2962
+ const stats = {
2963
+ duplicatesRemoved: 0,
2964
+ whitespaceSavedChars: 0,
2965
+ dictionarySubstitutions: 0,
2966
+ pathsShortened: 0,
2967
+ jsonCompactedChars: 0,
2968
+ observationsCompressed: 0,
2969
+ observationCharsSaved: 0,
2970
+ dynamicSubstitutions: 0,
2971
+ dynamicCharsSaved: 0
2972
+ };
2973
+ let result = cloneMessages(messages);
2974
+ let usedCodes = /* @__PURE__ */ new Set();
2975
+ let pathMap = {};
2976
+ let dynamicCodes = {};
2977
+ if (fullConfig.layers.deduplication) {
2978
+ const dedupResult = deduplicateMessages(result);
2979
+ result = dedupResult.messages;
2980
+ stats.duplicatesRemoved = dedupResult.duplicatesRemoved;
2981
+ }
2982
+ if (fullConfig.layers.whitespace) {
2983
+ const wsResult = normalizeMessagesWhitespace(result);
2984
+ result = wsResult.messages;
2985
+ stats.whitespaceSavedChars = wsResult.charsSaved;
2986
+ }
2987
+ if (fullConfig.layers.dictionary) {
2988
+ const dictResult = encodeMessages(result);
2989
+ result = dictResult.messages;
2990
+ stats.dictionarySubstitutions = dictResult.substitutionCount;
2991
+ usedCodes = dictResult.usedCodes;
2992
+ }
2993
+ if (fullConfig.layers.paths) {
2994
+ const pathResult = shortenPaths(result);
2995
+ result = pathResult.messages;
2996
+ pathMap = pathResult.pathMap;
2997
+ stats.pathsShortened = Object.keys(pathMap).length;
2998
+ }
2999
+ if (fullConfig.layers.jsonCompact) {
3000
+ const jsonResult = compactMessagesJson(result);
3001
+ result = jsonResult.messages;
3002
+ stats.jsonCompactedChars = jsonResult.charsSaved;
3003
+ }
3004
+ if (fullConfig.layers.observation) {
3005
+ const obsResult = compressObservations(result);
3006
+ result = obsResult.messages;
3007
+ stats.observationsCompressed = obsResult.observationsCompressed;
3008
+ stats.observationCharsSaved = obsResult.charsSaved;
3009
+ }
3010
+ if (fullConfig.layers.dynamicCodebook) {
3011
+ const dynResult = applyDynamicCodebook(result);
3012
+ result = dynResult.messages;
3013
+ stats.dynamicSubstitutions = dynResult.substitutions;
3014
+ stats.dynamicCharsSaved = dynResult.charsSaved;
3015
+ dynamicCodes = dynResult.dynamicCodes;
3016
+ }
3017
+ if (fullConfig.dictionary.includeCodebookHeader && (usedCodes.size > 0 || Object.keys(pathMap).length > 0 || Object.keys(dynamicCodes).length > 0)) {
3018
+ result = prependCodebookHeader(result, usedCodes, pathMap);
3019
+ if (Object.keys(dynamicCodes).length > 0) {
3020
+ const dynHeader = generateDynamicCodebookHeader(dynamicCodes);
3021
+ if (dynHeader) {
3022
+ const systemIndex = result.findIndex((m) => m.role === "system");
3023
+ if (systemIndex >= 0) {
3024
+ result[systemIndex] = {
3025
+ ...result[systemIndex],
3026
+ content: `${dynHeader}
3027
+ ${result[systemIndex].content || ""}`
3028
+ };
3029
+ }
3030
+ }
3031
+ }
3032
+ }
3033
+ const compressedChars = calculateTotalChars(result);
3034
+ const compressionRatio = compressedChars / originalChars;
3035
+ const usedCodebook = {};
3036
+ usedCodes.forEach((code) => {
3037
+ usedCodebook[code] = STATIC_CODEBOOK[code];
3038
+ });
3039
+ return {
3040
+ messages: result,
3041
+ originalMessages,
3042
+ originalChars,
3043
+ compressedChars,
3044
+ compressionRatio,
3045
+ stats,
3046
+ codebook: usedCodebook,
3047
+ pathMap,
3048
+ dynamicCodes
3049
+ };
3050
+ }
3051
+ function shouldCompress(messages) {
3052
+ const chars = calculateTotalChars(messages);
3053
+ return chars > 5e3;
3054
+ }
3055
+
2311
3056
  // src/session.ts
2312
3057
  var DEFAULT_SESSION_CONFIG = {
2313
3058
  enabled: false,
@@ -2503,7 +3248,7 @@ var ROUTING_PROFILES = /* @__PURE__ */ new Set([
2503
3248
  var FREE_MODEL = "nvidia/gpt-oss-120b";
2504
3249
  var HEARTBEAT_INTERVAL_MS = 2e3;
2505
3250
  var DEFAULT_REQUEST_TIMEOUT_MS = 18e4;
2506
- var MAX_FALLBACK_ATTEMPTS = 3;
3251
+ var MAX_FALLBACK_ATTEMPTS = 5;
2507
3252
  var HEALTH_CHECK_TIMEOUT_MS = 2e3;
2508
3253
  var RATE_LIMIT_COOLDOWN_MS = 6e4;
2509
3254
  var PORT_RETRY_ATTEMPTS = 5;
@@ -2637,7 +3382,10 @@ var PROVIDER_ERROR_PATTERNS = [
2637
3382
  /overloaded/i,
2638
3383
  /temporarily.*unavailable/i,
2639
3384
  /api.*key.*invalid/i,
2640
- /authentication.*failed/i
3385
+ /authentication.*failed/i,
3386
+ /request too large/i,
3387
+ /request.*size.*exceeds/i,
3388
+ /payload too large/i
2641
3389
  ];
2642
3390
  var FALLBACK_STATUS_CODES = [
2643
3391
  400,
@@ -3277,6 +4025,83 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
3277
4025
  options.onError?.(new Error(`Routing failed: ${errorMsg}`));
3278
4026
  }
3279
4027
  }
4028
+ const autoCompress = options.autoCompressRequests ?? true;
4029
+ const compressionThreshold = options.compressionThresholdKB ?? 180;
4030
+ const sizeLimit = options.maxRequestSizeKB ?? 200;
4031
+ const requestSizeKB = Math.ceil(body.length / 1024);
4032
+ if (autoCompress && requestSizeKB > compressionThreshold) {
4033
+ try {
4034
+ console.log(`[ClawRouter] Request size ${requestSizeKB}KB exceeds threshold ${compressionThreshold}KB, applying compression...`);
4035
+ const parsed = JSON.parse(body.toString());
4036
+ if (parsed.messages && parsed.messages.length > 0 && shouldCompress(parsed.messages)) {
4037
+ const compressionResult = await compressContext(parsed.messages, {
4038
+ enabled: true,
4039
+ preserveRaw: false,
4040
+ // Don't need originals in proxy
4041
+ layers: {
4042
+ deduplication: true,
4043
+ // Safe: removes duplicate messages
4044
+ whitespace: true,
4045
+ // Safe: normalizes whitespace
4046
+ dictionary: false,
4047
+ // Disabled: requires model to understand codebook
4048
+ paths: false,
4049
+ // Disabled: requires model to understand path codes
4050
+ jsonCompact: true,
4051
+ // Safe: just removes JSON whitespace
4052
+ observation: false,
4053
+ // Disabled: may lose important context
4054
+ dynamicCodebook: false
4055
+ // Disabled: requires model to understand codes
4056
+ },
4057
+ dictionary: {
4058
+ maxEntries: 50,
4059
+ minPhraseLength: 15,
4060
+ includeCodebookHeader: false
4061
+ }
4062
+ });
4063
+ const compressedSizeKB = Math.ceil(compressionResult.compressedChars / 1024);
4064
+ const savings = ((requestSizeKB - compressedSizeKB) / requestSizeKB * 100).toFixed(1);
4065
+ console.log(
4066
+ `[ClawRouter] Compressed ${requestSizeKB}KB \u2192 ${compressedSizeKB}KB (${savings}% reduction)`
4067
+ );
4068
+ parsed.messages = compressionResult.messages;
4069
+ body = Buffer.from(JSON.stringify(parsed));
4070
+ if (compressedSizeKB > sizeLimit) {
4071
+ const errorMsg = {
4072
+ error: {
4073
+ message: `Request size ${compressedSizeKB}KB still exceeds limit after compression (original: ${requestSizeKB}KB). Please reduce context size.`,
4074
+ type: "request_too_large",
4075
+ original_size_kb: requestSizeKB,
4076
+ compressed_size_kb: compressedSizeKB,
4077
+ limit_kb: sizeLimit,
4078
+ help: "Try: 1) Remove old messages from history, 2) Summarize large tool results, 3) Use direct API for very large contexts"
4079
+ }
4080
+ };
4081
+ res.writeHead(413, { "Content-Type": "application/json" });
4082
+ res.end(JSON.stringify(errorMsg));
4083
+ return;
4084
+ }
4085
+ }
4086
+ } catch (err) {
4087
+ console.warn(`[ClawRouter] Compression failed: ${err instanceof Error ? err.message : String(err)}`);
4088
+ }
4089
+ }
4090
+ const finalSizeKB = Math.ceil(body.length / 1024);
4091
+ if (finalSizeKB > sizeLimit) {
4092
+ const errorMsg = {
4093
+ error: {
4094
+ message: `Request size ${finalSizeKB}KB exceeds limit ${sizeLimit}KB. Please reduce context size.`,
4095
+ type: "request_too_large",
4096
+ size_kb: finalSizeKB,
4097
+ limit_kb: sizeLimit,
4098
+ help: "Try: 1) Remove old messages from history, 2) Summarize large tool results, 3) Enable compression (autoCompressRequests: true)"
4099
+ }
4100
+ };
4101
+ res.writeHead(413, { "Content-Type": "application/json" });
4102
+ res.end(JSON.stringify(errorMsg));
4103
+ return;
4104
+ }
3280
4105
  const dedupKey = RequestDeduplicator.hash(body);
3281
4106
  const cached = deduplicator.getCached(dedupKey);
3282
4107
  if (cached) {
@@ -3688,8 +4513,17 @@ var WALLET_FILE = join4(WALLET_DIR, "wallet.key");
3688
4513
  async function loadSavedWallet() {
3689
4514
  try {
3690
4515
  const key = (await readFile2(WALLET_FILE, "utf-8")).trim();
3691
- if (key.startsWith("0x") && key.length === 66) return key;
3692
- } catch {
4516
+ if (key.startsWith("0x") && key.length === 66) {
4517
+ console.log(`[ClawRouter] \u2713 Loaded existing wallet from ${WALLET_FILE}`);
4518
+ return key;
4519
+ }
4520
+ console.warn(`[ClawRouter] \u26A0 Wallet file exists but is invalid (wrong format)`);
4521
+ } catch (err) {
4522
+ if (err.code !== "ENOENT") {
4523
+ console.error(
4524
+ `[ClawRouter] \u2717 Failed to read wallet file: ${err instanceof Error ? err.message : String(err)}`
4525
+ );
4526
+ }
3693
4527
  }
3694
4528
  return void 0;
3695
4529
  }
@@ -3698,6 +4532,17 @@ async function generateAndSaveWallet() {
3698
4532
  const account = privateKeyToAccount3(key);
3699
4533
  await mkdir2(WALLET_DIR, { recursive: true });
3700
4534
  await writeFile(WALLET_FILE, key + "\n", { mode: 384 });
4535
+ try {
4536
+ const verification = (await readFile2(WALLET_FILE, "utf-8")).trim();
4537
+ if (verification !== key) {
4538
+ throw new Error("Wallet file verification failed - content mismatch");
4539
+ }
4540
+ console.log(`[ClawRouter] \u2713 Wallet saved and verified at ${WALLET_FILE}`);
4541
+ } catch (err) {
4542
+ throw new Error(
4543
+ `Failed to verify wallet file after creation: ${err instanceof Error ? err.message : String(err)}`
4544
+ );
4545
+ }
3701
4546
  return { key, address: account.address };
3702
4547
  }
3703
4548
  async function resolveOrGenerateWalletKey() {