@agentscope-ai/agentscope 0.0.2
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/dist/agent/index.d.mts +234 -0
- package/dist/agent/index.d.ts +234 -0
- package/dist/agent/index.js +1412 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/index.mjs +1375 -0
- package/dist/agent/index.mjs.map +1 -0
- package/dist/base-BOx3UzOl.d.mts +41 -0
- package/dist/base-BoIps2RL.d.ts +41 -0
- package/dist/base-C7jwyH4Z.d.mts +52 -0
- package/dist/base-Cwi4bjze.d.ts +127 -0
- package/dist/base-DYlBMCy_.d.mts +127 -0
- package/dist/base-NX-knWOv.d.ts +52 -0
- package/dist/block-VsnHrllL.d.mts +48 -0
- package/dist/block-VsnHrllL.d.ts +48 -0
- package/dist/event/index.d.mts +181 -0
- package/dist/event/index.d.ts +181 -0
- package/dist/event/index.js +58 -0
- package/dist/event/index.js.map +1 -0
- package/dist/event/index.mjs +33 -0
- package/dist/event/index.mjs.map +1 -0
- package/dist/formatter/index.d.mts +187 -0
- package/dist/formatter/index.d.ts +187 -0
- package/dist/formatter/index.js +647 -0
- package/dist/formatter/index.js.map +1 -0
- package/dist/formatter/index.mjs +616 -0
- package/dist/formatter/index.mjs.map +1 -0
- package/dist/index-BTJDlKvQ.d.mts +195 -0
- package/dist/index-BcatlwXQ.d.ts +195 -0
- package/dist/index-CAxQAkiP.d.mts +21 -0
- package/dist/index-CAxQAkiP.d.ts +21 -0
- package/dist/mcp/index.d.mts +9 -0
- package/dist/mcp/index.d.ts +9 -0
- package/dist/mcp/index.js +432 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/index.mjs +408 -0
- package/dist/mcp/index.mjs.map +1 -0
- package/dist/message/index.d.mts +10 -0
- package/dist/message/index.d.ts +10 -0
- package/dist/message/index.js +67 -0
- package/dist/message/index.js.map +1 -0
- package/dist/message/index.mjs +37 -0
- package/dist/message/index.mjs.map +1 -0
- package/dist/message-CkN21KaY.d.mts +99 -0
- package/dist/message-CzLeTlua.d.ts +99 -0
- package/dist/model/index.d.mts +377 -0
- package/dist/model/index.d.ts +377 -0
- package/dist/model/index.js +1880 -0
- package/dist/model/index.js.map +1 -0
- package/dist/model/index.mjs +1849 -0
- package/dist/model/index.mjs.map +1 -0
- package/dist/storage/index.d.mts +68 -0
- package/dist/storage/index.d.ts +68 -0
- package/dist/storage/index.js +250 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/index.mjs +212 -0
- package/dist/storage/index.mjs.map +1 -0
- package/dist/tool/index.d.mts +311 -0
- package/dist/tool/index.d.ts +311 -0
- package/dist/tool/index.js +1494 -0
- package/dist/tool/index.js.map +1 -0
- package/dist/tool/index.mjs +1447 -0
- package/dist/tool/index.mjs.map +1 -0
- package/dist/toolkit-CEpulFi0.d.ts +99 -0
- package/dist/toolkit-CGEZSZPa.d.mts +99 -0
- package/jest.config.js +11 -0
- package/package.json +92 -0
- package/src/_utils/common.ts +104 -0
- package/src/_utils/index.ts +1 -0
- package/src/agent/agent-base.ts +0 -0
- package/src/agent/agent.test.ts +1028 -0
- package/src/agent/agent.ts +1032 -0
- package/src/agent/index.ts +2 -0
- package/src/agent/interfaces.ts +23 -0
- package/src/agent/test-compression.ts +72 -0
- package/src/event/index.ts +250 -0
- package/src/formatter/base.ts +133 -0
- package/src/formatter/dashscope-chat-formatter.test.ts +372 -0
- package/src/formatter/dashscope-chat-formatter.ts +163 -0
- package/src/formatter/deepseek-chat-formatter.ts +130 -0
- package/src/formatter/index.ts +5 -0
- package/src/formatter/ollama-chat-formatter.ts +67 -0
- package/src/formatter/openai-chat-formatter.test.ts +263 -0
- package/src/formatter/openai-chat-formatter.ts +301 -0
- package/src/formatter/openai.md +767 -0
- package/src/mcp/base.ts +114 -0
- package/src/mcp/http.test.ts +303 -0
- package/src/mcp/http.ts +224 -0
- package/src/mcp/index.ts +2 -0
- package/src/mcp/stdio.test.ts +91 -0
- package/src/mcp/stdio.ts +119 -0
- package/src/message/block.ts +60 -0
- package/src/message/enums.ts +4 -0
- package/src/message/index.ts +12 -0
- package/src/message/message.test.ts +80 -0
- package/src/message/message.ts +131 -0
- package/src/model/base.ts +226 -0
- package/src/model/dashscope-model.test.ts +335 -0
- package/src/model/dashscope-model.ts +441 -0
- package/src/model/deepseek-model.test.ts +279 -0
- package/src/model/deepseek-model.ts +401 -0
- package/src/model/index.ts +7 -0
- package/src/model/ollama-model.test.ts +307 -0
- package/src/model/ollama-model.ts +356 -0
- package/src/model/openai-model.ts +327 -0
- package/src/model/response.ts +22 -0
- package/src/model/usage.ts +12 -0
- package/src/storage/base.ts +52 -0
- package/src/storage/file-system.test.ts +587 -0
- package/src/storage/file-system.ts +269 -0
- package/src/storage/index.ts +2 -0
- package/src/tool/base.ts +23 -0
- package/src/tool/bash.test.ts +174 -0
- package/src/tool/bash.ts +152 -0
- package/src/tool/edit.test.ts +83 -0
- package/src/tool/edit.ts +95 -0
- package/src/tool/glob.test.ts +63 -0
- package/src/tool/glob.ts +166 -0
- package/src/tool/grep.test.ts +74 -0
- package/src/tool/grep.ts +256 -0
- package/src/tool/index.ts +10 -0
- package/src/tool/read.test.ts +77 -0
- package/src/tool/read.ts +117 -0
- package/src/tool/response.ts +82 -0
- package/src/tool/task.test.ts +299 -0
- package/src/tool/task.ts +399 -0
- package/src/tool/toolkit.test.ts +636 -0
- package/src/tool/toolkit.ts +601 -0
- package/src/tool/write.test.ts +52 -0
- package/src/tool/write.ts +57 -0
- package/src/type/index.ts +52 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.cjs.json +11 -0
- package/tsconfig.esm.json +10 -0
- package/tsconfig.json +14 -0
- package/tsup.config.ts +20 -0
- package/typedoc.json +52 -0
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
import { LocalFileStorage } from './file-system';
|
|
5
|
+
import { createMsg } from '../message';
|
|
6
|
+
|
|
7
|
+
describe('LocalFileStorage', () => {
|
|
8
|
+
const testDir = path.join(__dirname, '.test-storage');
|
|
9
|
+
let storage: LocalFileStorage;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
// Clean up test directory before each test
|
|
13
|
+
if (fs.existsSync(testDir)) {
|
|
14
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
15
|
+
}
|
|
16
|
+
storage = new LocalFileStorage({ pathSegments: [testDir, 'test-user', 'test-session'] });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
// Clean up test directory after each test
|
|
21
|
+
if (fs.existsSync(testDir)) {
|
|
22
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('Basic save and load', () => {
|
|
27
|
+
it('should save and load agent state correctly', async () => {
|
|
28
|
+
const context = [
|
|
29
|
+
createMsg({
|
|
30
|
+
name: 'user',
|
|
31
|
+
content: [{ type: 'text', text: 'Hello', id: 'text-1' }],
|
|
32
|
+
role: 'user',
|
|
33
|
+
}),
|
|
34
|
+
createMsg({
|
|
35
|
+
name: 'assistant',
|
|
36
|
+
content: [{ type: 'text', text: 'Hi there!', id: 'text-2' }],
|
|
37
|
+
role: 'assistant',
|
|
38
|
+
}),
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const metadata = {
|
|
42
|
+
replyId: 'reply-123',
|
|
43
|
+
curIter: 1,
|
|
44
|
+
curSummary: '',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Save state
|
|
48
|
+
await storage.saveAgentState({
|
|
49
|
+
agentId: 'agent-1',
|
|
50
|
+
context,
|
|
51
|
+
metadata,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Load state
|
|
55
|
+
const loaded = await storage.loadAgentState({ agentId: 'agent-1' });
|
|
56
|
+
|
|
57
|
+
expect(loaded.context).toHaveLength(2);
|
|
58
|
+
expect(loaded.context[0].content[0]).toEqual({
|
|
59
|
+
type: 'text',
|
|
60
|
+
text: 'Hello',
|
|
61
|
+
id: 'text-1',
|
|
62
|
+
});
|
|
63
|
+
expect(loaded.context[1].content[0]).toEqual({
|
|
64
|
+
type: 'text',
|
|
65
|
+
text: 'Hi there!',
|
|
66
|
+
id: 'text-2',
|
|
67
|
+
});
|
|
68
|
+
expect(loaded.metadata.replyId).toBe('reply-123');
|
|
69
|
+
expect(loaded.metadata.curIter).toBe(1);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should return empty state when loading non-existent agent', async () => {
|
|
73
|
+
const loaded = await storage.loadAgentState({ agentId: 'non-existent' });
|
|
74
|
+
|
|
75
|
+
expect(loaded.context).toEqual([]);
|
|
76
|
+
expect(loaded.metadata).toEqual({});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should handle incremental context updates', async () => {
|
|
80
|
+
const msg1 = createMsg({
|
|
81
|
+
name: 'user',
|
|
82
|
+
content: [{ type: 'text', text: 'Message 1', id: 'text-3' }],
|
|
83
|
+
role: 'user',
|
|
84
|
+
});
|
|
85
|
+
const msg2 = createMsg({
|
|
86
|
+
name: 'assistant',
|
|
87
|
+
content: [{ type: 'text', text: 'Response 1', id: 'text-4' }],
|
|
88
|
+
role: 'assistant',
|
|
89
|
+
});
|
|
90
|
+
const msg3 = createMsg({
|
|
91
|
+
name: 'user',
|
|
92
|
+
content: [{ type: 'text', text: 'Message 2', id: 'text-5' }],
|
|
93
|
+
role: 'user',
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// First save
|
|
97
|
+
await storage.saveAgentState({
|
|
98
|
+
agentId: 'agent-1',
|
|
99
|
+
context: [msg1, msg2],
|
|
100
|
+
metadata: { replyId: 'reply-1' },
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Second save with additional message
|
|
104
|
+
await storage.saveAgentState({
|
|
105
|
+
agentId: 'agent-1',
|
|
106
|
+
context: [msg1, msg2, msg3],
|
|
107
|
+
metadata: { replyId: 'reply-2' },
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Load and verify all messages are present
|
|
111
|
+
const loaded = await storage.loadAgentState({ agentId: 'agent-1' });
|
|
112
|
+
expect(loaded.context).toHaveLength(3);
|
|
113
|
+
expect(loaded.context[2].content[0]).toEqual({
|
|
114
|
+
type: 'text',
|
|
115
|
+
text: 'Message 2',
|
|
116
|
+
id: 'text-5',
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('Compression support', () => {
|
|
122
|
+
it('should store full history but load only from compression boundary', async () => {
|
|
123
|
+
// Create 10 messages
|
|
124
|
+
const allMessages = Array.from({ length: 10 }, (_, i) =>
|
|
125
|
+
createMsg({
|
|
126
|
+
name: i % 2 === 0 ? 'user' : 'assistant',
|
|
127
|
+
content: [{ type: 'text', text: `Message ${i + 1}`, id: `text-${i + 6}` }],
|
|
128
|
+
role: i % 2 === 0 ? 'user' : 'assistant',
|
|
129
|
+
})
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// First save: all 10 messages
|
|
133
|
+
await storage.saveAgentState({
|
|
134
|
+
agentId: 'agent-1',
|
|
135
|
+
context: allMessages,
|
|
136
|
+
metadata: {
|
|
137
|
+
replyId: 'reply-1',
|
|
138
|
+
curIter: 5,
|
|
139
|
+
curSummary: '',
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Verify all messages are saved to file
|
|
144
|
+
const contextFile = path.join(
|
|
145
|
+
testDir,
|
|
146
|
+
'test-user',
|
|
147
|
+
'test-session',
|
|
148
|
+
'agent-1',
|
|
149
|
+
'context.jsonl'
|
|
150
|
+
);
|
|
151
|
+
const fileContent = fs.readFileSync(contextFile, 'utf-8');
|
|
152
|
+
const savedMessages = fileContent.trim().split('\n');
|
|
153
|
+
expect(savedMessages).toHaveLength(10);
|
|
154
|
+
|
|
155
|
+
// Simulate compression: agent only keeps last 3 messages
|
|
156
|
+
const compressedContext = allMessages.slice(7); // Messages 8, 9, 10
|
|
157
|
+
|
|
158
|
+
// Second save: only 3 messages (after compression)
|
|
159
|
+
await storage.saveAgentState({
|
|
160
|
+
agentId: 'agent-1',
|
|
161
|
+
context: compressedContext,
|
|
162
|
+
metadata: {
|
|
163
|
+
replyId: 'reply-2',
|
|
164
|
+
curIter: 6,
|
|
165
|
+
curSummary: '<system-info>Summary of messages 1-7</system-info>',
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Verify file still contains all 10 messages (full history preserved)
|
|
170
|
+
const updatedFileContent = fs.readFileSync(contextFile, 'utf-8');
|
|
171
|
+
const allSavedMessages = updatedFileContent.trim().split('\n');
|
|
172
|
+
expect(allSavedMessages).toHaveLength(10);
|
|
173
|
+
|
|
174
|
+
// Load state: should only get messages from compression boundary (last 3)
|
|
175
|
+
const loaded = await storage.loadAgentState({ agentId: 'agent-1' });
|
|
176
|
+
expect(loaded.context).toHaveLength(3);
|
|
177
|
+
expect(loaded.context[0].content[0]).toEqual({
|
|
178
|
+
type: 'text',
|
|
179
|
+
text: 'Message 8',
|
|
180
|
+
id: 'text-13',
|
|
181
|
+
});
|
|
182
|
+
expect(loaded.context[1].content[0]).toEqual({
|
|
183
|
+
type: 'text',
|
|
184
|
+
text: 'Message 9',
|
|
185
|
+
id: 'text-14',
|
|
186
|
+
});
|
|
187
|
+
expect(loaded.context[2].content[0]).toEqual({
|
|
188
|
+
type: 'text',
|
|
189
|
+
text: 'Message 10',
|
|
190
|
+
id: 'text-15',
|
|
191
|
+
});
|
|
192
|
+
expect(loaded.metadata.curSummary).toBe(
|
|
193
|
+
'<system-info>Summary of messages 1-7</system-info>'
|
|
194
|
+
);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should handle multiple compression cycles', async () => {
|
|
198
|
+
// First cycle: 5 messages
|
|
199
|
+
const batch1 = Array.from({ length: 5 }, (_, i) =>
|
|
200
|
+
createMsg({
|
|
201
|
+
name: 'user',
|
|
202
|
+
content: [
|
|
203
|
+
{ type: 'text', text: `Batch 1 Message ${i + 1}`, id: `text-${i + 16}` },
|
|
204
|
+
],
|
|
205
|
+
role: 'user',
|
|
206
|
+
})
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
await storage.saveAgentState({
|
|
210
|
+
agentId: 'agent-1',
|
|
211
|
+
context: batch1,
|
|
212
|
+
metadata: { replyId: 'reply-1' },
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// First compression: keep last 2
|
|
216
|
+
await storage.saveAgentState({
|
|
217
|
+
agentId: 'agent-1',
|
|
218
|
+
context: batch1.slice(3),
|
|
219
|
+
metadata: { replyId: 'reply-2', curSummary: 'Summary 1' },
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Add more messages
|
|
223
|
+
const batch2 = [
|
|
224
|
+
...batch1.slice(3),
|
|
225
|
+
...Array.from({ length: 5 }, (_, i) =>
|
|
226
|
+
createMsg({
|
|
227
|
+
name: 'user',
|
|
228
|
+
content: [
|
|
229
|
+
{
|
|
230
|
+
type: 'text',
|
|
231
|
+
text: `Batch 2 Message ${i + 1}`,
|
|
232
|
+
id: `text-${i + 21}`,
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
role: 'user',
|
|
236
|
+
})
|
|
237
|
+
),
|
|
238
|
+
];
|
|
239
|
+
|
|
240
|
+
await storage.saveAgentState({
|
|
241
|
+
agentId: 'agent-1',
|
|
242
|
+
context: batch2,
|
|
243
|
+
metadata: { replyId: 'reply-3' },
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Second compression: keep last 3
|
|
247
|
+
await storage.saveAgentState({
|
|
248
|
+
agentId: 'agent-1',
|
|
249
|
+
context: batch2.slice(-3),
|
|
250
|
+
metadata: { replyId: 'reply-4', curSummary: 'Summary 2' },
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Load and verify only last 3 messages are returned
|
|
254
|
+
const loaded = await storage.loadAgentState({ agentId: 'agent-1' });
|
|
255
|
+
expect(loaded.context).toHaveLength(3);
|
|
256
|
+
expect(loaded.context[0].content[0]).toEqual({
|
|
257
|
+
type: 'text',
|
|
258
|
+
text: 'Batch 2 Message 3',
|
|
259
|
+
id: 'text-23',
|
|
260
|
+
});
|
|
261
|
+
expect(loaded.metadata.curSummary).toBe('Summary 2');
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should not expose internal storage fields to agent layer', async () => {
|
|
265
|
+
const context = [
|
|
266
|
+
createMsg({
|
|
267
|
+
name: 'user',
|
|
268
|
+
content: [{ type: 'text', text: 'Test', id: 'text-26' }],
|
|
269
|
+
role: 'user',
|
|
270
|
+
}),
|
|
271
|
+
];
|
|
272
|
+
|
|
273
|
+
await storage.saveAgentState({
|
|
274
|
+
agentId: 'agent-1',
|
|
275
|
+
context,
|
|
276
|
+
metadata: { replyId: 'reply-1' },
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Check that internal field is saved in file
|
|
280
|
+
const stateFile = path.join(
|
|
281
|
+
testDir,
|
|
282
|
+
'test-user',
|
|
283
|
+
'test-session',
|
|
284
|
+
'agent-1',
|
|
285
|
+
'state.json'
|
|
286
|
+
);
|
|
287
|
+
const fileContent = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
|
|
288
|
+
expect(fileContent).toHaveProperty('_storage_compressionBoundaryMsgId');
|
|
289
|
+
|
|
290
|
+
// Check that internal field is filtered out when loading
|
|
291
|
+
const loaded = await storage.loadAgentState({ agentId: 'agent-1' });
|
|
292
|
+
expect(loaded.metadata).not.toHaveProperty('_storage_compressionBoundaryMsgId');
|
|
293
|
+
expect(loaded.metadata.replyId).toBe('reply-1');
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
describe('Multiple agents', () => {
|
|
298
|
+
it('should handle multiple agents independently', async () => {
|
|
299
|
+
const agent1Context = [
|
|
300
|
+
createMsg({
|
|
301
|
+
name: 'user',
|
|
302
|
+
content: [{ type: 'text', text: 'Agent 1 message', id: 'text-27' }],
|
|
303
|
+
role: 'user',
|
|
304
|
+
}),
|
|
305
|
+
];
|
|
306
|
+
const agent2Context = [
|
|
307
|
+
createMsg({
|
|
308
|
+
name: 'user',
|
|
309
|
+
content: [{ type: 'text', text: 'Agent 2 message', id: 'text-28' }],
|
|
310
|
+
role: 'user',
|
|
311
|
+
}),
|
|
312
|
+
];
|
|
313
|
+
|
|
314
|
+
await storage.saveAgentState({
|
|
315
|
+
agentId: 'agent-1',
|
|
316
|
+
context: agent1Context,
|
|
317
|
+
metadata: { replyId: 'reply-1' },
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
await storage.saveAgentState({
|
|
321
|
+
agentId: 'agent-2',
|
|
322
|
+
context: agent2Context,
|
|
323
|
+
metadata: { replyId: 'reply-2' },
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const loaded1 = await storage.loadAgentState({ agentId: 'agent-1' });
|
|
327
|
+
const loaded2 = await storage.loadAgentState({ agentId: 'agent-2' });
|
|
328
|
+
|
|
329
|
+
expect(loaded1.context[0].content[0]).toEqual({
|
|
330
|
+
type: 'text',
|
|
331
|
+
text: 'Agent 1 message',
|
|
332
|
+
id: 'text-27',
|
|
333
|
+
});
|
|
334
|
+
expect(loaded2.context[0].content[0]).toEqual({
|
|
335
|
+
type: 'text',
|
|
336
|
+
text: 'Agent 2 message',
|
|
337
|
+
id: 'text-28',
|
|
338
|
+
});
|
|
339
|
+
expect(loaded1.metadata.replyId).toBe('reply-1');
|
|
340
|
+
expect(loaded2.metadata.replyId).toBe('reply-2');
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
describe('Context offloading', () => {
|
|
345
|
+
it('should return undefined when offloadDir is not configured', async () => {
|
|
346
|
+
const msgs = [
|
|
347
|
+
createMsg({
|
|
348
|
+
name: 'user',
|
|
349
|
+
content: [{ type: 'text', text: 'Test message', id: 'text-29' }],
|
|
350
|
+
role: 'user',
|
|
351
|
+
}),
|
|
352
|
+
];
|
|
353
|
+
|
|
354
|
+
const result = await storage.offloadContext({ agentId: 'agent-1', msgs });
|
|
355
|
+
expect(result).toBeUndefined();
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('should offload text messages to a date-based file', async () => {
|
|
359
|
+
const offloadDir = path.join(testDir, 'offload');
|
|
360
|
+
const storageWithOffload = new LocalFileStorage({
|
|
361
|
+
pathSegments: [testDir, 'test-user', 'test-session'],
|
|
362
|
+
offloadPathSegments: [offloadDir, 'test-user', 'test-session'],
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const msgs = [
|
|
366
|
+
createMsg({
|
|
367
|
+
name: 'user',
|
|
368
|
+
content: [{ type: 'text', text: 'Hello world', id: 'text-30' }],
|
|
369
|
+
role: 'user',
|
|
370
|
+
}),
|
|
371
|
+
createMsg({
|
|
372
|
+
name: 'assistant',
|
|
373
|
+
content: [{ type: 'text', text: 'Hi there!', id: 'text-31' }],
|
|
374
|
+
role: 'assistant',
|
|
375
|
+
}),
|
|
376
|
+
];
|
|
377
|
+
|
|
378
|
+
const offloadPath = await storageWithOffload.offloadContext({
|
|
379
|
+
agentId: 'agent-1',
|
|
380
|
+
msgs,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
expect(offloadPath).toBeDefined();
|
|
384
|
+
expect(fs.existsSync(offloadPath!)).toBe(true);
|
|
385
|
+
|
|
386
|
+
const content = fs.readFileSync(offloadPath!, 'utf-8');
|
|
387
|
+
expect(content).toContain('user: Hello world');
|
|
388
|
+
expect(content).toContain('assistant: Hi there!');
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('should append to existing offload file on same day', async () => {
|
|
392
|
+
const offloadDir = path.join(testDir, 'offload');
|
|
393
|
+
const storageWithOffload = new LocalFileStorage({
|
|
394
|
+
pathSegments: [testDir, 'test-user', 'test-session'],
|
|
395
|
+
offloadPathSegments: [offloadDir, 'test-user', 'test-session'],
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
const msgs1 = [
|
|
399
|
+
createMsg({
|
|
400
|
+
name: 'user',
|
|
401
|
+
content: [{ type: 'text', text: 'First batch', id: 'text-32' }],
|
|
402
|
+
role: 'user',
|
|
403
|
+
}),
|
|
404
|
+
];
|
|
405
|
+
|
|
406
|
+
const msgs2 = [
|
|
407
|
+
createMsg({
|
|
408
|
+
name: 'user',
|
|
409
|
+
content: [{ type: 'text', text: 'Second batch', id: 'text-33' }],
|
|
410
|
+
role: 'user',
|
|
411
|
+
}),
|
|
412
|
+
];
|
|
413
|
+
|
|
414
|
+
const path1 = await storageWithOffload.offloadContext({
|
|
415
|
+
agentId: 'agent-1',
|
|
416
|
+
msgs: msgs1,
|
|
417
|
+
});
|
|
418
|
+
const path2 = await storageWithOffload.offloadContext({
|
|
419
|
+
agentId: 'agent-1',
|
|
420
|
+
msgs: msgs2,
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
expect(path1).toBe(path2);
|
|
424
|
+
|
|
425
|
+
const content = fs.readFileSync(path1!, 'utf-8');
|
|
426
|
+
expect(content).toContain('First batch');
|
|
427
|
+
expect(content).toContain('Second batch');
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('should handle data blocks with URL source', async () => {
|
|
431
|
+
const offloadDir = path.join(testDir, 'offload');
|
|
432
|
+
const storageWithOffload = new LocalFileStorage({
|
|
433
|
+
pathSegments: [testDir, 'test-user', 'test-session'],
|
|
434
|
+
offloadPathSegments: [offloadDir, 'test-user', 'test-session'],
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const msgs = [
|
|
438
|
+
createMsg({
|
|
439
|
+
name: 'user',
|
|
440
|
+
content: [
|
|
441
|
+
{
|
|
442
|
+
type: 'data',
|
|
443
|
+
id: 'data-1',
|
|
444
|
+
source: {
|
|
445
|
+
type: 'url',
|
|
446
|
+
url: 'https://example.com/image.png',
|
|
447
|
+
mediaType: 'image/png',
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
],
|
|
451
|
+
role: 'user',
|
|
452
|
+
}),
|
|
453
|
+
];
|
|
454
|
+
|
|
455
|
+
const offloadPath = await storageWithOffload.offloadContext({
|
|
456
|
+
agentId: 'agent-1',
|
|
457
|
+
msgs,
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
const content = fs.readFileSync(offloadPath!, 'utf-8');
|
|
461
|
+
expect(content).toContain(
|
|
462
|
+
'<data src={https://example.com/image.png} type={image/png} />'
|
|
463
|
+
);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it('should handle data blocks with base64 source', async () => {
|
|
467
|
+
const offloadDir = path.join(testDir, 'offload');
|
|
468
|
+
const storageWithOffload = new LocalFileStorage({
|
|
469
|
+
pathSegments: [testDir, 'test-user', 'test-session'],
|
|
470
|
+
offloadPathSegments: [offloadDir, 'test-user', 'test-session'],
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// Create a simple base64 encoded text
|
|
474
|
+
const base64Data = Buffer.from('Hello, World!').toString('base64');
|
|
475
|
+
|
|
476
|
+
const msgs = [
|
|
477
|
+
createMsg({
|
|
478
|
+
name: 'user',
|
|
479
|
+
content: [
|
|
480
|
+
{
|
|
481
|
+
type: 'data',
|
|
482
|
+
id: 'data-2',
|
|
483
|
+
source: {
|
|
484
|
+
type: 'base64',
|
|
485
|
+
data: base64Data,
|
|
486
|
+
mediaType: 'text/plain',
|
|
487
|
+
},
|
|
488
|
+
},
|
|
489
|
+
],
|
|
490
|
+
role: 'user',
|
|
491
|
+
}),
|
|
492
|
+
];
|
|
493
|
+
|
|
494
|
+
const offloadPath = await storageWithOffload.offloadContext({
|
|
495
|
+
agentId: 'agent-1',
|
|
496
|
+
msgs,
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
const content = fs.readFileSync(offloadPath!, 'utf-8');
|
|
500
|
+
expect(content).toMatch(/user: <data src=\{.*\.txt\} type=\{text\/plain\} \/>/);
|
|
501
|
+
|
|
502
|
+
// Verify the data file was created
|
|
503
|
+
const dataDir = path.join(offloadDir, 'test-user', 'test-session', 'agent-1', 'data');
|
|
504
|
+
expect(fs.existsSync(dataDir)).toBe(true);
|
|
505
|
+
|
|
506
|
+
const dataFiles = fs.readdirSync(dataDir);
|
|
507
|
+
expect(dataFiles.length).toBeGreaterThan(0);
|
|
508
|
+
expect(dataFiles[0]).toMatch(/text-\d+\.txt/);
|
|
509
|
+
|
|
510
|
+
// Verify the content of the data file
|
|
511
|
+
const dataFilePath = path.join(dataDir, dataFiles[0]);
|
|
512
|
+
const dataContent = fs.readFileSync(dataFilePath, 'utf-8');
|
|
513
|
+
expect(dataContent).toBe('Hello, World!');
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
it('should handle tool_call blocks', async () => {
|
|
517
|
+
const offloadDir = path.join(testDir, 'offload');
|
|
518
|
+
const storageWithOffload = new LocalFileStorage({
|
|
519
|
+
pathSegments: [testDir, 'test-user', 'test-session'],
|
|
520
|
+
offloadPathSegments: [offloadDir, 'test-user', 'test-session'],
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
const msgs = [
|
|
524
|
+
createMsg({
|
|
525
|
+
name: 'assistant',
|
|
526
|
+
content: [
|
|
527
|
+
{
|
|
528
|
+
type: 'tool_call',
|
|
529
|
+
id: 'call-123',
|
|
530
|
+
name: 'search',
|
|
531
|
+
input: JSON.stringify({ query: 'test' }),
|
|
532
|
+
},
|
|
533
|
+
],
|
|
534
|
+
role: 'assistant',
|
|
535
|
+
}),
|
|
536
|
+
];
|
|
537
|
+
|
|
538
|
+
const offloadPath = await storageWithOffload.offloadContext({
|
|
539
|
+
agentId: 'agent-1',
|
|
540
|
+
msgs,
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
const content = fs.readFileSync(offloadPath!, 'utf-8');
|
|
544
|
+
expect(content).toContain('assistant: Calling tool search ...');
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it('should handle mixed content types in a single message', async () => {
|
|
548
|
+
const offloadDir = path.join(testDir, 'offload');
|
|
549
|
+
const storageWithOffload = new LocalFileStorage({
|
|
550
|
+
pathSegments: [testDir, 'test-user', 'test-session'],
|
|
551
|
+
offloadPathSegments: [offloadDir, 'test-user', 'test-session'],
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
const msgs = [
|
|
555
|
+
createMsg({
|
|
556
|
+
name: 'user',
|
|
557
|
+
content: [
|
|
558
|
+
{ type: 'text', text: 'Here is an image:', id: 'text-34' },
|
|
559
|
+
{
|
|
560
|
+
type: 'data',
|
|
561
|
+
id: 'data-3',
|
|
562
|
+
source: {
|
|
563
|
+
type: 'url',
|
|
564
|
+
url: 'https://example.com/image.png',
|
|
565
|
+
mediaType: 'image/png',
|
|
566
|
+
},
|
|
567
|
+
},
|
|
568
|
+
{ type: 'text', text: 'What do you see?', id: 'text-35' },
|
|
569
|
+
],
|
|
570
|
+
role: 'user',
|
|
571
|
+
}),
|
|
572
|
+
];
|
|
573
|
+
|
|
574
|
+
const offloadPath = await storageWithOffload.offloadContext({
|
|
575
|
+
agentId: 'agent-1',
|
|
576
|
+
msgs,
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
const content = fs.readFileSync(offloadPath!, 'utf-8');
|
|
580
|
+
expect(content).toContain('user: Here is an image:');
|
|
581
|
+
expect(content).toContain(
|
|
582
|
+
'<data src={https://example.com/image.png} type={image/png} />'
|
|
583
|
+
);
|
|
584
|
+
expect(content).toContain('user: What do you see?');
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
});
|