@elizaos/core 1.5.4 → 1.5.5

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.
Files changed (90) hide show
  1. package/README.md +0 -15
  2. package/dist/index.d.ts +4886 -3
  3. package/dist/index.js +5287 -4
  4. package/package.json +21 -39
  5. package/dist/browser/index.browser.js +0 -1109
  6. package/dist/browser/index.browser.js.map +0 -707
  7. package/dist/browser/index.d.ts +0 -3
  8. package/dist/node/index.d.ts +0 -3
  9. package/dist/node/index.node.js +0 -74530
  10. package/dist/node/index.node.js.map +0 -1027
  11. package/src/__tests__/action-chaining-simple.test.ts +0 -203
  12. package/src/__tests__/actions.test.ts +0 -218
  13. package/src/__tests__/buffer.test.ts +0 -337
  14. package/src/__tests__/character-validation.test.ts +0 -309
  15. package/src/__tests__/database.test.ts +0 -750
  16. package/src/__tests__/entities.test.ts +0 -727
  17. package/src/__tests__/env.test.ts +0 -23
  18. package/src/__tests__/environment.test.ts +0 -285
  19. package/src/__tests__/logger-browser-node.test.ts +0 -716
  20. package/src/__tests__/logger.test.ts +0 -403
  21. package/src/__tests__/messages.test.ts +0 -196
  22. package/src/__tests__/mockCharacter.ts +0 -544
  23. package/src/__tests__/parsing.test.ts +0 -58
  24. package/src/__tests__/prompts.test.ts +0 -159
  25. package/src/__tests__/roles.test.ts +0 -331
  26. package/src/__tests__/runtime-embedding.test.ts +0 -343
  27. package/src/__tests__/runtime.test.ts +0 -978
  28. package/src/__tests__/search.test.ts +0 -15
  29. package/src/__tests__/services-by-type.test.ts +0 -204
  30. package/src/__tests__/services.test.ts +0 -136
  31. package/src/__tests__/settings.test.ts +0 -810
  32. package/src/__tests__/utils.test.ts +0 -1105
  33. package/src/__tests__/uuid.test.ts +0 -94
  34. package/src/actions.ts +0 -122
  35. package/src/database.ts +0 -579
  36. package/src/entities.ts +0 -406
  37. package/src/index.browser.ts +0 -48
  38. package/src/index.node.ts +0 -39
  39. package/src/index.ts +0 -50
  40. package/src/logger.ts +0 -527
  41. package/src/prompts.ts +0 -243
  42. package/src/roles.ts +0 -85
  43. package/src/runtime.ts +0 -2514
  44. package/src/schemas/character.ts +0 -149
  45. package/src/search.ts +0 -1543
  46. package/src/sentry/instrument.browser.ts +0 -65
  47. package/src/sentry/instrument.node.ts +0 -57
  48. package/src/sentry/instrument.ts +0 -82
  49. package/src/services.ts +0 -105
  50. package/src/settings.ts +0 -409
  51. package/src/test_resources/constants.ts +0 -12
  52. package/src/test_resources/testSetup.ts +0 -21
  53. package/src/test_resources/types.ts +0 -22
  54. package/src/types/agent.ts +0 -112
  55. package/src/types/browser.ts +0 -145
  56. package/src/types/components.ts +0 -184
  57. package/src/types/database.ts +0 -348
  58. package/src/types/email.ts +0 -162
  59. package/src/types/environment.ts +0 -129
  60. package/src/types/events.ts +0 -249
  61. package/src/types/index.ts +0 -29
  62. package/src/types/knowledge.ts +0 -65
  63. package/src/types/lp.ts +0 -124
  64. package/src/types/memory.ts +0 -228
  65. package/src/types/message.ts +0 -233
  66. package/src/types/messaging.ts +0 -57
  67. package/src/types/model.ts +0 -359
  68. package/src/types/pdf.ts +0 -77
  69. package/src/types/plugin.ts +0 -78
  70. package/src/types/post.ts +0 -271
  71. package/src/types/primitives.ts +0 -97
  72. package/src/types/runtime.ts +0 -190
  73. package/src/types/service.ts +0 -198
  74. package/src/types/settings.ts +0 -30
  75. package/src/types/state.ts +0 -60
  76. package/src/types/task.ts +0 -72
  77. package/src/types/tee.ts +0 -107
  78. package/src/types/testing.ts +0 -30
  79. package/src/types/token.ts +0 -96
  80. package/src/types/transcription.ts +0 -133
  81. package/src/types/video.ts +0 -108
  82. package/src/types/wallet.ts +0 -56
  83. package/src/types/web-search.ts +0 -146
  84. package/src/utils/__tests__/buffer.test.ts +0 -80
  85. package/src/utils/__tests__/environment.test.ts +0 -58
  86. package/src/utils/__tests__/stringToUuid.test.ts +0 -88
  87. package/src/utils/buffer.ts +0 -312
  88. package/src/utils/environment.ts +0 -316
  89. package/src/utils/server-health.ts +0 -117
  90. package/src/utils.ts +0 -1076
