@aj-archipelago/cortex 1.3.21 → 1.3.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/README.md +64 -0
- package/config.js +26 -1
- package/helper-apps/cortex-realtime-voice-server/src/cortex/memory.ts +2 -2
- package/helper-apps/cortex-realtime-voice-server/src/realtime/client.ts +9 -4
- package/helper-apps/cortex-realtime-voice-server/src/realtime/realtimeTypes.ts +1 -0
- package/lib/util.js +5 -25
- package/package.json +5 -2
- package/pathways/system/entity/memory/shared/sys_memory_helpers.js +228 -0
- package/pathways/system/entity/memory/sys_memory_format.js +30 -0
- package/pathways/system/entity/memory/sys_memory_manager.js +85 -27
- package/pathways/system/entity/memory/sys_memory_process.js +154 -0
- package/pathways/system/entity/memory/sys_memory_required.js +4 -2
- package/pathways/system/entity/memory/sys_memory_topic.js +22 -0
- package/pathways/system/entity/memory/sys_memory_update.js +50 -150
- package/pathways/system/entity/memory/sys_read_memory.js +67 -69
- package/pathways/system/entity/memory/sys_save_memory.js +1 -1
- package/pathways/system/entity/memory/sys_search_memory.js +1 -1
- package/pathways/system/entity/sys_entity_start.js +9 -6
- package/pathways/system/entity/sys_generator_image.js +5 -41
- package/pathways/system/entity/sys_generator_memory.js +3 -1
- package/pathways/system/entity/sys_generator_reasoning.js +1 -1
- package/pathways/system/entity/sys_router_tool.js +3 -4
- package/pathways/system/rest_streaming/sys_claude_35_sonnet.js +1 -1
- package/pathways/system/rest_streaming/sys_claude_3_haiku.js +1 -1
- package/pathways/system/rest_streaming/sys_google_gemini_chat.js +1 -1
- package/pathways/system/rest_streaming/sys_ollama_chat.js +21 -0
- package/pathways/system/rest_streaming/sys_ollama_completion.js +14 -0
- package/pathways/system/rest_streaming/sys_openai_chat_o1.js +1 -1
- package/pathways/system/rest_streaming/sys_openai_chat_o3_mini.js +1 -1
- package/pathways/transcribe_gemini.js +525 -0
- package/server/modelExecutor.js +8 -0
- package/server/pathwayResolver.js +13 -8
- package/server/plugins/claude3VertexPlugin.js +150 -18
- package/server/plugins/gemini15ChatPlugin.js +90 -1
- package/server/plugins/gemini15VisionPlugin.js +16 -3
- package/server/plugins/modelPlugin.js +12 -9
- package/server/plugins/ollamaChatPlugin.js +158 -0
- package/server/plugins/ollamaCompletionPlugin.js +147 -0
- package/server/rest.js +70 -8
- package/tests/claude3VertexToolConversion.test.js +411 -0
- package/tests/memoryfunction.test.js +560 -46
- package/tests/multimodal_conversion.test.js +169 -0
- package/tests/openai_api.test.js +332 -0
- package/tests/transcribe_gemini.test.js +217 -0
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import test from 'ava';
|
|
2
|
-
import {
|
|
2
|
+
import { modifyText, enforceTokenLimit } from '../pathways/system/entity/memory/shared/sys_memory_helpers.js';
|
|
3
|
+
import { processMemoryContent } from '../pathways/system/entity/memory/sys_read_memory.js';
|
|
3
4
|
|
|
4
5
|
test('enforceTokenLimit preserves priority order correctly', t => {
|
|
5
6
|
const input = `
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
1|2024-03-19T10:00:00Z|Highest priority item
|
|
8
|
+
2|2024-03-19T11:00:00Z|High priority item
|
|
9
|
+
3|2024-03-19T12:00:00Z|Medium priority item
|
|
10
|
+
4|2024-03-19T13:00:00Z|Low priority item
|
|
11
|
+
5|2024-03-19T14:00:00Z|Lowest priority item`.trim();
|
|
11
12
|
|
|
12
13
|
// Enforce trimming
|
|
13
|
-
const result = enforceTokenLimit(input,
|
|
14
|
+
const result = enforceTokenLimit(input, 40);
|
|
14
15
|
|
|
15
16
|
// Should keep P1, P2 only
|
|
16
|
-
t.true(result.includes('
|
|
17
|
-
t.true(result.includes('
|
|
18
|
-
t.false(result.includes('
|
|
19
|
-
t.false(result.includes('
|
|
20
|
-
t.false(result.includes('
|
|
17
|
+
t.true(result.includes('1|2024-03-19T10:00:00Z|Highest priority item'));
|
|
18
|
+
t.true(result.includes('2|2024-03-19T11:00:00Z|High priority item'));
|
|
19
|
+
t.false(result.includes('3|2024-03-19T12:00:00Z|Medium priority item'));
|
|
20
|
+
t.false(result.includes('4|2024-03-19T13:00:00Z|Low priority item'));
|
|
21
|
+
t.false(result.includes('5|2024-03-19T14:00:00Z|Lowest priority item'));
|
|
21
22
|
});
|
|
22
23
|
|
|
23
24
|
test('enforceTokenLimit handles empty input', t => {
|
|
@@ -27,9 +28,9 @@ test('enforceTokenLimit handles empty input', t => {
|
|
|
27
28
|
|
|
28
29
|
test('enforceTokenLimit removes duplicates', t => {
|
|
29
30
|
const input = `
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
3|2024-03-19T10:00:00Z|Duplicate item
|
|
32
|
+
3|2024-03-19T11:00:00Z|Duplicate item
|
|
33
|
+
2|2024-03-19T12:00:00Z|Unique item`.trim();
|
|
33
34
|
|
|
34
35
|
const result = enforceTokenLimit(input);
|
|
35
36
|
|
|
@@ -40,9 +41,9 @@ test('enforceTokenLimit removes duplicates', t => {
|
|
|
40
41
|
|
|
41
42
|
test('enforceTokenLimit handles topics section differently', t => {
|
|
42
43
|
const input = `
|
|
43
|
-
2024-03-19T10:00:00Z
|
|
44
|
-
2024-03-19T11:00:00Z
|
|
45
|
-
2024-03-19T12:00:00Z
|
|
44
|
+
3|2024-03-19T10:00:00Z|Discussed AI ethics
|
|
45
|
+
2|2024-03-19T11:00:00Z|Discussed programming
|
|
46
|
+
1|2024-03-19T12:00:00Z|Discussed testing`.trim();
|
|
46
47
|
|
|
47
48
|
const result = enforceTokenLimit(input, 20, true);
|
|
48
49
|
|
|
@@ -52,22 +53,11 @@ test('enforceTokenLimit handles topics section differently', t => {
|
|
|
52
53
|
t.true(result.includes('12:00:00Z'));
|
|
53
54
|
});
|
|
54
55
|
|
|
55
|
-
test('enforceTokenLimit adds P3 to unprioritized lines', t => {
|
|
56
|
-
const input = `
|
|
57
|
-
Item without priority
|
|
58
|
-
[P1] Item with priority`.trim();
|
|
59
|
-
|
|
60
|
-
const result = enforceTokenLimit(input);
|
|
61
|
-
|
|
62
|
-
t.true(result.includes('[P3] Item without priority'));
|
|
63
|
-
t.true(result.includes('[P1] Item with priority'));
|
|
64
|
-
});
|
|
65
|
-
|
|
66
56
|
test('modifyText handles delete operations with escaped characters', t => {
|
|
67
|
-
const input = '
|
|
57
|
+
const input = '2|2024-03-19T10:00:00Z|Pizza Connection: Has special appreciation for Pisanello\'s';
|
|
68
58
|
const modifications = [{
|
|
69
59
|
type: 'delete',
|
|
70
|
-
pattern: '
|
|
60
|
+
pattern: 'Pizza Connection: Has special appreciation for Pisanello\'s'
|
|
71
61
|
}];
|
|
72
62
|
|
|
73
63
|
const result = modifyText(input, modifications);
|
|
@@ -75,7 +65,7 @@ test('modifyText handles delete operations with escaped characters', t => {
|
|
|
75
65
|
});
|
|
76
66
|
|
|
77
67
|
test('modifyText handles delete with partial priority match', t => {
|
|
78
|
-
const input = '
|
|
68
|
+
const input = '3|2024-03-19T10:00:00Z|Test memory item';
|
|
79
69
|
const modifications = [{
|
|
80
70
|
type: 'delete',
|
|
81
71
|
pattern: 'Test memory item'
|
|
@@ -86,7 +76,7 @@ test('modifyText handles delete with partial priority match', t => {
|
|
|
86
76
|
});
|
|
87
77
|
|
|
88
78
|
test('modifyText handles multiple modifications in sequence', t => {
|
|
89
|
-
const input = '
|
|
79
|
+
const input = '2|2024-03-19T10:00:00Z|Keep this line\n3|2024-03-19T11:00:00Z|Delete this line\n1|2024-03-19T12:00:00Z|Also keep this';
|
|
90
80
|
const modifications = [
|
|
91
81
|
{ type: 'delete', pattern: 'Delete this line' },
|
|
92
82
|
{ type: 'add', newtext: 'New line added', priority: '2' }
|
|
@@ -95,12 +85,12 @@ test('modifyText handles multiple modifications in sequence', t => {
|
|
|
95
85
|
const result = modifyText(input, modifications);
|
|
96
86
|
t.true(result.includes('Keep this line'));
|
|
97
87
|
t.true(result.includes('Also keep this'));
|
|
98
|
-
t.true(result.includes('
|
|
88
|
+
t.true(result.includes('New line added'));
|
|
99
89
|
t.false(result.includes('Delete this line'));
|
|
100
90
|
});
|
|
101
91
|
|
|
102
92
|
test('modifyText handles delete with whitespace variations', t => {
|
|
103
|
-
const input = '
|
|
93
|
+
const input = ' 2|2024-03-19T10:00:00Z|Item with spaces \n3|2024-03-19T11:00:00Z|Item without spaces';
|
|
104
94
|
const modifications = [
|
|
105
95
|
{ type: 'delete', pattern: 'Item with spaces' },
|
|
106
96
|
{ type: 'delete', pattern: 'Item without spaces' }
|
|
@@ -111,26 +101,550 @@ test('modifyText handles delete with whitespace variations', t => {
|
|
|
111
101
|
t.false(result.includes('Item without spaces'));
|
|
112
102
|
});
|
|
113
103
|
|
|
114
|
-
test('modifyText
|
|
115
|
-
const input = '';
|
|
104
|
+
test('modifyText handles delete with regex special characters', t => {
|
|
105
|
+
const input = '2|2024-03-19T10:00:00Z|Special (chars) [test] {here} *star*';
|
|
116
106
|
const modifications = [{
|
|
117
|
-
type: '
|
|
118
|
-
|
|
119
|
-
priority: '3' // This should be ignored since priority is in text
|
|
107
|
+
type: 'delete',
|
|
108
|
+
pattern: 'Special \\(chars\\) \\[test\\] \\{here\\} \\*star\\*'
|
|
120
109
|
}];
|
|
121
110
|
|
|
122
111
|
const result = modifyText(input, modifications);
|
|
123
|
-
t.
|
|
124
|
-
t.false(result.includes('[P3] [P1] High priority item'));
|
|
112
|
+
t.false(result.includes('Special (chars)'));
|
|
125
113
|
});
|
|
126
114
|
|
|
127
|
-
test('modifyText handles
|
|
128
|
-
const input = '
|
|
115
|
+
test('modifyText handles content with pipe characters', t => {
|
|
116
|
+
const input = '2|2024-03-19T10:00:00Z|Memory about|pipes|in|content';
|
|
117
|
+
const modifications = [{
|
|
118
|
+
type: 'change',
|
|
119
|
+
pattern: 'Memory about\\|pipes\\|in\\|content',
|
|
120
|
+
newtext: 'Updated memory'
|
|
121
|
+
}];
|
|
122
|
+
|
|
123
|
+
const result = modifyText(input, modifications);
|
|
124
|
+
t.true(result.includes('Updated memory'));
|
|
125
|
+
t.false(result.includes('Memory about|pipes|in|content'));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('modifyText performs case insensitive matching', t => {
|
|
129
|
+
const input = '2|2024-03-19T10:00:00Z|UPPER CASE Memory\n3|2024-03-19T11:00:00Z|lower case memory';
|
|
129
130
|
const modifications = [{
|
|
130
131
|
type: 'delete',
|
|
131
|
-
pattern: '
|
|
132
|
+
pattern: 'upper case memory'
|
|
132
133
|
}];
|
|
133
134
|
|
|
134
135
|
const result = modifyText(input, modifications);
|
|
135
|
-
t.false(result.includes('
|
|
136
|
+
t.false(result.includes('UPPER CASE Memory'));
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('modifyText handles multiple overlapping patterns', t => {
|
|
140
|
+
const input = `
|
|
141
|
+
2|2024-03-19T10:00:00Z|Memory about AI and ML
|
|
142
|
+
3|2024-03-19T11:00:00Z|Memory about AI and robotics
|
|
143
|
+
1|2024-03-19T12:00:00Z|Memory about ML and data`.trim();
|
|
144
|
+
|
|
145
|
+
const modifications = [
|
|
146
|
+
{ type: 'delete', pattern: '.*AI.*' },
|
|
147
|
+
{ type: 'change', pattern: '.*ML.*', newtext: 'Updated ML memory' }
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
const result = modifyText(input, modifications);
|
|
151
|
+
t.false(result.includes('Memory about AI'));
|
|
152
|
+
t.true(result.includes('Updated ML memory'));
|
|
153
|
+
t.is((result.match(/Updated ML memory/g) || []).length, 1);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('modifyText preserves and updates priorities correctly', t => {
|
|
157
|
+
const input = '2|2024-03-19T10:00:00Z|Original priority 2 memory';
|
|
158
|
+
|
|
159
|
+
const modifications = [{
|
|
160
|
+
type: 'change',
|
|
161
|
+
pattern: 'Original priority 2 memory',
|
|
162
|
+
newtext: 'Changed memory',
|
|
163
|
+
priority: '1'
|
|
164
|
+
}];
|
|
165
|
+
|
|
166
|
+
const result = modifyText(input, modifications);
|
|
167
|
+
// Check priority was updated to 1
|
|
168
|
+
t.true(result.startsWith('1|'));
|
|
169
|
+
// Check content was updated
|
|
170
|
+
t.true(result.endsWith('|Changed memory'));
|
|
171
|
+
// Verify timestamp format
|
|
172
|
+
t.regex(result, /^1\|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z\|Changed memory$/);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('modifyText handles empty and invalid modifications', t => {
|
|
176
|
+
const input = '2|2024-03-19T10:00:00Z|Test memory';
|
|
177
|
+
|
|
178
|
+
const modifications = [
|
|
179
|
+
{ type: 'delete' }, // Missing pattern
|
|
180
|
+
{ type: 'change', pattern: 'Test' }, // Missing newtext
|
|
181
|
+
{ type: 'invalid', pattern: 'Test', newtext: 'Invalid' }, // Invalid type
|
|
182
|
+
{ type: 'add' } // Missing newtext
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
const result = modifyText(input, modifications);
|
|
186
|
+
// Check that priority and content remain unchanged
|
|
187
|
+
t.true(result.startsWith('2|'));
|
|
188
|
+
t.true(result.includes('|Test memory'));
|
|
189
|
+
// Verify timestamp format
|
|
190
|
+
t.regex(result, /^2\|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z\|Test memory$/);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('modifyText handles regex patterns in change operations', t => {
|
|
194
|
+
const input = '2|2024-03-19T10:00:00Z|Memory from 2024-03-19';
|
|
195
|
+
|
|
196
|
+
const modifications = [{
|
|
197
|
+
type: 'change',
|
|
198
|
+
pattern: 'Memory from \\d{4}-\\d{2}-\\d{2}',
|
|
199
|
+
newtext: 'Updated date memory'
|
|
200
|
+
}];
|
|
201
|
+
|
|
202
|
+
const result = modifyText(input, modifications);
|
|
203
|
+
t.true(result.includes('Updated date memory'));
|
|
204
|
+
t.false(result.includes('Memory from 2024'));
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('modifyText handles malformed memory lines gracefully', t => {
|
|
208
|
+
const input = `
|
|
209
|
+
no_separators_at_all
|
|
210
|
+
2|missing_content
|
|
211
|
+
|2024-03-19T10:00:00Z|missing_priority
|
|
212
|
+
2||missing_timestamp|content
|
|
213
|
+
2|invalid|timestamp|here|content
|
|
214
|
+
|||| empty parts
|
|
215
|
+
`.trim();
|
|
216
|
+
|
|
217
|
+
const modifications = [{
|
|
218
|
+
type: 'add',
|
|
219
|
+
newtext: 'New valid memory'
|
|
220
|
+
}];
|
|
221
|
+
|
|
222
|
+
const result = modifyText(input, modifications);
|
|
223
|
+
// Should preserve valid parts and add new memory
|
|
224
|
+
t.true(result.includes('New valid memory'));
|
|
225
|
+
// Should handle malformed lines without crashing
|
|
226
|
+
t.true(result.split('\n').length > 0);
|
|
227
|
+
// Verify output format for the added line
|
|
228
|
+
t.regex(result, /3\|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z\|New valid memory$/m);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('modifyText handles memory with special regex characters', t => {
|
|
232
|
+
const input = '2|2024-03-19T10:00:00Z|Memory with (parens) and [brackets] and {braces} and +*?^$\\.';
|
|
233
|
+
|
|
234
|
+
// Try to modify it with a pattern containing regex special chars
|
|
235
|
+
const modifications = [{
|
|
236
|
+
type: 'change',
|
|
237
|
+
pattern: 'Memory with \\(parens\\) and \\[brackets\\]',
|
|
238
|
+
newtext: 'Updated memory'
|
|
239
|
+
}];
|
|
240
|
+
|
|
241
|
+
const result = modifyText(input, modifications);
|
|
242
|
+
t.true(result.includes('Updated memory'));
|
|
243
|
+
t.false(result.includes('Memory with (parens)'));
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('modifyText handles extremely long memory lines', t => {
|
|
247
|
+
const longContent = 'a'.repeat(1000);
|
|
248
|
+
const input = `2|2024-03-19T10:00:00Z|${longContent}`;
|
|
249
|
+
|
|
250
|
+
const modifications = [{
|
|
251
|
+
type: 'change',
|
|
252
|
+
pattern: 'a+',
|
|
253
|
+
newtext: 'Shortened memory'
|
|
254
|
+
}];
|
|
255
|
+
|
|
256
|
+
const result = modifyText(input, modifications);
|
|
257
|
+
t.true(result.includes('Shortened memory'));
|
|
258
|
+
t.false(result.includes(longContent));
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test('modifyText handles unicode and special characters', t => {
|
|
262
|
+
const input = '2|2024-03-19T10:00:00Z|Memory with 🚀 emoji and üñîçødé and "quotes" and \'apostrophes\'';
|
|
263
|
+
|
|
264
|
+
const modifications = [{
|
|
265
|
+
type: 'change',
|
|
266
|
+
pattern: 'Memory with.*emoji',
|
|
267
|
+
newtext: 'Updated with more 🎉 emoji'
|
|
268
|
+
}];
|
|
269
|
+
|
|
270
|
+
const result = modifyText(input, modifications);
|
|
271
|
+
t.true(result.includes('Updated with more 🎉 emoji'));
|
|
272
|
+
t.false(result.includes('Memory with 🚀 emoji'));
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test('modifyText handles various regex pattern types', t => {
|
|
276
|
+
const input = `
|
|
277
|
+
2|2024-03-19T10:00:00Z|Start of line memory
|
|
278
|
+
3|2024-03-19T11:00:00Z|Memory in the middle
|
|
279
|
+
1|2024-03-19T12:00:00Z|Memory at the end
|
|
280
|
+
2|2024-03-19T13:00:00Z|12345 numeric memory
|
|
281
|
+
3|2024-03-19T14:00:00Z|Multiple spaces here
|
|
282
|
+
2|2024-03-19T15:00:00Z|Line.with.dots
|
|
283
|
+
2|2024-03-19T16:00:00Z|Line_with_underscores
|
|
284
|
+
2|2024-03-19T17:00:00Z|This is a long memory that we want to match partially`.trim();
|
|
285
|
+
|
|
286
|
+
const modifications = [
|
|
287
|
+
{ type: 'delete', pattern: '^Start.*memory$' }, // Start of line to end
|
|
288
|
+
{ type: 'delete', pattern: 'Memory at the end$' }, // End of line
|
|
289
|
+
{ type: 'delete', pattern: '\\d{5}.*memory' }, // Numbers followed by text
|
|
290
|
+
{ type: 'change', pattern: 'Multiple\\s+spaces\\s+here', newtext: 'Single Space', priority: '1' }, // Multiple spaces
|
|
291
|
+
{ type: 'delete', pattern: 'Line\\.with\\.dots' }, // Escaped dots
|
|
292
|
+
{ type: 'delete', pattern: 'Line_with_underscores' }, // Underscores
|
|
293
|
+
{ type: 'change', pattern: '^This.*partially$', newtext: 'Shortened' } // Full line match
|
|
294
|
+
];
|
|
295
|
+
|
|
296
|
+
const result = modifyText(input, modifications);
|
|
297
|
+
t.false(result.includes('Start of line memory'));
|
|
298
|
+
t.false(result.includes('Memory at the end'));
|
|
299
|
+
t.false(result.includes('12345'));
|
|
300
|
+
t.true(result.includes('Single Space'));
|
|
301
|
+
t.false(result.includes('Line.with.dots'));
|
|
302
|
+
t.false(result.includes('Line_with_underscores'));
|
|
303
|
+
t.true(result.includes('Shortened'));
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test('modifyText handles regex pattern edge cases', t => {
|
|
307
|
+
const input = `
|
|
308
|
+
2|2024-03-19T10:00:00Z|Empty string match:
|
|
309
|
+
3|2024-03-19T11:00:00Z|Zero-width match: abc
|
|
310
|
+
1|2024-03-19T12:00:00Z|Lookahead: testing123
|
|
311
|
+
2|2024-03-19T13:00:00Z|Backreference: hello hello
|
|
312
|
+
3|2024-03-19T14:00:00Z|Non-greedy: <tag>content</tag>`.trim();
|
|
313
|
+
|
|
314
|
+
const modifications = [
|
|
315
|
+
{ type: 'change', pattern: '^Empty string match:\\s*$', newtext: 'EMPTY' }, // Empty string
|
|
316
|
+
{ type: 'change', pattern: 'Zero-width match: \\w{3}', newtext: 'ZERO_WIDTH' }, // Word chars
|
|
317
|
+
{ type: 'change', pattern: 'Lookahead: \\w+\\d+', newtext: 'LOOKAHEAD' }, // Word chars followed by numbers
|
|
318
|
+
{ type: 'change', pattern: 'Backreference: (\\w+)\\s\\1', newtext: 'REPEATED' }, // Same word repeated
|
|
319
|
+
{ type: 'change', pattern: 'Non-greedy: <[^>]+>[^<]+</[^>]+>', newtext: 'TAG' } // XML-like tag
|
|
320
|
+
];
|
|
321
|
+
|
|
322
|
+
const result = modifyText(input, modifications);
|
|
323
|
+
t.true(result.includes('EMPTY'));
|
|
324
|
+
t.true(result.includes('ZERO_WIDTH'));
|
|
325
|
+
t.true(result.includes('LOOKAHEAD'));
|
|
326
|
+
t.true(result.includes('REPEATED'));
|
|
327
|
+
t.true(result.includes('TAG'));
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test('modifyText handles pattern groups and alternation', t => {
|
|
331
|
+
const input = `
|
|
332
|
+
2|2024-03-19T10:00:00Z|Group1: apple orange
|
|
333
|
+
3|2024-03-19T11:00:00Z|Group2: banana grape
|
|
334
|
+
1|2024-03-19T12:00:00Z|Either: cat
|
|
335
|
+
2|2024-03-19T13:00:00Z|Either: dog
|
|
336
|
+
3|2024-03-19T14:00:00Z|Mixed: apple dog banana`.trim();
|
|
337
|
+
|
|
338
|
+
const modifications = [
|
|
339
|
+
{ type: 'change', pattern: 'Group\\d: (apple|banana) \\w+', newtext: 'Category: $1 fruit' }, // Capture and alternation
|
|
340
|
+
{ type: 'change', pattern: 'Either: (cat|dog)', newtext: 'Either: pet' }, // Simple alternation
|
|
341
|
+
{ type: 'change', pattern: 'Mixed: (\\w+) (\\w+) (\\w+)', newtext: 'Mixed: $1 and $2 with $3' } // Multiple captures
|
|
342
|
+
];
|
|
343
|
+
|
|
344
|
+
const result = modifyText(input, modifications);
|
|
345
|
+
t.true(result.includes('Category: apple fruit'));
|
|
346
|
+
t.true(result.includes('Category: banana fruit'));
|
|
347
|
+
t.true(result.includes('Either: pet'));
|
|
348
|
+
t.true(result.includes('Mixed: apple and dog with banana'));
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test('enforceTokenLimit sorts topics by timestamp before trimming', t => {
|
|
352
|
+
const input = `
|
|
353
|
+
3|2024-03-19T15:00:00Z|Newest topic
|
|
354
|
+
3|2024-03-19T10:00:00Z|Older topic
|
|
355
|
+
3|2024-03-19T12:00:00Z|Middle topic
|
|
356
|
+
3|2024-03-19T14:00:00Z|Recent topic
|
|
357
|
+
3|2024-03-19T11:00:00Z|Old topic`.trim();
|
|
358
|
+
|
|
359
|
+
// Set a small token limit to force trimming
|
|
360
|
+
const result = enforceTokenLimit(input, 40, true);
|
|
361
|
+
|
|
362
|
+
// Should keep newest topics
|
|
363
|
+
t.true(result.includes('Newest topic'));
|
|
364
|
+
t.true(result.includes('Recent topic'));
|
|
365
|
+
// Should remove oldest topics
|
|
366
|
+
t.false(result.includes('Older topic'));
|
|
367
|
+
t.false(result.includes('Old topic'));
|
|
368
|
+
|
|
369
|
+
// Verify order
|
|
370
|
+
const lines = result.split('\n');
|
|
371
|
+
t.true(lines[0].includes('Newest topic')); // First line should be newest
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test('enforceTokenLimit handles missing timestamps in topics', t => {
|
|
375
|
+
const input = `
|
|
376
|
+
3||Current topic
|
|
377
|
+
3|2024-03-19T15:00:00Z|Newer topic
|
|
378
|
+
3|2024-03-19T10:00:00Z|Older topic`.trim();
|
|
379
|
+
|
|
380
|
+
const result = enforceTokenLimit(input, 40, true);
|
|
381
|
+
|
|
382
|
+
// Missing timestamp should be treated as oldest
|
|
383
|
+
t.false(result.includes('Current topic'));
|
|
384
|
+
t.true(result.includes('Newer topic'));
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
test('enforceTokenLimit handles large content with pipes', t => {
|
|
388
|
+
const input = `
|
|
389
|
+
1|2024-03-19T15:00:00Z|Memory with|multiple|pipes|in|content
|
|
390
|
+
2|2024-03-19T14:00:00Z|Another|piped|memory
|
|
391
|
+
3|2024-03-19T13:00:00Z|Simple memory`.trim();
|
|
392
|
+
|
|
393
|
+
const result = enforceTokenLimit(input, 50);
|
|
394
|
+
|
|
395
|
+
// Should preserve pipes in content when under limit
|
|
396
|
+
t.true(result.includes('Memory with|multiple|pipes|in|content'));
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test('enforceTokenLimit removes duplicates while preserving newest timestamp', t => {
|
|
400
|
+
const input = `
|
|
401
|
+
3|2024-03-19T10:00:00Z|Duplicate content
|
|
402
|
+
3|2024-03-19T15:00:00Z|Duplicate content
|
|
403
|
+
3|2024-03-19T12:00:00Z|Duplicate content
|
|
404
|
+
2|2024-03-19T14:00:00Z|Unique content`.trim();
|
|
405
|
+
|
|
406
|
+
const result = enforceTokenLimit(input, 1000, true);
|
|
407
|
+
|
|
408
|
+
// Should only have one instance of duplicate content
|
|
409
|
+
t.is((result.match(/Duplicate content/g) || []).length, 1);
|
|
410
|
+
// Should keep the newest timestamp version
|
|
411
|
+
t.true(result.includes('2024-03-19T15:00:00Z|Duplicate content'));
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
test('enforceTokenLimit handles recursive trimming when estimation is off', t => {
|
|
415
|
+
// Create content that will need multiple trim passes
|
|
416
|
+
const input = Array.from({ length: 20 }, (_, i) =>
|
|
417
|
+
`${i % 3 + 1}|2024-03-19T${String(i).padStart(2, '0')}:00:00Z|Memory ${i} ${'x'.repeat(50)}`
|
|
418
|
+
).join('\n');
|
|
419
|
+
|
|
420
|
+
const result = enforceTokenLimit(input, 200);
|
|
421
|
+
|
|
422
|
+
// Should have significantly fewer lines than input
|
|
423
|
+
t.true(result.split('\n').length < input.split('\n').length);
|
|
424
|
+
// Should still maintain priority order
|
|
425
|
+
const priorities = result.split('\n').map(line => parseInt(line.split('|')[0]));
|
|
426
|
+
t.deepEqual(priorities, [...priorities].sort((a, b) => b - a));
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test('enforceTokenLimit preserves empty lines in non-topics mode', t => {
|
|
430
|
+
const input = `
|
|
431
|
+
1|2024-03-19T15:00:00Z|First memory
|
|
432
|
+
|
|
433
|
+
2|2024-03-19T14:00:00Z|Second memory
|
|
434
|
+
|
|
435
|
+
3|2024-03-19T13:00:00Z|Third memory`.trim();
|
|
436
|
+
|
|
437
|
+
const result = enforceTokenLimit(input, 1000);
|
|
438
|
+
|
|
439
|
+
// Should preserve content but remove empty lines
|
|
440
|
+
t.true(result.includes('First memory'));
|
|
441
|
+
t.true(result.includes('Second memory'));
|
|
442
|
+
t.true(result.includes('Third memory'));
|
|
443
|
+
// Should not have empty lines
|
|
444
|
+
t.false(result.includes('\n\n'));
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test('enforceTokenLimit handles malformed input gracefully', t => {
|
|
448
|
+
const input = `
|
|
449
|
+
invalid_line_no_separators
|
|
450
|
+
1|2024-03-19T15:00:00Z|Valid memory
|
|
451
|
+
||
|
|
452
|
+
2||Invalid but with separators
|
|
453
|
+
3|invalid_timestamp|Still valid content`.trim();
|
|
454
|
+
|
|
455
|
+
const result = enforceTokenLimit(input, 100, true);
|
|
456
|
+
|
|
457
|
+
// Should keep valid content
|
|
458
|
+
t.true(result.includes('Valid memory'));
|
|
459
|
+
// Should handle malformed lines without crashing
|
|
460
|
+
t.true(result.length > 0);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// processMemoryContent tests
|
|
464
|
+
test('processMemoryContent handles empty and null input', t => {
|
|
465
|
+
t.is(processMemoryContent('', {}), '');
|
|
466
|
+
t.is(processMemoryContent(null, {}), null);
|
|
467
|
+
t.is(processMemoryContent(undefined, {}), undefined);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
test('processMemoryContent returns unmodified content when no options set', t => {
|
|
471
|
+
const input = '1|2024-03-19T10:00:00Z|Test content';
|
|
472
|
+
t.is(processMemoryContent(input, {}), input);
|
|
473
|
+
t.is(processMemoryContent(input, { priority: 0, recentHours: 0, numResults: 0, stripMetadata: false }), input);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
test('processMemoryContent filters by priority correctly', t => {
|
|
477
|
+
const input = `
|
|
478
|
+
1|2024-03-19T10:00:00Z|Priority 1
|
|
479
|
+
2|2024-03-19T10:00:00Z|Priority 2
|
|
480
|
+
3|2024-03-19T10:00:00Z|Priority 3
|
|
481
|
+
invalid|2024-03-19T10:00:00Z|Invalid priority
|
|
482
|
+
|2024-03-19T10:00:00Z|Missing priority`.trim();
|
|
483
|
+
|
|
484
|
+
const result = processMemoryContent(input, { priority: 2 });
|
|
485
|
+
t.true(result.includes('Priority 1'));
|
|
486
|
+
t.true(result.includes('Priority 2'));
|
|
487
|
+
t.false(result.includes('Priority 3'));
|
|
488
|
+
t.false(result.includes('Invalid priority'));
|
|
489
|
+
t.false(result.includes('Missing priority'));
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test('processMemoryContent filters by recency correctly', t => {
|
|
493
|
+
const now = Date.now();
|
|
494
|
+
const hour = 60 * 60 * 1000;
|
|
495
|
+
const recentTime = new Date(now - hour).toISOString();
|
|
496
|
+
const oldTime = new Date(now - 3 * hour).toISOString();
|
|
497
|
+
|
|
498
|
+
const input = `
|
|
499
|
+
1|${recentTime}|Recent memory
|
|
500
|
+
2|${oldTime}|Old memory
|
|
501
|
+
3|invalid_time|Invalid timestamp
|
|
502
|
+
4||Missing timestamp`.trim();
|
|
503
|
+
|
|
504
|
+
const result = processMemoryContent(input, { recentHours: 2 });
|
|
505
|
+
t.true(result.includes('Recent memory'));
|
|
506
|
+
t.false(result.includes('Old memory'));
|
|
507
|
+
t.false(result.includes('Invalid timestamp'));
|
|
508
|
+
t.false(result.includes('Missing timestamp'));
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
test('processMemoryContent applies numResults limit correctly', t => {
|
|
512
|
+
const input = `
|
|
513
|
+
1|2024-03-19T10:00:00.000Z|First
|
|
514
|
+
2|2024-03-19T11:00:00.000Z|Second
|
|
515
|
+
3|2024-03-19T12:00:00.000Z|Third`.trim();
|
|
516
|
+
|
|
517
|
+
const result = processMemoryContent(input, { numResults: 2 });
|
|
518
|
+
t.true(result.includes('Third')); // Newest
|
|
519
|
+
t.true(result.includes('Second')); // Second newest
|
|
520
|
+
t.false(result.includes('First')); // Oldest
|
|
521
|
+
t.is(result.split('\n').length, 2);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
test('processMemoryContent strips metadata correctly', t => {
|
|
525
|
+
const input = `
|
|
526
|
+
1|2024-03-19T10:00:00Z|Normal content
|
|
527
|
+
2|2024-03-19T11:00:00Z|Content with|pipes|inside
|
|
528
|
+
malformed_line_no_separators
|
|
529
|
+
3|2024-03-19T12:00:00Z|More|content
|
|
530
|
+
4|invalid_timestamp|Still valid
|
|
531
|
+
5||No timestamp but valid
|
|
532
|
+
|2024-03-19T13:00:00Z|No priority
|
|
533
|
+
||No priority or timestamp`.trim();
|
|
534
|
+
|
|
535
|
+
const result = processMemoryContent(input, { stripMetadata: true });
|
|
536
|
+
t.true(result.includes('Normal content'));
|
|
537
|
+
t.true(result.includes('Content with|pipes|inside'));
|
|
538
|
+
t.true(result.includes('More|content'));
|
|
539
|
+
t.true(result.includes('Still valid'));
|
|
540
|
+
t.true(result.includes('No timestamp but valid'));
|
|
541
|
+
t.false(result.includes('2024-03-19'));
|
|
542
|
+
t.false(result.includes('|2024'));
|
|
543
|
+
// Malformed lines should be preserved as-is
|
|
544
|
+
t.true(result.includes('malformed_line_no_separators'));
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
test('processMemoryContent combines all filters correctly', t => {
|
|
548
|
+
const now = Date.now();
|
|
549
|
+
const hour = 60 * 60 * 1000;
|
|
550
|
+
const recentTime = new Date(now - hour).toISOString();
|
|
551
|
+
const oldTime = new Date(now - 3 * hour).toISOString();
|
|
552
|
+
|
|
553
|
+
const input = `
|
|
554
|
+
1|${recentTime}|High priority recent
|
|
555
|
+
2|${recentTime}|Medium priority recent
|
|
556
|
+
3|${recentTime}|Low priority recent
|
|
557
|
+
1|${oldTime}|High priority old
|
|
558
|
+
2|${oldTime}|Medium priority old
|
|
559
|
+
3|${oldTime}|Low priority old`.trim();
|
|
560
|
+
|
|
561
|
+
const result = processMemoryContent(input, {
|
|
562
|
+
priority: 2,
|
|
563
|
+
recentHours: 2,
|
|
564
|
+
numResults: 2,
|
|
565
|
+
stripMetadata: true
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
// Should only include recent memories with priority 1-2, limited to 2 results
|
|
569
|
+
t.true(result.includes('High priority recent'));
|
|
570
|
+
t.true(result.includes('Medium priority recent'));
|
|
571
|
+
t.false(result.includes('Low priority recent'));
|
|
572
|
+
t.false(result.includes('High priority old'));
|
|
573
|
+
t.false(result.includes('Medium priority old'));
|
|
574
|
+
t.false(result.includes('Low priority old'));
|
|
575
|
+
t.false(result.includes('|')); // Metadata should be stripped
|
|
576
|
+
t.is(result.split('\n').length, 2); // Should only have 2 results
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
test('processMemoryContent handles empty lines and whitespace', t => {
|
|
580
|
+
const input = `
|
|
581
|
+
|
|
582
|
+
1|2024-03-19T10:00:00Z|Content 1
|
|
583
|
+
|
|
584
|
+
2|2024-03-19T11:00:00Z|Content 2
|
|
585
|
+
|
|
586
|
+
`.trim();
|
|
587
|
+
|
|
588
|
+
const result = processMemoryContent(input, { stripMetadata: true });
|
|
589
|
+
t.is(result.split('\n').filter(Boolean).length, 2);
|
|
590
|
+
t.true(result.includes('Content 1'));
|
|
591
|
+
t.true(result.includes('Content 2'));
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
test('processMemoryContent handles special characters in content', t => {
|
|
595
|
+
const input = `
|
|
596
|
+
1|2024-03-19T10:00:00Z|Content with 🚀 emoji
|
|
597
|
+
2|2024-03-19T11:00:00Z|Content with üñîçødé
|
|
598
|
+
3|2024-03-19T12:00:00Z|Content with "quotes" and 'apostrophes'
|
|
599
|
+
4|2024-03-19T13:00:00Z|Content with \n\t\r escape chars`.trim();
|
|
600
|
+
|
|
601
|
+
const result = processMemoryContent(input, { stripMetadata: true });
|
|
602
|
+
t.true(result.includes('Content with 🚀 emoji'));
|
|
603
|
+
t.true(result.includes('Content with üñîçødé'));
|
|
604
|
+
t.true(result.includes('Content with "quotes" and \'apostrophes\''));
|
|
605
|
+
t.true(result.includes('Content with \n\t\r escape chars'));
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
test('processMemoryContent handles extremely long content', t => {
|
|
609
|
+
const longContent = 'a'.repeat(10000);
|
|
610
|
+
const input = `1|2024-03-19T10:00:00Z|${longContent}`;
|
|
611
|
+
|
|
612
|
+
const result = processMemoryContent(input, { stripMetadata: true });
|
|
613
|
+
t.is(result, longContent);
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
test('processMemoryContent priority filtering edge cases', t => {
|
|
617
|
+
const input = `
|
|
618
|
+
1|2024-03-19T10:00:00Z|Valid priority 1
|
|
619
|
+
01|2024-03-19T10:00:00Z|Leading zero priority
|
|
620
|
+
1.5|2024-03-19T10:00:00Z|Decimal priority
|
|
621
|
+
-1|2024-03-19T10:00:00Z|Negative priority
|
|
622
|
+
a|2024-03-19T10:00:00Z|Letter priority
|
|
623
|
+
9999|2024-03-19T10:00:00Z|Large priority`.trim();
|
|
624
|
+
|
|
625
|
+
const result = processMemoryContent(input, { priority: 2 });
|
|
626
|
+
t.true(result.includes('Valid priority 1'));
|
|
627
|
+
t.false(result.includes('Decimal priority'));
|
|
628
|
+
t.false(result.includes('Negative priority'));
|
|
629
|
+
t.false(result.includes('Letter priority'));
|
|
630
|
+
t.false(result.includes('Large priority'));
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
test('processMemoryContent timestamp filtering edge cases', t => {
|
|
634
|
+
const now = new Date();
|
|
635
|
+
const input = `
|
|
636
|
+
1|${now.toISOString()}|Valid ISO timestamp
|
|
637
|
+
1|${now.toUTCString()}|UTC timestamp
|
|
638
|
+
1|${now.getTime()}|Timestamp as number
|
|
639
|
+
1|2024-03-19|Partial date
|
|
640
|
+
1|Invalid Date|Invalid date string
|
|
641
|
+
1|9999999999999|Future timestamp`.trim();
|
|
642
|
+
|
|
643
|
+
const result = processMemoryContent(input, { recentHours: 1 });
|
|
644
|
+
t.true(result.includes('Valid ISO timestamp'));
|
|
645
|
+
t.false(result.includes('UTC timestamp'));
|
|
646
|
+
t.false(result.includes('Timestamp as number'));
|
|
647
|
+
t.false(result.includes('Partial date'));
|
|
648
|
+
t.false(result.includes('Invalid date string'));
|
|
649
|
+
t.false(result.includes('Future timestamp'));
|
|
136
650
|
});
|