@strands-agents/sdk 1.1.0 → 1.2.0
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 +6 -0
- package/dist/src/__tests__/interrupt.test.js +13 -8
- package/dist/src/__tests__/interrupt.test.js.map +1 -1
- package/dist/src/__tests__/mcp.test.js +221 -7
- package/dist/src/__tests__/mcp.test.js.map +1 -1
- package/dist/src/agent/__tests__/agent.interrupt.test.js +54 -5
- package/dist/src/agent/__tests__/agent.interrupt.test.js.map +1 -1
- package/dist/src/agent/__tests__/agent.test.js +56 -0
- package/dist/src/agent/__tests__/agent.test.js.map +1 -1
- package/dist/src/agent/__tests__/snapshot.test.js +98 -0
- package/dist/src/agent/__tests__/snapshot.test.js.map +1 -1
- package/dist/src/agent/agent-as-tool.d.ts.map +1 -1
- package/dist/src/agent/agent-as-tool.js +2 -3
- package/dist/src/agent/agent-as-tool.js.map +1 -1
- package/dist/src/agent/agent.d.ts +59 -0
- package/dist/src/agent/agent.d.ts.map +1 -1
- package/dist/src/agent/agent.js +86 -10
- package/dist/src/agent/agent.js.map +1 -1
- package/dist/src/agent/snapshot.d.ts +9 -17
- package/dist/src/agent/snapshot.d.ts.map +1 -1
- package/dist/src/agent/snapshot.js +9 -17
- package/dist/src/agent/snapshot.js.map +1 -1
- package/dist/src/conversation-manager/__tests__/sliding-window-conversation-manager.test.js +371 -39
- package/dist/src/conversation-manager/__tests__/sliding-window-conversation-manager.test.js.map +1 -1
- package/dist/src/conversation-manager/sliding-window-conversation-manager.d.ts +26 -7
- package/dist/src/conversation-manager/sliding-window-conversation-manager.d.ts.map +1 -1
- package/dist/src/conversation-manager/sliding-window-conversation-manager.js +192 -41
- package/dist/src/conversation-manager/sliding-window-conversation-manager.js.map +1 -1
- package/dist/src/hooks/events.d.ts +24 -3
- package/dist/src/hooks/events.d.ts.map +1 -1
- package/dist/src/hooks/events.js +26 -4
- package/dist/src/hooks/events.js.map +1 -1
- package/dist/src/hooks/index.d.ts +1 -1
- package/dist/src/hooks/index.d.ts.map +1 -1
- package/dist/src/hooks/index.js +1 -1
- package/dist/src/hooks/index.js.map +1 -1
- package/dist/src/index.d.ts +6 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/interrupt.d.ts +29 -2
- package/dist/src/interrupt.d.ts.map +1 -1
- package/dist/src/interrupt.js +45 -3
- package/dist/src/interrupt.js.map +1 -1
- package/dist/src/mcp.d.ts +38 -2
- package/dist/src/mcp.d.ts.map +1 -1
- package/dist/src/mcp.js +84 -7
- package/dist/src/mcp.js.map +1 -1
- package/dist/src/models/__tests__/anthropic.test.js +23 -8
- package/dist/src/models/__tests__/anthropic.test.js.map +1 -1
- package/dist/src/models/__tests__/bedrock.test.js +43 -20
- package/dist/src/models/__tests__/bedrock.test.js.map +1 -1
- package/dist/src/models/__tests__/google.test.js +14 -6
- package/dist/src/models/__tests__/google.test.js.map +1 -1
- package/dist/src/models/anthropic.d.ts +5 -3
- package/dist/src/models/anthropic.d.ts.map +1 -1
- package/dist/src/models/anthropic.js +11 -3
- package/dist/src/models/anthropic.js.map +1 -1
- package/dist/src/models/bedrock.d.ts +6 -7
- package/dist/src/models/bedrock.d.ts.map +1 -1
- package/dist/src/models/bedrock.js +31 -18
- package/dist/src/models/bedrock.js.map +1 -1
- package/dist/src/models/google/model.js +1 -1
- package/dist/src/models/google/model.js.map +1 -1
- package/dist/src/models/google/types.d.ts +5 -3
- package/dist/src/models/google/types.d.ts.map +1 -1
- package/dist/src/models/openai/__tests__/chat.test.js +10 -2
- package/dist/src/models/openai/__tests__/chat.test.js.map +1 -1
- package/dist/src/models/openai/__tests__/responses.test.js +19 -0
- package/dist/src/models/openai/__tests__/responses.test.js.map +1 -1
- package/dist/src/models/openai/errors.d.ts.map +1 -1
- package/dist/src/models/openai/errors.js +7 -4
- package/dist/src/models/openai/errors.js.map +1 -1
- package/dist/src/multiagent/__tests__/graph.tracer.test.js +14 -0
- package/dist/src/multiagent/__tests__/graph.tracer.test.js.map +1 -1
- package/dist/src/multiagent/__tests__/interrupts.test.d.ts +2 -0
- package/dist/src/multiagent/__tests__/interrupts.test.d.ts.map +1 -0
- package/dist/src/multiagent/__tests__/interrupts.test.js +390 -0
- package/dist/src/multiagent/__tests__/interrupts.test.js.map +1 -0
- package/dist/src/multiagent/__tests__/state.test.js +139 -1
- package/dist/src/multiagent/__tests__/state.test.js.map +1 -1
- package/dist/src/multiagent/events.d.ts +15 -1
- package/dist/src/multiagent/events.d.ts.map +1 -1
- package/dist/src/multiagent/events.js +18 -0
- package/dist/src/multiagent/events.js.map +1 -1
- package/dist/src/multiagent/graph.d.ts +37 -1
- package/dist/src/multiagent/graph.d.ts.map +1 -1
- package/dist/src/multiagent/graph.js +171 -43
- package/dist/src/multiagent/graph.js.map +1 -1
- package/dist/src/multiagent/multiagent.d.ts +78 -6
- package/dist/src/multiagent/multiagent.d.ts.map +1 -1
- package/dist/src/multiagent/multiagent.js +115 -1
- package/dist/src/multiagent/multiagent.js.map +1 -1
- package/dist/src/multiagent/nodes.d.ts.map +1 -1
- package/dist/src/multiagent/nodes.js +55 -21
- package/dist/src/multiagent/nodes.js.map +1 -1
- package/dist/src/multiagent/state.d.ts +39 -3
- package/dist/src/multiagent/state.d.ts.map +1 -1
- package/dist/src/multiagent/state.js +80 -1
- package/dist/src/multiagent/state.js.map +1 -1
- package/dist/src/multiagent/swarm.d.ts +15 -0
- package/dist/src/multiagent/swarm.d.ts.map +1 -1
- package/dist/src/multiagent/swarm.js +127 -37
- package/dist/src/multiagent/swarm.js.map +1 -1
- package/dist/src/registry/__tests__/tool-registry.test.js +26 -0
- package/dist/src/registry/__tests__/tool-registry.test.js.map +1 -1
- package/dist/src/registry/tool-registry.d.ts +9 -7
- package/dist/src/registry/tool-registry.d.ts.map +1 -1
- package/dist/src/registry/tool-registry.js +29 -10
- package/dist/src/registry/tool-registry.js.map +1 -1
- package/dist/src/session/__tests__/session-manager.test.js +45 -3
- package/dist/src/session/__tests__/session-manager.test.js.map +1 -1
- package/dist/src/session/session-manager.d.ts +5 -2
- package/dist/src/session/session-manager.d.ts.map +1 -1
- package/dist/src/session/session-manager.js +9 -6
- package/dist/src/session/session-manager.js.map +1 -1
- package/dist/src/telemetry/__tests__/meter.test.js +5 -27
- package/dist/src/telemetry/__tests__/meter.test.js.map +1 -1
- package/dist/src/telemetry/meter.d.ts +12 -4
- package/dist/src/telemetry/meter.d.ts.map +1 -1
- package/dist/src/telemetry/meter.js +13 -8
- package/dist/src/telemetry/meter.js.map +1 -1
- package/dist/src/tools/mcp-tool.d.ts.map +1 -1
- package/dist/src/tools/mcp-tool.js +3 -2
- package/dist/src/tools/mcp-tool.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/src/types/__tests__/agent.test.js +97 -0
- package/dist/src/types/__tests__/agent.test.js.map +1 -1
- package/dist/src/types/agent.d.ts +26 -5
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/agent.js +20 -3
- package/dist/src/types/agent.js.map +1 -1
- package/dist/src/vended-tools/notebook/notebook.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
2
|
import { SlidingWindowConversationManager } from '../sliding-window-conversation-manager.js';
|
|
3
|
-
import { ContextWindowOverflowError, Message, TextBlock, ToolUseBlock, ToolResultBlock, } from '../../index.js';
|
|
3
|
+
import { ContextWindowOverflowError, DocumentBlock, ImageBlock, JsonBlock, Message, TextBlock, ToolUseBlock, ToolResultBlock, VideoBlock, } from '../../index.js';
|
|
4
4
|
import { AfterInvocationEvent, AfterModelCallEvent, BeforeModelCallEvent } from '../../hooks/events.js';
|
|
5
5
|
import { createMockAgent, invokeTrackedHook } from '../../__fixtures__/agent-helpers.js';
|
|
6
6
|
async function triggerSlidingWindow(manager, agent) {
|
|
@@ -46,7 +46,7 @@ describe('SlidingWindowConversationManager', () => {
|
|
|
46
46
|
new ToolResultBlock({
|
|
47
47
|
toolUseId: 'tool-1',
|
|
48
48
|
status: 'success',
|
|
49
|
-
content: [new TextBlock('
|
|
49
|
+
content: [new TextBlock('x'.repeat(500))],
|
|
50
50
|
}),
|
|
51
51
|
],
|
|
52
52
|
}),
|
|
@@ -123,8 +123,10 @@ describe('SlidingWindowConversationManager', () => {
|
|
|
123
123
|
});
|
|
124
124
|
});
|
|
125
125
|
describe('reduceContext - tool result truncation', () => {
|
|
126
|
-
it('truncates tool results
|
|
126
|
+
it('partially truncates large tool results preserving first and last 200 chars', async () => {
|
|
127
127
|
const manager = new SlidingWindowConversationManager({ shouldTruncateResults: true });
|
|
128
|
+
const middle = 'MIDDLE_CONTENT_TO_REMOVE'.repeat(10); // 240 chars, safely above MIN_TRUNCATION_GAIN
|
|
129
|
+
const original = 'A'.repeat(200) + middle + 'B'.repeat(200);
|
|
128
130
|
const messages = [
|
|
129
131
|
new Message({
|
|
130
132
|
role: 'user',
|
|
@@ -132,19 +134,41 @@ describe('SlidingWindowConversationManager', () => {
|
|
|
132
134
|
new ToolResultBlock({
|
|
133
135
|
toolUseId: 'tool-1',
|
|
134
136
|
status: 'success',
|
|
135
|
-
content: [new TextBlock(
|
|
137
|
+
content: [new TextBlock(original)],
|
|
136
138
|
}),
|
|
137
139
|
],
|
|
138
140
|
}),
|
|
139
141
|
];
|
|
140
142
|
const mockAgent = createMockAgent({ messages });
|
|
141
143
|
await triggerContextOverflow(manager, mockAgent, new ContextWindowOverflowError('Context overflow'));
|
|
142
|
-
const
|
|
143
|
-
expect(
|
|
144
|
-
|
|
144
|
+
const expectedText = `${'A'.repeat(200)}\n<truncated chars="${middle.length}"/>\n${'B'.repeat(200)}`;
|
|
145
|
+
expect(messages[0].content[0]).toEqual(new ToolResultBlock({
|
|
146
|
+
toolUseId: 'tool-1',
|
|
147
|
+
status: 'success',
|
|
148
|
+
content: [new TextBlock(expectedText)],
|
|
149
|
+
}));
|
|
145
150
|
});
|
|
146
|
-
it('
|
|
151
|
+
it('leaves small tool results unchanged', () => {
|
|
147
152
|
const manager = new SlidingWindowConversationManager({ shouldTruncateResults: true });
|
|
153
|
+
const messages = [
|
|
154
|
+
new Message({
|
|
155
|
+
role: 'user',
|
|
156
|
+
content: [
|
|
157
|
+
new ToolResultBlock({
|
|
158
|
+
toolUseId: 'tool-1',
|
|
159
|
+
status: 'success',
|
|
160
|
+
content: [new TextBlock('Small result')],
|
|
161
|
+
}),
|
|
162
|
+
],
|
|
163
|
+
}),
|
|
164
|
+
];
|
|
165
|
+
const result = manager._truncateToolResults(messages, 0);
|
|
166
|
+
expect(result).toBe(false);
|
|
167
|
+
});
|
|
168
|
+
it('finds oldest message with tool results', async () => {
|
|
169
|
+
const manager = new SlidingWindowConversationManager({ shouldTruncateResults: true });
|
|
170
|
+
const firstOriginal = 'F'.repeat(500);
|
|
171
|
+
const secondOriginal = 'S'.repeat(500);
|
|
148
172
|
const messages = [
|
|
149
173
|
new Message({ role: 'user', content: [new TextBlock('Message 1')] }),
|
|
150
174
|
new Message({
|
|
@@ -153,7 +177,7 @@ describe('SlidingWindowConversationManager', () => {
|
|
|
153
177
|
new ToolResultBlock({
|
|
154
178
|
toolUseId: 'tool-1',
|
|
155
179
|
status: 'success',
|
|
156
|
-
content: [new TextBlock(
|
|
180
|
+
content: [new TextBlock(firstOriginal)],
|
|
157
181
|
}),
|
|
158
182
|
],
|
|
159
183
|
}),
|
|
@@ -164,21 +188,25 @@ describe('SlidingWindowConversationManager', () => {
|
|
|
164
188
|
new ToolResultBlock({
|
|
165
189
|
toolUseId: 'tool-2',
|
|
166
190
|
status: 'success',
|
|
167
|
-
content: [new TextBlock(
|
|
191
|
+
content: [new TextBlock(secondOriginal)],
|
|
168
192
|
}),
|
|
169
193
|
],
|
|
170
194
|
}),
|
|
171
195
|
];
|
|
172
196
|
const mockAgent = createMockAgent({ messages });
|
|
173
197
|
await triggerContextOverflow(manager, mockAgent, new ContextWindowOverflowError('Context overflow'));
|
|
174
|
-
//
|
|
175
|
-
const
|
|
176
|
-
expect(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
expect(
|
|
198
|
+
// Oldest tool-result message is truncated; newer one is untouched.
|
|
199
|
+
const expectedTruncated = `${'F'.repeat(200)}\n<truncated chars="100"/>\n${'F'.repeat(200)}`;
|
|
200
|
+
expect(messages[1].content[0]).toEqual(new ToolResultBlock({
|
|
201
|
+
toolUseId: 'tool-1',
|
|
202
|
+
status: 'success',
|
|
203
|
+
content: [new TextBlock(expectedTruncated)],
|
|
204
|
+
}));
|
|
205
|
+
expect(messages[3].content[0]).toEqual(new ToolResultBlock({
|
|
206
|
+
toolUseId: 'tool-2',
|
|
207
|
+
status: 'success',
|
|
208
|
+
content: [new TextBlock(secondOriginal)],
|
|
209
|
+
}));
|
|
182
210
|
});
|
|
183
211
|
it('returns after successful truncation without trimming messages', async () => {
|
|
184
212
|
const manager = new SlidingWindowConversationManager({ windowSize: 2, shouldTruncateResults: true });
|
|
@@ -191,7 +219,7 @@ describe('SlidingWindowConversationManager', () => {
|
|
|
191
219
|
new ToolResultBlock({
|
|
192
220
|
toolUseId: 'tool-1',
|
|
193
221
|
status: 'success',
|
|
194
|
-
content: [new TextBlock('
|
|
222
|
+
content: [new TextBlock('L'.repeat(500))],
|
|
195
223
|
}),
|
|
196
224
|
],
|
|
197
225
|
}),
|
|
@@ -217,7 +245,7 @@ describe('SlidingWindowConversationManager', () => {
|
|
|
217
245
|
new ToolResultBlock({
|
|
218
246
|
toolUseId: 'tool-1',
|
|
219
247
|
status: 'success',
|
|
220
|
-
content: [new TextBlock('
|
|
248
|
+
content: [new TextBlock('L'.repeat(500))],
|
|
221
249
|
}),
|
|
222
250
|
],
|
|
223
251
|
}),
|
|
@@ -228,24 +256,30 @@ describe('SlidingWindowConversationManager', () => {
|
|
|
228
256
|
expect(mockAgent.messages).toHaveLength(3);
|
|
229
257
|
expect(mockAgent.messages[0].role).toBe('user');
|
|
230
258
|
// Tool result should not be truncated
|
|
231
|
-
|
|
232
|
-
|
|
259
|
+
expect(mockAgent.messages[2].content[0]).toEqual(new ToolResultBlock({
|
|
260
|
+
toolUseId: 'tool-1',
|
|
261
|
+
status: 'success',
|
|
262
|
+
content: [new TextBlock('L'.repeat(500))],
|
|
263
|
+
}));
|
|
233
264
|
});
|
|
234
|
-
it('does not truncate already-truncated results', async () => {
|
|
265
|
+
it('does not re-truncate already-truncated results', async () => {
|
|
235
266
|
const manager = new SlidingWindowConversationManager({ shouldTruncateResults: true });
|
|
267
|
+
// Produced by an earlier run: 200 chars + marker + 200 chars = well under the 450-char
|
|
268
|
+
// threshold below which truncation is not worth running.
|
|
269
|
+
const alreadyTruncated = 'A'.repeat(200) + '\n<truncated chars="1000"/>\n' + 'B'.repeat(200);
|
|
236
270
|
const messages = [
|
|
237
271
|
new Message({
|
|
238
272
|
role: 'user',
|
|
239
273
|
content: [
|
|
240
274
|
new ToolResultBlock({
|
|
241
275
|
toolUseId: 'tool-1',
|
|
242
|
-
status: '
|
|
243
|
-
content: [new TextBlock(
|
|
276
|
+
status: 'success',
|
|
277
|
+
content: [new TextBlock(alreadyTruncated)],
|
|
244
278
|
}),
|
|
245
279
|
],
|
|
246
280
|
}),
|
|
247
281
|
];
|
|
248
|
-
// First call should return false (
|
|
282
|
+
// First call should return false (too short to gain anything from re-truncating)
|
|
249
283
|
const result = manager._truncateToolResults(messages, 0);
|
|
250
284
|
expect(result).toBe(false);
|
|
251
285
|
// reduceContext should fall through to message trimming
|
|
@@ -255,19 +289,316 @@ describe('SlidingWindowConversationManager', () => {
|
|
|
255
289
|
content: [
|
|
256
290
|
new ToolResultBlock({
|
|
257
291
|
toolUseId: 'tool-1',
|
|
258
|
-
status: '
|
|
259
|
-
content: [new TextBlock(
|
|
292
|
+
status: 'success',
|
|
293
|
+
content: [new TextBlock(alreadyTruncated)],
|
|
260
294
|
}),
|
|
261
295
|
],
|
|
262
296
|
}),
|
|
263
297
|
new Message({ role: 'assistant', content: [new TextBlock('Response')] }),
|
|
264
298
|
new Message({ role: 'user', content: [new TextBlock('Message')] }),
|
|
265
299
|
];
|
|
266
|
-
const mockAgent = { messages: messages2 };
|
|
300
|
+
const mockAgent = createMockAgent({ messages: messages2 });
|
|
267
301
|
await triggerContextOverflow(manager, mockAgent, new ContextWindowOverflowError('Context overflow'));
|
|
268
302
|
// Should have trimmed messages since truncation was skipped
|
|
269
303
|
expect(mockAgent.messages.length).toBeLessThan(3);
|
|
270
304
|
});
|
|
305
|
+
it('replaces image blocks nested in tool results with descriptive placeholders', () => {
|
|
306
|
+
const manager = new SlidingWindowConversationManager({ shouldTruncateResults: true });
|
|
307
|
+
const bytes = new Uint8Array(1234);
|
|
308
|
+
const messages = [
|
|
309
|
+
new Message({
|
|
310
|
+
role: 'user',
|
|
311
|
+
content: [
|
|
312
|
+
new ToolResultBlock({
|
|
313
|
+
toolUseId: 'tool-1',
|
|
314
|
+
status: 'success',
|
|
315
|
+
content: [new ImageBlock({ format: 'png', source: { bytes } }), new TextBlock('tail')],
|
|
316
|
+
}),
|
|
317
|
+
],
|
|
318
|
+
}),
|
|
319
|
+
];
|
|
320
|
+
const changed = manager._truncateToolResults(messages, 0);
|
|
321
|
+
expect(changed).toBe(true);
|
|
322
|
+
expect(messages[0].content[0]).toEqual(new ToolResultBlock({
|
|
323
|
+
toolUseId: 'tool-1',
|
|
324
|
+
status: 'success',
|
|
325
|
+
content: [new TextBlock('[image: png, source: bytes, 1234 bytes]'), new TextBlock('tail')],
|
|
326
|
+
}));
|
|
327
|
+
});
|
|
328
|
+
it('preserves the error field on truncated tool results', () => {
|
|
329
|
+
const manager = new SlidingWindowConversationManager({ shouldTruncateResults: true });
|
|
330
|
+
const originalError = new Error('tool blew up');
|
|
331
|
+
const messages = [
|
|
332
|
+
new Message({
|
|
333
|
+
role: 'user',
|
|
334
|
+
content: [
|
|
335
|
+
new ToolResultBlock({
|
|
336
|
+
toolUseId: 'tool-1',
|
|
337
|
+
status: 'error',
|
|
338
|
+
content: [new TextBlock('x'.repeat(500))],
|
|
339
|
+
error: originalError,
|
|
340
|
+
}),
|
|
341
|
+
],
|
|
342
|
+
}),
|
|
343
|
+
];
|
|
344
|
+
const changed = manager._truncateToolResults(messages, 0);
|
|
345
|
+
expect(changed).toBe(true);
|
|
346
|
+
const expectedText = `${'x'.repeat(200)}\n<truncated chars="100"/>\n${'x'.repeat(200)}`;
|
|
347
|
+
expect(messages[0].content[0]).toEqual(new ToolResultBlock({
|
|
348
|
+
toolUseId: 'tool-1',
|
|
349
|
+
status: 'error',
|
|
350
|
+
content: [new TextBlock(expectedText)],
|
|
351
|
+
error: originalError,
|
|
352
|
+
}));
|
|
353
|
+
});
|
|
354
|
+
it('image placeholder reflects non-bytes source kinds honestly', () => {
|
|
355
|
+
const manager = new SlidingWindowConversationManager({ shouldTruncateResults: true });
|
|
356
|
+
const messages = [
|
|
357
|
+
new Message({
|
|
358
|
+
role: 'user',
|
|
359
|
+
content: [
|
|
360
|
+
new ToolResultBlock({
|
|
361
|
+
toolUseId: 'tool-1',
|
|
362
|
+
status: 'success',
|
|
363
|
+
content: [
|
|
364
|
+
new ImageBlock({ format: 'jpeg', source: { url: 'https://example.com/x.jpg' } }),
|
|
365
|
+
new ImageBlock({ format: 'png', source: { location: { type: 's3', uri: 's3://bucket/key' } } }),
|
|
366
|
+
],
|
|
367
|
+
}),
|
|
368
|
+
],
|
|
369
|
+
}),
|
|
370
|
+
];
|
|
371
|
+
manager._truncateToolResults(messages, 0);
|
|
372
|
+
expect(messages[0].content[0]).toEqual(new ToolResultBlock({
|
|
373
|
+
toolUseId: 'tool-1',
|
|
374
|
+
status: 'success',
|
|
375
|
+
content: [new TextBlock('[image: jpeg, source: url]'), new TextBlock('[image: png, source: s3]')],
|
|
376
|
+
}));
|
|
377
|
+
});
|
|
378
|
+
it('replaces video bytes blocks with a descriptive placeholder', () => {
|
|
379
|
+
const manager = new SlidingWindowConversationManager({ shouldTruncateResults: true });
|
|
380
|
+
const messages = [
|
|
381
|
+
new Message({
|
|
382
|
+
role: 'user',
|
|
383
|
+
content: [
|
|
384
|
+
new ToolResultBlock({
|
|
385
|
+
toolUseId: 'tool-1',
|
|
386
|
+
status: 'success',
|
|
387
|
+
content: [new VideoBlock({ format: 'mp4', source: { bytes: new Uint8Array(4096) } })],
|
|
388
|
+
}),
|
|
389
|
+
],
|
|
390
|
+
}),
|
|
391
|
+
];
|
|
392
|
+
const changed = manager._truncateToolResults(messages, 0);
|
|
393
|
+
expect(changed).toBe(true);
|
|
394
|
+
expect(messages[0].content[0]).toEqual(new ToolResultBlock({
|
|
395
|
+
toolUseId: 'tool-1',
|
|
396
|
+
status: 'success',
|
|
397
|
+
content: [new TextBlock('[video: mp4, source: bytes, 4096 bytes]')],
|
|
398
|
+
}));
|
|
399
|
+
});
|
|
400
|
+
it('replaces video s3 blocks with a descriptive placeholder', () => {
|
|
401
|
+
const manager = new SlidingWindowConversationManager({ shouldTruncateResults: true });
|
|
402
|
+
const messages = [
|
|
403
|
+
new Message({
|
|
404
|
+
role: 'user',
|
|
405
|
+
content: [
|
|
406
|
+
new ToolResultBlock({
|
|
407
|
+
toolUseId: 'tool-1',
|
|
408
|
+
status: 'success',
|
|
409
|
+
content: [
|
|
410
|
+
new VideoBlock({
|
|
411
|
+
format: 'mp4',
|
|
412
|
+
source: { location: { type: 's3', uri: 's3://bucket/key' } },
|
|
413
|
+
}),
|
|
414
|
+
],
|
|
415
|
+
}),
|
|
416
|
+
],
|
|
417
|
+
}),
|
|
418
|
+
];
|
|
419
|
+
const changed = manager._truncateToolResults(messages, 0);
|
|
420
|
+
expect(changed).toBe(true);
|
|
421
|
+
expect(messages[0].content[0]).toEqual(new ToolResultBlock({
|
|
422
|
+
toolUseId: 'tool-1',
|
|
423
|
+
status: 'success',
|
|
424
|
+
content: [new TextBlock('[video: mp4, source: s3]')],
|
|
425
|
+
}));
|
|
426
|
+
});
|
|
427
|
+
it('replaces document bytes blocks with a descriptive placeholder', () => {
|
|
428
|
+
const manager = new SlidingWindowConversationManager({ shouldTruncateResults: true });
|
|
429
|
+
const messages = [
|
|
430
|
+
new Message({
|
|
431
|
+
role: 'user',
|
|
432
|
+
content: [
|
|
433
|
+
new ToolResultBlock({
|
|
434
|
+
toolUseId: 'tool-1',
|
|
435
|
+
status: 'success',
|
|
436
|
+
content: [
|
|
437
|
+
new DocumentBlock({
|
|
438
|
+
name: 'report',
|
|
439
|
+
format: 'pdf',
|
|
440
|
+
source: { bytes: new Uint8Array(8192) },
|
|
441
|
+
}),
|
|
442
|
+
],
|
|
443
|
+
}),
|
|
444
|
+
],
|
|
445
|
+
}),
|
|
446
|
+
];
|
|
447
|
+
const changed = manager._truncateToolResults(messages, 0);
|
|
448
|
+
expect(changed).toBe(true);
|
|
449
|
+
expect(messages[0].content[0]).toEqual(new ToolResultBlock({
|
|
450
|
+
toolUseId: 'tool-1',
|
|
451
|
+
status: 'success',
|
|
452
|
+
content: [new TextBlock('[document: report, pdf, source: bytes, 8192 bytes]')],
|
|
453
|
+
}));
|
|
454
|
+
});
|
|
455
|
+
it('replaces document s3 blocks with a descriptive placeholder', () => {
|
|
456
|
+
const manager = new SlidingWindowConversationManager({ shouldTruncateResults: true });
|
|
457
|
+
const messages = [
|
|
458
|
+
new Message({
|
|
459
|
+
role: 'user',
|
|
460
|
+
content: [
|
|
461
|
+
new ToolResultBlock({
|
|
462
|
+
toolUseId: 'tool-1',
|
|
463
|
+
status: 'success',
|
|
464
|
+
content: [
|
|
465
|
+
new DocumentBlock({
|
|
466
|
+
name: 'spec',
|
|
467
|
+
format: 'pdf',
|
|
468
|
+
source: { location: { type: 's3', uri: 's3://b/k' } },
|
|
469
|
+
}),
|
|
470
|
+
],
|
|
471
|
+
}),
|
|
472
|
+
],
|
|
473
|
+
}),
|
|
474
|
+
];
|
|
475
|
+
const changed = manager._truncateToolResults(messages, 0);
|
|
476
|
+
expect(changed).toBe(true);
|
|
477
|
+
expect(messages[0].content[0]).toEqual(new ToolResultBlock({
|
|
478
|
+
toolUseId: 'tool-1',
|
|
479
|
+
status: 'success',
|
|
480
|
+
content: [new TextBlock('[document: spec, pdf, source: s3]')],
|
|
481
|
+
}));
|
|
482
|
+
});
|
|
483
|
+
it('partially truncates large text inside a document text source', () => {
|
|
484
|
+
const manager = new SlidingWindowConversationManager({ shouldTruncateResults: true });
|
|
485
|
+
const middle = 'M'.repeat(240);
|
|
486
|
+
const originalText = 'A'.repeat(200) + middle + 'B'.repeat(200);
|
|
487
|
+
const messages = [
|
|
488
|
+
new Message({
|
|
489
|
+
role: 'user',
|
|
490
|
+
content: [
|
|
491
|
+
new ToolResultBlock({
|
|
492
|
+
toolUseId: 'tool-1',
|
|
493
|
+
status: 'success',
|
|
494
|
+
content: [new DocumentBlock({ name: 'report', format: 'txt', source: { text: originalText } })],
|
|
495
|
+
}),
|
|
496
|
+
],
|
|
497
|
+
}),
|
|
498
|
+
];
|
|
499
|
+
const changed = manager._truncateToolResults(messages, 0);
|
|
500
|
+
expect(changed).toBe(true);
|
|
501
|
+
const expectedText = `${'A'.repeat(200)}\n<truncated chars="${middle.length}"/>\n${'B'.repeat(200)}`;
|
|
502
|
+
expect(messages[0].content[0]).toEqual(new ToolResultBlock({
|
|
503
|
+
toolUseId: 'tool-1',
|
|
504
|
+
status: 'success',
|
|
505
|
+
content: [new DocumentBlock({ name: 'report', format: 'txt', source: { text: expectedText } })],
|
|
506
|
+
}));
|
|
507
|
+
});
|
|
508
|
+
it('leaves small text inside a document text source unchanged', () => {
|
|
509
|
+
const manager = new SlidingWindowConversationManager({ shouldTruncateResults: true });
|
|
510
|
+
const messages = [
|
|
511
|
+
new Message({
|
|
512
|
+
role: 'user',
|
|
513
|
+
content: [
|
|
514
|
+
new ToolResultBlock({
|
|
515
|
+
toolUseId: 'tool-1',
|
|
516
|
+
status: 'success',
|
|
517
|
+
content: [new DocumentBlock({ name: 'short', format: 'txt', source: { text: 'hello' } })],
|
|
518
|
+
}),
|
|
519
|
+
],
|
|
520
|
+
}),
|
|
521
|
+
];
|
|
522
|
+
const changed = manager._truncateToolResults(messages, 0);
|
|
523
|
+
expect(changed).toBe(false);
|
|
524
|
+
});
|
|
525
|
+
it('truncates long nested text blocks inside a document content source', () => {
|
|
526
|
+
const manager = new SlidingWindowConversationManager({ shouldTruncateResults: true });
|
|
527
|
+
const longText = 'A'.repeat(200) + 'M'.repeat(240) + 'B'.repeat(200);
|
|
528
|
+
const messages = [
|
|
529
|
+
new Message({
|
|
530
|
+
role: 'user',
|
|
531
|
+
content: [
|
|
532
|
+
new ToolResultBlock({
|
|
533
|
+
toolUseId: 'tool-1',
|
|
534
|
+
status: 'success',
|
|
535
|
+
content: [
|
|
536
|
+
new DocumentBlock({
|
|
537
|
+
name: 'pages',
|
|
538
|
+
format: 'txt',
|
|
539
|
+
source: { content: [new TextBlock(longText), new TextBlock('short')] },
|
|
540
|
+
}),
|
|
541
|
+
],
|
|
542
|
+
}),
|
|
543
|
+
],
|
|
544
|
+
}),
|
|
545
|
+
];
|
|
546
|
+
const changed = manager._truncateToolResults(messages, 0);
|
|
547
|
+
expect(changed).toBe(true);
|
|
548
|
+
const expectedText = `${'A'.repeat(200)}\n<truncated chars="240"/>\n${'B'.repeat(200)}`;
|
|
549
|
+
expect(messages[0].content[0]).toEqual(new ToolResultBlock({
|
|
550
|
+
toolUseId: 'tool-1',
|
|
551
|
+
status: 'success',
|
|
552
|
+
content: [
|
|
553
|
+
new DocumentBlock({
|
|
554
|
+
name: 'pages',
|
|
555
|
+
format: 'txt',
|
|
556
|
+
source: { content: [new TextBlock(expectedText), new TextBlock('short')] },
|
|
557
|
+
}),
|
|
558
|
+
],
|
|
559
|
+
}));
|
|
560
|
+
});
|
|
561
|
+
it('replaces large json blocks with a size placeholder', () => {
|
|
562
|
+
const manager = new SlidingWindowConversationManager({ shouldTruncateResults: true });
|
|
563
|
+
const big = { payload: 'x'.repeat(1000) };
|
|
564
|
+
const size = JSON.stringify(big).length;
|
|
565
|
+
const messages = [
|
|
566
|
+
new Message({
|
|
567
|
+
role: 'user',
|
|
568
|
+
content: [
|
|
569
|
+
new ToolResultBlock({
|
|
570
|
+
toolUseId: 'tool-1',
|
|
571
|
+
status: 'success',
|
|
572
|
+
content: [new JsonBlock({ json: big })],
|
|
573
|
+
}),
|
|
574
|
+
],
|
|
575
|
+
}),
|
|
576
|
+
];
|
|
577
|
+
const changed = manager._truncateToolResults(messages, 0);
|
|
578
|
+
expect(changed).toBe(true);
|
|
579
|
+
expect(messages[0].content[0]).toEqual(new ToolResultBlock({
|
|
580
|
+
toolUseId: 'tool-1',
|
|
581
|
+
status: 'success',
|
|
582
|
+
content: [new TextBlock(`[json: ${size} chars]`)],
|
|
583
|
+
}));
|
|
584
|
+
});
|
|
585
|
+
it('leaves small json blocks unchanged', () => {
|
|
586
|
+
const manager = new SlidingWindowConversationManager({ shouldTruncateResults: true });
|
|
587
|
+
const messages = [
|
|
588
|
+
new Message({
|
|
589
|
+
role: 'user',
|
|
590
|
+
content: [
|
|
591
|
+
new ToolResultBlock({
|
|
592
|
+
toolUseId: 'tool-1',
|
|
593
|
+
status: 'success',
|
|
594
|
+
content: [new JsonBlock({ json: { ok: true } })],
|
|
595
|
+
}),
|
|
596
|
+
],
|
|
597
|
+
}),
|
|
598
|
+
];
|
|
599
|
+
const changed = manager._truncateToolResults(messages, 0);
|
|
600
|
+
expect(changed).toBe(false);
|
|
601
|
+
});
|
|
271
602
|
it('does not call truncateToolResults unless an error is passed in', async () => {
|
|
272
603
|
const manager = new SlidingWindowConversationManager({ windowSize: 2, shouldTruncateResults: true });
|
|
273
604
|
const messages = [
|
|
@@ -545,7 +876,7 @@ describe('SlidingWindowConversationManager', () => {
|
|
|
545
876
|
});
|
|
546
877
|
});
|
|
547
878
|
describe('helper methods', () => {
|
|
548
|
-
describe('
|
|
879
|
+
describe('findOldestMessageWithToolResults', () => {
|
|
549
880
|
it('returns correct index when tool results exist', () => {
|
|
550
881
|
const manager = new SlidingWindowConversationManager();
|
|
551
882
|
const messages = [
|
|
@@ -562,7 +893,7 @@ describe('SlidingWindowConversationManager', () => {
|
|
|
562
893
|
}),
|
|
563
894
|
new Message({ role: 'assistant', content: [new TextBlock('Response')] }),
|
|
564
895
|
];
|
|
565
|
-
const index = manager.
|
|
896
|
+
const index = manager._findOldestMessageWithToolResults(messages);
|
|
566
897
|
expect(index).toBe(1);
|
|
567
898
|
});
|
|
568
899
|
it('returns undefined when no tool results exist', () => {
|
|
@@ -571,10 +902,10 @@ describe('SlidingWindowConversationManager', () => {
|
|
|
571
902
|
new Message({ role: 'user', content: [new TextBlock('Message 1')] }),
|
|
572
903
|
new Message({ role: 'assistant', content: [new TextBlock('Response 1')] }),
|
|
573
904
|
];
|
|
574
|
-
const index = manager.
|
|
905
|
+
const index = manager._findOldestMessageWithToolResults(messages);
|
|
575
906
|
expect(index).toBeUndefined();
|
|
576
907
|
});
|
|
577
|
-
it('iterates
|
|
908
|
+
it('iterates forward from start', () => {
|
|
578
909
|
const manager = new SlidingWindowConversationManager();
|
|
579
910
|
const messages = [
|
|
580
911
|
new Message({
|
|
@@ -599,9 +930,9 @@ describe('SlidingWindowConversationManager', () => {
|
|
|
599
930
|
],
|
|
600
931
|
}),
|
|
601
932
|
];
|
|
602
|
-
const index = manager.
|
|
603
|
-
// Should find the
|
|
604
|
-
expect(index).toBe(
|
|
933
|
+
const index = manager._findOldestMessageWithToolResults(messages);
|
|
934
|
+
// Should find the first one (index 0), not the last (index 2)
|
|
935
|
+
expect(index).toBe(0);
|
|
605
936
|
});
|
|
606
937
|
});
|
|
607
938
|
describe('truncateToolResults', () => {
|
|
@@ -614,7 +945,7 @@ describe('SlidingWindowConversationManager', () => {
|
|
|
614
945
|
new ToolResultBlock({
|
|
615
946
|
toolUseId: 'id-1',
|
|
616
947
|
status: 'success',
|
|
617
|
-
content: [new TextBlock('
|
|
948
|
+
content: [new TextBlock('x'.repeat(500))],
|
|
618
949
|
}),
|
|
619
950
|
],
|
|
620
951
|
}),
|
|
@@ -624,14 +955,15 @@ describe('SlidingWindowConversationManager', () => {
|
|
|
624
955
|
});
|
|
625
956
|
it('returns false when already truncated', () => {
|
|
626
957
|
const manager = new SlidingWindowConversationManager();
|
|
958
|
+
const alreadyTruncated = 'A'.repeat(200) + '\n<truncated chars="1000"/>\n' + 'B'.repeat(200);
|
|
627
959
|
const messages = [
|
|
628
960
|
new Message({
|
|
629
961
|
role: 'user',
|
|
630
962
|
content: [
|
|
631
963
|
new ToolResultBlock({
|
|
632
964
|
toolUseId: 'id-1',
|
|
633
|
-
status: '
|
|
634
|
-
content: [new TextBlock(
|
|
965
|
+
status: 'success',
|
|
966
|
+
content: [new TextBlock(alreadyTruncated)],
|
|
635
967
|
}),
|
|
636
968
|
],
|
|
637
969
|
}),
|