@@ -1,1105 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, setSystemTime, mock } from 'bun:test';
2
- import { Content, Entity, IAgentRuntime, Memory, ModelType, State } from '../types';
3
- import * as utils from '../utils';
4
- import {
5
- addHeader,
6
- composePrompt,
7
- composePromptFromState,
8
- formatMessages,
9
- formatPosts,
10
- formatTimestamp,
11
- normalizeJsonString,
12
- parseBooleanFromText,
13
- parseJSONObjectFromText,
14
- parseKeyValueXml,
15
- safeReplacer,
16
- splitChunks,
17
- stringToUuid,
18
- trimTokens,
19
- truncateToCompleteSentence,
20
- validateUuid,
21
- } from '../utils';
22
-
23
- describe('Utils Comprehensive Tests', () => {
24
- describe('parseBooleanFromText', () => {
25
- it('should return true for affirmative values', () => {
26
- const affirmativeValues = [
27
- 'YES',
28
- 'Y',
29
- 'TRUE',
30
- 'T',
31
- '1',
32
- 'ON',
33
- 'ENABLE',
34
- 'yes',
35
- 'y',
36
- 'true',
37
- 't',
38
- ' YES ',
39
- ' true ',
40
- ];
41
-
42
- affirmativeValues.forEach((value) => {
43
- expect(parseBooleanFromText(value)).toBe(true);
44
- });
45
- });
46
-
47
- it('should return false for negative values', () => {
48
- const negativeValues = [
49
- 'NO',
50
- 'N',
51
- 'FALSE',
52
- 'F',
53
- '0',
54
- 'OFF',
55
- 'DISABLE',
56
- 'no',
57
- 'n',
58
- 'false',
59
- 'f',
60
- ' NO ',
61
- ' false ',
62
- ];
63
-
64
- negativeValues.forEach((value) => {
65
- expect(parseBooleanFromText(value)).toBe(false);
66
- });
67
- });
68
-
69
- it('should return false for null, undefined, or empty values', () => {
70
- expect(parseBooleanFromText(null)).toBe(false);
71
- expect(parseBooleanFromText(undefined)).toBe(false);
72
- expect(parseBooleanFromText('')).toBe(false);
73
- });
74
-
75
- it('should return false for unrecognized values', () => {
76
- expect(parseBooleanFromText('maybe')).toBe(false);
77
- expect(parseBooleanFromText('123')).toBe(false);
78
- expect(parseBooleanFromText('YESNO')).toBe(false);
79
- expect(parseBooleanFromText('random')).toBe(false);
80
- });
81
- });
82
-
83
- describe('formatTimestamp', () => {
84
- beforeEach(() => {
85
- // Mock Date.now() to ensure consistent tests
86
- setSystemTime(new Date('2024-01-15T12:00:00Z'));
87
- });
88
-
89
- afterEach(() => {
90
- setSystemTime(); // Reset to real time
91
- });
92
-
93
- it("should return 'just now' for recent timestamps", () => {
94
- const now = Date.now();
95
- expect(formatTimestamp(now)).toBe('just now');
96
- expect(formatTimestamp(now - 30000)).toBe('just now'); // 30 seconds ago
97
- expect(formatTimestamp(now - 59999)).toBe('just now'); // Just under 1 minute
98
- });
99
-
100
- it('should return minutes ago for timestamps within an hour', () => {
101
- const now = Date.now();
102
- expect(formatTimestamp(now - 60000)).toBe('1 minute ago');
103
- expect(formatTimestamp(now - 120000)).toBe('2 minutes ago');
104
- expect(formatTimestamp(now - 1800000)).toBe('30 minutes ago');
105
- expect(formatTimestamp(now - 3599000)).toBe('59 minutes ago');
106
- });
107
-
108
- it('should return hours ago for timestamps within 24 hours', () => {
109
- const now = Date.now();
110
- expect(formatTimestamp(now - 3600000)).toBe('1 hour ago');
111
- expect(formatTimestamp(now - 7200000)).toBe('2 hours ago');
112
- expect(formatTimestamp(now - 43200000)).toBe('12 hours ago');
113
- expect(formatTimestamp(now - 86399000)).toBe('23 hours ago');
114
- });
115
-
116
- it('should return days ago for older timestamps', () => {
117
- const now = Date.now();
118
- expect(formatTimestamp(now - 86400000)).toBe('1 day ago');
119
- expect(formatTimestamp(now - 172800000)).toBe('2 days ago');
120
- expect(formatTimestamp(now - 604800000)).toBe('7 days ago');
121
- });
122
- });
123
-
124
- describe('parseJSONObjectFromText', () => {
125
- it('should parse JSON from code blocks', () => {
126
- const text = 'Here is some JSON:\n```json\n{"key": "value", "number": 42}\n```';
127
- const result = parseJSONObjectFromText(text);
128
- // Note: normalizeJsonString converts numbers to strings
129
- expect(result).toEqual({ key: 'value', number: '42' });
130
- });
131
-
132
- it('should parse direct JSON without code blocks', () => {
133
- const text = '{"name": "Alice", "age": 30}';
134
- const result = parseJSONObjectFromText(text);
135
- // Note: normalizeJsonString converts numbers to strings
136
- expect(result).toEqual({ name: 'Alice', age: '30' });
137
- });
138
-
139
- it('should return null for invalid JSON', () => {
140
- expect(parseJSONObjectFromText('not json')).toBeNull();
141
- expect(parseJSONObjectFromText('{invalid json}')).toBeNull();
142
- expect(parseJSONObjectFromText('')).toBeNull();
143
- });
144
-
145
- it('should return null for arrays', () => {
146
- expect(parseJSONObjectFromText('["item1", "item2"]')).toBeNull();
147
- expect(parseJSONObjectFromText('```json\n[1, 2, 3]\n```')).toBeNull();
148
- });
149
-
150
- it('should return null for non-object values', () => {
151
- expect(parseJSONObjectFromText('"string"')).toBeNull();
152
- expect(parseJSONObjectFromText('42')).toBeNull();
153
- expect(parseJSONObjectFromText('true')).toBeNull();
154
- expect(parseJSONObjectFromText('null')).toBeNull();
155
- });
156
- });
157
-
158
- describe('normalizeJsonString', () => {
159
- it('should remove extra spaces after { and before }', () => {
160
- // Note: normalizeJsonString doesn't handle unquoted keys
161
- expect(normalizeJsonString('{ "key": "value" }')).toBe('{"key": "value"}');
162
- expect(normalizeJsonString('{\n "key": "value"\n}')).toBe('{"key": "value"}');
163
- });
164
-
165
- it('should wrap unquoted values in double quotes', () => {
166
- expect(normalizeJsonString('{"key": value}')).toBe('{"key": "value"}');
167
- expect(normalizeJsonString('{"key": someWord}')).toBe('{"key": "someWord"}');
168
- });
169
-
170
- it('should convert single quotes to double quotes', () => {
171
- // The function only converts when format is exactly "key": 'value'
172
- expect(normalizeJsonString('"key": \'value\'')).toBe('"key": "value"');
173
- // When already inside JSON braces, single quotes inside are preserved
174
- const result = normalizeJsonString('{"key": \'value\', "key2": \'value2\'}');
175
- // The function converts the quotes but may not work perfectly for nested quotes
176
- expect(result).toContain('key');
177
- expect(result).toContain('value');
178
- });
179
-
180
- it('should handle mixed formatting', () => {
181
- const input = '{ "key1": value1, "key2": \'value2\', "key3": "value3" }';
182
- const result = normalizeJsonString(input);
183
- // Check that the function at least wraps unquoted values
184
- expect(result).toContain('"key1": "value1"');
185
- expect(result).toContain('"key3": "value3"');
186
- });
187
-
188
- it('should wrap numbers in quotes', () => {
189
- // normalizeJsonString wraps ALL unquoted values including numbers
190
- const input = '{"key": "value", "number": 42}';
191
- const result = normalizeJsonString(input);
192
- expect(result).toBe('{"key": "value", "number": "42"}');
193
- });
194
- });
195
-
196
- describe('truncateToCompleteSentence', () => {
197
- it('should return text unchanged if within limit', () => {
198
- const text = 'Short text.';
199
- expect(truncateToCompleteSentence(text, 50)).toBe(text);
200
- });
201
-
202
- it('should truncate at last period within limit', () => {
203
- const text = 'First sentence. Second sentence. Third sentence that is very long.';
204
- expect(truncateToCompleteSentence(text, 35)).toBe('First sentence. Second sentence.');
205
- });
206
-
207
- it('should truncate at last space if no period found', () => {
208
- const text = 'This is a very long sentence without any periods that needs truncation';
209
- expect(truncateToCompleteSentence(text, 30)).toBe('This is a very long sentence...');
210
- });
211
-
212
- it('should hard truncate if no space found', () => {
213
- const text = 'Verylongwordwithoutanyspacesorperiods';
214
- // maxLength 10 - 3 = 7 chars for the text part
215
- expect(truncateToCompleteSentence(text, 10)).toBe('Verylon...');
216
- });
217
-
218
- it('should handle edge cases', () => {
219
- expect(truncateToCompleteSentence('', 10)).toBe('');
220
- expect(truncateToCompleteSentence('No period', 5)).toBe('No...');
221
- });
222
- });
223
-
224
- describe('splitChunks', () => {
225
- it('should split text into chunks', async () => {
226
- const text = 'a'.repeat(2000); // Long text
227
- const chunks = await splitChunks(text, 512, 20);
228
-
229
- expect(chunks).toBeInstanceOf(Array);
230
- expect(chunks.length).toBeGreaterThan(0);
231
- chunks.forEach((chunk) => {
232
- expect(chunk.length).toBeLessThanOrEqual(512 * 4); // Accounting for character to token ratio
233
- });
234
- });
235
-
236
- it('should handle short text', async () => {
237
- const text = 'Short text';
238
- const chunks = await splitChunks(text);
239
-
240
- expect(chunks).toHaveLength(1);
241
- expect(chunks[0]).toBe(text);
242
- });
243
-
244
- it('should respect chunk overlap', async () => {
245
- const text = 'Word1 Word2 Word3 Word4 Word5 ' + 'Word6 '.repeat(100);
246
- const chunks = await splitChunks(text, 50, 10);
247
-
248
- expect(chunks.length).toBeGreaterThan(1);
249
- });
250
- });
251
-
252
- describe('trimTokens', () => {
253
- let mockRuntime: IAgentRuntime;
254
-
255
- beforeEach(() => {
256
- mockRuntime = {
257
- useModel: mock(async (type, params) => {
258
- if (type === 'TEXT_TOKENIZER_ENCODE') {
259
- // Simple mock: each word is a token
260
- return params.prompt.split(' ');
261
- }
262
- if (type === 'TEXT_TOKENIZER_DECODE') {
263
- // Simple mock: join tokens back
264
- return params.tokens.join(' ');
265
- }
266
- return null;
267
- }),
268
- } as unknown as IAgentRuntime;
269
- });
270
-
271
- it('should trim tokens to specified limit', async () => {
272
- const prompt = 'one two three four five six seven eight nine ten';
273
- const result = await trimTokens(prompt, 5, mockRuntime);
274
-
275
- expect(result).toBe('six seven eight nine ten');
276
- expect(mockRuntime.useModel).toHaveBeenCalledTimes(2);
277
- });
278
-
279
- it('should return unchanged if within limit', async () => {
280
- const prompt = 'short text';
281
- const result = await trimTokens(prompt, 10, mockRuntime);
282
-
283
- expect(result).toBe(prompt);
284
- });
285
-
286
- it('should throw error for invalid inputs', async () => {
287
- await expect(trimTokens('', 10, mockRuntime)).rejects.toThrow();
288
- await expect(trimTokens('text', 0, mockRuntime)).rejects.toThrow();
289
- await expect(trimTokens('text', -1, mockRuntime)).rejects.toThrow();
290
- });
291
-
292
- it('should skip tokenization for very short prompts', async () => {
293
- const prompt = 'hi';
294
- const result = await trimTokens(prompt, 100, mockRuntime);
295
-
296
- expect(result).toBe(prompt);
297
- expect(mockRuntime.useModel).not.toHaveBeenCalled();
298
- });
299
- });
300
-
301
- describe('parseKeyValueXml', () => {
302
- it('should parse response XML blocks', () => {
303
- const xml = `<response>
304
- <name>TestAgent</name>
305
- <reasoning>Test reasoning</reasoning>
306
- <action>RESPOND</action>
307
- </response>`;
308
-
309
- const result = parseKeyValueXml(xml);
310
- expect(result).toEqual({
311
- name: 'TestAgent',
312
- reasoning: 'Test reasoning',
313
- action: 'RESPOND',
314
- });
315
- });
316
-
317
- it('should handle comma-separated lists for specific keys', () => {
318
- const xml = `<response>
319
- <actions>ACTION1,ACTION2,ACTION3</actions>
320
- <providers>PROVIDER1, PROVIDER2</providers>
321
- <evaluators>EVAL1, EVAL2</evaluators>
322
- </response>`;
323
-
324
- const result = parseKeyValueXml(xml);
325
- expect(result).toEqual({
326
- actions: ['ACTION1', 'ACTION2', 'ACTION3'],
327
- providers: ['PROVIDER1', 'PROVIDER2'],
328
- evaluators: ['EVAL1', 'EVAL2'],
329
- });
330
- });
331
-
332
- it('should parse boolean values', () => {
333
- const xml = '<response><simple>true</simple><complex>false</complex></response>';
334
-
335
- const result = parseKeyValueXml(xml);
336
- expect(result).toEqual({
337
- simple: true,
338
- complex: 'false', // Only 'simple' key is treated as boolean
339
- });
340
- });
341
-
342
- it('should handle XML entities', () => {
343
- const xml = '<response><text>Value with &lt;tags&gt; &amp; entities</text></response>';
344
-
345
- const result = parseKeyValueXml(xml);
346
- expect(result).toEqual({
347
- text: 'Value with <tags> & entities',
348
- });
349
- });
350
-
351
- it('should return null for invalid XML', () => {
352
- expect(parseKeyValueXml('')).toBeNull();
353
- expect(parseKeyValueXml('not xml')).toBeNull();
354
- expect(parseKeyValueXml('<unclosed>')).toBeNull();
355
- });
356
-
357
- it('should handle any root tag name', () => {
358
- const xml = '<custom><key>value</key></custom>';
359
- const result = parseKeyValueXml(xml);
360
- expect(result).toEqual({ key: 'value' });
361
- });
362
-
363
- it('should handle mismatched XML tags', () => {
364
- // This tests lines 393-395 - warning about mismatched tags
365
- const xml = '<response><start>value</end></response>';
366
- const result = parseKeyValueXml(xml);
367
- // Should still try to parse what it can
368
- expect(result).toBeNull();
369
- });
370
-
371
- it('should return null when no key-value pairs are found', () => {
372
- // This tests lines 400-403 - no key-value pairs extracted
373
- const xml = '<response></response>'; // Empty response
374
- const result = parseKeyValueXml(xml);
375
- expect(result).toBeNull();
376
-
377
- // Also test with content that doesn't match the pattern
378
- const xml2 = '<response>Just plain text</response>';
379
- const result2 = parseKeyValueXml(xml2);
380
- expect(result2).toBeNull();
381
- });
382
-
383
- it('should handle empty comma-separated lists', () => {
384
- const xml = `<response>
385
- <actions></actions>
386
- <providers> </providers>
387
- </response>`;
388
-
389
- const result = parseKeyValueXml(xml);
390
- expect(result).toEqual({
391
- actions: [],
392
- providers: [],
393
- });
394
- });
395
-
396
- it('should correctly handle nested same-name tags in fallback scan', () => {
397
- // No <response> tag so fallback scanner is used
398
- const xml = '<container><text><text>inner</text>outer</text></container>';
399
- const result = parseKeyValueXml(xml);
400
- // Ensure we did not truncate at the inner </text>
401
- expect(result).not.toBeNull();
402
- expect(result!.text).toContain('outer');
403
- expect(result!.text).toContain('<text>inner</text>');
404
- });
405
-
406
- it('should treat self-closing tags with whitespace as self-closing and skip them', () => {
407
- // First tag is self-closing with whitespace before '/>'
408
- const xml = '<sc /> <container><key>value</key></container>';
409
- const result = parseKeyValueXml(xml);
410
- expect(result).toEqual({ key: 'value' });
411
- });
412
- });
413
-
414
- describe('formatMessages', () => {
415
- const mockEntities: Entity[] = [
416
- {
417
- id: 'entity-1' as any,
418
- names: ['Alice'],
419
- agentId: 'agent-1' as any,
420
- },
421
- {
422
- id: 'entity-2' as any,
423
- names: ['Bob'],
424
- agentId: 'agent-1' as any,
425
- },
426
- ];
427
-
428
- it('should format messages with basic content', () => {
429
- const messages: Memory[] = [
430
- {
431
- id: 'msg-1' as any,
432
- entityId: 'entity-1' as any,
433
- roomId: 'room-1' as any,
434
- createdAt: Date.now() - 60000, // 1 minute ago
435
- content: {
436
- text: 'Hello world',
437
- source: 'chat',
438
- } as Content,
439
- },
440
- ];
441
-
442
- const result = formatMessages({ messages, entities: mockEntities });
443
-
444
- expect(result).toContain('Alice');
445
- expect(result).toContain('Hello world');
446
- expect(result).toContain('1 minute ago');
447
- expect(result).toContain('[entity-1]');
448
- });
449
-
450
- it('should format messages with actions and thoughts', () => {
451
- const messages: Memory[] = [
452
- {
453
- id: 'msg-1' as any,
454
- entityId: 'entity-2' as any,
455
- roomId: 'room-1' as any,
456
- createdAt: Date.now(),
457
- content: {
458
- text: 'Doing something',
459
- thought: 'I should help',
460
- actions: ['SEARCH', 'REPLY'],
461
- source: 'chat',
462
- } as Content,
463
- },
464
- ];
465
-
466
- const result = formatMessages({ messages, entities: mockEntities });
467
-
468
- expect(result).toContain('Bob');
469
- expect(result).toContain('Doing something');
470
- expect(result).toContain("(Bob's internal thought: I should help)");
471
- expect(result).toContain("(Bob's actions: SEARCH, REPLY)");
472
- });
473
-
474
- it('should handle attachments', () => {
475
- const messages: Memory[] = [
476
- {
477
- id: 'msg-1' as any,
478
- entityId: 'entity-1' as any,
479
- roomId: 'room-1' as any,
480
- createdAt: Date.now(),
481
- content: {
482
- text: 'Check this out',
483
- attachments: [
484
- { id: 'att-1', title: 'Image', url: 'http://example.com/img.jpg' },
485
- { id: 'att-2', title: 'Document', url: 'http://example.com/doc.pdf' },
486
- ],
487
- source: 'chat',
488
- } as Content,
489
- },
490
- ];
491
-
492
- const result = formatMessages({ messages, entities: mockEntities });
493
-
494
- expect(result).toContain(
495
- '(Attachments: [att-1 - Image (http://example.com/img.jpg)], [att-2 - Document (http://example.com/doc.pdf)])'
496
- );
497
- });
498
-
499
- it('should filter out messages without entityId', () => {
500
- const messages: Memory[] = [
501
- {
502
- id: 'msg-1' as any,
503
- entityId: null as any,
504
- roomId: 'room-1' as any,
505
- createdAt: Date.now(),
506
- content: { text: 'Should be filtered', source: 'chat' } as Content,
507
- },
508
- {
509
- id: 'msg-2' as any,
510
- entityId: 'entity-1' as any,
511
- roomId: 'room-1' as any,
512
- createdAt: Date.now(),
513
- content: { text: 'Should appear', source: 'chat' } as Content,
514
- },
515
- ];
516
-
517
- const result = formatMessages({ messages, entities: mockEntities });
518
-
519
- expect(result).not.toContain('Should be filtered');
520
- expect(result).toContain('Should appear');
521
- });
522
- });
523
-
524
- describe('formatPosts', () => {
525
- const mockEntities: Entity[] = [
526
- {
527
- id: 'entity-1' as any,
528
- names: ['Alice'],
529
- agentId: 'agent-1' as any,
530
- },
531
- ];
532
-
533
- it('should format posts grouped by room', () => {
534
- const messages: Memory[] = [
535
- {
536
- id: 'msg-1' as any,
537
- entityId: 'entity-1' as any,
538
- roomId: 'room-1' as any,
539
- createdAt: Date.now() - 3600000,
540
- content: {
541
- text: 'First message',
542
- source: 'twitter',
543
- } as Content,
544
- },
545
- {
546
- id: 'msg-2' as any,
547
- entityId: 'entity-1' as any,
548
- roomId: 'room-1' as any,
549
- createdAt: Date.now(),
550
- content: {
551
- text: 'Second message',
552
- source: 'twitter',
553
- } as Content,
554
- },
555
- ];
556
-
557
- const result = formatPosts({ messages, entities: mockEntities });
558
-
559
- // formatPosts uses roomId.slice(-5) to show only last 5 chars
560
- expect(result).toContain('Conversation: oom-1');
561
- expect(result).toContain('Name: Alice');
562
- expect(result).toContain('First message');
563
- expect(result).toContain('Second message');
564
- expect(result).toContain('Source: twitter');
565
- });
566
-
567
- it('should include reply information', () => {
568
- const messages: Memory[] = [
569
- {
570
- id: 'msg-1' as any,
571
- entityId: 'entity-1' as any,
572
- roomId: 'room-1' as any,
573
- createdAt: Date.now(),
574
- content: {
575
- text: 'Reply message',
576
- inReplyTo: '12345678-1234-1234-1234-123456789012' as any,
577
- source: 'chat',
578
- } as Content,
579
- },
580
- ];
581
-
582
- const result = formatPosts({ messages, entities: mockEntities });
583
-
584
- expect(result).toContain('In reply to: 12345678-1234-1234-1234-123456789012');
585
- });
586
-
587
- it('should format without conversation header when specified', () => {
588
- const messages: Memory[] = [
589
- {
590
- id: 'msg-1' as any,
591
- entityId: 'entity-1' as any,
592
- roomId: 'room-1' as any,
593
- createdAt: Date.now(),
594
- content: { text: 'Message', source: 'chat' } as Content,
595
- },
596
- ];
597
-
598
- const result = formatPosts({
599
- messages,
600
- entities: mockEntities,
601
- conversationHeader: false,
602
- });
603
-
604
- expect(result).not.toContain('Conversation:');
605
- });
606
-
607
- it('should handle missing entity with warning', () => {
608
- // This tests line 209 - logger.warn when entity not found
609
- const messages: Memory[] = [
610
- {
611
- id: 'msg-1' as any,
612
- entityId: 'non-existent-entity' as any,
613
- roomId: 'room-1' as any,
614
- createdAt: Date.now(),
615
- content: {
616
- text: 'Message from unknown entity',
617
- source: 'chat',
618
- } as Content,
619
- },
620
- ];
621
-
622
- // Empty entities array to ensure entity is not found
623
- const result = formatPosts({ messages, entities: [] });
624
-
625
- // Should use "Unknown User" when entity not found
626
- expect(result).toContain('Unknown User');
627
- expect(result).toContain('unknown');
628
- expect(result).toContain('non-existent-entity');
629
- });
630
-
631
- it('should handle messages without roomId', () => {
632
- const messages: Memory[] = [
633
- {
634
- id: 'msg-1' as any,
635
- entityId: 'entity-1' as any,
636
- roomId: null as any, // No roomId
637
- createdAt: Date.now(),
638
- content: {
639
- text: 'Message without room',
640
- source: 'chat',
641
- } as Content,
642
- },
643
- ];
644
-
645
- const result = formatPosts({ messages, entities: mockEntities });
646
-
647
- // Messages without roomId should be filtered out
648
- expect(result).toBe('');
649
- });
650
- });
651
-
652
- describe('validateUuid', () => {
653
- it('should validate correct UUID format', () => {
654
- const validUuid = '123e4567-e89b-12d3-a456-426614174000';
655
- const result = validateUuid(validUuid);
656
- expect(result).toBe(validUuid);
657
- });
658
-
659
- it('should return null for invalid UUID format', () => {
660
- expect(validateUuid('not-a-uuid')).toBeNull();
661
- expect(validateUuid('123')).toBeNull();
662
- expect(validateUuid('')).toBeNull();
663
- expect(validateUuid(null)).toBeNull();
664
- expect(validateUuid(undefined)).toBeNull();
665
- expect(validateUuid(123)).toBeNull();
666
- expect(validateUuid({})).toBeNull();
667
- });
668
-
669
- it('should handle UUID-like strings that are invalid', () => {
670
- // Wrong format variations
671
- expect(validateUuid('123e4567-e89b-12d3-a456')).toBeNull(); // Too short
672
- expect(validateUuid('123e4567-e89b-12d3-a456-426614174000-extra')).toBeNull(); // Too long
673
- expect(validateUuid('123e4567e89b12d3a456426614174000')).toBeNull(); // No dashes
674
- expect(validateUuid('XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX')).toBeNull(); // Invalid chars
675
- });
676
- });
677
-
678
- describe('stringToUuid', () => {
679
- it('should convert string to UUID', () => {
680
- const result = stringToUuid('test-string');
681
- expect(result).toBeDefined();
682
- expect(typeof result).toBe('string');
683
- // Should be in UUID format
684
- expect(result).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
685
- });
686
-
687
- it('should convert number to UUID', () => {
688
- const result = stringToUuid(12345);
689
- expect(result).toBeDefined();
690
- expect(typeof result).toBe('string');
691
- expect(result).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
692
- });
693
-
694
- it('should throw TypeError for non-string/non-number input', () => {
695
- expect(() => stringToUuid(null as any)).toThrow(TypeError);
696
- expect(() => stringToUuid(undefined as any)).toThrow(TypeError);
697
- expect(() => stringToUuid({} as any)).toThrow(TypeError);
698
- expect(() => stringToUuid([] as any)).toThrow(TypeError);
699
- expect(() => stringToUuid(true as any)).toThrow(TypeError);
700
- });
701
-
702
- it('should generate consistent UUID for same input', () => {
703
- const input = 'consistent-input';
704
- const uuid1 = stringToUuid(input);
705
- const uuid2 = stringToUuid(input);
706
- expect(uuid1).toBe(uuid2);
707
- });
708
-
709
- it('should generate different UUIDs for different inputs', () => {
710
- const uuid1 = stringToUuid('input1');
711
- const uuid2 = stringToUuid('input2');
712
- expect(uuid1).not.toBe(uuid2);
713
- });
714
-
715
- it('should handle special characters in string input', () => {
716
- const specialChars = 'test@#$%^&*()_+-=[]{}|;\':",./<>?';
717
- const result = stringToUuid(specialChars);
718
- expect(result).toBeDefined();
719
- expect(result).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
720
- });
721
-
722
- it('should handle empty string', () => {
723
- const result = stringToUuid('');
724
- expect(result).toBeDefined();
725
- expect(result).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
726
- });
727
-
728
- it('should handle very long strings', () => {
729
- const longString = 'a'.repeat(1000);
730
- const result = stringToUuid(longString);
731
- expect(result).toBeDefined();
732
- expect(result).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
733
- });
734
- });
735
-
736
- describe('safeReplacer', () => {
737
- it('should handle circular references', () => {
738
- const obj: any = { a: 1, b: { c: 2 } };
739
- obj.circular = obj; // Create circular reference
740
- obj.b.parent = obj; // Another circular reference
741
-
742
- const replacer = safeReplacer();
743
- const result = JSON.stringify(obj, replacer);
744
-
745
- expect(result).toContain('[Circular]');
746
- expect(result).toContain('"a":1');
747
- expect(result).toContain('"c":2');
748
-
749
- // Should not throw error
750
- expect(() => JSON.stringify(obj, replacer)).not.toThrow();
751
- });
752
-
753
- it('should handle non-circular objects normally', () => {
754
- const obj = {
755
- a: 1,
756
- b: { c: 2 },
757
- d: [1, 2, 3],
758
- e: null,
759
- f: 'string',
760
- };
761
-
762
- const replacer = safeReplacer();
763
- const result = JSON.stringify(obj, replacer);
764
-
765
- expect(result).toBe(JSON.stringify(obj)); // Should be identical
766
- expect(result).not.toContain('[Circular]');
767
- });
768
-
769
- it('should handle nested circular references', () => {
770
- const obj: any = {
771
- level1: {
772
- level2: {
773
- level3: {},
774
- },
775
- },
776
- };
777
- obj.level1.level2.level3.back = obj.level1; // Circular reference
778
-
779
- const replacer = safeReplacer();
780
- const result = JSON.stringify(obj, replacer);
781
-
782
- expect(result).toContain('[Circular]');
783
- expect(() => JSON.stringify(obj, replacer)).not.toThrow();
784
- });
785
-
786
- it('should handle arrays with circular references', () => {
787
- const arr: any[] = [1, 2, 3];
788
- const obj = { arr, value: 'test' };
789
- arr.push(obj); // Circular reference through array
790
-
791
- const replacer = safeReplacer();
792
- const result = JSON.stringify({ data: arr }, replacer);
793
-
794
- expect(result).toContain('[Circular]');
795
- expect(result).toContain('1');
796
- expect(result).toContain('2');
797
- expect(result).toContain('3');
798
- });
799
- });
800
-
801
- describe('composePrompt', () => {
802
- it('should compose prompt with state values', () => {
803
- const state = {
804
- name: 'Alice',
805
- role: 'developer',
806
- task: 'write tests',
807
- };
808
- const template = `Hello {{name}}, as a {{role}}, please {{task}}.`;
809
-
810
- const result = composePrompt({ state, template });
811
-
812
- expect(result).toBe('Hello Alice, as a developer, please write tests.');
813
- });
814
-
815
- it('should handle missing state values', () => {
816
- const state = {
817
- name: 'Bob',
818
- };
819
- const template = `Hello {{name}}, your role is {{role}}.`;
820
-
821
- const result = composePrompt({ state, template });
822
-
823
- // Handlebars replaces missing values with empty string
824
- expect(result).toBe('Hello Bob, your role is .');
825
- });
826
-
827
- it('should handle empty state', () => {
828
- const state = {};
829
- const template = `Template with {{placeholder}}.`;
830
-
831
- const result = composePrompt({ state, template });
832
-
833
- // Handlebars replaces missing values with empty string
834
- expect(result).toBe('Template with .');
835
- });
836
-
837
- it('should handle multiple occurrences of same placeholder', () => {
838
- const state = {
839
- word: 'test',
840
- };
841
- const template = `{{word}} {{word}} {{word}}`;
842
-
843
- const result = composePrompt({ state, template });
844
-
845
- expect(result).toBe('test test test');
846
- });
847
- });
848
-
849
- describe('composePromptFromState', () => {
850
- it('should compose prompt from State object', () => {
851
- const mockState: State = {
852
- // Required State properties
853
- userId: 'user-123' as any,
854
- agentId: 'agent-123' as any,
855
- roomId: 'room-123' as any,
856
- bio: 'I am a helpful assistant',
857
- lore: 'Created to help with tasks',
858
- senderName: 'Assistant',
859
- actors: 'user123',
860
- actorsData: [
861
- {
862
- id: 'actor-1' as any,
863
- names: ['John'],
864
- agentId: 'agent-123' as any,
865
- },
866
- ],
867
- // Template values
868
- recentMessages: 'Recent conversation history',
869
- relevantKnowledge: 'Relevant facts',
870
- recentMessagesData: [
871
- {
872
- id: 'msg-1' as any,
873
- entityId: 'entity-1' as any,
874
- roomId: 'room-123' as any,
875
- createdAt: Date.now(),
876
- content: {
877
- text: 'Hello',
878
- source: 'chat',
879
- } as Content,
880
- },
881
- ],
882
- // Additional required State properties
883
- values: {},
884
- data: {},
885
- text: '',
886
- };
887
-
888
- const template = `Bio: {{bio}}\nLore: {{lore}}\nRecent: {{recentMessages}}`;
889
-
890
- const result = composePromptFromState({ state: mockState, template });
891
-
892
- expect(result).toContain('Bio: I am a helpful assistant');
893
- expect(result).toContain('Lore: Created to help with tasks');
894
- expect(result).toContain('Recent: Recent conversation history');
895
- });
896
-
897
- it('should handle State with array data', () => {
898
- const mockState: State = {
899
- userId: 'user-123' as any,
900
- agentId: 'agent-123' as any,
901
- roomId: 'room-123' as any,
902
- bio: 'Assistant bio',
903
- lore: 'Assistant lore',
904
- senderName: 'User',
905
- actors: '',
906
- actorsData: [
907
- {
908
- id: 'actor-1' as any,
909
- names: ['Alice'],
910
- agentId: 'agent-123' as any,
911
- },
912
- {
913
- id: 'actor-2' as any,
914
- names: ['Bob'],
915
- agentId: 'agent-123' as any,
916
- },
917
- ],
918
- recentMessagesData: [],
919
- values: {},
920
- data: {},
921
- text: '',
922
- };
923
-
924
- const template = `Actors: {{actors}}\nSender: {{senderName}}`;
925
-
926
- const result = composePromptFromState({ state: mockState, template });
927
-
928
- expect(result).toContain('Sender: User');
929
- // actors field might be generated from actorsData
930
- expect(result).toBeDefined();
931
- });
932
-
933
- it('should handle missing properties in State', () => {
934
- const mockState: State = {
935
- userId: 'user-123' as any,
936
- agentId: 'agent-123' as any,
937
- roomId: 'room-123' as any,
938
- bio: '',
939
- lore: '',
940
- senderName: '',
941
- actors: '',
942
- actorsData: [],
943
- recentMessagesData: [],
944
- values: {},
945
- data: {},
946
- text: '',
947
- };
948
-
949
- const template = `Bio: {{bio}}\nMissing: {{missingProp}}`;
950
-
951
- const result = composePromptFromState({ state: mockState, template });
952
-
953
- expect(result).toContain('Bio: ');
954
- // Handlebars replaces missing values with empty string
955
- expect(result).toBe('Bio: \nMissing: ');
956
- });
957
- });
958
-
959
- describe('addHeader', () => {
960
- it('should add header to body', () => {
961
- const header = '# Title';
962
- const body = 'This is the body content.';
963
-
964
- const result = addHeader(header, body);
965
-
966
- // addHeader adds only single newline between header and body, and adds newline at end
967
- expect(result).toBe('# Title\nThis is the body content.\n');
968
- });
969
-
970
- it('should handle empty header', () => {
971
- const header = '';
972
- const body = 'Body content';
973
-
974
- const result = addHeader(header, body);
975
-
976
- // Empty header results in just body with newline at end
977
- expect(result).toBe('Body content\n');
978
- });
979
-
980
- it('should handle empty body', () => {
981
- const header = 'Header';
982
- const body = '';
983
-
984
- const result = addHeader(header, body);
985
-
986
- // Empty body returns empty string regardless of header
987
- expect(result).toBe('');
988
- });
989
-
990
- it('should handle both empty', () => {
991
- const result = addHeader('', '');
992
-
993
- // Both empty returns empty string
994
- expect(result).toBe('');
995
- });
996
-
997
- it('should handle multiline header and body', () => {
998
- const header = 'Line 1\nLine 2';
999
- const body = 'Body line 1\nBody line 2';
1000
-
1001
- const result = addHeader(header, body);
1002
-
1003
- // Single newline between header and body, newline at end
1004
- expect(result).toBe('Line 1\nLine 2\nBody line 1\nBody line 2\n');
1005
- });
1006
- });
1007
-
1008
- // we test this via composePrompt and composePromptFromState
1009
- /*
1010
- it('upgradeDoubleToTriple converts double braces to triple', () => {
1011
- const tpl = 'Hello {{name}} and {{#if cond}}{{value}}{{/if}}';
1012
- const out = upgradeDoubleToTriple(tpl);
1013
- expect(out).toBe('Hello {{{name}}} and {{#if cond}}{{{value}}}{{/if}}');
1014
- });
1015
- */
1016
-
1017
- it('addHeader prepends header when body exists', () => {
1018
- expect(addHeader('Head', 'Body')).toBe('Head\nBody\n');
1019
- expect(addHeader('Head', '')).toBe('');
1020
- });
1021
-
1022
- // we test this via composePrompt and composePromptFromState
1023
- /*
1024
- it('composeRandomUser replaces placeholders', () => {
1025
- const result = composeRandomUser('hi {{name1}} {{name2}}', 2);
1026
- expect(result).not.toContain('{{');
1027
- });
1028
- */
1029
-
1030
- it('parseKeyValueXml parses simple xml block', () => {
1031
- const xml = '<response><key>value</key><actions>a,b</actions><simple>true</simple></response>';
1032
- const parsed = parseKeyValueXml(xml);
1033
- expect(parsed).toEqual({ key: 'value', actions: ['a', 'b'], simple: true });
1034
- });
1035
-
1036
- it('safeReplacer handles circular objects', () => {
1037
- const obj: any = { a: 1 };
1038
- obj.self = obj;
1039
- const str = JSON.stringify(obj, safeReplacer());
1040
- expect(str).toContain('[Circular]');
1041
- });
1042
-
1043
- it('validateUuid validates correct uuid and rejects bad values', () => {
1044
- const valid = validateUuid('123e4567-e89b-12d3-a456-426614174000');
1045
- const invalid = validateUuid('not-a-uuid');
1046
- expect(valid).toBe('123e4567-e89b-12d3-a456-426614174000');
1047
- expect(invalid).toBeNull();
1048
- });
1049
-
1050
- it('composePrompt inserts state values', () => {
1051
- //const spy = vi.spyOn(utils, 'composeRandomUser').mockImplementation((t) => t);
1052
- const out = utils.composePrompt({ state: { a: 'x' }, template: 'Hello {{a}}' });
1053
- expect(out).toBe('Hello x');
1054
- //spy.mockRestore();
1055
- });
1056
-
1057
- it('composePromptFromState flattens state values', () => {
1058
- //const spy = vi.spyOn(utils, 'composeRandomUser').mockImplementation((t) => t);
1059
- const out = utils.composePromptFromState({
1060
- state: { values: { b: 'y' }, data: {}, text: '', c: 'z' },
1061
- template: '{{b}} {{c}}',
1062
- });
1063
- expect(out).toBe('y z');
1064
- //spy.mockRestore();
1065
- });
1066
-
1067
- it('formatPosts formats conversation text', () => {
1068
- const messages = [
1069
- {
1070
- id: '1',
1071
- entityId: 'e1',
1072
- roomId: 'r1',
1073
- createdAt: 1,
1074
- content: { text: 'hi', source: 'chat' },
1075
- },
1076
- {
1077
- id: '2',
1078
- entityId: 'e1',
1079
- roomId: 'r1',
1080
- createdAt: 2,
1081
- content: { text: 'there', source: 'chat' },
1082
- },
1083
- ] as any;
1084
- const entities = [{ id: 'e1', names: ['Alice'] }] as any;
1085
- const result = utils.formatPosts({ messages, entities });
1086
- expect(result).toContain('Conversation:');
1087
- expect(result).toContain('Alice');
1088
- expect(result).toContain('hi');
1089
- expect(result).toContain('there');
1090
- });
1091
-
1092
- it('trimTokens truncates using runtime tokenizer', async () => {
1093
- const runtime = {
1094
- useModel: mock(
1095
- async (type: (typeof ModelType)[keyof typeof ModelType], { prompt, tokens }: any) => {
1096
- if (type === ModelType.TEXT_TOKENIZER_ENCODE) return prompt.split(' ');
1097
- if (type === ModelType.TEXT_TOKENIZER_DECODE) return tokens.join(' ');
1098
- return [];
1099
- }
1100
- ),
1101
- } as any;
1102
- const result = await utils.trimTokens('a b c d e', 3, runtime);
1103
- expect(result).toBe('c d e');
1104
- });
1105
- });