@aj-archipelago/cortex 1.4.22 → 1.4.23
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/FILE_SYSTEM_DOCUMENTATION.md +116 -48
- package/config.js +9 -0
- package/lib/fileUtils.js +226 -201
- package/package.json +1 -1
- package/pathways/system/entity/files/sys_read_file_collection.js +13 -11
- package/pathways/system/entity/files/sys_update_file_metadata.js +16 -7
- package/pathways/system/entity/sys_entity_agent.js +8 -6
- package/pathways/system/entity/tools/sys_tool_codingagent.js +4 -4
- package/pathways/system/entity/tools/sys_tool_editfile.js +27 -22
- package/pathways/system/entity/tools/sys_tool_file_collection.js +15 -10
- package/pathways/system/entity/tools/sys_tool_image.js +1 -1
- package/pathways/system/entity/tools/sys_tool_image_gemini.js +1 -1
- package/pathways/system/entity/tools/sys_tool_readfile.js +4 -4
- package/pathways/system/entity/tools/sys_tool_slides_gemini.js +1 -1
- package/pathways/system/entity/tools/sys_tool_video_veo.js +1 -1
- package/pathways/system/entity/tools/sys_tool_view_image.js +10 -5
- package/pathways/system/workspaces/run_workspace_agent.js +4 -1
- package/pathways/video_seedance.js +2 -0
- package/server/executeWorkspace.js +45 -2
- package/server/pathwayResolver.js +18 -0
- package/server/plugins/replicateApiPlugin.js +18 -0
- package/server/typeDef.js +10 -1
- package/test.log +39427 -0
- package/tests/integration/features/tools/fileCollection.test.js +254 -248
- package/tests/integration/features/tools/fileOperations.test.js +131 -81
- package/tests/integration/graphql/async/stream/vendors/claude_streaming.test.js +3 -4
- package/tests/integration/graphql/async/stream/vendors/gemini_streaming.test.js +3 -4
- package/tests/integration/graphql/async/stream/vendors/grok_streaming.test.js +3 -4
- package/tests/integration/graphql/async/stream/vendors/openai_streaming.test.js +5 -5
- package/tests/unit/core/fileCollection.test.js +86 -25
- package/pathways/system/workspaces/run_workspace_research_agent.js +0 -27
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
import test from 'ava';
|
|
5
5
|
import serverFactory from '../../../../index.js';
|
|
6
6
|
import { callPathway } from '../../../../lib/pathwayTools.js';
|
|
7
|
-
import { generateFileMessageContent, resolveFileParameter, loadFileCollection } from '../../../../lib/fileUtils.js';
|
|
7
|
+
import { generateFileMessageContent, resolveFileParameter, loadFileCollection, syncAndStripFilesFromChatHistory, loadMergedFileCollection } from '../../../../lib/fileUtils.js';
|
|
8
|
+
|
|
9
|
+
// Helper to create agentContext from contextId/contextKey
|
|
10
|
+
const createAgentContext = (contextId, contextKey = null) => [{ contextId, contextKey, default: true }];
|
|
8
11
|
|
|
9
12
|
let testServer;
|
|
10
13
|
|
|
@@ -47,7 +50,7 @@ test('File collection: Add file to collection', async t => {
|
|
|
47
50
|
|
|
48
51
|
try {
|
|
49
52
|
const result = await callPathway('sys_tool_file_collection', {
|
|
50
|
-
contextId,
|
|
53
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
51
54
|
url: 'https://example.com/test.jpg',
|
|
52
55
|
gcs: 'gs://bucket/test.jpg',
|
|
53
56
|
filename: 'test.jpg',
|
|
@@ -80,14 +83,14 @@ test('File collection: List files', async t => {
|
|
|
80
83
|
try {
|
|
81
84
|
// Add a few files first
|
|
82
85
|
await callPathway('sys_tool_file_collection', {
|
|
83
|
-
contextId,
|
|
86
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
84
87
|
url: 'https://example.com/file1.jpg',
|
|
85
88
|
filename: 'file1.jpg',
|
|
86
89
|
userMessage: 'Add file 1'
|
|
87
90
|
});
|
|
88
91
|
|
|
89
92
|
await callPathway('sys_tool_file_collection', {
|
|
90
|
-
contextId,
|
|
93
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
91
94
|
url: 'https://example.com/file2.pdf',
|
|
92
95
|
filename: 'file2.pdf',
|
|
93
96
|
tags: ['document'],
|
|
@@ -96,7 +99,7 @@ test('File collection: List files', async t => {
|
|
|
96
99
|
|
|
97
100
|
// List files
|
|
98
101
|
const result = await callPathway('sys_tool_file_collection', {
|
|
99
|
-
contextId,
|
|
102
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
100
103
|
userMessage: 'List files'
|
|
101
104
|
});
|
|
102
105
|
|
|
@@ -118,7 +121,7 @@ test('File collection: Search files', async t => {
|
|
|
118
121
|
try {
|
|
119
122
|
// Add files with different metadata
|
|
120
123
|
await callPathway('sys_tool_file_collection', {
|
|
121
|
-
contextId,
|
|
124
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
122
125
|
url: 'https://example.com/report.pdf',
|
|
123
126
|
filename: 'report.pdf',
|
|
124
127
|
tags: ['document', 'report'],
|
|
@@ -127,7 +130,7 @@ test('File collection: Search files', async t => {
|
|
|
127
130
|
});
|
|
128
131
|
|
|
129
132
|
await callPathway('sys_tool_file_collection', {
|
|
130
|
-
contextId,
|
|
133
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
131
134
|
url: 'https://example.com/image.jpg',
|
|
132
135
|
filename: 'image.jpg',
|
|
133
136
|
tags: ['photo'],
|
|
@@ -137,7 +140,7 @@ test('File collection: Search files', async t => {
|
|
|
137
140
|
|
|
138
141
|
// Search by filename
|
|
139
142
|
const result1 = await callPathway('sys_tool_file_collection', {
|
|
140
|
-
contextId,
|
|
143
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
141
144
|
query: 'report',
|
|
142
145
|
userMessage: 'Search for report'
|
|
143
146
|
});
|
|
@@ -149,7 +152,7 @@ test('File collection: Search files', async t => {
|
|
|
149
152
|
|
|
150
153
|
// Search by tag
|
|
151
154
|
const result2 = await callPathway('sys_tool_file_collection', {
|
|
152
|
-
contextId,
|
|
155
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
153
156
|
query: 'photo',
|
|
154
157
|
userMessage: 'Search for photo'
|
|
155
158
|
});
|
|
@@ -161,7 +164,7 @@ test('File collection: Search files', async t => {
|
|
|
161
164
|
|
|
162
165
|
// Search by notes
|
|
163
166
|
const result3 = await callPathway('sys_tool_file_collection', {
|
|
164
|
-
contextId,
|
|
167
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
165
168
|
query: 'office',
|
|
166
169
|
userMessage: 'Search for office'
|
|
167
170
|
});
|
|
@@ -182,7 +185,7 @@ test('File collection: Search by filename when displayFilename not set', async t
|
|
|
182
185
|
// Add file with only filename (no displayFilename)
|
|
183
186
|
// This tests the bug fix where search only checked displayFilename
|
|
184
187
|
await callPathway('sys_tool_file_collection', {
|
|
185
|
-
contextId,
|
|
188
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
186
189
|
url: 'https://example.com/smoketest-tools.txt',
|
|
187
190
|
filename: 'smoketest-tools.txt',
|
|
188
191
|
tags: ['smoketest', 'text'],
|
|
@@ -192,7 +195,7 @@ test('File collection: Search by filename when displayFilename not set', async t
|
|
|
192
195
|
|
|
193
196
|
// Search by filename - should find it even if displayFilename not set
|
|
194
197
|
const result1 = await callPathway('sys_tool_file_collection', {
|
|
195
|
-
contextId,
|
|
198
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
196
199
|
query: 'smoketest',
|
|
197
200
|
userMessage: 'Search for smoketest'
|
|
198
201
|
});
|
|
@@ -205,7 +208,7 @@ test('File collection: Search by filename when displayFilename not set', async t
|
|
|
205
208
|
|
|
206
209
|
// Search by full filename
|
|
207
210
|
const result2 = await callPathway('sys_tool_file_collection', {
|
|
208
|
-
contextId,
|
|
211
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
209
212
|
query: 'smoketest-tools',
|
|
210
213
|
userMessage: 'Search for smoketest-tools'
|
|
211
214
|
});
|
|
@@ -224,7 +227,7 @@ test('File collection: Remove single file', async t => {
|
|
|
224
227
|
try {
|
|
225
228
|
// Add files
|
|
226
229
|
const addResult1 = await callPathway('sys_tool_file_collection', {
|
|
227
|
-
contextId,
|
|
230
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
228
231
|
url: 'https://example.com/file1.jpg',
|
|
229
232
|
filename: 'file1.jpg',
|
|
230
233
|
userMessage: 'Add file 1'
|
|
@@ -232,7 +235,7 @@ test('File collection: Remove single file', async t => {
|
|
|
232
235
|
const file1Id = JSON.parse(addResult1).fileId;
|
|
233
236
|
|
|
234
237
|
await callPathway('sys_tool_file_collection', {
|
|
235
|
-
contextId,
|
|
238
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
236
239
|
url: 'https://example.com/file2.pdf',
|
|
237
240
|
filename: 'file2.pdf',
|
|
238
241
|
userMessage: 'Add file 2'
|
|
@@ -240,7 +243,7 @@ test('File collection: Remove single file', async t => {
|
|
|
240
243
|
|
|
241
244
|
// Remove file1
|
|
242
245
|
const result = await callPathway('sys_tool_file_collection', {
|
|
243
|
-
contextId,
|
|
246
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
244
247
|
fileIds: [file1Id],
|
|
245
248
|
userMessage: 'Remove file 1'
|
|
246
249
|
});
|
|
@@ -255,7 +258,7 @@ test('File collection: Remove single file', async t => {
|
|
|
255
258
|
|
|
256
259
|
// Verify it was removed (cache should be invalidated immediately)
|
|
257
260
|
const listResult = await callPathway('sys_tool_file_collection', {
|
|
258
|
-
contextId,
|
|
261
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
259
262
|
userMessage: 'List files'
|
|
260
263
|
});
|
|
261
264
|
const listParsed = JSON.parse(listResult);
|
|
@@ -273,7 +276,7 @@ test('File collection: Remove file - cache invalidation', async t => {
|
|
|
273
276
|
try {
|
|
274
277
|
// Add files
|
|
275
278
|
const addResult1 = await callPathway('sys_tool_file_collection', {
|
|
276
|
-
contextId,
|
|
279
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
277
280
|
url: 'https://example.com/file1.jpg',
|
|
278
281
|
filename: 'file1.jpg',
|
|
279
282
|
userMessage: 'Add file 1'
|
|
@@ -281,7 +284,7 @@ test('File collection: Remove file - cache invalidation', async t => {
|
|
|
281
284
|
const file1Id = JSON.parse(addResult1).fileId;
|
|
282
285
|
|
|
283
286
|
const addResult2 = await callPathway('sys_tool_file_collection', {
|
|
284
|
-
contextId,
|
|
287
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
285
288
|
url: 'https://example.com/file2.pdf',
|
|
286
289
|
filename: 'file2.pdf',
|
|
287
290
|
userMessage: 'Add file 2'
|
|
@@ -290,7 +293,7 @@ test('File collection: Remove file - cache invalidation', async t => {
|
|
|
290
293
|
|
|
291
294
|
// Verify both files are in collection
|
|
292
295
|
const listBefore = await callPathway('sys_tool_file_collection', {
|
|
293
|
-
contextId,
|
|
296
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
294
297
|
userMessage: 'List files before removal'
|
|
295
298
|
});
|
|
296
299
|
const listBeforeParsed = JSON.parse(listBefore);
|
|
@@ -298,7 +301,7 @@ test('File collection: Remove file - cache invalidation', async t => {
|
|
|
298
301
|
|
|
299
302
|
// Remove file1
|
|
300
303
|
const removeResult = await callPathway('sys_tool_file_collection', {
|
|
301
|
-
contextId,
|
|
304
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
302
305
|
fileIds: [file1Id],
|
|
303
306
|
userMessage: 'Remove file 1'
|
|
304
307
|
});
|
|
@@ -309,7 +312,7 @@ test('File collection: Remove file - cache invalidation', async t => {
|
|
|
309
312
|
|
|
310
313
|
// Immediately list files - should reflect removal (cache invalidation test)
|
|
311
314
|
const listAfter = await callPathway('sys_tool_file_collection', {
|
|
312
|
-
contextId,
|
|
315
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
313
316
|
userMessage: 'List files after removal'
|
|
314
317
|
});
|
|
315
318
|
const listAfterParsed = JSON.parse(listAfter);
|
|
@@ -327,7 +330,7 @@ test('File collection: Remove multiple files', async t => {
|
|
|
327
330
|
try {
|
|
328
331
|
// Add files
|
|
329
332
|
const addResult1 = await callPathway('sys_tool_file_collection', {
|
|
330
|
-
contextId,
|
|
333
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
331
334
|
url: 'https://example.com/file1.jpg',
|
|
332
335
|
filename: 'file1.jpg',
|
|
333
336
|
userMessage: 'Add file 1'
|
|
@@ -335,7 +338,7 @@ test('File collection: Remove multiple files', async t => {
|
|
|
335
338
|
const file1Id = JSON.parse(addResult1).fileId;
|
|
336
339
|
|
|
337
340
|
const addResult2 = await callPathway('sys_tool_file_collection', {
|
|
338
|
-
contextId,
|
|
341
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
339
342
|
url: 'https://example.com/file2.pdf',
|
|
340
343
|
filename: 'file2.pdf',
|
|
341
344
|
userMessage: 'Add file 2'
|
|
@@ -344,7 +347,7 @@ test('File collection: Remove multiple files', async t => {
|
|
|
344
347
|
|
|
345
348
|
// Remove multiple files
|
|
346
349
|
const result = await callPathway('sys_tool_file_collection', {
|
|
347
|
-
contextId,
|
|
350
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
348
351
|
fileIds: [file1Id, file2Id],
|
|
349
352
|
userMessage: 'Remove files 1 and 2'
|
|
350
353
|
});
|
|
@@ -358,7 +361,7 @@ test('File collection: Remove multiple files', async t => {
|
|
|
358
361
|
|
|
359
362
|
// Verify collection is empty
|
|
360
363
|
const listResult = await callPathway('sys_tool_file_collection', {
|
|
361
|
-
contextId,
|
|
364
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
362
365
|
userMessage: 'List files'
|
|
363
366
|
});
|
|
364
367
|
const listParsed = JSON.parse(listResult);
|
|
@@ -370,15 +373,35 @@ test('File collection: Remove multiple files', async t => {
|
|
|
370
373
|
|
|
371
374
|
|
|
372
375
|
test('File collection: Error handling - missing contextId', async t => {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
376
|
+
try {
|
|
377
|
+
const result = await callPathway('sys_tool_file_collection', {
|
|
378
|
+
url: 'https://example.com/test.jpg',
|
|
379
|
+
filename: 'test.jpg',
|
|
380
|
+
userMessage: 'Test'
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// Result might be JSON or error string
|
|
384
|
+
let parsed;
|
|
385
|
+
try {
|
|
386
|
+
parsed = JSON.parse(result);
|
|
387
|
+
} catch {
|
|
388
|
+
// If not JSON, it's an error string - that's fine
|
|
389
|
+
t.true(typeof result === 'string');
|
|
390
|
+
t.true(result.includes('required') || result.includes('agentContext') || result.includes('contextId'));
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// If it's JSON, check for error
|
|
395
|
+
if (parsed.success === false) {
|
|
396
|
+
t.true(parsed.error.includes('required') || parsed.error.includes('agentContext') || parsed.error.includes('contextId'));
|
|
397
|
+
} else {
|
|
398
|
+
// If no error, that's also a failure case
|
|
399
|
+
t.fail('Expected error when contextId is missing');
|
|
400
|
+
}
|
|
401
|
+
} catch (error) {
|
|
402
|
+
// Error thrown is also acceptable
|
|
403
|
+
t.true(error.message.includes('required') || error.message.includes('agentContext') || error.message.includes('contextId') || error.message.includes('EADDRINUSE'));
|
|
404
|
+
}
|
|
382
405
|
});
|
|
383
406
|
|
|
384
407
|
test('File collection: Error handling - remove non-existent file', async t => {
|
|
@@ -386,7 +409,7 @@ test('File collection: Error handling - remove non-existent file', async t => {
|
|
|
386
409
|
|
|
387
410
|
try {
|
|
388
411
|
const result = await callPathway('sys_tool_file_collection', {
|
|
389
|
-
contextId,
|
|
412
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
390
413
|
fileIds: ['non-existent-id'],
|
|
391
414
|
userMessage: 'Remove file'
|
|
392
415
|
});
|
|
@@ -405,7 +428,7 @@ test('File collection: List with filters and sorting', async t => {
|
|
|
405
428
|
try {
|
|
406
429
|
// Add files with different tags and dates
|
|
407
430
|
await callPathway('sys_tool_file_collection', {
|
|
408
|
-
contextId,
|
|
431
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
409
432
|
url: 'https://example.com/file1.jpg',
|
|
410
433
|
filename: 'a_file.jpg',
|
|
411
434
|
tags: ['photo'],
|
|
@@ -416,7 +439,7 @@ test('File collection: List with filters and sorting', async t => {
|
|
|
416
439
|
await new Promise(resolve => setTimeout(resolve, 10));
|
|
417
440
|
|
|
418
441
|
await callPathway('sys_tool_file_collection', {
|
|
419
|
-
contextId,
|
|
442
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
420
443
|
url: 'https://example.com/file2.pdf',
|
|
421
444
|
filename: 'z_file.pdf',
|
|
422
445
|
tags: ['document'],
|
|
@@ -425,7 +448,7 @@ test('File collection: List with filters and sorting', async t => {
|
|
|
425
448
|
|
|
426
449
|
// List sorted by filename
|
|
427
450
|
const result1 = await callPathway('sys_tool_file_collection', {
|
|
428
|
-
contextId,
|
|
451
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
429
452
|
sortBy: 'filename',
|
|
430
453
|
userMessage: 'List sorted by filename'
|
|
431
454
|
});
|
|
@@ -436,7 +459,7 @@ test('File collection: List with filters and sorting', async t => {
|
|
|
436
459
|
|
|
437
460
|
// List filtered by tag
|
|
438
461
|
const result2 = await callPathway('sys_tool_file_collection', {
|
|
439
|
-
contextId,
|
|
462
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
440
463
|
tags: ['photo'],
|
|
441
464
|
userMessage: 'List photos'
|
|
442
465
|
});
|
|
@@ -449,108 +472,6 @@ test('File collection: List with filters and sorting', async t => {
|
|
|
449
472
|
}
|
|
450
473
|
});
|
|
451
474
|
|
|
452
|
-
test('Memory system: file collections excluded from memoryAll (memoryFiles deprecated)', async t => {
|
|
453
|
-
const contextId = createTestContext();
|
|
454
|
-
|
|
455
|
-
try {
|
|
456
|
-
// Save a file collection directly to Redis (file collections are stored separately, not in memory system)
|
|
457
|
-
const { saveFileCollection } = await import('../../../../lib/fileUtils.js');
|
|
458
|
-
await saveFileCollection(contextId, null, [{
|
|
459
|
-
id: 'test-1',
|
|
460
|
-
url: 'https://example.com/test.jpg',
|
|
461
|
-
displayFilename: 'test.jpg'
|
|
462
|
-
}]);
|
|
463
|
-
|
|
464
|
-
// Save other memory
|
|
465
|
-
await callPathway('sys_save_memory', {
|
|
466
|
-
contextId,
|
|
467
|
-
section: 'memorySelf',
|
|
468
|
-
aiMemory: 'Test memory content'
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
// Read all memory - should not include file collections (memoryFiles section is deprecated and not returned)
|
|
472
|
-
const allMemory = await callPathway('sys_read_memory', {
|
|
473
|
-
contextId,
|
|
474
|
-
section: 'memoryAll'
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
const parsed = JSON.parse(allMemory);
|
|
478
|
-
t.truthy(parsed.memorySelf);
|
|
479
|
-
t.falsy(parsed.memoryFiles); // memoryFiles is deprecated - file collections are stored in Redis hash maps
|
|
480
|
-
|
|
481
|
-
// But should be accessible via loadFileCollection
|
|
482
|
-
const files = await loadFileCollection(contextId, null, false);
|
|
483
|
-
t.is(files.length, 1);
|
|
484
|
-
t.is(files[0].displayFilename, 'test.jpg');
|
|
485
|
-
} finally {
|
|
486
|
-
await cleanup(contextId);
|
|
487
|
-
}
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
test('Memory system: file collections not cleared by memoryAll clear', async t => {
|
|
491
|
-
const contextId = createTestContext();
|
|
492
|
-
|
|
493
|
-
try {
|
|
494
|
-
// Save file collection directly to Redis
|
|
495
|
-
const { saveFileCollection } = await import('../../../../lib/fileUtils.js');
|
|
496
|
-
await saveFileCollection(contextId, null, [{
|
|
497
|
-
id: 'test-1',
|
|
498
|
-
url: 'https://example.com/test.jpg',
|
|
499
|
-
displayFilename: 'test.jpg'
|
|
500
|
-
}]);
|
|
501
|
-
|
|
502
|
-
// Clear all memory
|
|
503
|
-
await callPathway('sys_save_memory', {
|
|
504
|
-
contextId,
|
|
505
|
-
section: 'memoryAll',
|
|
506
|
-
aiMemory: ''
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
// Verify files are still there (file collections are separate from memory system)
|
|
510
|
-
const files = await loadFileCollection(contextId, null, false);
|
|
511
|
-
t.is(files.length, 1);
|
|
512
|
-
t.is(files[0].displayFilename, 'test.jpg');
|
|
513
|
-
} finally {
|
|
514
|
-
await cleanup(contextId);
|
|
515
|
-
}
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
test('Memory system: file collections ignored in memoryAll save (memoryFiles deprecated)', async t => {
|
|
519
|
-
const contextId = createTestContext();
|
|
520
|
-
|
|
521
|
-
try {
|
|
522
|
-
// Save file collection first directly to Redis (file collections are stored separately, not in memory system)
|
|
523
|
-
const { saveFileCollection } = await import('../../../../lib/fileUtils.js');
|
|
524
|
-
await saveFileCollection(contextId, null, [{
|
|
525
|
-
id: 'original',
|
|
526
|
-
url: 'https://example.com/original.jpg',
|
|
527
|
-
displayFilename: 'original.jpg'
|
|
528
|
-
}]);
|
|
529
|
-
|
|
530
|
-
// Try to save all memory with memoryFiles included (should be ignored - memoryFiles is deprecated)
|
|
531
|
-
// File collections are now stored in Redis hash maps (FileStoreMap:ctx:<contextId>), not in memory system
|
|
532
|
-
await callPathway('sys_save_memory', {
|
|
533
|
-
contextId,
|
|
534
|
-
section: 'memoryAll',
|
|
535
|
-
aiMemory: JSON.stringify({
|
|
536
|
-
memorySelf: 'Test content',
|
|
537
|
-
memoryFiles: JSON.stringify([{
|
|
538
|
-
id: 'new',
|
|
539
|
-
url: 'https://example.com/new.jpg',
|
|
540
|
-
displayFilename: 'new.jpg'
|
|
541
|
-
}])
|
|
542
|
-
})
|
|
543
|
-
});
|
|
544
|
-
|
|
545
|
-
// Verify original files are still there (not overwritten - memoryFiles section is ignored by sys_save_memory)
|
|
546
|
-
const files = await loadFileCollection(contextId, null, false);
|
|
547
|
-
t.is(files.length, 1);
|
|
548
|
-
t.is(files[0].displayFilename, 'original.jpg');
|
|
549
|
-
} finally {
|
|
550
|
-
await cleanup(contextId);
|
|
551
|
-
}
|
|
552
|
-
});
|
|
553
|
-
|
|
554
475
|
// Test generateFileMessageContent function (integration tests)
|
|
555
476
|
// Note: These tests verify basic functionality. If WHISPER_MEDIA_API_URL is configured,
|
|
556
477
|
// generateFileMessageContent will automatically use short-lived URLs when file hashes are available.
|
|
@@ -560,7 +481,7 @@ test('generateFileMessageContent should find file by ID', async t => {
|
|
|
560
481
|
try {
|
|
561
482
|
// Add a file to collection
|
|
562
483
|
await callPathway('sys_tool_file_collection', {
|
|
563
|
-
contextId,
|
|
484
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
564
485
|
url: 'https://example.com/test.pdf',
|
|
565
486
|
gcs: 'gs://bucket/test.pdf',
|
|
566
487
|
filename: 'test.pdf',
|
|
@@ -572,7 +493,7 @@ test('generateFileMessageContent should find file by ID', async t => {
|
|
|
572
493
|
const fileId = collection[0].id;
|
|
573
494
|
|
|
574
495
|
// Normalize by ID
|
|
575
|
-
const result = await generateFileMessageContent(fileId, contextId);
|
|
496
|
+
const result = await generateFileMessageContent(fileId, createAgentContext(contextId));
|
|
576
497
|
|
|
577
498
|
t.truthy(result);
|
|
578
499
|
t.is(result.type, 'image_url');
|
|
@@ -592,7 +513,7 @@ test('generateFileMessageContent should find file by URL', async t => {
|
|
|
592
513
|
try {
|
|
593
514
|
// Add a file to collection
|
|
594
515
|
await callPathway('sys_tool_file_collection', {
|
|
595
|
-
contextId,
|
|
516
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
596
517
|
url: 'https://example.com/test.pdf',
|
|
597
518
|
gcs: 'gs://bucket/test.pdf',
|
|
598
519
|
filename: 'test.pdf',
|
|
@@ -600,7 +521,7 @@ test('generateFileMessageContent should find file by URL', async t => {
|
|
|
600
521
|
});
|
|
601
522
|
|
|
602
523
|
// Normalize by URL
|
|
603
|
-
const result = await generateFileMessageContent('https://example.com/test.pdf', contextId);
|
|
524
|
+
const result = await generateFileMessageContent('https://example.com/test.pdf', createAgentContext(contextId));
|
|
604
525
|
|
|
605
526
|
t.truthy(result);
|
|
606
527
|
t.is(result.url, 'https://example.com/test.pdf');
|
|
@@ -616,28 +537,28 @@ test('generateFileMessageContent should find file by fuzzy filename match', asyn
|
|
|
616
537
|
try {
|
|
617
538
|
// Add files to collection
|
|
618
539
|
await callPathway('sys_tool_file_collection', {
|
|
619
|
-
contextId,
|
|
540
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
620
541
|
url: 'https://example.com/document.pdf',
|
|
621
542
|
filename: 'document.pdf',
|
|
622
543
|
userMessage: 'Add document'
|
|
623
544
|
});
|
|
624
545
|
|
|
625
546
|
await callPathway('sys_tool_file_collection', {
|
|
626
|
-
contextId,
|
|
547
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
627
548
|
url: 'https://example.com/image.jpg',
|
|
628
549
|
filename: 'image.jpg',
|
|
629
550
|
userMessage: 'Add image'
|
|
630
551
|
});
|
|
631
552
|
|
|
632
553
|
// Normalize by partial filename
|
|
633
|
-
const result1 = await generateFileMessageContent('document', contextId);
|
|
554
|
+
const result1 = await generateFileMessageContent('document', createAgentContext(contextId));
|
|
634
555
|
t.truthy(result1);
|
|
635
556
|
// originalFilename is no longer returned in message content objects
|
|
636
557
|
t.truthy(result1.url);
|
|
637
558
|
t.truthy(result1.hash);
|
|
638
559
|
|
|
639
560
|
// Normalize by full filename
|
|
640
|
-
const result2 = await generateFileMessageContent('image.jpg', contextId);
|
|
561
|
+
const result2 = await generateFileMessageContent('image.jpg', createAgentContext(contextId));
|
|
641
562
|
t.truthy(result2);
|
|
642
563
|
// originalFilename is no longer returned in message content objects
|
|
643
564
|
t.truthy(result2.url);
|
|
@@ -653,7 +574,7 @@ test('generateFileMessageContent should detect image type', async t => {
|
|
|
653
574
|
try {
|
|
654
575
|
// Add an image file
|
|
655
576
|
await callPathway('sys_tool_file_collection', {
|
|
656
|
-
contextId,
|
|
577
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
657
578
|
url: 'https://example.com/image.jpg',
|
|
658
579
|
filename: 'image.jpg',
|
|
659
580
|
userMessage: 'Add image'
|
|
@@ -662,7 +583,7 @@ test('generateFileMessageContent should detect image type', async t => {
|
|
|
662
583
|
const collection = await loadFileCollection(contextId, null, false);
|
|
663
584
|
const fileId = collection[0].id;
|
|
664
585
|
|
|
665
|
-
const result = await generateFileMessageContent(fileId, contextId);
|
|
586
|
+
const result = await generateFileMessageContent(fileId, createAgentContext(contextId));
|
|
666
587
|
|
|
667
588
|
t.truthy(result);
|
|
668
589
|
t.is(result.type, 'image_url');
|
|
@@ -679,7 +600,7 @@ test('resolveFileParameter: Resolve by file ID', async t => {
|
|
|
679
600
|
try {
|
|
680
601
|
// Add a file to collection
|
|
681
602
|
const addResult = await callPathway('sys_tool_file_collection', {
|
|
682
|
-
contextId,
|
|
603
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
683
604
|
url: 'https://example.com/test-doc.pdf',
|
|
684
605
|
gcs: 'gs://bucket/test-doc.pdf',
|
|
685
606
|
filename: 'test-doc.pdf',
|
|
@@ -690,7 +611,7 @@ test('resolveFileParameter: Resolve by file ID', async t => {
|
|
|
690
611
|
const fileId = addParsed.fileId;
|
|
691
612
|
|
|
692
613
|
// Resolve by file ID
|
|
693
|
-
const resolved = await resolveFileParameter(fileId, contextId);
|
|
614
|
+
const resolved = await resolveFileParameter(fileId, createAgentContext(contextId));
|
|
694
615
|
t.is(resolved, 'https://example.com/test-doc.pdf');
|
|
695
616
|
} finally {
|
|
696
617
|
await cleanup(contextId);
|
|
@@ -703,7 +624,7 @@ test('resolveFileParameter: Resolve by filename', async t => {
|
|
|
703
624
|
try {
|
|
704
625
|
// Add a file to collection
|
|
705
626
|
await callPathway('sys_tool_file_collection', {
|
|
706
|
-
contextId,
|
|
627
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
707
628
|
url: 'https://example.com/my-file.txt',
|
|
708
629
|
gcs: 'gs://bucket/my-file.txt',
|
|
709
630
|
filename: 'my-file.txt',
|
|
@@ -711,7 +632,7 @@ test('resolveFileParameter: Resolve by filename', async t => {
|
|
|
711
632
|
});
|
|
712
633
|
|
|
713
634
|
// Resolve by filename
|
|
714
|
-
const resolved = await resolveFileParameter('my-file.txt', contextId);
|
|
635
|
+
const resolved = await resolveFileParameter('my-file.txt', createAgentContext(contextId));
|
|
715
636
|
t.is(resolved, 'https://example.com/my-file.txt');
|
|
716
637
|
} finally {
|
|
717
638
|
await cleanup(contextId);
|
|
@@ -725,7 +646,7 @@ test('resolveFileParameter: Resolve by hash', async t => {
|
|
|
725
646
|
try {
|
|
726
647
|
// Add a file to collection with hash
|
|
727
648
|
await callPathway('sys_tool_file_collection', {
|
|
728
|
-
contextId,
|
|
649
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
729
650
|
url: 'https://example.com/hashed-file.jpg',
|
|
730
651
|
gcs: 'gs://bucket/hashed-file.jpg',
|
|
731
652
|
filename: 'hashed-file.jpg',
|
|
@@ -734,7 +655,7 @@ test('resolveFileParameter: Resolve by hash', async t => {
|
|
|
734
655
|
});
|
|
735
656
|
|
|
736
657
|
// Resolve by hash
|
|
737
|
-
const resolved = await resolveFileParameter(testHash, contextId);
|
|
658
|
+
const resolved = await resolveFileParameter(testHash, createAgentContext(contextId));
|
|
738
659
|
t.is(resolved, 'https://example.com/hashed-file.jpg');
|
|
739
660
|
} finally {
|
|
740
661
|
await cleanup(contextId);
|
|
@@ -748,7 +669,7 @@ test('resolveFileParameter: Resolve by Azure URL', async t => {
|
|
|
748
669
|
try {
|
|
749
670
|
// Add a file to collection
|
|
750
671
|
await callPathway('sys_tool_file_collection', {
|
|
751
|
-
contextId,
|
|
672
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
752
673
|
url: testUrl,
|
|
753
674
|
gcs: 'gs://bucket/existing-file.pdf',
|
|
754
675
|
filename: 'existing-file.pdf',
|
|
@@ -756,7 +677,7 @@ test('resolveFileParameter: Resolve by Azure URL', async t => {
|
|
|
756
677
|
});
|
|
757
678
|
|
|
758
679
|
// Resolve by Azure URL
|
|
759
|
-
const resolved = await resolveFileParameter(testUrl, contextId);
|
|
680
|
+
const resolved = await resolveFileParameter(testUrl, createAgentContext(contextId));
|
|
760
681
|
t.is(resolved, testUrl);
|
|
761
682
|
} finally {
|
|
762
683
|
await cleanup(contextId);
|
|
@@ -770,7 +691,7 @@ test('resolveFileParameter: Resolve by GCS URL', async t => {
|
|
|
770
691
|
try {
|
|
771
692
|
// Add a file to collection
|
|
772
693
|
await callPathway('sys_tool_file_collection', {
|
|
773
|
-
contextId,
|
|
694
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
774
695
|
url: 'https://example.com/gcs-file.pdf',
|
|
775
696
|
gcs: testGcsUrl,
|
|
776
697
|
filename: 'gcs-file.pdf',
|
|
@@ -778,7 +699,7 @@ test('resolveFileParameter: Resolve by GCS URL', async t => {
|
|
|
778
699
|
});
|
|
779
700
|
|
|
780
701
|
// Resolve by GCS URL
|
|
781
|
-
const resolved = await resolveFileParameter(testGcsUrl, contextId);
|
|
702
|
+
const resolved = await resolveFileParameter(testGcsUrl, createAgentContext(contextId));
|
|
782
703
|
t.is(resolved, 'https://example.com/gcs-file.pdf');
|
|
783
704
|
} finally {
|
|
784
705
|
await cleanup(contextId);
|
|
@@ -793,7 +714,7 @@ test('resolveFileParameter: Prefer GCS URL when preferGcs is true', async t => {
|
|
|
793
714
|
try {
|
|
794
715
|
// Add a file to collection with both URLs
|
|
795
716
|
await callPathway('sys_tool_file_collection', {
|
|
796
|
-
contextId,
|
|
717
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
797
718
|
url: testAzureUrl,
|
|
798
719
|
gcs: testGcsUrl,
|
|
799
720
|
filename: 'prefer-gcs-file.pdf',
|
|
@@ -801,11 +722,11 @@ test('resolveFileParameter: Prefer GCS URL when preferGcs is true', async t => {
|
|
|
801
722
|
});
|
|
802
723
|
|
|
803
724
|
// Resolve by filename without preferGcs (should return Azure URL)
|
|
804
|
-
const resolvedDefault = await resolveFileParameter('prefer-gcs-file.pdf', contextId);
|
|
725
|
+
const resolvedDefault = await resolveFileParameter('prefer-gcs-file.pdf', createAgentContext(contextId));
|
|
805
726
|
t.is(resolvedDefault, testAzureUrl);
|
|
806
727
|
|
|
807
728
|
// Resolve by filename with preferGcs (should return GCS URL)
|
|
808
|
-
const resolvedGcs = await resolveFileParameter('prefer-gcs-file.pdf', contextId,
|
|
729
|
+
const resolvedGcs = await resolveFileParameter('prefer-gcs-file.pdf', createAgentContext(contextId), { preferGcs: true });
|
|
809
730
|
t.is(resolvedGcs, testGcsUrl);
|
|
810
731
|
} finally {
|
|
811
732
|
await cleanup(contextId);
|
|
@@ -817,7 +738,7 @@ test('resolveFileParameter: Return null when file not found', async t => {
|
|
|
817
738
|
|
|
818
739
|
try {
|
|
819
740
|
// Try to resolve a non-existent file
|
|
820
|
-
const resolved = await resolveFileParameter('non-existent-file.txt', contextId);
|
|
741
|
+
const resolved = await resolveFileParameter('non-existent-file.txt', createAgentContext(contextId));
|
|
821
742
|
t.is(resolved, null);
|
|
822
743
|
} finally {
|
|
823
744
|
await cleanup(contextId);
|
|
@@ -835,15 +756,15 @@ test('resolveFileParameter: Return null when fileParam is empty', async t => {
|
|
|
835
756
|
|
|
836
757
|
try {
|
|
837
758
|
// Try with empty string
|
|
838
|
-
const resolved1 = await resolveFileParameter('', contextId);
|
|
759
|
+
const resolved1 = await resolveFileParameter('', createAgentContext(contextId));
|
|
839
760
|
t.is(resolved1, null);
|
|
840
761
|
|
|
841
762
|
// Try with null
|
|
842
|
-
const resolved2 = await resolveFileParameter(null, contextId);
|
|
763
|
+
const resolved2 = await resolveFileParameter(null, createAgentContext(contextId));
|
|
843
764
|
t.is(resolved2, null);
|
|
844
765
|
|
|
845
766
|
// Try with undefined
|
|
846
|
-
const resolved3 = await resolveFileParameter(undefined, contextId);
|
|
767
|
+
const resolved3 = await resolveFileParameter(undefined, createAgentContext(contextId));
|
|
847
768
|
t.is(resolved3, null);
|
|
848
769
|
} finally {
|
|
849
770
|
await cleanup(contextId);
|
|
@@ -856,7 +777,7 @@ test('resolveFileParameter: Contains match on filename', async t => {
|
|
|
856
777
|
try {
|
|
857
778
|
// Add a file with a specific filename
|
|
858
779
|
await callPathway('sys_tool_file_collection', {
|
|
859
|
-
contextId,
|
|
780
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
860
781
|
url: 'https://example.com/my-document.pdf',
|
|
861
782
|
gcs: 'gs://bucket/my-document.pdf',
|
|
862
783
|
filename: 'my-document.pdf',
|
|
@@ -864,7 +785,7 @@ test('resolveFileParameter: Contains match on filename', async t => {
|
|
|
864
785
|
});
|
|
865
786
|
|
|
866
787
|
// Resolve by partial filename (contains match)
|
|
867
|
-
const resolved = await resolveFileParameter('document.pdf', contextId);
|
|
788
|
+
const resolved = await resolveFileParameter('document.pdf', createAgentContext(contextId));
|
|
868
789
|
t.is(resolved, 'https://example.com/my-document.pdf');
|
|
869
790
|
} finally {
|
|
870
791
|
await cleanup(contextId);
|
|
@@ -877,7 +798,7 @@ test('resolveFileParameter: Contains match requires minimum 4 characters', async
|
|
|
877
798
|
try {
|
|
878
799
|
// Add a file with a specific filename
|
|
879
800
|
await callPathway('sys_tool_file_collection', {
|
|
880
|
-
contextId,
|
|
801
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
881
802
|
url: 'https://example.com/test.pdf',
|
|
882
803
|
gcs: 'gs://bucket/test.pdf',
|
|
883
804
|
filename: 'test.pdf',
|
|
@@ -885,11 +806,11 @@ test('resolveFileParameter: Contains match requires minimum 4 characters', async
|
|
|
885
806
|
});
|
|
886
807
|
|
|
887
808
|
// Try to resolve with a 3-character parameter (should fail - too short)
|
|
888
|
-
const resolvedShort = await resolveFileParameter('pdf', contextId);
|
|
809
|
+
const resolvedShort = await resolveFileParameter('pdf', createAgentContext(contextId));
|
|
889
810
|
t.is(resolvedShort, null, 'Should not match with parameter shorter than 4 characters');
|
|
890
811
|
|
|
891
812
|
// Try to resolve with a 4-character parameter (should succeed)
|
|
892
|
-
const resolvedLong = await resolveFileParameter('test', contextId);
|
|
813
|
+
const resolvedLong = await resolveFileParameter('test', createAgentContext(contextId));
|
|
893
814
|
t.is(resolvedLong, 'https://example.com/test.pdf', 'Should match with parameter 4+ characters');
|
|
894
815
|
} finally {
|
|
895
816
|
await cleanup(contextId);
|
|
@@ -903,14 +824,14 @@ test('resolveFileParameter: Fallback to Azure URL when GCS not available and pre
|
|
|
903
824
|
try {
|
|
904
825
|
// Add a file without GCS URL
|
|
905
826
|
await callPathway('sys_tool_file_collection', {
|
|
906
|
-
contextId,
|
|
827
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
907
828
|
url: testAzureUrl,
|
|
908
829
|
filename: 'no-gcs-file.pdf',
|
|
909
830
|
userMessage: 'Adding test file'
|
|
910
831
|
});
|
|
911
832
|
|
|
912
833
|
// Resolve with preferGcs=true, but no GCS available (should fallback to Azure URL)
|
|
913
|
-
const resolved = await resolveFileParameter('no-gcs-file.pdf', contextId,
|
|
834
|
+
const resolved = await resolveFileParameter('no-gcs-file.pdf', createAgentContext(contextId), { preferGcs: true });
|
|
914
835
|
t.is(resolved, testAzureUrl);
|
|
915
836
|
} finally {
|
|
916
837
|
await cleanup(contextId);
|
|
@@ -924,7 +845,7 @@ test('resolveFileParameter: Handle contextKey for encrypted collections', async
|
|
|
924
845
|
try {
|
|
925
846
|
// Add a file to collection with contextKey
|
|
926
847
|
await callPathway('sys_tool_file_collection', {
|
|
927
|
-
contextId,
|
|
848
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
928
849
|
contextKey,
|
|
929
850
|
url: 'https://example.com/encrypted-file.pdf',
|
|
930
851
|
gcs: 'gs://bucket/encrypted-file.pdf',
|
|
@@ -933,7 +854,7 @@ test('resolveFileParameter: Handle contextKey for encrypted collections', async
|
|
|
933
854
|
});
|
|
934
855
|
|
|
935
856
|
// Resolve with contextKey
|
|
936
|
-
const resolved = await resolveFileParameter('encrypted-file.pdf', contextId, contextKey);
|
|
857
|
+
const resolved = await resolveFileParameter('encrypted-file.pdf', createAgentContext(contextId, contextKey));
|
|
937
858
|
t.is(resolved, 'https://example.com/encrypted-file.pdf');
|
|
938
859
|
} finally {
|
|
939
860
|
await cleanup(contextId);
|
|
@@ -946,7 +867,7 @@ test('File collection: Update file metadata', async t => {
|
|
|
946
867
|
try {
|
|
947
868
|
// Add a file first
|
|
948
869
|
const addResult = await callPathway('sys_tool_file_collection', {
|
|
949
|
-
contextId,
|
|
870
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
950
871
|
url: 'https://example.com/original.pdf',
|
|
951
872
|
filename: 'original.pdf',
|
|
952
873
|
tags: ['initial'],
|
|
@@ -998,7 +919,7 @@ test('updateFileMetadata should allow updating inCollection', async (t) => {
|
|
|
998
919
|
try {
|
|
999
920
|
// Add a file to collection
|
|
1000
921
|
const addResult = await callPathway('sys_tool_file_collection', {
|
|
1001
|
-
contextId,
|
|
922
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1002
923
|
url: 'https://example.com/test-incollection.pdf',
|
|
1003
924
|
filename: 'test-incollection.pdf',
|
|
1004
925
|
userMessage: 'Add file'
|
|
@@ -1072,7 +993,7 @@ test('File collection: Permanent files not deleted on remove', async t => {
|
|
|
1072
993
|
try {
|
|
1073
994
|
// Add a permanent file
|
|
1074
995
|
const addResult = await callPathway('sys_tool_file_collection', {
|
|
1075
|
-
contextId,
|
|
996
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1076
997
|
url: 'https://example.com/permanent.pdf',
|
|
1077
998
|
filename: 'permanent.pdf',
|
|
1078
999
|
userMessage: 'Add permanent file'
|
|
@@ -1090,7 +1011,7 @@ test('File collection: Permanent files not deleted on remove', async t => {
|
|
|
1090
1011
|
|
|
1091
1012
|
// Remove from collection
|
|
1092
1013
|
const removeResult = await callPathway('sys_tool_file_collection', {
|
|
1093
|
-
contextId,
|
|
1014
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1094
1015
|
fileIds: [fileId],
|
|
1095
1016
|
userMessage: 'Remove permanent file'
|
|
1096
1017
|
});
|
|
@@ -1103,7 +1024,7 @@ test('File collection: Permanent files not deleted on remove', async t => {
|
|
|
1103
1024
|
|
|
1104
1025
|
// Verify file was removed from collection
|
|
1105
1026
|
const listResult = await callPathway('sys_tool_file_collection', {
|
|
1106
|
-
contextId,
|
|
1027
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1107
1028
|
userMessage: 'List files'
|
|
1108
1029
|
});
|
|
1109
1030
|
const listParsed = JSON.parse(listResult);
|
|
@@ -1113,46 +1034,67 @@ test('File collection: Permanent files not deleted on remove', async t => {
|
|
|
1113
1034
|
}
|
|
1114
1035
|
});
|
|
1115
1036
|
|
|
1116
|
-
test('File collection:
|
|
1037
|
+
test('File collection: syncAndStripFilesFromChatHistory only strips collection files', async t => {
|
|
1117
1038
|
const contextId = createTestContext();
|
|
1118
1039
|
|
|
1119
1040
|
try {
|
|
1120
|
-
const {
|
|
1041
|
+
const { syncAndStripFilesFromChatHistory, addFileToCollection } = await import('../../../../lib/fileUtils.js');
|
|
1042
|
+
|
|
1043
|
+
// Add one file to collection
|
|
1044
|
+
await addFileToCollection(
|
|
1045
|
+
contextId,
|
|
1046
|
+
null,
|
|
1047
|
+
'https://example.com/in-collection.jpg',
|
|
1048
|
+
'gs://bucket/in-collection.jpg',
|
|
1049
|
+
'in-collection.jpg',
|
|
1050
|
+
[],
|
|
1051
|
+
'',
|
|
1052
|
+
'hash-in-coll'
|
|
1053
|
+
);
|
|
1121
1054
|
|
|
1122
|
-
// Create chat history with files
|
|
1055
|
+
// Create chat history with two files - one in collection, one not
|
|
1123
1056
|
const chatHistory = [
|
|
1124
1057
|
{
|
|
1125
1058
|
role: 'user',
|
|
1126
1059
|
content: [
|
|
1127
1060
|
{
|
|
1128
1061
|
type: 'image_url',
|
|
1129
|
-
image_url: { url: 'https://example.com/
|
|
1130
|
-
gcs: 'gs://bucket/
|
|
1131
|
-
hash: '
|
|
1062
|
+
image_url: { url: 'https://example.com/in-collection.jpg' },
|
|
1063
|
+
gcs: 'gs://bucket/in-collection.jpg',
|
|
1064
|
+
hash: 'hash-in-coll'
|
|
1132
1065
|
},
|
|
1133
1066
|
{
|
|
1134
1067
|
type: 'file',
|
|
1135
|
-
url: 'https://example.com/
|
|
1136
|
-
gcs: 'gs://bucket/
|
|
1137
|
-
hash: '
|
|
1068
|
+
url: 'https://example.com/external.pdf',
|
|
1069
|
+
gcs: 'gs://bucket/external.pdf',
|
|
1070
|
+
hash: 'hash-external'
|
|
1138
1071
|
}
|
|
1139
1072
|
]
|
|
1140
1073
|
}
|
|
1141
1074
|
];
|
|
1142
1075
|
|
|
1143
|
-
//
|
|
1144
|
-
await
|
|
1076
|
+
// Process chat history
|
|
1077
|
+
const { chatHistory: processed, availableFiles } = await syncAndStripFilesFromChatHistory(chatHistory, createAgentContext(contextId));
|
|
1078
|
+
|
|
1079
|
+
// Verify only collection file was stripped
|
|
1080
|
+
const content = processed[0].content;
|
|
1081
|
+
t.true(Array.isArray(content));
|
|
1082
|
+
|
|
1083
|
+
// First file (in collection) should be stripped to placeholder
|
|
1084
|
+
t.is(content[0].type, 'text');
|
|
1085
|
+
t.true(content[0].text.includes('[File:'));
|
|
1086
|
+
t.true(content[0].text.includes('available via file tools'));
|
|
1087
|
+
|
|
1088
|
+
// Second file (not in collection) should remain as-is
|
|
1089
|
+
t.is(content[1].type, 'file');
|
|
1090
|
+
t.is(content[1].url, 'https://example.com/external.pdf');
|
|
1145
1091
|
|
|
1146
|
-
//
|
|
1092
|
+
// Collection should still have only 1 file (no auto-syncing)
|
|
1147
1093
|
const collection = await loadFileCollection(contextId, null, false);
|
|
1148
|
-
t.is(collection.length,
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
// Sync again (should update lastAccessed, not duplicate)
|
|
1153
|
-
await syncFilesToCollection(chatHistory, contextId, null);
|
|
1154
|
-
const collection2 = await loadFileCollection(contextId, null, false);
|
|
1155
|
-
t.is(collection2.length, 2); // Should still be 2, not 4
|
|
1094
|
+
t.is(collection.length, 1);
|
|
1095
|
+
|
|
1096
|
+
// Available files should list the collection file
|
|
1097
|
+
t.true(availableFiles.includes('in-collection.jpg'));
|
|
1156
1098
|
} finally {
|
|
1157
1099
|
await cleanup(contextId);
|
|
1158
1100
|
}
|
|
@@ -1168,7 +1110,7 @@ test('File collection: UpdateFileMetadata tool - Rename file', async t => {
|
|
|
1168
1110
|
try {
|
|
1169
1111
|
// Add a file first
|
|
1170
1112
|
const addResult = await callPathway('sys_tool_file_collection', {
|
|
1171
|
-
contextId,
|
|
1113
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1172
1114
|
url: 'https://example.com/old-name.pdf',
|
|
1173
1115
|
filename: 'old-name.pdf',
|
|
1174
1116
|
tags: ['test'],
|
|
@@ -1181,7 +1123,7 @@ test('File collection: UpdateFileMetadata tool - Rename file', async t => {
|
|
|
1181
1123
|
|
|
1182
1124
|
// Rename using UpdateFileMetadata tool
|
|
1183
1125
|
const updateResult = await callPathway('sys_tool_file_collection', {
|
|
1184
|
-
contextId,
|
|
1126
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1185
1127
|
file: 'old-name.pdf',
|
|
1186
1128
|
newFilename: 'new-name.pdf',
|
|
1187
1129
|
userMessage: 'Rename file'
|
|
@@ -1210,7 +1152,7 @@ test('File collection: UpdateFileMetadata tool - Replace all tags', async t => {
|
|
|
1210
1152
|
try {
|
|
1211
1153
|
// Add file with initial tags
|
|
1212
1154
|
const addResult = await callPathway('sys_tool_file_collection', {
|
|
1213
|
-
contextId,
|
|
1155
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1214
1156
|
url: 'https://example.com/test.pdf',
|
|
1215
1157
|
filename: 'test.pdf',
|
|
1216
1158
|
tags: ['old', 'tags'],
|
|
@@ -1222,7 +1164,7 @@ test('File collection: UpdateFileMetadata tool - Replace all tags', async t => {
|
|
|
1222
1164
|
|
|
1223
1165
|
// Replace all tags
|
|
1224
1166
|
const updateResult = await callPathway('sys_tool_file_collection', {
|
|
1225
|
-
contextId,
|
|
1167
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1226
1168
|
file: 'test.pdf',
|
|
1227
1169
|
tags: ['new', 'replaced', 'tags'],
|
|
1228
1170
|
userMessage: 'Replace tags'
|
|
@@ -1246,7 +1188,7 @@ test('File collection: UpdateFileMetadata tool - Add tags', async t => {
|
|
|
1246
1188
|
try {
|
|
1247
1189
|
// Add file with initial tags
|
|
1248
1190
|
const addResult = await callPathway('sys_tool_file_collection', {
|
|
1249
|
-
contextId,
|
|
1191
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1250
1192
|
url: 'https://example.com/test.pdf',
|
|
1251
1193
|
filename: 'test.pdf',
|
|
1252
1194
|
tags: ['existing', 'tag'],
|
|
@@ -1258,7 +1200,7 @@ test('File collection: UpdateFileMetadata tool - Add tags', async t => {
|
|
|
1258
1200
|
|
|
1259
1201
|
// Add more tags
|
|
1260
1202
|
const updateResult = await callPathway('sys_tool_file_collection', {
|
|
1261
|
-
contextId,
|
|
1203
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1262
1204
|
file: 'test.pdf',
|
|
1263
1205
|
addTags: ['new', 'added'],
|
|
1264
1206
|
userMessage: 'Add tags'
|
|
@@ -1286,7 +1228,7 @@ test('File collection: UpdateFileMetadata tool - Remove tags', async t => {
|
|
|
1286
1228
|
try {
|
|
1287
1229
|
// Add file with tags
|
|
1288
1230
|
const addResult = await callPathway('sys_tool_file_collection', {
|
|
1289
|
-
contextId,
|
|
1231
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1290
1232
|
url: 'https://example.com/test.pdf',
|
|
1291
1233
|
filename: 'test.pdf',
|
|
1292
1234
|
tags: ['keep', 'remove1', 'remove2', 'also-keep'],
|
|
@@ -1298,7 +1240,7 @@ test('File collection: UpdateFileMetadata tool - Remove tags', async t => {
|
|
|
1298
1240
|
|
|
1299
1241
|
// Remove specific tags
|
|
1300
1242
|
const updateResult = await callPathway('sys_tool_file_collection', {
|
|
1301
|
-
contextId,
|
|
1243
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1302
1244
|
file: 'test.pdf',
|
|
1303
1245
|
removeTags: ['remove1', 'remove2'],
|
|
1304
1246
|
userMessage: 'Remove tags'
|
|
@@ -1326,7 +1268,7 @@ test('File collection: UpdateFileMetadata tool - Add and remove tags together',
|
|
|
1326
1268
|
try {
|
|
1327
1269
|
// Add file with tags
|
|
1328
1270
|
const addResult = await callPathway('sys_tool_file_collection', {
|
|
1329
|
-
contextId,
|
|
1271
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1330
1272
|
url: 'https://example.com/test.pdf',
|
|
1331
1273
|
filename: 'test.pdf',
|
|
1332
1274
|
tags: ['old1', 'old2', 'remove-me'],
|
|
@@ -1338,7 +1280,7 @@ test('File collection: UpdateFileMetadata tool - Add and remove tags together',
|
|
|
1338
1280
|
|
|
1339
1281
|
// Add and remove tags in one operation
|
|
1340
1282
|
const updateResult = await callPathway('sys_tool_file_collection', {
|
|
1341
|
-
contextId,
|
|
1283
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1342
1284
|
file: 'test.pdf',
|
|
1343
1285
|
addTags: ['new1', 'new2'],
|
|
1344
1286
|
removeTags: ['remove-me'],
|
|
@@ -1368,7 +1310,7 @@ test('File collection: UpdateFileMetadata tool - Update notes', async t => {
|
|
|
1368
1310
|
try {
|
|
1369
1311
|
// Add file with initial notes
|
|
1370
1312
|
const addResult = await callPathway('sys_tool_file_collection', {
|
|
1371
|
-
contextId,
|
|
1313
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1372
1314
|
url: 'https://example.com/test.pdf',
|
|
1373
1315
|
filename: 'test.pdf',
|
|
1374
1316
|
notes: 'Initial notes',
|
|
@@ -1380,7 +1322,7 @@ test('File collection: UpdateFileMetadata tool - Update notes', async t => {
|
|
|
1380
1322
|
|
|
1381
1323
|
// Update notes
|
|
1382
1324
|
const updateResult = await callPathway('sys_tool_file_collection', {
|
|
1383
|
-
contextId,
|
|
1325
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1384
1326
|
file: 'test.pdf',
|
|
1385
1327
|
notes: 'Updated notes with more detail',
|
|
1386
1328
|
userMessage: 'Update notes'
|
|
@@ -1404,7 +1346,7 @@ test('File collection: UpdateFileMetadata tool - Update permanent flag', async t
|
|
|
1404
1346
|
try {
|
|
1405
1347
|
// Add file (defaults to temporary)
|
|
1406
1348
|
const addResult = await callPathway('sys_tool_file_collection', {
|
|
1407
|
-
contextId,
|
|
1349
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1408
1350
|
url: 'https://example.com/test.pdf',
|
|
1409
1351
|
filename: 'test.pdf',
|
|
1410
1352
|
userMessage: 'Add file'
|
|
@@ -1415,7 +1357,7 @@ test('File collection: UpdateFileMetadata tool - Update permanent flag', async t
|
|
|
1415
1357
|
|
|
1416
1358
|
// Mark as permanent
|
|
1417
1359
|
const updateResult = await callPathway('sys_tool_file_collection', {
|
|
1418
|
-
contextId,
|
|
1360
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1419
1361
|
file: 'test.pdf',
|
|
1420
1362
|
permanent: true,
|
|
1421
1363
|
userMessage: 'Mark as permanent'
|
|
@@ -1439,7 +1381,7 @@ test('File collection: UpdateFileMetadata tool - Combined updates', async t => {
|
|
|
1439
1381
|
try {
|
|
1440
1382
|
// Add file
|
|
1441
1383
|
const addResult = await callPathway('sys_tool_file_collection', {
|
|
1442
|
-
contextId,
|
|
1384
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1443
1385
|
url: 'https://example.com/original.pdf',
|
|
1444
1386
|
filename: 'original.pdf',
|
|
1445
1387
|
tags: ['old'],
|
|
@@ -1453,7 +1395,7 @@ test('File collection: UpdateFileMetadata tool - Combined updates', async t => {
|
|
|
1453
1395
|
|
|
1454
1396
|
// Update everything at once
|
|
1455
1397
|
const updateResult = await callPathway('sys_tool_file_collection', {
|
|
1456
|
-
contextId,
|
|
1398
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1457
1399
|
file: 'original.pdf',
|
|
1458
1400
|
newFilename: 'renamed-and-tagged.pdf',
|
|
1459
1401
|
tags: ['new', 'tags'],
|
|
@@ -1488,7 +1430,7 @@ test('File collection: UpdateFileMetadata tool - File not found error', async t
|
|
|
1488
1430
|
try {
|
|
1489
1431
|
// Try to update a non-existent file
|
|
1490
1432
|
const updateResult = await callPathway('sys_tool_file_collection', {
|
|
1491
|
-
contextId,
|
|
1433
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1492
1434
|
file: 'nonexistent.pdf',
|
|
1493
1435
|
newFilename: 'new-name.pdf',
|
|
1494
1436
|
userMessage: 'Update missing file'
|
|
@@ -1508,7 +1450,7 @@ test('File collection: UpdateFileMetadata tool - Find file by ID', async t => {
|
|
|
1508
1450
|
try {
|
|
1509
1451
|
// Add file
|
|
1510
1452
|
const addResult = await callPathway('sys_tool_file_collection', {
|
|
1511
|
-
contextId,
|
|
1453
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1512
1454
|
url: 'https://example.com/test.pdf',
|
|
1513
1455
|
filename: 'test.pdf',
|
|
1514
1456
|
userMessage: 'Add file'
|
|
@@ -1520,7 +1462,7 @@ test('File collection: UpdateFileMetadata tool - Find file by ID', async t => {
|
|
|
1520
1462
|
|
|
1521
1463
|
// Update using file ID instead of filename
|
|
1522
1464
|
const updateResult = await callPathway('sys_tool_file_collection', {
|
|
1523
|
-
contextId,
|
|
1465
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1524
1466
|
file: fileId,
|
|
1525
1467
|
newFilename: 'renamed-by-id.pdf',
|
|
1526
1468
|
userMessage: 'Update by ID'
|
|
@@ -1544,7 +1486,7 @@ test('File collection: addFileToCollection returns correct ID for existing files
|
|
|
1544
1486
|
try {
|
|
1545
1487
|
// Add file first time
|
|
1546
1488
|
const addResult1 = await callPathway('sys_tool_file_collection', {
|
|
1547
|
-
contextId,
|
|
1489
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1548
1490
|
url: 'https://example.com/duplicate.pdf',
|
|
1549
1491
|
filename: 'first.pdf',
|
|
1550
1492
|
tags: ['first'],
|
|
@@ -1557,7 +1499,7 @@ test('File collection: addFileToCollection returns correct ID for existing files
|
|
|
1557
1499
|
|
|
1558
1500
|
// Add same file again (same URL = same hash)
|
|
1559
1501
|
const addResult2 = await callPathway('sys_tool_file_collection', {
|
|
1560
|
-
contextId,
|
|
1502
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1561
1503
|
url: 'https://example.com/duplicate.pdf',
|
|
1562
1504
|
filename: 'second.pdf',
|
|
1563
1505
|
tags: ['second'],
|
|
@@ -1595,8 +1537,7 @@ test('File collection encryption: Encrypt tags and notes with contextKey', async
|
|
|
1595
1537
|
try {
|
|
1596
1538
|
// Add file with tags and notes
|
|
1597
1539
|
const result = await callPathway('sys_tool_file_collection', {
|
|
1598
|
-
contextId,
|
|
1599
|
-
contextKey,
|
|
1540
|
+
agentContext: [{ contextId, contextKey, default: true }],
|
|
1600
1541
|
url: 'https://example.com/encrypted.pdf',
|
|
1601
1542
|
filename: 'encrypted.pdf',
|
|
1602
1543
|
tags: ['sensitive', 'private', 'confidential'],
|
|
@@ -1644,7 +1585,7 @@ test('File collection encryption: Empty tags and notes are not encrypted', async
|
|
|
1644
1585
|
try {
|
|
1645
1586
|
// Add file with empty tags and notes
|
|
1646
1587
|
const result = await callPathway('sys_tool_file_collection', {
|
|
1647
|
-
contextId,
|
|
1588
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1648
1589
|
contextKey,
|
|
1649
1590
|
url: 'https://example.com/empty.pdf',
|
|
1650
1591
|
filename: 'empty.pdf',
|
|
@@ -1687,7 +1628,7 @@ test('File collection encryption: Decryption fails with wrong contextKey', async
|
|
|
1687
1628
|
try {
|
|
1688
1629
|
// Add file with contextKey
|
|
1689
1630
|
const result = await callPathway('sys_tool_file_collection', {
|
|
1690
|
-
contextId,
|
|
1631
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1691
1632
|
contextKey,
|
|
1692
1633
|
url: 'https://example.com/wrong-key.pdf',
|
|
1693
1634
|
filename: 'wrong-key.pdf',
|
|
@@ -1735,7 +1676,7 @@ test('File collection encryption: Migration from unencrypted to encrypted', asyn
|
|
|
1735
1676
|
try {
|
|
1736
1677
|
// First, add file without contextKey (unencrypted)
|
|
1737
1678
|
const result1 = await callPathway('sys_tool_file_collection', {
|
|
1738
|
-
contextId,
|
|
1679
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1739
1680
|
url: 'https://example.com/migration.pdf',
|
|
1740
1681
|
filename: 'migration.pdf',
|
|
1741
1682
|
tags: ['unencrypted'],
|
|
@@ -1764,8 +1705,7 @@ test('File collection encryption: Migration from unencrypted to encrypted', asyn
|
|
|
1764
1705
|
|
|
1765
1706
|
// Now update with contextKey (should encrypt on next write)
|
|
1766
1707
|
await callPathway('sys_update_file_metadata', {
|
|
1767
|
-
contextId,
|
|
1768
|
-
contextKey,
|
|
1708
|
+
agentContext: [{ contextId, contextKey, default: true }],
|
|
1769
1709
|
hash: file1.hash,
|
|
1770
1710
|
tags: ['encrypted'],
|
|
1771
1711
|
notes: 'Encrypted notes'
|
|
@@ -1797,7 +1737,7 @@ test('File collection encryption: Core fields are never encrypted', async t => {
|
|
|
1797
1737
|
try {
|
|
1798
1738
|
// Add file with all fields
|
|
1799
1739
|
const result = await callPathway('sys_tool_file_collection', {
|
|
1800
|
-
contextId,
|
|
1740
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1801
1741
|
contextKey,
|
|
1802
1742
|
url: 'https://example.com/core-fields.pdf',
|
|
1803
1743
|
filename: 'core-fields.pdf',
|
|
@@ -1840,7 +1780,7 @@ test('File collection encryption: Works without contextKey (no encryption)', asy
|
|
|
1840
1780
|
try {
|
|
1841
1781
|
// Add file without contextKey
|
|
1842
1782
|
const result = await callPathway('sys_tool_file_collection', {
|
|
1843
|
-
contextId,
|
|
1783
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1844
1784
|
url: 'https://example.com/no-encryption.pdf',
|
|
1845
1785
|
filename: 'no-encryption.pdf',
|
|
1846
1786
|
tags: ['public'],
|
|
@@ -1882,7 +1822,7 @@ test('File collection: YouTube URLs are rejected (cannot be added to collection)
|
|
|
1882
1822
|
try {
|
|
1883
1823
|
// Attempt to add YouTube URL - should be rejected
|
|
1884
1824
|
const result = await callPathway('sys_tool_file_collection', {
|
|
1885
|
-
contextId,
|
|
1825
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1886
1826
|
fileUrl: youtubeUrl,
|
|
1887
1827
|
filename: 'Test YouTube Video',
|
|
1888
1828
|
tags: ['video', 'youtube'],
|
|
@@ -1932,7 +1872,7 @@ test('File collection: YouTube Shorts URLs are rejected', async t => {
|
|
|
1932
1872
|
|
|
1933
1873
|
try {
|
|
1934
1874
|
const result = await callPathway('sys_tool_file_collection', {
|
|
1935
|
-
contextId,
|
|
1875
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1936
1876
|
fileUrl: shortsUrl,
|
|
1937
1877
|
filename: 'YouTube Short',
|
|
1938
1878
|
userMessage: 'Add YouTube short'
|
|
@@ -1963,7 +1903,7 @@ test('File collection: youtu.be URLs are rejected', async t => {
|
|
|
1963
1903
|
|
|
1964
1904
|
try {
|
|
1965
1905
|
const result = await callPathway('sys_tool_file_collection', {
|
|
1966
|
-
contextId,
|
|
1906
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
1967
1907
|
fileUrl: youtuBeUrl,
|
|
1968
1908
|
filename: 'YouTube Video',
|
|
1969
1909
|
userMessage: 'Add YouTube video'
|
|
@@ -1995,7 +1935,7 @@ test('generateFileMessageContent: Accepts direct YouTube URL without collection'
|
|
|
1995
1935
|
try {
|
|
1996
1936
|
// Test that generateFileMessageContent accepts YouTube URL directly
|
|
1997
1937
|
// even if it's not in the collection
|
|
1998
|
-
const fileContent = await generateFileMessageContent(youtubeUrl, contextId);
|
|
1938
|
+
const fileContent = await generateFileMessageContent(youtubeUrl, createAgentContext(contextId));
|
|
1999
1939
|
t.truthy(fileContent);
|
|
2000
1940
|
t.is(fileContent.url, youtubeUrl);
|
|
2001
1941
|
t.is(fileContent.type, 'image_url');
|
|
@@ -2015,7 +1955,7 @@ test('generateFileMessageContent: Accepts direct youtu.be URL without collection
|
|
|
2015
1955
|
const youtuBeUrl = 'https://youtu.be/dQw4w9WgXcQ';
|
|
2016
1956
|
|
|
2017
1957
|
try {
|
|
2018
|
-
const fileContent = await generateFileMessageContent(youtuBeUrl, contextId);
|
|
1958
|
+
const fileContent = await generateFileMessageContent(youtuBeUrl, createAgentContext(contextId));
|
|
2019
1959
|
t.truthy(fileContent);
|
|
2020
1960
|
t.is(fileContent.url, youtuBeUrl);
|
|
2021
1961
|
t.is(fileContent.type, 'image_url');
|
|
@@ -2029,7 +1969,7 @@ test('Analyzer tool: Returns error JSON format when file not found', async t =>
|
|
|
2029
1969
|
|
|
2030
1970
|
try {
|
|
2031
1971
|
const result = await callPathway('sys_tool_analyzefile', {
|
|
2032
|
-
contextId,
|
|
1972
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
2033
1973
|
file: 'non-existent-file.jpg',
|
|
2034
1974
|
detailedInstructions: 'Analyze this file',
|
|
2035
1975
|
userMessage: 'Testing error handling'
|
|
@@ -2067,7 +2007,7 @@ test('Converted files: displayFilename .docx but URL .md - MIME type from URL',
|
|
|
2067
2007
|
// Add a file where displayFilename is .docx but URL points to converted .md file
|
|
2068
2008
|
// This simulates the case where a docx file was converted to markdown
|
|
2069
2009
|
const addResult = await callPathway('sys_tool_file_collection', {
|
|
2070
|
-
contextId,
|
|
2010
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
2071
2011
|
url: 'https://example.com/converted-document.md', // Converted to markdown
|
|
2072
2012
|
gcs: 'gs://bucket/converted-document.md',
|
|
2073
2013
|
filename: 'original-document.docx', // Original filename preserved
|
|
@@ -2098,7 +2038,7 @@ test('Converted files: EditFile should use URL MIME type, not displayFilename',
|
|
|
2098
2038
|
try {
|
|
2099
2039
|
// Add a converted file: displayFilename is .docx but URL is .md
|
|
2100
2040
|
const addResult = await callPathway('sys_tool_file_collection', {
|
|
2101
|
-
contextId,
|
|
2041
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
2102
2042
|
url: 'https://example.com/report.md', // Converted markdown
|
|
2103
2043
|
gcs: 'gs://bucket/report.md',
|
|
2104
2044
|
filename: 'report.docx', // Original filename
|
|
@@ -2131,7 +2071,7 @@ test('Converted files: ReadFile should accept text files based on URL, not displ
|
|
|
2131
2071
|
try {
|
|
2132
2072
|
// Add a converted file: displayFilename is .docx but URL is .md
|
|
2133
2073
|
const addResult = await callPathway('sys_tool_file_collection', {
|
|
2134
|
-
contextId,
|
|
2074
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
2135
2075
|
url: 'https://example.com/document.md', // Converted markdown (text file)
|
|
2136
2076
|
gcs: 'gs://bucket/document.md',
|
|
2137
2077
|
filename: 'document.docx', // Original filename (would be binary if checked)
|
|
@@ -2150,7 +2090,7 @@ test('Converted files: ReadFile should accept text files based on URL, not displ
|
|
|
2150
2090
|
// ReadFile should use resolveFileParameter which returns the URL
|
|
2151
2091
|
// The URL (.md) should be recognized as text, not the displayFilename (.docx)
|
|
2152
2092
|
const { resolveFileParameter } = await import('../../../../lib/fileUtils.js');
|
|
2153
|
-
const resolvedUrl = await resolveFileParameter('document.docx', contextId);
|
|
2093
|
+
const resolvedUrl = await resolveFileParameter('document.docx', createAgentContext(contextId));
|
|
2154
2094
|
t.is(resolvedUrl, 'https://example.com/document.md', 'Should resolve to URL');
|
|
2155
2095
|
|
|
2156
2096
|
// The isTextFile function in ReadFile should check the URL, not displayFilename
|
|
@@ -2170,21 +2110,21 @@ test('Converted files: Multiple converted files with different extensions', asyn
|
|
|
2170
2110
|
try {
|
|
2171
2111
|
// Add multiple converted files
|
|
2172
2112
|
await callPathway('sys_tool_file_collection', {
|
|
2173
|
-
contextId,
|
|
2113
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
2174
2114
|
url: 'https://example.com/doc1.md', // docx -> md
|
|
2175
2115
|
filename: 'document1.docx',
|
|
2176
2116
|
userMessage: 'Add docx->md'
|
|
2177
2117
|
});
|
|
2178
2118
|
|
|
2179
2119
|
await callPathway('sys_tool_file_collection', {
|
|
2180
|
-
contextId,
|
|
2120
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
2181
2121
|
url: 'https://example.com/doc2.txt', // xlsx -> txt (CSV)
|
|
2182
2122
|
filename: 'spreadsheet.xlsx',
|
|
2183
2123
|
userMessage: 'Add xlsx->txt'
|
|
2184
2124
|
});
|
|
2185
2125
|
|
|
2186
2126
|
await callPathway('sys_tool_file_collection', {
|
|
2187
|
-
contextId,
|
|
2127
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
2188
2128
|
url: 'https://example.com/doc3.json', // pptx -> json (structured data)
|
|
2189
2129
|
filename: 'presentation.pptx',
|
|
2190
2130
|
userMessage: 'Add pptx->json'
|
|
@@ -2272,7 +2212,7 @@ test('Converted files: loadFileCollection should use converted values as primary
|
|
|
2272
2212
|
|
|
2273
2213
|
// Verify resolveFileParameter returns converted URL (now the main URL)
|
|
2274
2214
|
const { resolveFileParameter } = await import('../../../../lib/fileUtils.js');
|
|
2275
|
-
const resolvedUrl = await resolveFileParameter('original.docx', contextId);
|
|
2215
|
+
const resolvedUrl = await resolveFileParameter('original.docx', createAgentContext(contextId));
|
|
2276
2216
|
t.is(resolvedUrl, 'https://example.com/converted.md', 'Should resolve to converted URL (now main URL)');
|
|
2277
2217
|
|
|
2278
2218
|
// Verify converted files can be read (text type)
|
|
@@ -2281,7 +2221,7 @@ test('Converted files: loadFileCollection should use converted values as primary
|
|
|
2281
2221
|
|
|
2282
2222
|
// Verify converted files cannot be edited
|
|
2283
2223
|
const editResult = await callPathway('sys_tool_editfile', {
|
|
2284
|
-
contextId,
|
|
2224
|
+
agentContext: [{ contextId, contextKey: null, default: true }],
|
|
2285
2225
|
file: 'original.docx',
|
|
2286
2226
|
startLine: 1,
|
|
2287
2227
|
endLine: 1,
|
|
@@ -2295,3 +2235,69 @@ test('Converted files: loadFileCollection should use converted values as primary
|
|
|
2295
2235
|
await cleanup(contextId);
|
|
2296
2236
|
}
|
|
2297
2237
|
});
|
|
2238
|
+
|
|
2239
|
+
test('loadMergedFileCollection should merge collections from contextId and altContextId', async t => {
|
|
2240
|
+
const { loadMergedFileCollection, addFileToCollection, getRedisClient } = await import('../../../../lib/fileUtils.js');
|
|
2241
|
+
|
|
2242
|
+
const contextId = `test-primary-${Date.now()}`;
|
|
2243
|
+
const altContextId = `test-alt-${Date.now()}`;
|
|
2244
|
+
|
|
2245
|
+
try {
|
|
2246
|
+
// Add file to primary context
|
|
2247
|
+
await addFileToCollection(contextId, null, 'https://example.com/primary.jpg', null, 'primary.jpg', [], '', 'hash-primary');
|
|
2248
|
+
|
|
2249
|
+
// Add file to alt context
|
|
2250
|
+
await addFileToCollection(altContextId, null, 'https://example.com/alt.jpg', null, 'alt.jpg', [], '', 'hash-alt');
|
|
2251
|
+
|
|
2252
|
+
// Load just primary - should have 1 file
|
|
2253
|
+
const primaryOnly = await loadMergedFileCollection([{ contextId, contextKey: null, default: true }]);
|
|
2254
|
+
t.is(primaryOnly.length, 1);
|
|
2255
|
+
t.is(primaryOnly[0].hash, 'hash-primary');
|
|
2256
|
+
|
|
2257
|
+
// Load merged - should have 2 files (both contexts unencrypted)
|
|
2258
|
+
const merged = await loadMergedFileCollection([
|
|
2259
|
+
{ contextId, contextKey: null, default: true },
|
|
2260
|
+
{ contextId: altContextId, contextKey: null, default: false }
|
|
2261
|
+
]);
|
|
2262
|
+
t.is(merged.length, 2);
|
|
2263
|
+
t.true(merged.some(f => f.hash === 'hash-primary'));
|
|
2264
|
+
t.true(merged.some(f => f.hash === 'hash-alt'));
|
|
2265
|
+
} finally {
|
|
2266
|
+
const redisClient = await getRedisClient();
|
|
2267
|
+
if (redisClient) {
|
|
2268
|
+
await redisClient.del(`FileStoreMap:ctx:${contextId}`);
|
|
2269
|
+
await redisClient.del(`FileStoreMap:ctx:${altContextId}`);
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
});
|
|
2273
|
+
|
|
2274
|
+
test('loadMergedFileCollection should dedupe files present in both contexts', async t => {
|
|
2275
|
+
const { loadMergedFileCollection, addFileToCollection, getRedisClient } = await import('../../../../lib/fileUtils.js');
|
|
2276
|
+
|
|
2277
|
+
const contextId = `test-primary-dupe-${Date.now()}`;
|
|
2278
|
+
const altContextId = `test-alt-dupe-${Date.now()}`;
|
|
2279
|
+
|
|
2280
|
+
try {
|
|
2281
|
+
// Add same file (same hash) to both contexts
|
|
2282
|
+
await addFileToCollection(contextId, null, 'https://example.com/shared.jpg', null, 'shared.jpg', [], '', 'hash-shared');
|
|
2283
|
+
await addFileToCollection(altContextId, null, 'https://example.com/shared.jpg', null, 'shared.jpg', [], '', 'hash-shared');
|
|
2284
|
+
|
|
2285
|
+
// Add unique file to alt context
|
|
2286
|
+
await addFileToCollection(altContextId, null, 'https://example.com/alt-only.jpg', null, 'alt-only.jpg', [], '', 'hash-alt-only');
|
|
2287
|
+
|
|
2288
|
+
// Load merged - should have 2 files (deduped shared file, both contexts unencrypted)
|
|
2289
|
+
const merged = await loadMergedFileCollection([
|
|
2290
|
+
{ contextId, contextKey: null, default: true },
|
|
2291
|
+
{ contextId: altContextId, contextKey: null, default: false }
|
|
2292
|
+
]);
|
|
2293
|
+
t.is(merged.length, 2);
|
|
2294
|
+
t.true(merged.some(f => f.hash === 'hash-shared'));
|
|
2295
|
+
t.true(merged.some(f => f.hash === 'hash-alt-only'));
|
|
2296
|
+
} finally {
|
|
2297
|
+
const redisClient = await getRedisClient();
|
|
2298
|
+
if (redisClient) {
|
|
2299
|
+
await redisClient.del(`FileStoreMap:ctx:${contextId}`);
|
|
2300
|
+
await redisClient.del(`FileStoreMap:ctx:${altContextId}`);
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
});
|