@tyvm/knowhow 0.0.63 → 0.0.65
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 +2 -1
- package/src/chat/modules/AgentModule.ts +18 -2
- package/src/cli.ts +5 -4
- package/src/clients/anthropic.ts +68 -5
- package/src/config.ts +7 -0
- package/src/processors/Base64ImageDetector.ts +193 -40
- package/src/processors/index.ts +1 -1
- package/src/services/AgentSynchronization.ts +27 -10
- package/src/types.ts +11 -0
- package/src/worker.ts +145 -8
- package/src/workers/tools/index.ts +10 -0
- package/src/workers/tools/listAllowedPorts.ts +30 -0
- package/tests/plugins/language/languagePlugin-content-triggers.test.ts +5 -1
- package/tests/plugins/language/languagePlugin.test.ts +5 -1
- package/tests/processors/Base64ImageDetector.test.ts +263 -70
- package/tests/services/Tools.test.ts +6 -4
- package/ts_build/package.json +2 -1
- package/ts_build/src/chat/modules/AgentModule.js +12 -2
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/cli.js +5 -4
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/anthropic.d.ts +9 -0
- package/ts_build/src/clients/anthropic.js +61 -5
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/config.js +6 -0
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/processors/Base64ImageDetector.d.ts +7 -3
- package/ts_build/src/processors/Base64ImageDetector.js +147 -27
- package/ts_build/src/processors/Base64ImageDetector.js.map +1 -1
- package/ts_build/src/processors/index.d.ts +1 -1
- package/ts_build/src/processors/index.js +2 -2
- package/ts_build/src/processors/index.js.map +1 -1
- package/ts_build/src/services/AgentSynchronization.d.ts +2 -0
- package/ts_build/src/services/AgentSynchronization.js +20 -10
- package/ts_build/src/services/AgentSynchronization.js.map +1 -1
- package/ts_build/src/types.d.ts +11 -0
- package/ts_build/src/types.js +1 -0
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/worker/handlers/proxyHandler.d.ts +2 -0
- package/ts_build/src/worker/handlers/proxyHandler.js +41 -0
- package/ts_build/src/worker/handlers/proxyHandler.js.map +1 -0
- package/ts_build/src/worker/tools/index.d.ts +1 -0
- package/ts_build/src/worker/tools/index.js +18 -0
- package/ts_build/src/worker/tools/index.js.map +1 -0
- package/ts_build/src/worker/tools/portForwarding.d.ts +49 -0
- package/ts_build/src/worker/tools/portForwarding.js +173 -0
- package/ts_build/src/worker/tools/portForwarding.js.map +1 -0
- package/ts_build/src/worker/types/proxy.d.ts +18 -0
- package/ts_build/src/worker/types/proxy.js +3 -0
- package/ts_build/src/worker/types/proxy.js.map +1 -0
- package/ts_build/src/worker.js +119 -3
- package/ts_build/src/worker.js.map +1 -1
- package/ts_build/src/workers/tools/index.d.ts +9 -0
- package/ts_build/src/workers/tools/index.js +23 -0
- package/ts_build/src/workers/tools/index.js.map +1 -0
- package/ts_build/src/workers/tools/listAllowedPorts.d.ts +3 -0
- package/ts_build/src/workers/tools/listAllowedPorts.js +25 -0
- package/ts_build/src/workers/tools/listAllowedPorts.js.map +1 -0
- package/ts_build/src/workers/tools/listForwardedPorts.d.ts +10 -0
- package/ts_build/src/workers/tools/listForwardedPorts.js +22 -0
- package/ts_build/src/workers/tools/listForwardedPorts.js.map +1 -0
- package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js +5 -1
- package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js.map +1 -1
- package/ts_build/tests/plugins/language/languagePlugin.test.js +5 -1
- package/ts_build/tests/plugins/language/languagePlugin.test.js.map +1 -1
- package/ts_build/tests/processors/Base64ImageDetector.test.js +221 -59
- package/ts_build/tests/processors/Base64ImageDetector.test.js.map +1 -1
- package/ts_build/tests/services/Tools.test.js +3 -3
- package/ts_build/tests/services/Tools.test.js.map +1 -1
- package/ts_build/tests/worker/handlers/proxyHandler.test.d.ts +1 -0
- package/ts_build/tests/worker/handlers/proxyHandler.test.js +170 -0
- package/ts_build/tests/worker/handlers/proxyHandler.test.js.map +1 -0
- package/tsconfig.json +1 -1
|
@@ -1,27 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Base64ImageProcessor,
|
|
3
|
+
globalBase64ImageDetector,
|
|
4
|
+
} from "../../src/processors/Base64ImageDetector";
|
|
2
5
|
import { Message } from "../../src/clients/types";
|
|
6
|
+
import { ToolsService } from "../../src/services/Tools";
|
|
7
|
+
import * as fs from "fs";
|
|
3
8
|
|
|
4
9
|
describe("Base64ImageDetector", () => {
|
|
5
|
-
let detector:
|
|
10
|
+
let detector: Base64ImageProcessor;
|
|
6
11
|
|
|
7
12
|
beforeEach(() => {
|
|
8
|
-
detector = new
|
|
13
|
+
detector = new Base64ImageProcessor();
|
|
9
14
|
});
|
|
10
15
|
|
|
11
16
|
describe("Constructor", () => {
|
|
12
17
|
it("should create instance with default parameters", () => {
|
|
13
|
-
const instance = new
|
|
14
|
-
expect(instance).toBeInstanceOf(
|
|
18
|
+
const instance = new Base64ImageProcessor();
|
|
19
|
+
expect(instance).toBeInstanceOf(Base64ImageProcessor);
|
|
15
20
|
});
|
|
16
21
|
|
|
17
22
|
it("should create instance with custom image detail", () => {
|
|
18
|
-
const instance = new
|
|
19
|
-
|
|
23
|
+
const instance = new Base64ImageProcessor();
|
|
24
|
+
instance.setImageDetail("high");
|
|
25
|
+
expect(instance).toBeInstanceOf(Base64ImageProcessor);
|
|
20
26
|
});
|
|
21
27
|
|
|
22
28
|
it("should create instance with custom supported formats", () => {
|
|
23
|
-
const instance = new
|
|
24
|
-
|
|
29
|
+
const instance = new Base64ImageProcessor();
|
|
30
|
+
instance.setImageDetail("low");
|
|
31
|
+
instance.setSupportedFormats(["png", "jpeg"]);
|
|
32
|
+
expect(instance).toBeInstanceOf(Base64ImageProcessor);
|
|
25
33
|
});
|
|
26
34
|
});
|
|
27
35
|
|
|
@@ -51,9 +59,12 @@ describe("Base64ImageDetector", () => {
|
|
|
51
59
|
});
|
|
52
60
|
});
|
|
53
61
|
describe("String content processing", () => {
|
|
54
|
-
const validPngBase64 =
|
|
55
|
-
|
|
56
|
-
const
|
|
62
|
+
const validPngBase64 =
|
|
63
|
+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==";
|
|
64
|
+
const validJpegBase64 =
|
|
65
|
+
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD//gA7Q1JFQVR";
|
|
66
|
+
const plainBase64 =
|
|
67
|
+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==";
|
|
57
68
|
const regularText = "This is just regular text";
|
|
58
69
|
|
|
59
70
|
it("should convert data URL base64 string to image content", () => {
|
|
@@ -61,8 +72,8 @@ describe("Base64ImageDetector", () => {
|
|
|
61
72
|
const modifiedMessages: Message[] = [
|
|
62
73
|
{
|
|
63
74
|
role: "user",
|
|
64
|
-
content: validPngBase64
|
|
65
|
-
}
|
|
75
|
+
content: validPngBase64,
|
|
76
|
+
},
|
|
66
77
|
];
|
|
67
78
|
|
|
68
79
|
const processor = detector.createProcessor();
|
|
@@ -74,8 +85,8 @@ describe("Base64ImageDetector", () => {
|
|
|
74
85
|
type: "image_url",
|
|
75
86
|
image_url: {
|
|
76
87
|
url: validPngBase64,
|
|
77
|
-
detail: "auto"
|
|
78
|
-
}
|
|
88
|
+
detail: "auto",
|
|
89
|
+
},
|
|
79
90
|
});
|
|
80
91
|
});
|
|
81
92
|
it("should convert plain base64 PNG to image content", () => {
|
|
@@ -83,8 +94,9 @@ describe("Base64ImageDetector", () => {
|
|
|
83
94
|
const modifiedMessages: Message[] = [
|
|
84
95
|
{
|
|
85
96
|
role: "user",
|
|
86
|
-
content:
|
|
87
|
-
|
|
97
|
+
content:
|
|
98
|
+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==",
|
|
99
|
+
},
|
|
88
100
|
];
|
|
89
101
|
|
|
90
102
|
const processor = detector.createProcessor();
|
|
@@ -107,8 +119,8 @@ describe("Base64ImageDetector", () => {
|
|
|
107
119
|
const modifiedMessages: Message[] = [
|
|
108
120
|
{
|
|
109
121
|
role: "user",
|
|
110
|
-
content: regularText
|
|
111
|
-
}
|
|
122
|
+
content: regularText,
|
|
123
|
+
},
|
|
112
124
|
];
|
|
113
125
|
|
|
114
126
|
const processor = detector.createProcessor();
|
|
@@ -121,12 +133,12 @@ describe("Base64ImageDetector", () => {
|
|
|
121
133
|
const modifiedMessages: Message[] = [
|
|
122
134
|
{
|
|
123
135
|
role: "assistant",
|
|
124
|
-
content: validPngBase64
|
|
136
|
+
content: validPngBase64,
|
|
125
137
|
},
|
|
126
138
|
{
|
|
127
|
-
role: "system",
|
|
128
|
-
content: validPngBase64
|
|
129
|
-
}
|
|
139
|
+
role: "system",
|
|
140
|
+
content: validPngBase64,
|
|
141
|
+
},
|
|
130
142
|
];
|
|
131
143
|
|
|
132
144
|
const processor = detector.createProcessor();
|
|
@@ -136,15 +148,91 @@ describe("Base64ImageDetector", () => {
|
|
|
136
148
|
expect(modifiedMessages[1].content).toBe(validPngBase64);
|
|
137
149
|
});
|
|
138
150
|
|
|
151
|
+
it("should process tool messages with JSON string containing image_url", () => {
|
|
152
|
+
const imageDataUrl =
|
|
153
|
+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==";
|
|
154
|
+
|
|
155
|
+
const toolResponseJson = JSON.stringify({
|
|
156
|
+
type: "image_url",
|
|
157
|
+
image_url: {
|
|
158
|
+
url: imageDataUrl,
|
|
159
|
+
detail: "auto",
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const originalMessages: Message[] = [];
|
|
164
|
+
const modifiedMessages: Message[] = [
|
|
165
|
+
{
|
|
166
|
+
role: "tool",
|
|
167
|
+
content: toolResponseJson,
|
|
168
|
+
tool_call_id: "call_123",
|
|
169
|
+
},
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
const processor = detector.createProcessor();
|
|
173
|
+
processor(originalMessages, modifiedMessages);
|
|
174
|
+
|
|
175
|
+
// Tool message content should be converted from JSON string to array
|
|
176
|
+
expect(Array.isArray(modifiedMessages[0].content)).toBe(true);
|
|
177
|
+
const content = modifiedMessages[0].content as any[];
|
|
178
|
+
expect(content).toHaveLength(1);
|
|
179
|
+
expect(content[0]).toEqual({
|
|
180
|
+
type: "image_url",
|
|
181
|
+
image_url: {
|
|
182
|
+
url: imageDataUrl,
|
|
183
|
+
detail: "auto",
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("should process tool messages with plain base64 string", () => {
|
|
189
|
+
const validPngBase64 =
|
|
190
|
+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==";
|
|
191
|
+
|
|
192
|
+
const originalMessages: Message[] = [];
|
|
193
|
+
const modifiedMessages: Message[] = [
|
|
194
|
+
{
|
|
195
|
+
role: "tool",
|
|
196
|
+
content: validPngBase64,
|
|
197
|
+
tool_call_id: "call_456",
|
|
198
|
+
},
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
const processor = detector.createProcessor();
|
|
202
|
+
processor(originalMessages, modifiedMessages);
|
|
203
|
+
|
|
204
|
+
// Should convert to array if it's detected as an image
|
|
205
|
+
if (Array.isArray(modifiedMessages[0].content)) {
|
|
206
|
+
const content = modifiedMessages[0].content as any[];
|
|
207
|
+
expect(content[0].type).toBe("image_url");
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("should not process tool messages with regular text", () => {
|
|
212
|
+
const originalMessages: Message[] = [];
|
|
213
|
+
const modifiedMessages: Message[] = [
|
|
214
|
+
{
|
|
215
|
+
role: "tool",
|
|
216
|
+
content: "Tool completed successfully",
|
|
217
|
+
tool_call_id: "call_789",
|
|
218
|
+
},
|
|
219
|
+
];
|
|
220
|
+
|
|
221
|
+
const processor = detector.createProcessor();
|
|
222
|
+
processor(originalMessages, modifiedMessages);
|
|
223
|
+
|
|
224
|
+
expect(modifiedMessages[0].content).toBe("Tool completed successfully");
|
|
225
|
+
});
|
|
226
|
+
|
|
139
227
|
it("should use custom image detail setting", () => {
|
|
140
228
|
detector.setImageDetail("high");
|
|
141
|
-
|
|
229
|
+
|
|
142
230
|
const originalMessages: Message[] = [];
|
|
143
231
|
const modifiedMessages: Message[] = [
|
|
144
232
|
{
|
|
145
233
|
role: "user",
|
|
146
|
-
content: validPngBase64
|
|
147
|
-
}
|
|
234
|
+
content: validPngBase64,
|
|
235
|
+
},
|
|
148
236
|
];
|
|
149
237
|
|
|
150
238
|
const processor = detector.createProcessor();
|
|
@@ -155,7 +243,8 @@ describe("Base64ImageDetector", () => {
|
|
|
155
243
|
});
|
|
156
244
|
});
|
|
157
245
|
describe("Array content processing", () => {
|
|
158
|
-
const validPngBase64 =
|
|
246
|
+
const validPngBase64 =
|
|
247
|
+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==";
|
|
159
248
|
|
|
160
249
|
it("should process text items in content array", () => {
|
|
161
250
|
const originalMessages: Message[] = [];
|
|
@@ -165,9 +254,9 @@ describe("Base64ImageDetector", () => {
|
|
|
165
254
|
content: [
|
|
166
255
|
{ type: "text", text: "Here is an image:" },
|
|
167
256
|
{ type: "text", text: validPngBase64 },
|
|
168
|
-
{ type: "text", text: "And some more text" }
|
|
169
|
-
]
|
|
170
|
-
}
|
|
257
|
+
{ type: "text", text: "And some more text" },
|
|
258
|
+
],
|
|
259
|
+
},
|
|
171
260
|
];
|
|
172
261
|
|
|
173
262
|
const processor = detector.createProcessor();
|
|
@@ -186,9 +275,12 @@ describe("Base64ImageDetector", () => {
|
|
|
186
275
|
role: "user",
|
|
187
276
|
content: [
|
|
188
277
|
{ type: "text", text: "Some text" },
|
|
189
|
-
{
|
|
190
|
-
|
|
191
|
-
|
|
278
|
+
{
|
|
279
|
+
type: "image_url",
|
|
280
|
+
image_url: { url: "http://example.com/image.png" },
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
},
|
|
192
284
|
];
|
|
193
285
|
|
|
194
286
|
const processor = detector.createProcessor();
|
|
@@ -206,8 +298,8 @@ describe("Base64ImageDetector", () => {
|
|
|
206
298
|
const modifiedMessages: Message[] = [
|
|
207
299
|
{
|
|
208
300
|
role: "user",
|
|
209
|
-
content: []
|
|
210
|
-
}
|
|
301
|
+
content: [],
|
|
302
|
+
},
|
|
211
303
|
];
|
|
212
304
|
|
|
213
305
|
const processor = detector.createProcessor();
|
|
@@ -216,7 +308,8 @@ describe("Base64ImageDetector", () => {
|
|
|
216
308
|
});
|
|
217
309
|
});
|
|
218
310
|
describe("Tool call argument processing", () => {
|
|
219
|
-
const validPngBase64 =
|
|
311
|
+
const validPngBase64 =
|
|
312
|
+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==";
|
|
220
313
|
|
|
221
314
|
it("should process base64 images in tool call arguments", () => {
|
|
222
315
|
const originalMessages: Message[] = [];
|
|
@@ -232,12 +325,12 @@ describe("Base64ImageDetector", () => {
|
|
|
232
325
|
name: "analyze_image",
|
|
233
326
|
arguments: JSON.stringify({
|
|
234
327
|
image: validPngBase64,
|
|
235
|
-
description: "Test image"
|
|
236
|
-
})
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
]
|
|
240
|
-
}
|
|
328
|
+
description: "Test image",
|
|
329
|
+
}),
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
},
|
|
241
334
|
];
|
|
242
335
|
|
|
243
336
|
const processor = detector.createProcessor();
|
|
@@ -264,14 +357,14 @@ describe("Base64ImageDetector", () => {
|
|
|
264
357
|
data: {
|
|
265
358
|
images: [validPngBase64, "regular text"],
|
|
266
359
|
metadata: {
|
|
267
|
-
thumbnail: validPngBase64
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
})
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
]
|
|
274
|
-
}
|
|
360
|
+
thumbnail: validPngBase64,
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
}),
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
],
|
|
367
|
+
},
|
|
275
368
|
];
|
|
276
369
|
|
|
277
370
|
const processor = detector.createProcessor();
|
|
@@ -295,11 +388,11 @@ describe("Base64ImageDetector", () => {
|
|
|
295
388
|
type: "function",
|
|
296
389
|
function: {
|
|
297
390
|
name: "tool_with_plain_text",
|
|
298
|
-
arguments: validPngBase64
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
]
|
|
302
|
-
}
|
|
391
|
+
arguments: validPngBase64,
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
],
|
|
395
|
+
},
|
|
303
396
|
];
|
|
304
397
|
|
|
305
398
|
const processor = detector.createProcessor();
|
|
@@ -314,8 +407,8 @@ describe("Base64ImageDetector", () => {
|
|
|
314
407
|
const modifiedMessages: Message[] = [
|
|
315
408
|
{
|
|
316
409
|
role: "assistant",
|
|
317
|
-
content: "Just a regular message"
|
|
318
|
-
}
|
|
410
|
+
content: "Just a regular message",
|
|
411
|
+
},
|
|
319
412
|
];
|
|
320
413
|
|
|
321
414
|
const processor = detector.createProcessor();
|
|
@@ -336,12 +429,12 @@ describe("Base64ImageDetector", () => {
|
|
|
336
429
|
const modifiedMessages: Message[] = [
|
|
337
430
|
{
|
|
338
431
|
role: "user",
|
|
339
|
-
content: null as any
|
|
432
|
+
content: null as any,
|
|
340
433
|
},
|
|
341
434
|
{
|
|
342
435
|
role: "user",
|
|
343
|
-
content: undefined as any
|
|
344
|
-
}
|
|
436
|
+
content: undefined as any,
|
|
437
|
+
},
|
|
345
438
|
];
|
|
346
439
|
|
|
347
440
|
const processor = detector.createProcessor();
|
|
@@ -350,13 +443,14 @@ describe("Base64ImageDetector", () => {
|
|
|
350
443
|
|
|
351
444
|
it("should handle unsupported image formats", () => {
|
|
352
445
|
detector.setSupportedFormats(["png"]);
|
|
353
|
-
|
|
446
|
+
|
|
354
447
|
const originalMessages: Message[] = [];
|
|
355
448
|
const modifiedMessages: Message[] = [
|
|
356
449
|
{
|
|
357
450
|
role: "user",
|
|
358
|
-
content:
|
|
359
|
-
|
|
451
|
+
content:
|
|
452
|
+
"data:image/bmp;base64,Qk1GAAAAAAAAADYAAAAoAAAAAQAAAAEAAAABACAAAAAAAAAAAAATCwAAEwsAAAAAAAAAAAAA/////wA=",
|
|
453
|
+
},
|
|
360
454
|
];
|
|
361
455
|
|
|
362
456
|
const processor = detector.createProcessor();
|
|
@@ -369,8 +463,8 @@ describe("Base64ImageDetector", () => {
|
|
|
369
463
|
const modifiedMessages: Message[] = [
|
|
370
464
|
{
|
|
371
465
|
role: "user",
|
|
372
|
-
content: "SGVsbG8="
|
|
373
|
-
}
|
|
466
|
+
content: "SGVsbG8=", // "Hello" in base64, but too short to be an image
|
|
467
|
+
},
|
|
374
468
|
];
|
|
375
469
|
|
|
376
470
|
const processor = detector.createProcessor();
|
|
@@ -384,8 +478,8 @@ describe("Base64ImageDetector", () => {
|
|
|
384
478
|
const modifiedMessages: Message[] = [
|
|
385
479
|
{
|
|
386
480
|
role: "user",
|
|
387
|
-
content: "data:image/png;base64"
|
|
388
|
-
}
|
|
481
|
+
content: "data:image/png;base64", // Missing comma and data
|
|
482
|
+
},
|
|
389
483
|
];
|
|
390
484
|
|
|
391
485
|
const processor = detector.createProcessor();
|
|
@@ -397,7 +491,106 @@ describe("Base64ImageDetector", () => {
|
|
|
397
491
|
|
|
398
492
|
describe("Global instance", () => {
|
|
399
493
|
it("should provide global instance", () => {
|
|
400
|
-
expect(globalBase64ImageDetector).toBeInstanceOf(
|
|
494
|
+
expect(globalBase64ImageDetector).toBeInstanceOf(Base64ImageProcessor);
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
describe("loadImageAsBase64 tool", () => {
|
|
499
|
+
let toolsService: ToolsService;
|
|
500
|
+
let testImagePath: string;
|
|
501
|
+
|
|
502
|
+
beforeEach(() => {
|
|
503
|
+
toolsService = new ToolsService();
|
|
504
|
+
detector.registerTool(toolsService);
|
|
505
|
+
|
|
506
|
+
// Create a minimal test PNG image (1x1 red pixel)
|
|
507
|
+
testImagePath = "/tmp/test-image.png";
|
|
508
|
+
const pngBuffer = Buffer.from(
|
|
509
|
+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==",
|
|
510
|
+
"base64"
|
|
511
|
+
);
|
|
512
|
+
fs.writeFileSync(testImagePath, pngBuffer);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
afterEach(() => {
|
|
516
|
+
// Clean up test file
|
|
517
|
+
if (fs.existsSync(testImagePath)) {
|
|
518
|
+
fs.unlinkSync(testImagePath);
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it("should register loadImageAsBase64 tool", () => {
|
|
523
|
+
const tools = toolsService.getTools();
|
|
524
|
+
const loadImageTool = tools.find(
|
|
525
|
+
(t) => t.function.name === "loadImageAsBase64"
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
expect(loadImageTool).toBeDefined();
|
|
529
|
+
expect(loadImageTool?.function.name).toBe("loadImageAsBase64");
|
|
530
|
+
expect(loadImageTool?.function.description).toContain(
|
|
531
|
+
"Load an image file"
|
|
532
|
+
);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it("should have function implementation", () => {
|
|
536
|
+
const func = toolsService.getFunction("loadImageAsBase64");
|
|
537
|
+
expect(func).toBeDefined();
|
|
538
|
+
expect(typeof func).toBe("function");
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it("should load image and return base64 data URL", async () => {
|
|
542
|
+
const func = toolsService.getFunction("loadImageAsBase64");
|
|
543
|
+
const result = await func(testImagePath);
|
|
544
|
+
|
|
545
|
+
expect(typeof result).toBe("string");
|
|
546
|
+
const parsed = JSON.parse(result);
|
|
547
|
+
expect(parsed.type).toBe("image_url");
|
|
548
|
+
expect(parsed.image_url.url).toContain("data:image/png;base64,");
|
|
549
|
+
expect(parsed.image_url.detail).toBe("auto");
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it("should accept custom detail parameter", async () => {
|
|
553
|
+
const func = toolsService.getFunction("loadImageAsBase64");
|
|
554
|
+
const result = await func(testImagePath, "high");
|
|
555
|
+
|
|
556
|
+
const parsed = JSON.parse(result);
|
|
557
|
+
expect(parsed.image_url.detail).toBe("high");
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it("should throw error for non-existent file", async () => {
|
|
561
|
+
const func = toolsService.getFunction("loadImageAsBase64");
|
|
562
|
+
|
|
563
|
+
await expect(func("/path/to/nonexistent.png")).rejects.toThrow(
|
|
564
|
+
"File not found"
|
|
565
|
+
);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it("should throw error for unsupported format", async () => {
|
|
569
|
+
const txtPath = "/tmp/test-file.txt";
|
|
570
|
+
fs.writeFileSync(txtPath, "test content");
|
|
571
|
+
|
|
572
|
+
const func = toolsService.getFunction("loadImageAsBase64");
|
|
573
|
+
|
|
574
|
+
try {
|
|
575
|
+
await expect(func(txtPath)).rejects.toThrow("Unsupported image format");
|
|
576
|
+
} finally {
|
|
577
|
+
fs.unlinkSync(txtPath);
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
it("should throw error for directory path", async () => {
|
|
582
|
+
const dirPath = "/tmp/test-dir";
|
|
583
|
+
if (!fs.existsSync(dirPath)) {
|
|
584
|
+
fs.mkdirSync(dirPath);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const func = toolsService.getFunction("loadImageAsBase64");
|
|
588
|
+
|
|
589
|
+
try {
|
|
590
|
+
await expect(func(dirPath)).rejects.toThrow("Path is not a file");
|
|
591
|
+
} finally {
|
|
592
|
+
fs.rmdirSync(dirPath);
|
|
593
|
+
}
|
|
401
594
|
});
|
|
402
595
|
});
|
|
403
|
-
});
|
|
596
|
+
});
|
|
@@ -606,8 +606,9 @@ describe("ToolsService", () => {
|
|
|
606
606
|
const originalFunction = jest.fn().mockResolvedValue("original result");
|
|
607
607
|
const wrapperFunction = jest
|
|
608
608
|
.fn()
|
|
609
|
-
.mockImplementation(async (originalFn, args) => {
|
|
610
|
-
|
|
609
|
+
.mockImplementation(async (originalFn, args, tool) => {
|
|
610
|
+
// args is now an array of arguments
|
|
611
|
+
const result = await originalFn(...args);
|
|
611
612
|
return `wrapped: ${result}`;
|
|
612
613
|
});
|
|
613
614
|
|
|
@@ -1311,8 +1312,9 @@ describe("ToolsService", () => {
|
|
|
1311
1312
|
}
|
|
1312
1313
|
|
|
1313
1314
|
const wrapper = (originalFunc: Function, args: any, tool: Tool) => {
|
|
1314
|
-
// The wrapper should preserve the binding
|
|
1315
|
-
|
|
1315
|
+
// The wrapper should preserve the binding, args is now an array
|
|
1316
|
+
// We need to spread the args array when calling the function
|
|
1317
|
+
const result = originalFunc.call(toolsService, ...args);
|
|
1316
1318
|
return `Wrapped: ${result}`;
|
|
1317
1319
|
};
|
|
1318
1320
|
|
package/ts_build/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tyvm/knowhow",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.65",
|
|
4
4
|
"description": "ai cli with plugins and agents",
|
|
5
5
|
"main": "ts_build/src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@anthropic-ai/sdk": "^0.39.0",
|
|
42
42
|
"@aws-sdk/client-s3": "^3.588.0",
|
|
43
|
+
"@tyvm/knowhow-tunnel": "^0.0.1",
|
|
43
44
|
"@google/genai": "^0.14.1",
|
|
44
45
|
"@inquirer/editor": "^4.2.18",
|
|
45
46
|
"@linear/sdk": "^12.0.0",
|
|
@@ -380,6 +380,7 @@ Please continue from where you left off and complete the original request.
|
|
|
380
380
|
new TokenCompressor_1.TokenCompressor(agent.tools).createProcessor((msg) => Boolean(msg.role === "tool" && msg.tool_call_id)),
|
|
381
381
|
];
|
|
382
382
|
agent.messageProcessor.setProcessors("pre_call", [
|
|
383
|
+
new index_3.Base64ImageProcessor(agent.tools).createProcessor(),
|
|
383
384
|
...caching,
|
|
384
385
|
new index_3.CustomVariables(agent.tools).createProcessor(),
|
|
385
386
|
]);
|
|
@@ -387,7 +388,10 @@ Please continue from where you left off and complete the original request.
|
|
|
387
388
|
new index_3.XmlToolCallProcessor().createProcessor(),
|
|
388
389
|
new index_3.HarmonyToolProcessor().createProcessor(),
|
|
389
390
|
]);
|
|
390
|
-
agent.messageProcessor.setProcessors("post_tools",
|
|
391
|
+
agent.messageProcessor.setProcessors("post_tools", [
|
|
392
|
+
new index_3.Base64ImageProcessor(agent.tools).createProcessor(),
|
|
393
|
+
...caching,
|
|
394
|
+
]);
|
|
391
395
|
if (!agent.agentEvents.listenerCount(agent.eventTypes.toolCall)) {
|
|
392
396
|
agent.agentEvents.on(agent.eventTypes.toolCall, (responseMsg) => {
|
|
393
397
|
console.time(JSON.stringify(responseMsg.toolCall.function.name));
|
|
@@ -403,10 +407,15 @@ Please continue from where you left off and complete the original request.
|
|
|
403
407
|
}
|
|
404
408
|
const taskCompleted = new Promise((resolve) => {
|
|
405
409
|
agent.agentEvents.once(agent.eventTypes.done, async (doneMsg) => {
|
|
406
|
-
console.log("
|
|
410
|
+
console.log("🎯 [AgentModule] Task Completed");
|
|
407
411
|
done = true;
|
|
408
412
|
output = doneMsg || "No response from the AI";
|
|
409
413
|
taskInfo = this.taskRegistry.get(taskId);
|
|
414
|
+
if (knowhowTaskId) {
|
|
415
|
+
console.log("🎯 [AgentModule] Waiting for sync finalization...");
|
|
416
|
+
await this.agentSync.waitForFinalization();
|
|
417
|
+
console.log("🎯 [AgentModule] Sync finalization complete");
|
|
418
|
+
}
|
|
410
419
|
if (taskInfo) {
|
|
411
420
|
taskInfo.status = "completed";
|
|
412
421
|
taskInfo.totalCost = agent.getTotalCostUsd();
|
|
@@ -414,6 +423,7 @@ Please continue from where you left off and complete the original request.
|
|
|
414
423
|
taskInfo.endTime = Date.now();
|
|
415
424
|
}
|
|
416
425
|
console.log(index_2.Marked.parse(output));
|
|
426
|
+
console.log("🎯 [AgentModule] Task Complete");
|
|
417
427
|
resolve(doneMsg);
|
|
418
428
|
});
|
|
419
429
|
});
|