@tyvm/knowhow 0.0.61 → 0.0.63
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/package.json +1 -1
- package/src/chat/modules/AgentModule.ts +6 -6
- package/src/processors/JsonCompressor.ts +496 -0
- package/src/processors/TokenCompressor.ts +194 -125
- package/src/processors/ToolResponseCache.ts +64 -11
- package/src/processors/index.ts +1 -0
- package/tests/compressor/bigstring.test.ts +352 -2
- package/tests/compressor/githubjson.txt +1 -0
- package/tests/compressor/toolResponseCache.test.ts +303 -0
- package/ts_build/package.json +1 -1
- package/ts_build/src/chat/modules/AgentModule.js +5 -4
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/processors/JsonCompressor.d.ts +36 -0
- package/ts_build/src/processors/JsonCompressor.js +295 -0
- package/ts_build/src/processors/JsonCompressor.js.map +1 -0
- package/ts_build/src/processors/TokenCompressor.d.ts +23 -5
- package/ts_build/src/processors/TokenCompressor.js +106 -70
- package/ts_build/src/processors/TokenCompressor.js.map +1 -1
- package/ts_build/src/processors/ToolResponseCache.d.ts +4 -2
- package/ts_build/src/processors/ToolResponseCache.js +50 -10
- package/ts_build/src/processors/ToolResponseCache.js.map +1 -1
- package/ts_build/src/processors/index.d.ts +1 -0
- package/ts_build/src/processors/index.js +3 -1
- package/ts_build/src/processors/index.js.map +1 -1
- package/ts_build/tests/compressor/bigstring.test.js +209 -0
- package/ts_build/tests/compressor/bigstring.test.js.map +1 -1
- package/ts_build/tests/compressor/toolResponseCache.test.d.ts +1 -0
- package/ts_build/tests/compressor/toolResponseCache.test.js +240 -0
- package/ts_build/tests/compressor/toolResponseCache.test.js.map +1 -0
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
import { Message, Tool } from "../clients/types";
|
|
2
2
|
import { MessageProcessorFunction } from "../services/MessageProcessor";
|
|
3
3
|
import { ToolsService } from "../services";
|
|
4
|
+
import {
|
|
5
|
+
JsonCompressor,
|
|
6
|
+
JsonSchema,
|
|
7
|
+
CompressionMetadata,
|
|
8
|
+
JsonCompressorStorage,
|
|
9
|
+
} from "./JsonCompressor";
|
|
10
|
+
|
|
11
|
+
export interface KeyInfo {
|
|
12
|
+
key: string;
|
|
13
|
+
size: number;
|
|
14
|
+
preview: string;
|
|
15
|
+
tokens?: number;
|
|
16
|
+
type?: string;
|
|
17
|
+
depth?: number;
|
|
18
|
+
childKeys?: string[];
|
|
19
|
+
nextChunkKey?: string;
|
|
20
|
+
}
|
|
4
21
|
|
|
5
22
|
interface TokenCompressorStorage {
|
|
6
23
|
[key: string]: string;
|
|
7
24
|
}
|
|
8
25
|
|
|
9
|
-
export class TokenCompressor {
|
|
26
|
+
export class TokenCompressor implements JsonCompressorStorage {
|
|
10
27
|
private storage: TokenCompressorStorage = {};
|
|
11
28
|
private keyPrefix: string = "compressed_";
|
|
12
29
|
private toolName: string = expandTokensDefinition.function.name;
|
|
@@ -16,38 +33,47 @@ export class TokenCompressor {
|
|
|
16
33
|
private characterLimit: number = this.compressionThreshold * 4;
|
|
17
34
|
|
|
18
35
|
// Largest size retrievable without re-compressing
|
|
19
|
-
|
|
36
|
+
public maxTokens: number = this.compressionThreshold * 2;
|
|
37
|
+
|
|
38
|
+
// JSON compression handler
|
|
39
|
+
private jsonCompressor: JsonCompressor;
|
|
20
40
|
|
|
21
41
|
constructor(toolsService?: ToolsService) {
|
|
42
|
+
this.jsonCompressor = new JsonCompressor(
|
|
43
|
+
this,
|
|
44
|
+
this.compressionThreshold,
|
|
45
|
+
this.maxTokens,
|
|
46
|
+
this.toolName
|
|
47
|
+
);
|
|
22
48
|
this.registerTool(toolsService);
|
|
23
49
|
}
|
|
24
50
|
|
|
25
51
|
// Rough token estimation (4 chars per token average)
|
|
26
|
-
|
|
52
|
+
public estimateTokens(text: string): number {
|
|
27
53
|
return Math.ceil(text.length / 4);
|
|
28
54
|
}
|
|
29
55
|
|
|
30
56
|
public setCompressionThreshold(threshold: number): void {
|
|
31
57
|
this.compressionThreshold = threshold;
|
|
32
58
|
this.characterLimit = threshold * 4; // Update character limit based on new threshold
|
|
59
|
+
this.jsonCompressor.updateSettings(threshold, this.maxTokens);
|
|
33
60
|
}
|
|
34
61
|
|
|
35
62
|
// Internally adjust to ensure we can always retrieve data
|
|
36
63
|
private setMaxTokens(maxTokens: number): void {
|
|
37
64
|
if (maxTokens > this.maxTokens) {
|
|
38
65
|
this.maxTokens = maxTokens;
|
|
66
|
+
this.jsonCompressor.updateSettings(this.compressionThreshold, maxTokens);
|
|
39
67
|
}
|
|
40
68
|
}
|
|
41
69
|
|
|
42
70
|
/**
|
|
43
|
-
* Attempts to parse content as JSON and returns parsed object if successful
|
|
71
|
+
* Attempts to parse content as JSON and returns parsed object if successful.
|
|
72
|
+
* Also handles MCP tool response format where actual data is in content[0].text
|
|
44
73
|
*/
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
} catch {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
74
|
+
|
|
75
|
+
public tryParseJson(content: string): any | null {
|
|
76
|
+
return this.jsonCompressor.tryParseJson(content);
|
|
51
77
|
}
|
|
52
78
|
|
|
53
79
|
/**
|
|
@@ -108,10 +134,33 @@ export class TokenCompressor {
|
|
|
108
134
|
} tool with key "${firstKey}" to retrieve content. Follow NEXT_CHUNK_KEY references for complete content]`;
|
|
109
135
|
}
|
|
110
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Check if content is already compressed
|
|
139
|
+
*/
|
|
140
|
+
private isAlreadyCompressed(content: string): boolean {
|
|
141
|
+
// Check for compressed string markers
|
|
142
|
+
if (content.includes("[COMPRESSED_STRING")) {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Check for compressed JSON structure with schema key
|
|
147
|
+
const parsed = this.tryParseJson(content);
|
|
148
|
+
if (parsed && parsed._schema_key && typeof parsed._schema_key === "string") {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
111
155
|
/**
|
|
112
156
|
* Enhanced content compression that handles both JSON and string chunking
|
|
113
157
|
*/
|
|
114
158
|
public compressContent(content: string, path: string = ""): string {
|
|
159
|
+
// Check if already compressed - don't compress again
|
|
160
|
+
if (this.isAlreadyCompressed(content)) {
|
|
161
|
+
return content;
|
|
162
|
+
}
|
|
163
|
+
|
|
115
164
|
const tokens = this.estimateTokens(content);
|
|
116
165
|
|
|
117
166
|
// For nested properties (path !== ""), use maxTokens to avoid recompressing stored data
|
|
@@ -123,16 +172,50 @@ export class TokenCompressor {
|
|
|
123
172
|
return content;
|
|
124
173
|
}
|
|
125
174
|
|
|
126
|
-
// Try to parse as JSON
|
|
175
|
+
// Try to parse as JSON and generate schema
|
|
127
176
|
const jsonObj = this.tryParseJson(content);
|
|
128
177
|
if (jsonObj) {
|
|
178
|
+
// For MCP format, work with the actual data
|
|
179
|
+
const dataToCompress = jsonObj._mcp_format ? jsonObj._data : jsonObj;
|
|
180
|
+
|
|
181
|
+
// Generate and store schema
|
|
182
|
+
const schema = this.jsonCompressor.generateSchema(jsonObj);
|
|
183
|
+
const schemaKey = this.generateKey();
|
|
184
|
+
this.storeString(schemaKey, JSON.stringify(schema));
|
|
185
|
+
|
|
129
186
|
// For JSON objects, compress individual properties
|
|
130
|
-
|
|
131
|
-
const
|
|
187
|
+
// Use a non-empty path to ensure compression logic is applied
|
|
188
|
+
const compressedObj = this.compressJsonProperties(
|
|
189
|
+
dataToCompress,
|
|
190
|
+
path || "data"
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// If this was MCP format, wrap the result back
|
|
194
|
+
const finalCompressedObj = jsonObj._mcp_format
|
|
195
|
+
? {
|
|
196
|
+
_mcp_format: true,
|
|
197
|
+
_raw_structure: jsonObj._raw_structure,
|
|
198
|
+
_data: compressedObj,
|
|
199
|
+
}
|
|
200
|
+
: compressedObj;
|
|
132
201
|
|
|
133
|
-
//
|
|
202
|
+
// Add schema reference to the compressed result
|
|
203
|
+
const resultWithSchema =
|
|
204
|
+
typeof finalCompressedObj === "object" &&
|
|
205
|
+
!Array.isArray(finalCompressedObj)
|
|
206
|
+
? { ...finalCompressedObj, _schema_key: schemaKey }
|
|
207
|
+
: { _schema_key: schemaKey, data: finalCompressedObj };
|
|
208
|
+
const compressedContent = JSON.stringify(resultWithSchema, null, 2);
|
|
209
|
+
|
|
210
|
+
// Check compression effectiveness
|
|
134
211
|
const compressedTokens = this.estimateTokens(compressedContent);
|
|
135
|
-
|
|
212
|
+
|
|
213
|
+
// For MCP format, we've successfully extracted and compressed the data
|
|
214
|
+
// The wrapper overhead is acceptable because we provide schema + structured access
|
|
215
|
+
// For non-MCP format, use the standard 60% threshold
|
|
216
|
+
const compressionThreshold = 0.6;
|
|
217
|
+
|
|
218
|
+
if (compressedTokens < tokens * compressionThreshold) {
|
|
136
219
|
return compressedContent;
|
|
137
220
|
}
|
|
138
221
|
}
|
|
@@ -146,118 +229,10 @@ export class TokenCompressor {
|
|
|
146
229
|
* Implements an efficient backward-iterating chunking strategy for large arrays.
|
|
147
230
|
*/
|
|
148
231
|
public compressJsonProperties(obj: any, path: string = ""): any {
|
|
149
|
-
|
|
150
|
-
path === "" &&
|
|
151
|
-
this.estimateTokens(JSON.stringify(obj)) <= this.maxTokens
|
|
152
|
-
) {
|
|
153
|
-
return obj;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (Array.isArray(obj)) {
|
|
157
|
-
// Step 1: Recursively compress all items first (depth-first).
|
|
158
|
-
const processedItems = obj.map((item, index) =>
|
|
159
|
-
this.compressJsonProperties(item, `${path}[${index}]`)
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
// Step 2: Early exit if the whole array is already small enough.
|
|
163
|
-
// maxTokens allows us to fetch objects from the store without recompressing
|
|
164
|
-
|
|
165
|
-
// Step 3: Iterate backwards, building chunks from the end.
|
|
166
|
-
const finalArray: any[] = [];
|
|
167
|
-
let currentChunk: any[] = [];
|
|
168
|
-
|
|
169
|
-
for (let i = processedItems.length - 1; i >= 0; i--) {
|
|
170
|
-
const item = processedItems[i];
|
|
171
|
-
currentChunk.unshift(item); // Add item to the front of the current chunk
|
|
172
|
-
|
|
173
|
-
const chunkString = JSON.stringify(currentChunk);
|
|
174
|
-
const chunkTokens = this.estimateTokens(chunkString);
|
|
175
|
-
|
|
176
|
-
if (chunkTokens > this.compressionThreshold) {
|
|
177
|
-
const key = this.generateKey();
|
|
178
|
-
this.storeString(key, chunkString);
|
|
179
|
-
|
|
180
|
-
const stub = `[COMPRESSED_JSON_ARRAY_CHUNK - ${chunkTokens} tokens, ${
|
|
181
|
-
currentChunk.length
|
|
182
|
-
} items]\nKey: ${key}\nPath: ${path}[${i}...${
|
|
183
|
-
i + currentChunk.length - 1
|
|
184
|
-
}]\nPreview: ${chunkString.substring(0, 100)}...\n[Use ${
|
|
185
|
-
this.toolName
|
|
186
|
-
} tool with key "${key}" to retrieve this chunk]`;
|
|
187
|
-
finalArray.unshift(stub); // Add stub to the start of our final result.
|
|
188
|
-
|
|
189
|
-
currentChunk = [];
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Step 4: After the loop, add any remaining items from the start of the
|
|
194
|
-
// array that did not form a full chunk.
|
|
195
|
-
if (currentChunk.length > 0) {
|
|
196
|
-
finalArray.unshift(...currentChunk);
|
|
197
|
-
}
|
|
198
|
-
return finalArray;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Handle objects - process all properties first (depth-first)
|
|
202
|
-
if (obj && typeof obj === "object") {
|
|
203
|
-
const result: any = {};
|
|
204
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
205
|
-
const newPath = path ? `${path}.${key}` : key;
|
|
206
|
-
result[key] = this.compressJsonProperties(value, newPath);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// After processing children, check if the entire object should be compressed
|
|
210
|
-
const objectAsString = JSON.stringify(result);
|
|
211
|
-
const tokens = this.estimateTokens(objectAsString);
|
|
212
|
-
if (tokens > this.compressionThreshold) {
|
|
213
|
-
const key = this.generateKey();
|
|
214
|
-
this.storeString(key, objectAsString);
|
|
215
|
-
|
|
216
|
-
return `[COMPRESSED_JSON_OBJECT - ${tokens} tokens]\nKey: ${key}\nPath: ${path}\nKeys: ${Object.keys(
|
|
217
|
-
result
|
|
218
|
-
).join(", ")}\nPreview: ${objectAsString.substring(0, 200)}...\n[Use ${
|
|
219
|
-
this.toolName
|
|
220
|
-
} tool with key "${key}" to retrieve full content]`;
|
|
221
|
-
}
|
|
222
|
-
return result;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Handle primitive values (strings, numbers, booleans, null)
|
|
226
|
-
if (typeof obj === "string") {
|
|
227
|
-
// First, check if this string contains JSON that we can parse and compress more granularly
|
|
228
|
-
const parsedJson = this.tryParseJson(obj);
|
|
229
|
-
if (parsedJson) {
|
|
230
|
-
const compressedJson = this.compressJsonProperties(parsedJson, path);
|
|
231
|
-
const compressedJsonString = JSON.stringify(compressedJson, null, 2);
|
|
232
|
-
|
|
233
|
-
const originalTokens = this.estimateTokens(obj);
|
|
234
|
-
const compressedTokens = this.estimateTokens(compressedJsonString);
|
|
235
|
-
|
|
236
|
-
if (compressedTokens < originalTokens * 0.8) {
|
|
237
|
-
return compressedJsonString;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// If not JSON or compression wasn't effective, handle as regular string
|
|
242
|
-
const tokens = this.estimateTokens(obj);
|
|
243
|
-
if (tokens > this.compressionThreshold) {
|
|
244
|
-
const key = this.generateKey();
|
|
245
|
-
this.storeString(key, obj);
|
|
246
|
-
|
|
247
|
-
return `[COMPRESSED_JSON_PROPERTY - ${tokens} tokens]\nKey: ${key}\nPath: ${path}\nPreview: ${obj.substring(
|
|
248
|
-
0,
|
|
249
|
-
200
|
|
250
|
-
)}...\n[Use ${
|
|
251
|
-
this.toolName
|
|
252
|
-
} tool with key "${key}" to retrieve full content]`;
|
|
253
|
-
}
|
|
254
|
-
return obj;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return obj;
|
|
232
|
+
return this.jsonCompressor.compressJsonProperties(obj, path);
|
|
258
233
|
}
|
|
259
234
|
|
|
260
|
-
|
|
235
|
+
public generateKey(): string {
|
|
261
236
|
return `${this.keyPrefix}${Date.now()}_${Math.random()
|
|
262
237
|
.toString(36)
|
|
263
238
|
.substr(2, 9)}`;
|
|
@@ -310,6 +285,7 @@ export class TokenCompressor {
|
|
|
310
285
|
|
|
311
286
|
clearStorage(): void {
|
|
312
287
|
this.storage = {};
|
|
288
|
+
this.jsonCompressor.clearDeduplication();
|
|
313
289
|
}
|
|
314
290
|
|
|
315
291
|
getStorageKeys(): string[] {
|
|
@@ -337,6 +313,99 @@ export class TokenCompressor {
|
|
|
337
313
|
});
|
|
338
314
|
}
|
|
339
315
|
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Get the schema for a compressed object
|
|
319
|
+
*/
|
|
320
|
+
getSchema(key: string): JsonSchema | null {
|
|
321
|
+
const schemaKey = `${key}_schema`;
|
|
322
|
+
const schemaStr = this.storage[schemaKey];
|
|
323
|
+
if (!schemaStr) {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
try {
|
|
327
|
+
return JSON.parse(schemaStr);
|
|
328
|
+
} catch (e) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Get compressed properties for an object
|
|
335
|
+
*/
|
|
336
|
+
getCompressedProperties(key: string): any | null {
|
|
337
|
+
const content = this.storage[key];
|
|
338
|
+
if (!content) {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
try {
|
|
342
|
+
const metadata = JSON.parse(content) as CompressionMetadata;
|
|
343
|
+
return metadata.compressed_properties || null;
|
|
344
|
+
} catch (e) {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Get full object by merging high-signal and compressed properties
|
|
351
|
+
*/
|
|
352
|
+
getFullObject(mainObj: any, compressedKey: string): any {
|
|
353
|
+
if (!mainObj || typeof mainObj !== "object") {
|
|
354
|
+
return mainObj;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const compressed = this.getCompressedProperties(compressedKey);
|
|
358
|
+
if (!compressed) {
|
|
359
|
+
return mainObj;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const {
|
|
363
|
+
_compressed_properties_key,
|
|
364
|
+
_compressed_property_names,
|
|
365
|
+
_compression_info,
|
|
366
|
+
...highSignal
|
|
367
|
+
} = mainObj;
|
|
368
|
+
return { ...highSignal, ...compressed };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Extract all keys from compressed content
|
|
373
|
+
*/
|
|
374
|
+
extractKeys(content: string): string[] {
|
|
375
|
+
const keys: string[] = [];
|
|
376
|
+
const keyPattern = /\$expandTokens\[([^\]]+)\]|Key:\s*([^\s\n]+)/g;
|
|
377
|
+
let match;
|
|
378
|
+
while ((match = keyPattern.exec(content)) !== null) {
|
|
379
|
+
const key = match[1] || match[2];
|
|
380
|
+
if (key && !keys.includes(key)) {
|
|
381
|
+
keys.push(key);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return keys;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Get the chain of keys for a given key (following NEXT_CHUNK_KEY references)
|
|
389
|
+
*/
|
|
390
|
+
getKeyChain(key: string): KeyInfo[] {
|
|
391
|
+
const chain: KeyInfo[] = [];
|
|
392
|
+
let currentKey: string | null = key;
|
|
393
|
+
|
|
394
|
+
while (currentKey) {
|
|
395
|
+
const content = this.storage[currentKey];
|
|
396
|
+
if (!content) break;
|
|
397
|
+
|
|
398
|
+
chain.push({
|
|
399
|
+
key: currentKey,
|
|
400
|
+
size: content.length,
|
|
401
|
+
preview: content.substring(0, 100),
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
const nextMatch = content.match(/NEXT_CHUNK_KEY:\s*([^\s\n]+)/);
|
|
405
|
+
currentKey = nextMatch ? nextMatch[1] : null;
|
|
406
|
+
}
|
|
407
|
+
return chain;
|
|
408
|
+
}
|
|
340
409
|
}
|
|
341
410
|
|
|
342
411
|
export const expandTokensDefinition: Tool = {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Message } from "../clients/types";
|
|
2
2
|
import { MessageProcessorFunction } from "../services/MessageProcessor";
|
|
3
3
|
import { ToolsService } from "../services";
|
|
4
|
+
import { JsonCompressor } from "./JsonCompressor";
|
|
4
5
|
import {
|
|
5
6
|
jqToolResponseDefinition,
|
|
6
7
|
executeJqQuery,
|
|
@@ -32,20 +33,34 @@ export class ToolResponseCache {
|
|
|
32
33
|
private storage: ToolResponseStorage = {};
|
|
33
34
|
private metadataStorage: ToolResponseMetadataStorage = {};
|
|
34
35
|
private toolNameMap: { [toolCallId: string]: string } = {};
|
|
36
|
+
private jsonCompressor: JsonCompressor;
|
|
35
37
|
|
|
36
|
-
constructor(toolsService: ToolsService) {
|
|
38
|
+
constructor(toolsService: ToolsService, jsonCompressor?: JsonCompressor) {
|
|
39
|
+
// Use provided JsonCompressor or create a minimal storage adapter
|
|
40
|
+
this.jsonCompressor = jsonCompressor || this.createMinimalJsonCompressor();
|
|
37
41
|
this.registerTool(toolsService);
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
/**
|
|
41
|
-
*
|
|
45
|
+
* Creates a minimal JsonCompressor instance for JSON parsing utilities
|
|
46
|
+
* This is used when no JsonCompressor is provided to the constructor
|
|
42
47
|
*/
|
|
43
|
-
private
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
private createMinimalJsonCompressor(): JsonCompressor {
|
|
49
|
+
// Create a minimal storage adapter that satisfies JsonCompressorStorage interface
|
|
50
|
+
const minimalStorage = {
|
|
51
|
+
storeString: (key: string, value: string) => {
|
|
52
|
+
// No-op for ToolResponseCache's internal use
|
|
53
|
+
},
|
|
54
|
+
generateKey: () => {
|
|
55
|
+
return `temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
56
|
+
},
|
|
57
|
+
estimateTokens: (text: string) => {
|
|
58
|
+
return Math.ceil(text.length / 4);
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Return a JsonCompressor instance with minimal settings
|
|
63
|
+
return new JsonCompressor(minimalStorage, 4000, 8000, "expandTokens");
|
|
49
64
|
}
|
|
50
65
|
|
|
51
66
|
/**
|
|
@@ -53,7 +68,7 @@ export class ToolResponseCache {
|
|
|
53
68
|
*/
|
|
54
69
|
public parseNestedJsonStrings(obj: any): any {
|
|
55
70
|
if (typeof obj === "string") {
|
|
56
|
-
const parsed = this.tryParseJson(obj);
|
|
71
|
+
const parsed = this.jsonCompressor.tryParseJson(obj);
|
|
57
72
|
if (parsed) {
|
|
58
73
|
return this.parseNestedJsonStrings(parsed);
|
|
59
74
|
}
|
|
@@ -90,8 +105,46 @@ export class ToolResponseCache {
|
|
|
90
105
|
return;
|
|
91
106
|
}
|
|
92
107
|
|
|
93
|
-
//
|
|
94
|
-
|
|
108
|
+
// Try to parse the content
|
|
109
|
+
const parsed = this.jsonCompressor.tryParseJson(content);
|
|
110
|
+
|
|
111
|
+
if (parsed && typeof parsed === 'object' && parsed._mcp_format === true && parsed._data) {
|
|
112
|
+
// For MCP format responses, store the data in a normalized structure
|
|
113
|
+
// This allows JQ queries to work directly against the data array
|
|
114
|
+
// Store as JSON string to maintain compatibility with existing query methods
|
|
115
|
+
this.storage[toolCallId] = JSON.stringify({
|
|
116
|
+
_mcp_format: true,
|
|
117
|
+
_raw_structure: parsed._raw_structure,
|
|
118
|
+
_data: parsed._data
|
|
119
|
+
});
|
|
120
|
+
} else if (parsed !== null) {
|
|
121
|
+
// Check if content is double-encoded by trying to parse again
|
|
122
|
+
// Only re-stringify if we detected and handled double-encoding
|
|
123
|
+
try {
|
|
124
|
+
const outerParse = JSON.parse(content);
|
|
125
|
+
if (typeof outerParse === 'string') {
|
|
126
|
+
// This is double-encoded JSON, store the fully parsed result
|
|
127
|
+
if (typeof parsed === 'object') {
|
|
128
|
+
this.storage[toolCallId] = JSON.stringify(parsed);
|
|
129
|
+
} else if (typeof parsed === 'string') {
|
|
130
|
+
// Parsed to a string, store it as-is
|
|
131
|
+
this.storage[toolCallId] = parsed;
|
|
132
|
+
} else {
|
|
133
|
+
// Store the original if we couldn't parse further
|
|
134
|
+
this.storage[toolCallId] = content;
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
// Not double-encoded, store original to preserve formatting
|
|
138
|
+
this.storage[toolCallId] = content;
|
|
139
|
+
}
|
|
140
|
+
} catch {
|
|
141
|
+
// Not valid JSON, store as-is
|
|
142
|
+
this.storage[toolCallId] = content;
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
// Could not parse as JSON, store as-is
|
|
146
|
+
this.storage[toolCallId] = content;
|
|
147
|
+
}
|
|
95
148
|
|
|
96
149
|
// Store metadata for reference
|
|
97
150
|
this.metadataStorage[toolCallId] = {
|
package/src/processors/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { Base64ImageDetector } from "./Base64ImageDetector";
|
|
2
2
|
export { CustomVariables } from "./CustomVariables";
|
|
3
3
|
export { TokenCompressor } from "./TokenCompressor";
|
|
4
|
+
export { JsonCompressor, JsonSchema, CompressionMetadata, JsonCompressorStorage } from "./JsonCompressor";
|
|
4
5
|
export { ToolResponseCache } from "./ToolResponseCache";
|
|
5
6
|
export { XmlToolCallProcessor } from "./XmlToolCallProcessor";
|
|
6
7
|
export { HarmonyToolProcessor } from "./HarmonyToolProcessor";
|