@blockrun/runcode 2.2.7 → 2.4.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/agent/commands.js +36 -0
- package/dist/agent/compact.js +9 -5
- package/dist/agent/loop.js +41 -16
- package/dist/agent/optimize.d.ts +4 -7
- package/dist/agent/optimize.js +29 -11
- package/dist/agent/tokens.js +2 -1
- package/dist/compression/adapter.d.ts +13 -0
- package/dist/compression/adapter.js +104 -0
- package/dist/compression/codebook.d.ts +23 -0
- package/dist/compression/codebook.js +118 -0
- package/dist/compression/index.d.ts +32 -0
- package/dist/compression/index.js +258 -0
- package/dist/compression/layers/deduplication.d.ts +27 -0
- package/dist/compression/layers/deduplication.js +97 -0
- package/dist/compression/layers/dictionary.d.ts +20 -0
- package/dist/compression/layers/dictionary.js +67 -0
- package/dist/compression/layers/dynamic-codebook.d.ts +25 -0
- package/dist/compression/layers/dynamic-codebook.js +145 -0
- package/dist/compression/layers/json-compact.d.ts +22 -0
- package/dist/compression/layers/json-compact.js +74 -0
- package/dist/compression/layers/observation.d.ts +20 -0
- package/dist/compression/layers/observation.js +126 -0
- package/dist/compression/layers/paths.d.ts +23 -0
- package/dist/compression/layers/paths.js +107 -0
- package/dist/compression/layers/whitespace.d.ts +26 -0
- package/dist/compression/layers/whitespace.js +57 -0
- package/dist/compression/types.d.ts +83 -0
- package/dist/compression/types.js +26 -0
- package/package.json +1 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 5: JSON Compaction
|
|
3
|
+
*
|
|
4
|
+
* Minifies JSON in tool_call arguments and tool results.
|
|
5
|
+
* Removes pretty-print whitespace from JSON strings.
|
|
6
|
+
*
|
|
7
|
+
* Safe for LLM: JSON semantics unchanged.
|
|
8
|
+
* Expected savings: 2-4%
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Compact a JSON string by parsing and re-stringifying without formatting.
|
|
12
|
+
*/
|
|
13
|
+
function compactJson(jsonString) {
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(jsonString);
|
|
16
|
+
return JSON.stringify(parsed);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Not valid JSON, return as-is
|
|
20
|
+
return jsonString;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Check if a string looks like JSON (starts with { or [).
|
|
25
|
+
*/
|
|
26
|
+
function looksLikeJson(str) {
|
|
27
|
+
const trimmed = str.trim();
|
|
28
|
+
return ((trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
|
29
|
+
(trimmed.startsWith("[") && trimmed.endsWith("]")));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Compact tool_call arguments in a message.
|
|
33
|
+
*/
|
|
34
|
+
function compactToolCalls(toolCalls) {
|
|
35
|
+
return toolCalls.map((tc) => ({
|
|
36
|
+
...tc,
|
|
37
|
+
function: {
|
|
38
|
+
...tc.function,
|
|
39
|
+
arguments: compactJson(tc.function.arguments),
|
|
40
|
+
},
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Apply JSON compaction to all messages.
|
|
45
|
+
*
|
|
46
|
+
* Targets:
|
|
47
|
+
* - tool_call arguments (in assistant messages)
|
|
48
|
+
* - tool message content (often JSON)
|
|
49
|
+
*/
|
|
50
|
+
export function compactMessagesJson(messages) {
|
|
51
|
+
let charsSaved = 0;
|
|
52
|
+
const result = messages.map((message) => {
|
|
53
|
+
const newMessage = { ...message };
|
|
54
|
+
// Compact tool_calls arguments
|
|
55
|
+
if (message.tool_calls && message.tool_calls.length > 0) {
|
|
56
|
+
const originalLength = JSON.stringify(message.tool_calls).length;
|
|
57
|
+
newMessage.tool_calls = compactToolCalls(message.tool_calls);
|
|
58
|
+
const newLength = JSON.stringify(newMessage.tool_calls).length;
|
|
59
|
+
charsSaved += originalLength - newLength;
|
|
60
|
+
}
|
|
61
|
+
// Compact tool message content if it looks like JSON
|
|
62
|
+
if (message.role === "tool" && message.content && typeof message.content === "string" && looksLikeJson(message.content)) {
|
|
63
|
+
const originalLength = message.content.length;
|
|
64
|
+
const compacted = compactJson(message.content);
|
|
65
|
+
charsSaved += originalLength - compacted.length;
|
|
66
|
+
newMessage.content = compacted;
|
|
67
|
+
}
|
|
68
|
+
return newMessage;
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
messages: result,
|
|
72
|
+
charsSaved,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L6: Observation Compression (AGGRESSIVE)
|
|
3
|
+
*
|
|
4
|
+
* Inspired by claw-compactor's 97% compression on tool results.
|
|
5
|
+
* Tool call results (especially large ones) are summarized to key info only.
|
|
6
|
+
*
|
|
7
|
+
* This is the biggest compression win - tool outputs can be 10KB+ but
|
|
8
|
+
* only ~200 chars of actual useful information.
|
|
9
|
+
*/
|
|
10
|
+
import { NormalizedMessage } from "../types.js";
|
|
11
|
+
interface ObservationResult {
|
|
12
|
+
messages: NormalizedMessage[];
|
|
13
|
+
charsSaved: number;
|
|
14
|
+
observationsCompressed: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Compress tool results in messages.
|
|
18
|
+
*/
|
|
19
|
+
export declare function compressObservations(messages: NormalizedMessage[]): ObservationResult;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L6: Observation Compression (AGGRESSIVE)
|
|
3
|
+
*
|
|
4
|
+
* Inspired by claw-compactor's 97% compression on tool results.
|
|
5
|
+
* Tool call results (especially large ones) are summarized to key info only.
|
|
6
|
+
*
|
|
7
|
+
* This is the biggest compression win - tool outputs can be 10KB+ but
|
|
8
|
+
* only ~200 chars of actual useful information.
|
|
9
|
+
*/
|
|
10
|
+
// Max length for tool results before compression kicks in
|
|
11
|
+
const TOOL_RESULT_THRESHOLD = 500;
|
|
12
|
+
// Max length to compress tool results down to
|
|
13
|
+
const COMPRESSED_RESULT_MAX = 300;
|
|
14
|
+
/**
|
|
15
|
+
* Extract key information from tool result.
|
|
16
|
+
* Keeps: errors, key values, status, first/last important lines.
|
|
17
|
+
*/
|
|
18
|
+
function compressToolResult(content) {
|
|
19
|
+
if (!content || content.length <= TOOL_RESULT_THRESHOLD) {
|
|
20
|
+
return content;
|
|
21
|
+
}
|
|
22
|
+
const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
23
|
+
// Priority 1: Error messages (always keep)
|
|
24
|
+
const errorLines = lines.filter((l) => /error|exception|failed|denied|refused|timeout|invalid/i.test(l) &&
|
|
25
|
+
l.length < 200);
|
|
26
|
+
// Priority 2: Status/result lines
|
|
27
|
+
const statusLines = lines.filter((l) => /success|complete|created|updated|found|result|status|total|count/i.test(l) &&
|
|
28
|
+
l.length < 150);
|
|
29
|
+
// Priority 3: Key JSON fields (extract important values)
|
|
30
|
+
const jsonMatches = [];
|
|
31
|
+
const jsonPattern = /"(id|name|status|error|message|count|total|url|path)":\s*"?([^",}\n]+)"?/gi;
|
|
32
|
+
let match;
|
|
33
|
+
while ((match = jsonPattern.exec(content)) !== null) {
|
|
34
|
+
jsonMatches.push(`${match[1]}: ${match[2].slice(0, 50)}`);
|
|
35
|
+
}
|
|
36
|
+
// Priority 4: First and last meaningful lines
|
|
37
|
+
const firstLine = lines[0]?.slice(0, 100);
|
|
38
|
+
const lastLine = lines.length > 1 ? lines[lines.length - 1]?.slice(0, 100) : "";
|
|
39
|
+
// Build compressed observation
|
|
40
|
+
const parts = [];
|
|
41
|
+
if (errorLines.length > 0) {
|
|
42
|
+
parts.push("[ERR] " + errorLines.slice(0, 3).join(" | "));
|
|
43
|
+
}
|
|
44
|
+
if (statusLines.length > 0) {
|
|
45
|
+
parts.push(statusLines.slice(0, 3).join(" | "));
|
|
46
|
+
}
|
|
47
|
+
if (jsonMatches.length > 0) {
|
|
48
|
+
parts.push(jsonMatches.slice(0, 5).join(", "));
|
|
49
|
+
}
|
|
50
|
+
if (parts.length === 0) {
|
|
51
|
+
// Fallback: keep first/last lines with truncation marker
|
|
52
|
+
parts.push(firstLine || "");
|
|
53
|
+
if (lines.length > 2) {
|
|
54
|
+
parts.push(`[...${lines.length - 2} lines...]`);
|
|
55
|
+
}
|
|
56
|
+
if (lastLine && lastLine !== firstLine) {
|
|
57
|
+
parts.push(lastLine);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
let result = parts.join("\n");
|
|
61
|
+
// Final length cap
|
|
62
|
+
if (result.length > COMPRESSED_RESULT_MAX) {
|
|
63
|
+
result = result.slice(0, COMPRESSED_RESULT_MAX - 20) + "\n[...truncated]";
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Compress large repeated content blocks.
|
|
69
|
+
* Detects when same large block appears multiple times.
|
|
70
|
+
*/
|
|
71
|
+
function deduplicateLargeBlocks(messages) {
|
|
72
|
+
const blockHashes = new Map(); // hash -> first occurrence index
|
|
73
|
+
let charsSaved = 0;
|
|
74
|
+
const result = messages.map((msg, idx) => {
|
|
75
|
+
if (!msg.content || typeof msg.content !== "string" || msg.content.length < 500) {
|
|
76
|
+
return msg;
|
|
77
|
+
}
|
|
78
|
+
// Hash first 200 chars as block identifier
|
|
79
|
+
const blockKey = msg.content.slice(0, 200);
|
|
80
|
+
if (blockHashes.has(blockKey)) {
|
|
81
|
+
const firstIdx = blockHashes.get(blockKey);
|
|
82
|
+
const original = msg.content;
|
|
83
|
+
const compressed = `[See message #${firstIdx + 1} - same content]`;
|
|
84
|
+
charsSaved += original.length - compressed.length;
|
|
85
|
+
return { ...msg, content: compressed };
|
|
86
|
+
}
|
|
87
|
+
blockHashes.set(blockKey, idx);
|
|
88
|
+
return msg;
|
|
89
|
+
});
|
|
90
|
+
return { messages: result, charsSaved };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Compress tool results in messages.
|
|
94
|
+
*/
|
|
95
|
+
export function compressObservations(messages) {
|
|
96
|
+
let charsSaved = 0;
|
|
97
|
+
let observationsCompressed = 0;
|
|
98
|
+
// First pass: compress individual tool results
|
|
99
|
+
let result = messages.map((msg) => {
|
|
100
|
+
// Only compress tool role messages (these are tool call results)
|
|
101
|
+
if (msg.role !== "tool" || !msg.content || typeof msg.content !== "string") {
|
|
102
|
+
return msg;
|
|
103
|
+
}
|
|
104
|
+
const original = msg.content;
|
|
105
|
+
if (original.length <= TOOL_RESULT_THRESHOLD) {
|
|
106
|
+
return msg;
|
|
107
|
+
}
|
|
108
|
+
const compressed = compressToolResult(original);
|
|
109
|
+
const saved = original.length - compressed.length;
|
|
110
|
+
if (saved > 50) {
|
|
111
|
+
charsSaved += saved;
|
|
112
|
+
observationsCompressed++;
|
|
113
|
+
return { ...msg, content: compressed };
|
|
114
|
+
}
|
|
115
|
+
return msg;
|
|
116
|
+
});
|
|
117
|
+
// Second pass: deduplicate large repeated blocks
|
|
118
|
+
const dedupResult = deduplicateLargeBlocks(result);
|
|
119
|
+
result = dedupResult.messages;
|
|
120
|
+
charsSaved += dedupResult.charsSaved;
|
|
121
|
+
return {
|
|
122
|
+
messages: result,
|
|
123
|
+
charsSaved,
|
|
124
|
+
observationsCompressed,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 4: Path Shortening
|
|
3
|
+
*
|
|
4
|
+
* Detects common filesystem path prefixes and replaces them with short codes.
|
|
5
|
+
* Common in coding assistant contexts with repeated file paths.
|
|
6
|
+
*
|
|
7
|
+
* Safe for LLM: Lossless abbreviation with path map header.
|
|
8
|
+
* Expected savings: 1-3%
|
|
9
|
+
*/
|
|
10
|
+
import { NormalizedMessage } from "../types.js";
|
|
11
|
+
export interface PathShorteningResult {
|
|
12
|
+
messages: NormalizedMessage[];
|
|
13
|
+
pathMap: Record<string, string>;
|
|
14
|
+
charsSaved: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Apply path shortening to all messages.
|
|
18
|
+
*/
|
|
19
|
+
export declare function shortenPaths(messages: NormalizedMessage[]): PathShorteningResult;
|
|
20
|
+
/**
|
|
21
|
+
* Generate the path map header for the codebook.
|
|
22
|
+
*/
|
|
23
|
+
export declare function generatePathMapHeader(pathMap: Record<string, string>): string;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 4: Path Shortening
|
|
3
|
+
*
|
|
4
|
+
* Detects common filesystem path prefixes and replaces them with short codes.
|
|
5
|
+
* Common in coding assistant contexts with repeated file paths.
|
|
6
|
+
*
|
|
7
|
+
* Safe for LLM: Lossless abbreviation with path map header.
|
|
8
|
+
* Expected savings: 1-3%
|
|
9
|
+
*/
|
|
10
|
+
// Regex to match filesystem paths
|
|
11
|
+
const PATH_REGEX = /(?:\/[\w.-]+){3,}/g;
|
|
12
|
+
/**
|
|
13
|
+
* Extract all paths from messages and find common prefixes.
|
|
14
|
+
*/
|
|
15
|
+
function extractPaths(messages) {
|
|
16
|
+
const paths = [];
|
|
17
|
+
for (const message of messages) {
|
|
18
|
+
if (!message.content || typeof message.content !== "string")
|
|
19
|
+
continue;
|
|
20
|
+
const matches = message.content.match(PATH_REGEX);
|
|
21
|
+
if (matches) {
|
|
22
|
+
paths.push(...matches);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return paths;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Group paths by their common prefixes.
|
|
29
|
+
* Returns prefixes that appear at least 3 times.
|
|
30
|
+
*/
|
|
31
|
+
function findFrequentPrefixes(paths) {
|
|
32
|
+
const prefixCounts = new Map();
|
|
33
|
+
for (const path of paths) {
|
|
34
|
+
const parts = path.split("/").filter(Boolean);
|
|
35
|
+
// Try prefixes of different lengths
|
|
36
|
+
for (let i = 2; i < parts.length; i++) {
|
|
37
|
+
const prefix = "/" + parts.slice(0, i).join("/") + "/";
|
|
38
|
+
prefixCounts.set(prefix, (prefixCounts.get(prefix) || 0) + 1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Return prefixes that appear 3+ times, sorted by length (longest first)
|
|
42
|
+
return Array.from(prefixCounts.entries())
|
|
43
|
+
.filter(([, count]) => count >= 3)
|
|
44
|
+
.sort((a, b) => b[0].length - a[0].length)
|
|
45
|
+
.slice(0, 5) // Max 5 path codes
|
|
46
|
+
.map(([prefix]) => prefix);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Apply path shortening to all messages.
|
|
50
|
+
*/
|
|
51
|
+
export function shortenPaths(messages) {
|
|
52
|
+
const allPaths = extractPaths(messages);
|
|
53
|
+
if (allPaths.length < 5) {
|
|
54
|
+
// Not enough paths to benefit from shortening
|
|
55
|
+
return {
|
|
56
|
+
messages,
|
|
57
|
+
pathMap: {},
|
|
58
|
+
charsSaved: 0,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const prefixes = findFrequentPrefixes(allPaths);
|
|
62
|
+
if (prefixes.length === 0) {
|
|
63
|
+
return {
|
|
64
|
+
messages,
|
|
65
|
+
pathMap: {},
|
|
66
|
+
charsSaved: 0,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// Create path map
|
|
70
|
+
const pathMap = {};
|
|
71
|
+
prefixes.forEach((prefix, i) => {
|
|
72
|
+
pathMap[`$P${i + 1}`] = prefix;
|
|
73
|
+
});
|
|
74
|
+
// Replace paths in messages
|
|
75
|
+
let charsSaved = 0;
|
|
76
|
+
const result = messages.map((message) => {
|
|
77
|
+
if (!message.content || typeof message.content !== "string")
|
|
78
|
+
return message;
|
|
79
|
+
let content = message.content;
|
|
80
|
+
const originalLength = content.length;
|
|
81
|
+
// Replace prefixes (longest first to avoid partial replacements)
|
|
82
|
+
for (const [code, prefix] of Object.entries(pathMap)) {
|
|
83
|
+
content = content.split(prefix).join(code + "/");
|
|
84
|
+
}
|
|
85
|
+
charsSaved += originalLength - content.length;
|
|
86
|
+
return {
|
|
87
|
+
...message,
|
|
88
|
+
content,
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
messages: result,
|
|
93
|
+
pathMap,
|
|
94
|
+
charsSaved,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Generate the path map header for the codebook.
|
|
99
|
+
*/
|
|
100
|
+
export function generatePathMapHeader(pathMap) {
|
|
101
|
+
if (Object.keys(pathMap).length === 0)
|
|
102
|
+
return "";
|
|
103
|
+
const entries = Object.entries(pathMap)
|
|
104
|
+
.map(([code, path]) => `${code}=${path}`)
|
|
105
|
+
.join(", ");
|
|
106
|
+
return `[Paths: ${entries}]`;
|
|
107
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 2: Whitespace Normalization
|
|
3
|
+
*
|
|
4
|
+
* Reduces excessive whitespace without changing semantic meaning.
|
|
5
|
+
*
|
|
6
|
+
* Safe for LLM: Tokenizers normalize whitespace anyway.
|
|
7
|
+
* Expected savings: 3-8%
|
|
8
|
+
*/
|
|
9
|
+
import { NormalizedMessage } from "../types.js";
|
|
10
|
+
export interface WhitespaceResult {
|
|
11
|
+
messages: NormalizedMessage[];
|
|
12
|
+
charsSaved: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Normalize whitespace in a string.
|
|
16
|
+
*
|
|
17
|
+
* - Max 2 consecutive newlines
|
|
18
|
+
* - Remove trailing whitespace from lines
|
|
19
|
+
* - Normalize tabs to spaces
|
|
20
|
+
* - Trim start/end
|
|
21
|
+
*/
|
|
22
|
+
export declare function normalizeWhitespace(content: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Apply whitespace normalization to all messages.
|
|
25
|
+
*/
|
|
26
|
+
export declare function normalizeMessagesWhitespace(messages: NormalizedMessage[]): WhitespaceResult;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 2: Whitespace Normalization
|
|
3
|
+
*
|
|
4
|
+
* Reduces excessive whitespace without changing semantic meaning.
|
|
5
|
+
*
|
|
6
|
+
* Safe for LLM: Tokenizers normalize whitespace anyway.
|
|
7
|
+
* Expected savings: 3-8%
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Normalize whitespace in a string.
|
|
11
|
+
*
|
|
12
|
+
* - Max 2 consecutive newlines
|
|
13
|
+
* - Remove trailing whitespace from lines
|
|
14
|
+
* - Normalize tabs to spaces
|
|
15
|
+
* - Trim start/end
|
|
16
|
+
*/
|
|
17
|
+
export function normalizeWhitespace(content) {
|
|
18
|
+
if (!content)
|
|
19
|
+
return content;
|
|
20
|
+
return content
|
|
21
|
+
// Normalize line endings
|
|
22
|
+
.replace(/\r\n/g, "\n")
|
|
23
|
+
.replace(/\r/g, "\n")
|
|
24
|
+
// Max 2 consecutive newlines (preserve paragraph breaks)
|
|
25
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
26
|
+
// Remove trailing whitespace from each line
|
|
27
|
+
.replace(/[ \t]+$/gm, "")
|
|
28
|
+
// Normalize multiple spaces to single (except at line start for indentation)
|
|
29
|
+
.replace(/([^\n]) {2,}/g, "$1 ")
|
|
30
|
+
// Reduce excessive indentation (more than 8 spaces → 2 spaces per level)
|
|
31
|
+
.replace(/^[ ]{8,}/gm, (match) => " ".repeat(Math.ceil(match.length / 4)))
|
|
32
|
+
// Normalize tabs to 2 spaces
|
|
33
|
+
.replace(/\t/g, " ")
|
|
34
|
+
// Trim
|
|
35
|
+
.trim();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Apply whitespace normalization to all messages.
|
|
39
|
+
*/
|
|
40
|
+
export function normalizeMessagesWhitespace(messages) {
|
|
41
|
+
let charsSaved = 0;
|
|
42
|
+
const result = messages.map((message) => {
|
|
43
|
+
if (!message.content || typeof message.content !== "string")
|
|
44
|
+
return message;
|
|
45
|
+
const originalLength = message.content.length;
|
|
46
|
+
const normalizedContent = normalizeWhitespace(message.content);
|
|
47
|
+
charsSaved += originalLength - normalizedContent.length;
|
|
48
|
+
return {
|
|
49
|
+
...message,
|
|
50
|
+
content: normalizedContent,
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
messages: result,
|
|
55
|
+
charsSaved,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM-Safe Context Compression Types
|
|
3
|
+
*
|
|
4
|
+
* Types for the 5-layer compression system that reduces token usage
|
|
5
|
+
* while preserving semantic meaning for LLM queries.
|
|
6
|
+
*/
|
|
7
|
+
export type ContentPart = {
|
|
8
|
+
type: string;
|
|
9
|
+
text?: string;
|
|
10
|
+
image_url?: {
|
|
11
|
+
url: string;
|
|
12
|
+
detail?: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export interface NormalizedMessage {
|
|
16
|
+
role: "system" | "user" | "assistant" | "tool";
|
|
17
|
+
content: string | ContentPart[] | null;
|
|
18
|
+
tool_call_id?: string;
|
|
19
|
+
tool_calls?: ToolCall[];
|
|
20
|
+
name?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ToolCall {
|
|
23
|
+
id: string;
|
|
24
|
+
type: "function";
|
|
25
|
+
function: {
|
|
26
|
+
name: string;
|
|
27
|
+
arguments: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface CompressionConfig {
|
|
31
|
+
enabled: boolean;
|
|
32
|
+
preserveRaw: boolean;
|
|
33
|
+
layers: {
|
|
34
|
+
deduplication: boolean;
|
|
35
|
+
whitespace: boolean;
|
|
36
|
+
dictionary: boolean;
|
|
37
|
+
paths: boolean;
|
|
38
|
+
jsonCompact: boolean;
|
|
39
|
+
observation: boolean;
|
|
40
|
+
dynamicCodebook: boolean;
|
|
41
|
+
};
|
|
42
|
+
dictionary: {
|
|
43
|
+
maxEntries: number;
|
|
44
|
+
minPhraseLength: number;
|
|
45
|
+
includeCodebookHeader: boolean;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export interface CompressionStats {
|
|
49
|
+
duplicatesRemoved: number;
|
|
50
|
+
whitespaceSavedChars: number;
|
|
51
|
+
dictionarySubstitutions: number;
|
|
52
|
+
pathsShortened: number;
|
|
53
|
+
jsonCompactedChars: number;
|
|
54
|
+
observationsCompressed: number;
|
|
55
|
+
observationCharsSaved: number;
|
|
56
|
+
dynamicSubstitutions: number;
|
|
57
|
+
dynamicCharsSaved: number;
|
|
58
|
+
}
|
|
59
|
+
export interface CompressionResult {
|
|
60
|
+
messages: NormalizedMessage[];
|
|
61
|
+
originalMessages: NormalizedMessage[];
|
|
62
|
+
originalChars: number;
|
|
63
|
+
compressedChars: number;
|
|
64
|
+
compressionRatio: number;
|
|
65
|
+
stats: CompressionStats;
|
|
66
|
+
codebook: Record<string, string>;
|
|
67
|
+
pathMap: Record<string, string>;
|
|
68
|
+
dynamicCodes: Record<string, string>;
|
|
69
|
+
}
|
|
70
|
+
export interface CompressionLogData {
|
|
71
|
+
enabled: boolean;
|
|
72
|
+
ratio: number;
|
|
73
|
+
original_chars: number;
|
|
74
|
+
compressed_chars: number;
|
|
75
|
+
stats: {
|
|
76
|
+
duplicates_removed: number;
|
|
77
|
+
whitespace_saved: number;
|
|
78
|
+
dictionary_subs: number;
|
|
79
|
+
paths_shortened: number;
|
|
80
|
+
json_compacted: number;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export declare const DEFAULT_COMPRESSION_CONFIG: CompressionConfig;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM-Safe Context Compression Types
|
|
3
|
+
*
|
|
4
|
+
* Types for the 5-layer compression system that reduces token usage
|
|
5
|
+
* while preserving semantic meaning for LLM queries.
|
|
6
|
+
*/
|
|
7
|
+
// Default configuration - CONSERVATIVE settings for model compatibility
|
|
8
|
+
// Only enable layers that don't require the model to decode anything
|
|
9
|
+
export const DEFAULT_COMPRESSION_CONFIG = {
|
|
10
|
+
enabled: true,
|
|
11
|
+
preserveRaw: true,
|
|
12
|
+
layers: {
|
|
13
|
+
deduplication: true, // Safe: removes duplicate messages
|
|
14
|
+
whitespace: true, // Safe: normalizes whitespace
|
|
15
|
+
dictionary: false, // DISABLED: requires model to understand codebook
|
|
16
|
+
paths: false, // DISABLED: requires model to understand path codes
|
|
17
|
+
jsonCompact: true, // Safe: just removes JSON whitespace
|
|
18
|
+
observation: false, // DISABLED: may lose important context
|
|
19
|
+
dynamicCodebook: false, // DISABLED: requires model to understand codes
|
|
20
|
+
},
|
|
21
|
+
dictionary: {
|
|
22
|
+
maxEntries: 50,
|
|
23
|
+
minPhraseLength: 15,
|
|
24
|
+
includeCodebookHeader: false, // No codebook header needed
|
|
25
|
+
},
|
|
26
|
+
};
|