@tyvm/knowhow 0.0.48 → 0.0.50
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/agents/base/base.ts +9 -1
- package/src/auth/browserLogin.ts +4 -4
- package/src/chat/modules/AgentModule.ts +22 -3
- package/src/embeddings.ts +2 -4
- package/src/login.ts +27 -20
- package/src/plugins/GitPlugin.ts +10 -5
- package/src/processors/CustomVariables.ts +48 -70
- package/src/processors/TokenCompressor.ts +5 -4
- package/src/processors/ToolResponseCache.ts +6 -9
- package/src/services/KnowhowClient.ts +43 -16
- package/tests/processors/CustomVariables.test.ts +110 -55
- package/tests/processors/TokenCompressor.test.ts +48 -49
- package/tests/processors/ToolResponseCache.test.ts +309 -261
- package/ts_build/package.json +1 -1
- package/ts_build/src/agents/base/base.d.ts +5 -0
- package/ts_build/src/agents/base/base.js +3 -1
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.js +9 -1
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/embeddings.js +2 -4
- package/ts_build/src/embeddings.js.map +1 -1
- package/ts_build/src/login.d.ts +4 -0
- package/ts_build/src/login.js +21 -16
- package/ts_build/src/login.js.map +1 -1
- package/ts_build/src/plugins/GitPlugin.js +5 -5
- package/ts_build/src/plugins/GitPlugin.js.map +1 -1
- package/ts_build/src/processors/CustomVariables.js +36 -47
- package/ts_build/src/processors/CustomVariables.js.map +1 -1
- package/ts_build/src/processors/TokenCompressor.js +2 -2
- package/ts_build/src/processors/TokenCompressor.js.map +1 -1
- package/ts_build/src/processors/ToolResponseCache.js +6 -8
- package/ts_build/src/processors/ToolResponseCache.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.d.ts +2 -1
- package/ts_build/src/services/KnowhowClient.js +36 -16
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/tests/processors/CustomVariables.test.js +41 -38
- package/ts_build/tests/processors/CustomVariables.test.js.map +1 -1
- package/ts_build/tests/processors/TokenCompressor.test.js +4 -5
- package/ts_build/tests/processors/TokenCompressor.test.js.map +1 -1
- package/ts_build/tests/processors/ToolResponseCache.test.js +89 -78
- package/ts_build/tests/processors/ToolResponseCache.test.js.map +1 -1
|
@@ -1,135 +1,148 @@
|
|
|
1
1
|
import { Message } from "../../src/clients/types";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ToolResponseCache,
|
|
4
|
+
jqToolResponseDefinition,
|
|
5
|
+
} from "../../src/processors/ToolResponseCache";
|
|
3
6
|
import { ToolsService } from "../../src/services";
|
|
4
7
|
|
|
5
8
|
// Mock node-jq
|
|
6
9
|
jest.mock("node-jq", () => ({
|
|
7
|
-
run: jest
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
run: jest
|
|
11
|
+
.fn()
|
|
12
|
+
.mockImplementation(async (query: string, data: any, options: any) => {
|
|
13
|
+
// Simulate common JQ queries based on the test data
|
|
14
|
+
|
|
15
|
+
// Handle .test query on {"test": "value"}
|
|
16
|
+
if (query === ".test") {
|
|
17
|
+
if (data && data.test !== undefined) {
|
|
18
|
+
return JSON.stringify(data.test);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Handle .id query for extracting id values
|
|
23
|
+
if (query === ".id") {
|
|
24
|
+
return data && data.id !== undefined ? data.id.toString() : "null";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Handle .data | length query for counting array elements
|
|
28
|
+
if (query === ".data | length") {
|
|
29
|
+
if (data && Array.isArray(data.data)) {
|
|
30
|
+
return data.data.length.toString();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Handle .data | add query for summing array elements
|
|
35
|
+
if (query === ".data | add") {
|
|
36
|
+
if (data && Array.isArray(data.data)) {
|
|
37
|
+
const sum = data.data.reduce((a, b) => a + b, 0);
|
|
38
|
+
return sum.toString();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Handle .unicode query for special characters test
|
|
43
|
+
if (query === ".unicode") {
|
|
44
|
+
if (data && data.unicode !== undefined) {
|
|
45
|
+
return JSON.stringify(data.unicode);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Handle .empty query for empty string test
|
|
50
|
+
if (query === ".empty") {
|
|
51
|
+
if (data && data.empty !== undefined) {
|
|
52
|
+
return JSON.stringify(data.empty);
|
|
53
|
+
}
|
|
14
54
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// Handle .data | length query for counting array elements
|
|
23
|
-
if (query === ".data | length") {
|
|
24
|
-
if (data && Array.isArray(data.data)) {
|
|
25
|
-
return data.data.length.toString();
|
|
55
|
+
|
|
56
|
+
// Handle .nullValue query for null value test
|
|
57
|
+
if (query === ".nullValue") {
|
|
58
|
+
if (data && data.nullValue !== undefined) {
|
|
59
|
+
return JSON.stringify(data.nullValue);
|
|
60
|
+
}
|
|
26
61
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return sum.toString();
|
|
62
|
+
|
|
63
|
+
// Handle .name query on {name: "test", data: [1, 2, 3]}
|
|
64
|
+
if (query === ".name") {
|
|
65
|
+
if (data && data.name) {
|
|
66
|
+
return JSON.stringify(data.name);
|
|
67
|
+
}
|
|
34
68
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
69
|
+
|
|
70
|
+
// Handle .data[] query on {name: "test", data: [1, 2, 3]}
|
|
71
|
+
if (query === ".data[]") {
|
|
72
|
+
if (data && Array.isArray(data.data)) {
|
|
73
|
+
return data.data.join("\n");
|
|
74
|
+
}
|
|
41
75
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
76
|
+
|
|
77
|
+
// Handle map(.value) on [{id: 1, value: "a"}, {id: 2, value: "b"}]
|
|
78
|
+
if (query === "map(.value)") {
|
|
79
|
+
if (Array.isArray(data)) {
|
|
80
|
+
const values = data.map((item) => item.value);
|
|
81
|
+
return JSON.stringify(values);
|
|
82
|
+
}
|
|
48
83
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
84
|
+
|
|
85
|
+
// Handle map({identifier: .id, content: .value}) transformation
|
|
86
|
+
if (query === "map({identifier: .id, content: .value})") {
|
|
87
|
+
if (Array.isArray(data)) {
|
|
88
|
+
const transformed = data.map((item) => ({
|
|
89
|
+
identifier: item.id,
|
|
90
|
+
content: item.value,
|
|
91
|
+
}));
|
|
92
|
+
return JSON.stringify(transformed);
|
|
93
|
+
}
|
|
55
94
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (data && data.name) {
|
|
61
|
-
return JSON.stringify(data.name);
|
|
95
|
+
|
|
96
|
+
// Handle "." query (return entire object, formatted)
|
|
97
|
+
if (query === ".") {
|
|
98
|
+
return JSON.stringify(data, null, 2);
|
|
62
99
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
100
|
+
|
|
101
|
+
// Handle .nested.inner query for nested JSON
|
|
102
|
+
if (query === ".nested.inner") {
|
|
103
|
+
if (data && data.nested && data.nested.inner) {
|
|
104
|
+
return JSON.stringify(data.nested.inner);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Handle map(select(.id > 10)) - empty result
|
|
109
|
+
if (query === "map(select(.id > 10))") {
|
|
110
|
+
return JSON.stringify([]);
|
|
69
111
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (Array.isArray(data)) {
|
|
75
|
-
const values = data.map(item => item.value);
|
|
76
|
-
return JSON.stringify(values);
|
|
112
|
+
|
|
113
|
+
// Handle invalid queries - throw error
|
|
114
|
+
if (query === ".invalid[") {
|
|
115
|
+
throw new Error("Invalid JQ query syntax");
|
|
77
116
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
117
|
+
|
|
118
|
+
// Handle deep nested queries
|
|
119
|
+
if (query === ".level1.level2.level3.level4.level5.deepValue") {
|
|
120
|
+
if (
|
|
121
|
+
data &&
|
|
122
|
+
data.level1 &&
|
|
123
|
+
data.level1.level2 &&
|
|
124
|
+
data.level1.level2.level3 &&
|
|
125
|
+
data.level1.level2.level3.level4 &&
|
|
126
|
+
data.level1.level2.level3.level4.level5
|
|
127
|
+
) {
|
|
128
|
+
return JSON.stringify(
|
|
129
|
+
data.level1.level2.level3.level4.level5.deepValue
|
|
130
|
+
);
|
|
131
|
+
}
|
|
88
132
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return JSON.stringify(data, null, 2);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Handle .nested.inner query for nested JSON
|
|
97
|
-
if (query === ".nested.inner") {
|
|
98
|
-
if (data && data.nested && data.nested.inner) {
|
|
99
|
-
return JSON.stringify(data.nested.inner);
|
|
133
|
+
|
|
134
|
+
// Handle queries on invalid JSON data
|
|
135
|
+
if (typeof data === "string" && data === "invalid json string") {
|
|
136
|
+
throw new Error("Invalid JSON input");
|
|
100
137
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
// Handle invalid queries - throw error
|
|
109
|
-
if (query === ".invalid[") {
|
|
110
|
-
throw new Error("Invalid JQ query syntax");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Handle deep nested queries
|
|
114
|
-
if (query === ".level1.level2.level3.level4.level5.deepValue") {
|
|
115
|
-
if (data && data.level1 && data.level1.level2 && data.level1.level2.level3 &&
|
|
116
|
-
data.level1.level2.level3.level4 && data.level1.level2.level3.level4.level5) {
|
|
117
|
-
return JSON.stringify(data.level1.level2.level3.level4.level5.deepValue);
|
|
138
|
+
|
|
139
|
+
// Default fallback - return stringified data
|
|
140
|
+
try {
|
|
141
|
+
return JSON.stringify(data);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
throw new Error(`JQ query failed: ${error}`);
|
|
118
144
|
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Handle queries on invalid JSON data
|
|
122
|
-
if (typeof data === "string" && data === "invalid json string") {
|
|
123
|
-
throw new Error("Invalid JSON input");
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Default fallback - return stringified data
|
|
127
|
-
try {
|
|
128
|
-
return JSON.stringify(data);
|
|
129
|
-
} catch (error) {
|
|
130
|
-
throw new Error(`JQ query failed: ${error}`);
|
|
131
|
-
}
|
|
132
|
-
})
|
|
145
|
+
}),
|
|
133
146
|
}));
|
|
134
147
|
|
|
135
148
|
const mockJq = require("node-jq");
|
|
@@ -140,9 +153,9 @@ describe("ToolResponseCache", () => {
|
|
|
140
153
|
|
|
141
154
|
beforeEach(() => {
|
|
142
155
|
jest.clearAllMocks();
|
|
143
|
-
|
|
156
|
+
|
|
144
157
|
mockToolsService = {
|
|
145
|
-
|
|
158
|
+
addTools: jest.fn(),
|
|
146
159
|
addFunctions: jest.fn(),
|
|
147
160
|
getTool: jest.fn().mockReturnValue(undefined),
|
|
148
161
|
callTool: jest.fn(),
|
|
@@ -155,18 +168,20 @@ describe("ToolResponseCache", () => {
|
|
|
155
168
|
it("should create an instance and register tool with ToolsService", () => {
|
|
156
169
|
expect(cache).toBeDefined();
|
|
157
170
|
expect(cache).toBeInstanceOf(ToolResponseCache);
|
|
158
|
-
expect(mockToolsService.
|
|
171
|
+
expect(mockToolsService.addTools).toHaveBeenCalledWith([
|
|
172
|
+
jqToolResponseDefinition,
|
|
173
|
+
]);
|
|
159
174
|
expect(mockToolsService.addFunctions).toHaveBeenCalledWith({
|
|
160
175
|
jqToolResponse: expect.any(Function),
|
|
161
176
|
});
|
|
162
177
|
});
|
|
163
178
|
|
|
164
|
-
it("should
|
|
179
|
+
it("should overwrite tool if it already exists", () => {
|
|
165
180
|
mockToolsService.getTool.mockReturnValue(jqToolResponseDefinition);
|
|
166
181
|
const newCache = new ToolResponseCache(mockToolsService);
|
|
167
|
-
|
|
168
|
-
// Should
|
|
169
|
-
expect(mockToolsService.
|
|
182
|
+
|
|
183
|
+
// Should be called each time
|
|
184
|
+
expect(mockToolsService.addTools).toHaveBeenCalledTimes(2);
|
|
170
185
|
});
|
|
171
186
|
});
|
|
172
187
|
|
|
@@ -178,100 +193,102 @@ describe("ToolResponseCache", () => {
|
|
|
178
193
|
|
|
179
194
|
it("should process tool response messages", async () => {
|
|
180
195
|
const processor = cache.createProcessor();
|
|
181
|
-
|
|
196
|
+
|
|
182
197
|
const messages: Message[] = [
|
|
183
198
|
{
|
|
184
199
|
role: "tool",
|
|
185
200
|
tool_call_id: "call_123",
|
|
186
|
-
content: '{"data": [{"name": "test", "value": 42}]}'
|
|
187
|
-
}
|
|
201
|
+
content: '{"data": [{"name": "test", "value": 42}]}',
|
|
202
|
+
},
|
|
188
203
|
];
|
|
189
204
|
|
|
190
205
|
await processor(messages, messages);
|
|
191
|
-
|
|
206
|
+
|
|
192
207
|
// Verify message was stored
|
|
193
208
|
expect(cache.getStorageKeys()).toContain("call_123");
|
|
194
|
-
expect(cache.retrieveRawResponse("call_123")).toBe(
|
|
209
|
+
expect(cache.retrieveRawResponse("call_123")).toBe(
|
|
210
|
+
'{"data": [{"name": "test", "value": 42}]}'
|
|
211
|
+
);
|
|
195
212
|
});
|
|
196
213
|
|
|
197
214
|
it("should ignore non-tool messages", async () => {
|
|
198
215
|
const processor = cache.createProcessor();
|
|
199
|
-
|
|
216
|
+
|
|
200
217
|
const messages: Message[] = [
|
|
201
218
|
{
|
|
202
219
|
role: "user",
|
|
203
|
-
content: "This is a user message"
|
|
220
|
+
content: "This is a user message",
|
|
204
221
|
},
|
|
205
222
|
{
|
|
206
|
-
role: "assistant",
|
|
207
|
-
content: "This is an assistant message"
|
|
208
|
-
}
|
|
223
|
+
role: "assistant",
|
|
224
|
+
content: "This is an assistant message",
|
|
225
|
+
},
|
|
209
226
|
];
|
|
210
227
|
|
|
211
228
|
await processor(messages, messages);
|
|
212
|
-
|
|
229
|
+
|
|
213
230
|
expect(cache.getStorageSize()).toBe(0);
|
|
214
231
|
});
|
|
215
232
|
it("should ignore tool messages without tool_call_id", async () => {
|
|
216
233
|
const processor = cache.createProcessor();
|
|
217
|
-
|
|
234
|
+
|
|
218
235
|
const messages: Message[] = [
|
|
219
236
|
{
|
|
220
237
|
role: "tool",
|
|
221
|
-
content: "Tool response without call ID"
|
|
222
|
-
} as Message
|
|
238
|
+
content: "Tool response without call ID",
|
|
239
|
+
} as Message,
|
|
223
240
|
];
|
|
224
241
|
|
|
225
242
|
await processor(messages, messages);
|
|
226
|
-
|
|
243
|
+
|
|
227
244
|
expect(cache.getStorageSize()).toBe(0);
|
|
228
245
|
});
|
|
229
246
|
|
|
230
247
|
it("should ignore tool messages with non-string content", async () => {
|
|
231
248
|
const processor = cache.createProcessor();
|
|
232
|
-
|
|
249
|
+
|
|
233
250
|
const messages: Message[] = [
|
|
234
251
|
{
|
|
235
252
|
role: "tool",
|
|
236
253
|
tool_call_id: "call_123",
|
|
237
|
-
content: null
|
|
254
|
+
content: null,
|
|
238
255
|
} as Message,
|
|
239
256
|
{
|
|
240
|
-
role: "tool",
|
|
257
|
+
role: "tool",
|
|
241
258
|
tool_call_id: "call_456",
|
|
242
|
-
content: undefined
|
|
259
|
+
content: undefined,
|
|
243
260
|
} as Message,
|
|
244
261
|
{
|
|
245
262
|
role: "tool",
|
|
246
|
-
tool_call_id: "call_789",
|
|
247
|
-
content: 42 as any
|
|
248
|
-
} as Message
|
|
263
|
+
tool_call_id: "call_789",
|
|
264
|
+
content: 42 as any,
|
|
265
|
+
} as Message,
|
|
249
266
|
];
|
|
250
267
|
|
|
251
268
|
await processor(messages, messages);
|
|
252
|
-
|
|
269
|
+
|
|
253
270
|
expect(cache.getStorageSize()).toBe(0);
|
|
254
271
|
});
|
|
255
272
|
|
|
256
273
|
it("should apply filter function when provided", async () => {
|
|
257
274
|
const filterFn = (msg: Message) => msg.tool_call_id === "call_allowed";
|
|
258
275
|
const processor = cache.createProcessor(filterFn);
|
|
259
|
-
|
|
276
|
+
|
|
260
277
|
const messages: Message[] = [
|
|
261
278
|
{
|
|
262
279
|
role: "tool",
|
|
263
280
|
tool_call_id: "call_allowed",
|
|
264
|
-
content: "Allowed content"
|
|
281
|
+
content: "Allowed content",
|
|
265
282
|
},
|
|
266
283
|
{
|
|
267
284
|
role: "tool",
|
|
268
285
|
tool_call_id: "call_blocked",
|
|
269
|
-
content: "Blocked content"
|
|
270
|
-
}
|
|
286
|
+
content: "Blocked content",
|
|
287
|
+
},
|
|
271
288
|
];
|
|
272
289
|
|
|
273
290
|
await processor(messages, messages);
|
|
274
|
-
|
|
291
|
+
|
|
275
292
|
expect(cache.getStorageKeys()).toContain("call_allowed");
|
|
276
293
|
expect(cache.getStorageKeys()).not.toContain("call_blocked");
|
|
277
294
|
expect(cache.getStorageSize()).toBe(1);
|
|
@@ -282,13 +299,13 @@ describe("ToolResponseCache", () => {
|
|
|
282
299
|
it("should store tool response content with metadata", () => {
|
|
283
300
|
const content = '{"test": "data"}';
|
|
284
301
|
const toolCallId = "call_123";
|
|
285
|
-
|
|
302
|
+
|
|
286
303
|
// Access private method for testing
|
|
287
304
|
(cache as any).storeToolResponse(content, toolCallId);
|
|
288
|
-
|
|
305
|
+
|
|
289
306
|
expect(cache.retrieveRawResponse(toolCallId)).toBe(content);
|
|
290
307
|
expect(cache.getStorageKeys()).toContain(toolCallId);
|
|
291
|
-
|
|
308
|
+
|
|
292
309
|
// Check metadata
|
|
293
310
|
const metadata = (cache as any).metadataStorage[toolCallId];
|
|
294
311
|
expect(metadata.toolCallId).toBe(toolCallId);
|
|
@@ -299,10 +316,19 @@ describe("ToolResponseCache", () => {
|
|
|
299
316
|
describe("queryToolResponse", () => {
|
|
300
317
|
beforeEach(() => {
|
|
301
318
|
// Store some test data
|
|
302
|
-
cache.storeToolResponse(
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
319
|
+
cache.storeToolResponse(
|
|
320
|
+
'{"name": "test", "data": [1, 2, 3]}',
|
|
321
|
+
"call_123"
|
|
322
|
+
);
|
|
323
|
+
cache.storeToolResponse(
|
|
324
|
+
'[{"id": 1, "value": "a"}, {"id": 2, "value": "b"}]',
|
|
325
|
+
"call_456"
|
|
326
|
+
);
|
|
327
|
+
cache.storeToolResponse(
|
|
328
|
+
'{"nested": "{\\"inner\\": \\"value\\"}"}',
|
|
329
|
+
"call_789"
|
|
330
|
+
);
|
|
331
|
+
cache.storeToolResponse("invalid json string", "call_invalid");
|
|
306
332
|
});
|
|
307
333
|
|
|
308
334
|
it("should execute simple JQ queries successfully", async () => {
|
|
@@ -316,16 +342,19 @@ describe("ToolResponseCache", () => {
|
|
|
316
342
|
});
|
|
317
343
|
|
|
318
344
|
it("should execute complex JQ queries", async () => {
|
|
319
|
-
const result = await cache.queryToolResponse("call_456",
|
|
345
|
+
const result = await cache.queryToolResponse("call_456", "map(.value)");
|
|
320
346
|
expect(result).toBe('["a","b"]');
|
|
321
347
|
});
|
|
322
348
|
|
|
323
349
|
it("should handle object transformation queries", async () => {
|
|
324
|
-
const result = await cache.queryToolResponse(
|
|
350
|
+
const result = await cache.queryToolResponse(
|
|
351
|
+
"call_456",
|
|
352
|
+
"map({identifier: .id, content: .value})"
|
|
353
|
+
);
|
|
325
354
|
const parsed = JSON.parse(result);
|
|
326
355
|
expect(parsed).toEqual([
|
|
327
|
-
{identifier: 1, content: "a"},
|
|
328
|
-
{identifier: 2, content: "b"}
|
|
356
|
+
{ identifier: 1, content: "a" },
|
|
357
|
+
{ identifier: 2, content: "b" },
|
|
329
358
|
]);
|
|
330
359
|
});
|
|
331
360
|
|
|
@@ -344,14 +373,14 @@ describe("ToolResponseCache", () => {
|
|
|
344
373
|
it("should handle non-JSON data with helpful error", async () => {
|
|
345
374
|
const result = await cache.queryToolResponse("call_invalid", ".test");
|
|
346
375
|
expect(result).toContain("Error: Tool response data is not valid JSON");
|
|
347
|
-
expect(result).toContain(
|
|
376
|
+
expect(result).toContain('toolCallId "call_invalid"');
|
|
348
377
|
});
|
|
349
378
|
|
|
350
379
|
it("should return formatted JSON for complex results", async () => {
|
|
351
380
|
const result = await cache.queryToolResponse("call_456", ".");
|
|
352
381
|
expect(result).toContain("[\n");
|
|
353
382
|
expect(result).toContain(" {");
|
|
354
|
-
expect(result).toContain(
|
|
383
|
+
expect(result).toContain(' "id":');
|
|
355
384
|
});
|
|
356
385
|
|
|
357
386
|
it("should handle nested JSON strings parsing", async () => {
|
|
@@ -360,7 +389,10 @@ describe("ToolResponseCache", () => {
|
|
|
360
389
|
});
|
|
361
390
|
|
|
362
391
|
it("should handle empty query results", async () => {
|
|
363
|
-
const result = await cache.queryToolResponse(
|
|
392
|
+
const result = await cache.queryToolResponse(
|
|
393
|
+
"call_456",
|
|
394
|
+
"map(select(.id > 10))"
|
|
395
|
+
);
|
|
364
396
|
expect(result).toBe("[]");
|
|
365
397
|
});
|
|
366
398
|
});
|
|
@@ -369,24 +401,21 @@ describe("ToolResponseCache", () => {
|
|
|
369
401
|
it("should parse simple JSON strings", () => {
|
|
370
402
|
const input = '{"test": "value"}';
|
|
371
403
|
const result = cache.parseNestedJsonStrings(input);
|
|
372
|
-
expect(result).toEqual({test: "value"});
|
|
404
|
+
expect(result).toEqual({ test: "value" });
|
|
373
405
|
});
|
|
374
406
|
|
|
375
407
|
it("should parse nested JSON strings recursively", () => {
|
|
376
408
|
const input = '{"outer": "{\\"inner\\": \\"value\\"}"}';
|
|
377
409
|
const result = cache.parseNestedJsonStrings(input);
|
|
378
410
|
expect(result).toEqual({
|
|
379
|
-
outer: {inner: "value"}
|
|
411
|
+
outer: { inner: "value" },
|
|
380
412
|
});
|
|
381
413
|
});
|
|
382
414
|
|
|
383
415
|
it("should handle arrays with JSON strings", () => {
|
|
384
416
|
const input = ['{"test": "value1"}', '{"test": "value2"}'];
|
|
385
417
|
const result = cache.parseNestedJsonStrings(input);
|
|
386
|
-
expect(result).toEqual([
|
|
387
|
-
{test: "value1"},
|
|
388
|
-
{test: "value2"}
|
|
389
|
-
]);
|
|
418
|
+
expect(result).toEqual([{ test: "value1" }, { test: "value2" }]);
|
|
390
419
|
});
|
|
391
420
|
|
|
392
421
|
it("should handle mixed nested structures", () => {
|
|
@@ -394,16 +423,16 @@ describe("ToolResponseCache", () => {
|
|
|
394
423
|
stringField: '{"nested": "value"}',
|
|
395
424
|
arrayField: ['{"item": 1}', '{"item": 2}'],
|
|
396
425
|
objectField: {
|
|
397
|
-
deepString: '{"deep": "nested"}'
|
|
398
|
-
}
|
|
426
|
+
deepString: '{"deep": "nested"}',
|
|
427
|
+
},
|
|
399
428
|
};
|
|
400
429
|
const result = cache.parseNestedJsonStrings(input);
|
|
401
430
|
expect(result).toEqual({
|
|
402
|
-
stringField: {nested: "value"},
|
|
403
|
-
arrayField: [{item: 1}, {item: 2}],
|
|
431
|
+
stringField: { nested: "value" },
|
|
432
|
+
arrayField: [{ item: 1 }, { item: 2 }],
|
|
404
433
|
objectField: {
|
|
405
|
-
deepString: {deep: "nested"}
|
|
406
|
-
}
|
|
434
|
+
deepString: { deep: "nested" },
|
|
435
|
+
},
|
|
407
436
|
});
|
|
408
437
|
});
|
|
409
438
|
|
|
@@ -412,14 +441,14 @@ describe("ToolResponseCache", () => {
|
|
|
412
441
|
jsonString: '{"test": "value"}',
|
|
413
442
|
regularString: "just a string",
|
|
414
443
|
number: 42,
|
|
415
|
-
boolean: true
|
|
444
|
+
boolean: true,
|
|
416
445
|
};
|
|
417
446
|
const result = cache.parseNestedJsonStrings(input);
|
|
418
447
|
expect(result).toEqual({
|
|
419
|
-
jsonString: {test: "value"},
|
|
420
|
-
regularString: "just a string",
|
|
448
|
+
jsonString: { test: "value" },
|
|
449
|
+
regularString: "just a string",
|
|
421
450
|
number: 42,
|
|
422
|
-
boolean: true
|
|
451
|
+
boolean: true,
|
|
423
452
|
});
|
|
424
453
|
});
|
|
425
454
|
|
|
@@ -433,7 +462,7 @@ describe("ToolResponseCache", () => {
|
|
|
433
462
|
it("should return stored raw content", () => {
|
|
434
463
|
const content = '{"test": "data"}';
|
|
435
464
|
cache.storeToolResponse(content, "call_123");
|
|
436
|
-
|
|
465
|
+
|
|
437
466
|
const result = cache.retrieveRawResponse("call_123");
|
|
438
467
|
expect(result).toBe(content);
|
|
439
468
|
});
|
|
@@ -448,11 +477,11 @@ describe("ToolResponseCache", () => {
|
|
|
448
477
|
it("should clear all stored data and metadata", () => {
|
|
449
478
|
cache.storeToolResponse('{"test": "data"}', "call_123");
|
|
450
479
|
cache.storeToolResponse('{"more": "data"}', "call_456");
|
|
451
|
-
|
|
480
|
+
|
|
452
481
|
expect(cache.getStorageSize()).toBe(2);
|
|
453
|
-
|
|
482
|
+
|
|
454
483
|
cache.clearStorage();
|
|
455
|
-
|
|
484
|
+
|
|
456
485
|
expect(cache.getStorageSize()).toBe(0);
|
|
457
486
|
expect(cache.getStorageKeys()).toEqual([]);
|
|
458
487
|
expect(cache.retrieveRawResponse("call_123")).toBeNull();
|
|
@@ -469,7 +498,7 @@ describe("ToolResponseCache", () => {
|
|
|
469
498
|
cache.storeToolResponse('{"test": "data1"}', "call_123");
|
|
470
499
|
cache.storeToolResponse('{"test": "data2"}', "call_456");
|
|
471
500
|
cache.storeToolResponse('{"test": "data3"}', "call_789");
|
|
472
|
-
|
|
501
|
+
|
|
473
502
|
const keys = cache.getStorageKeys();
|
|
474
503
|
expect(keys).toContain("call_123");
|
|
475
504
|
expect(keys).toContain("call_456");
|
|
@@ -485,13 +514,13 @@ describe("ToolResponseCache", () => {
|
|
|
485
514
|
|
|
486
515
|
it("should return correct count after storing responses", () => {
|
|
487
516
|
expect(cache.getStorageSize()).toBe(0);
|
|
488
|
-
|
|
517
|
+
|
|
489
518
|
cache.storeToolResponse('{"test": "data1"}', "call_123");
|
|
490
519
|
expect(cache.getStorageSize()).toBe(1);
|
|
491
|
-
|
|
520
|
+
|
|
492
521
|
cache.storeToolResponse('{"test": "data2"}', "call_456");
|
|
493
522
|
expect(cache.getStorageSize()).toBe(2);
|
|
494
|
-
|
|
523
|
+
|
|
495
524
|
cache.clearStorage();
|
|
496
525
|
expect(cache.getStorageSize()).toBe(0);
|
|
497
526
|
});
|
|
@@ -503,49 +532,49 @@ describe("ToolResponseCache", () => {
|
|
|
503
532
|
beforeEach(() => {
|
|
504
533
|
mockToolsService = {
|
|
505
534
|
getTool: jest.fn(),
|
|
506
|
-
|
|
507
|
-
addFunctions: jest.fn()
|
|
535
|
+
addTools: jest.fn(),
|
|
536
|
+
addFunctions: jest.fn(),
|
|
508
537
|
} as any;
|
|
509
538
|
});
|
|
510
539
|
|
|
511
540
|
it("should register tool when not already present", () => {
|
|
512
541
|
mockToolsService.getTool.mockReturnValue(null);
|
|
513
|
-
|
|
542
|
+
|
|
514
543
|
cache.registerTool(mockToolsService);
|
|
515
|
-
|
|
516
|
-
expect(mockToolsService.
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
544
|
+
|
|
545
|
+
expect(mockToolsService.addTools).toHaveBeenCalledWith([
|
|
546
|
+
{
|
|
547
|
+
type: "function",
|
|
548
|
+
function: expect.objectContaining({
|
|
549
|
+
name: "jqToolResponse",
|
|
550
|
+
}),
|
|
551
|
+
},
|
|
552
|
+
]);
|
|
523
553
|
expect(mockToolsService.addFunctions).toHaveBeenCalledWith({
|
|
524
|
-
jqToolResponse: expect.any(Function)
|
|
554
|
+
jqToolResponse: expect.any(Function),
|
|
525
555
|
});
|
|
526
556
|
});
|
|
527
557
|
|
|
528
|
-
it("should
|
|
558
|
+
it("should overwrite tool when already present", () => {
|
|
529
559
|
mockToolsService.getTool.mockReturnValue({} as any);
|
|
530
|
-
|
|
560
|
+
|
|
531
561
|
cache.registerTool(mockToolsService);
|
|
532
|
-
|
|
533
|
-
expect(mockToolsService.
|
|
534
|
-
expect(mockToolsService.
|
|
535
|
-
expect(mockToolsService.addFunctions).not.toHaveBeenCalled();
|
|
562
|
+
|
|
563
|
+
expect(mockToolsService.addTools).toHaveBeenCalled();
|
|
564
|
+
expect(mockToolsService.addFunctions).toHaveBeenCalled();
|
|
536
565
|
});
|
|
537
566
|
|
|
538
567
|
it("should register function that calls queryToolResponse", async () => {
|
|
539
568
|
mockToolsService.getTool.mockReturnValue(null);
|
|
540
|
-
|
|
569
|
+
|
|
541
570
|
cache.registerTool(mockToolsService);
|
|
542
|
-
|
|
571
|
+
|
|
543
572
|
const addFunctionsCall = mockToolsService.addFunctions.mock.calls[0][0];
|
|
544
573
|
const jqFunction = addFunctionsCall.jqToolResponse;
|
|
545
|
-
|
|
574
|
+
|
|
546
575
|
// Store test data
|
|
547
576
|
cache.storeToolResponse('{"test": "value"}', "call_123");
|
|
548
|
-
|
|
577
|
+
|
|
549
578
|
// Test the registered function
|
|
550
579
|
const result = await jqFunction("call_123", ".test");
|
|
551
580
|
expect(result).toBe('"value"');
|
|
@@ -555,17 +584,22 @@ describe("ToolResponseCache", () => {
|
|
|
555
584
|
describe("Edge Cases and Error Handling", () => {
|
|
556
585
|
it("should handle very large JSON objects", async () => {
|
|
557
586
|
const largeObject = {
|
|
558
|
-
data: Array(1000)
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
587
|
+
data: Array(1000)
|
|
588
|
+
.fill(0)
|
|
589
|
+
.map((_, i) => ({
|
|
590
|
+
id: i,
|
|
591
|
+
name: `item_${i}`,
|
|
592
|
+
description: `This is item number ${i} with some additional text to make it larger`,
|
|
593
|
+
})),
|
|
563
594
|
};
|
|
564
595
|
const largeContent = JSON.stringify(largeObject);
|
|
565
|
-
|
|
596
|
+
|
|
566
597
|
cache.storeToolResponse(largeContent, "call_large");
|
|
567
|
-
|
|
568
|
-
const result = await cache.queryToolResponse(
|
|
598
|
+
|
|
599
|
+
const result = await cache.queryToolResponse(
|
|
600
|
+
"call_large",
|
|
601
|
+
".data | length"
|
|
602
|
+
);
|
|
569
603
|
expect(result).toBe("1000");
|
|
570
604
|
});
|
|
571
605
|
|
|
@@ -573,11 +607,11 @@ describe("ToolResponseCache", () => {
|
|
|
573
607
|
const specialContent = JSON.stringify({
|
|
574
608
|
unicode: "Hello 世界 🌍",
|
|
575
609
|
escaped: "Line 1\nLine 2\tTabbed",
|
|
576
|
-
quotes: 'He said "Hello" to me'
|
|
610
|
+
quotes: 'He said "Hello" to me',
|
|
577
611
|
});
|
|
578
|
-
|
|
612
|
+
|
|
579
613
|
cache.storeToolResponse(specialContent, "call_special");
|
|
580
|
-
|
|
614
|
+
|
|
581
615
|
const result = await cache.queryToolResponse("call_special", ".unicode");
|
|
582
616
|
expect(result).toBe('"Hello 世界 🌍"');
|
|
583
617
|
});
|
|
@@ -587,33 +621,38 @@ describe("ToolResponseCache", () => {
|
|
|
587
621
|
empty: "",
|
|
588
622
|
nullValue: null,
|
|
589
623
|
zero: 0,
|
|
590
|
-
false: false
|
|
624
|
+
false: false,
|
|
591
625
|
});
|
|
592
|
-
|
|
626
|
+
|
|
593
627
|
cache.storeToolResponse(content, "call_empty");
|
|
594
|
-
|
|
628
|
+
|
|
595
629
|
const emptyResult = await cache.queryToolResponse("call_empty", ".empty");
|
|
596
630
|
expect(emptyResult).toBe('""');
|
|
597
|
-
|
|
598
|
-
const nullResult = await cache.queryToolResponse(
|
|
631
|
+
|
|
632
|
+
const nullResult = await cache.queryToolResponse(
|
|
633
|
+
"call_empty",
|
|
634
|
+
".nullValue"
|
|
635
|
+
);
|
|
599
636
|
expect(nullResult).toBe("null");
|
|
600
637
|
});
|
|
601
638
|
|
|
602
639
|
it("should handle concurrent storage operations", async () => {
|
|
603
|
-
const promises = Array(10)
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
640
|
+
const promises = Array(10)
|
|
641
|
+
.fill(0)
|
|
642
|
+
.map((_, i) =>
|
|
643
|
+
Promise.resolve(cache.storeToolResponse(`{"id": ${i}}`, `call_${i}`))
|
|
644
|
+
);
|
|
645
|
+
|
|
607
646
|
await Promise.all(promises);
|
|
608
|
-
|
|
647
|
+
|
|
609
648
|
expect(cache.getStorageSize()).toBe(10);
|
|
610
|
-
|
|
649
|
+
|
|
611
650
|
const results = await Promise.all(
|
|
612
|
-
Array(10)
|
|
613
|
-
|
|
614
|
-
|
|
651
|
+
Array(10)
|
|
652
|
+
.fill(0)
|
|
653
|
+
.map((_, i) => cache.queryToolResponse(`call_${i}`, ".id"))
|
|
615
654
|
);
|
|
616
|
-
|
|
655
|
+
|
|
617
656
|
results.forEach((result, i) => {
|
|
618
657
|
expect(result).toBe(i.toString());
|
|
619
658
|
});
|
|
@@ -626,17 +665,20 @@ describe("ToolResponseCache", () => {
|
|
|
626
665
|
level3: {
|
|
627
666
|
level4: {
|
|
628
667
|
level5: {
|
|
629
|
-
deepValue: "found it!"
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
}
|
|
668
|
+
deepValue: "found it!",
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
},
|
|
672
|
+
},
|
|
673
|
+
},
|
|
635
674
|
};
|
|
636
|
-
|
|
675
|
+
|
|
637
676
|
cache.storeToolResponse(JSON.stringify(deepObject), "call_deep");
|
|
638
|
-
|
|
639
|
-
const result = await cache.queryToolResponse(
|
|
677
|
+
|
|
678
|
+
const result = await cache.queryToolResponse(
|
|
679
|
+
"call_deep",
|
|
680
|
+
".level1.level2.level3.level4.level5.deepValue"
|
|
681
|
+
);
|
|
640
682
|
expect(result).toBe('"found it!"');
|
|
641
683
|
});
|
|
642
684
|
});
|
|
@@ -644,14 +686,14 @@ describe("ToolResponseCache", () => {
|
|
|
644
686
|
describe("Integration with Message Processing", () => {
|
|
645
687
|
it("should integrate with complete message processing workflow", async () => {
|
|
646
688
|
const processor = cache.createProcessor();
|
|
647
|
-
|
|
689
|
+
|
|
648
690
|
const messages: Message[] = [
|
|
649
691
|
{
|
|
650
692
|
role: "user",
|
|
651
|
-
content: "Test request"
|
|
693
|
+
content: "Test request",
|
|
652
694
|
},
|
|
653
695
|
{
|
|
654
|
-
role: "assistant",
|
|
696
|
+
role: "assistant",
|
|
655
697
|
content: "Processing...",
|
|
656
698
|
tool_calls: [
|
|
657
699
|
{
|
|
@@ -659,30 +701,36 @@ describe("ToolResponseCache", () => {
|
|
|
659
701
|
type: "function",
|
|
660
702
|
function: {
|
|
661
703
|
name: "testTool",
|
|
662
|
-
arguments: "{}"
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
]
|
|
704
|
+
arguments: "{}",
|
|
705
|
+
},
|
|
706
|
+
},
|
|
707
|
+
],
|
|
666
708
|
},
|
|
667
709
|
{
|
|
668
710
|
role: "tool",
|
|
669
711
|
tool_call_id: "call_123",
|
|
670
712
|
content: JSON.stringify({
|
|
671
713
|
result: "success",
|
|
672
|
-
data: [1, 2, 3, 4, 5]
|
|
673
|
-
})
|
|
674
|
-
}
|
|
714
|
+
data: [1, 2, 3, 4, 5],
|
|
715
|
+
}),
|
|
716
|
+
},
|
|
675
717
|
];
|
|
676
718
|
|
|
677
719
|
await processor(messages, messages);
|
|
678
|
-
|
|
720
|
+
|
|
679
721
|
expect(cache.getStorageSize()).toBe(1);
|
|
680
|
-
|
|
681
|
-
const result = await cache.queryToolResponse(
|
|
722
|
+
|
|
723
|
+
const result = await cache.queryToolResponse(
|
|
724
|
+
"call_123",
|
|
725
|
+
".data | length"
|
|
726
|
+
);
|
|
682
727
|
expect(result).toBe("5");
|
|
683
|
-
|
|
684
|
-
const sumResult = await cache.queryToolResponse(
|
|
728
|
+
|
|
729
|
+
const sumResult = await cache.queryToolResponse(
|
|
730
|
+
"call_123",
|
|
731
|
+
".data | add"
|
|
732
|
+
);
|
|
685
733
|
expect(result).toBe("5");
|
|
686
734
|
});
|
|
687
735
|
});
|
|
688
|
-
});
|
|
736
|
+
});
|