@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,1028 @@
|
|
|
1
|
+
import { Agent } from './agent';
|
|
2
|
+
import { AgentEvent, EventType, UserConfirmResultEvent } from '../event';
|
|
3
|
+
import { ContentBlock, Msg } from '../message';
|
|
4
|
+
import { ChatModelBase, ChatResponse } from '../model';
|
|
5
|
+
import { ChatModelRequestOptions } from '../model/base';
|
|
6
|
+
import { Bash, Edit, Glob, Grep, Read, Toolkit, Write } from '../tool';
|
|
7
|
+
import { ToolChoice, ToolSchema } from '../type';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A mock chat model for testing purposes.
|
|
11
|
+
*/
|
|
12
|
+
class MockChatModel extends ChatModelBase {
|
|
13
|
+
/**
|
|
14
|
+
* Mock implementations
|
|
15
|
+
* @param _tools
|
|
16
|
+
*/
|
|
17
|
+
_formatToolSchemas(_tools: ToolSchema[]): unknown[] {
|
|
18
|
+
throw new Error('Method not implemented.');
|
|
19
|
+
}
|
|
20
|
+
public mockContent: ContentBlock[];
|
|
21
|
+
/**
|
|
22
|
+
* Initialize a new instance of the MockChatModel class.
|
|
23
|
+
*/
|
|
24
|
+
constructor() {
|
|
25
|
+
super({ modelName: 'mock-model' });
|
|
26
|
+
this.mockContent = [];
|
|
27
|
+
this.stream = false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Simulate calling the API and return a ChatResponse with the mock content.
|
|
32
|
+
* @param _modelName
|
|
33
|
+
* @param _options
|
|
34
|
+
* @returns A promise that resolves to a ChatResponse containing the mock content.
|
|
35
|
+
*/
|
|
36
|
+
async _callAPI(
|
|
37
|
+
_modelName: string,
|
|
38
|
+
_options: ChatModelRequestOptions<unknown>
|
|
39
|
+
): Promise<ChatResponse> {
|
|
40
|
+
return {
|
|
41
|
+
type: 'chat',
|
|
42
|
+
id: 'mock-id',
|
|
43
|
+
createdAt: new Date().toISOString(),
|
|
44
|
+
content: [...this.mockContent],
|
|
45
|
+
} as ChatResponse;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Simulate formatting the tool choice. This method is not implemented in this mock model.
|
|
50
|
+
* @param _toolChoice
|
|
51
|
+
*/
|
|
52
|
+
_formatToolChoice(_toolChoice: ToolChoice): unknown {
|
|
53
|
+
throw new Error('Method not implemented.');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
describe('Human-in-the-loop', () => {
|
|
58
|
+
test('user confirm', async () => {
|
|
59
|
+
// Prepare tools and agent
|
|
60
|
+
const toolkit = new Toolkit({
|
|
61
|
+
tools: [Bash(), Glob(), Grep(), Read(), Write(), Edit()],
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const model = new MockChatModel();
|
|
65
|
+
const agent = new Agent({
|
|
66
|
+
name: 'Friday',
|
|
67
|
+
sysPrompt: 'You are a helpful assistant named Friday.',
|
|
68
|
+
model: model,
|
|
69
|
+
toolkit,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Set mock content to simulate model output with tool calls
|
|
73
|
+
model.mockContent = [
|
|
74
|
+
{
|
|
75
|
+
type: 'tool_call',
|
|
76
|
+
id: '1',
|
|
77
|
+
name: 'Bash',
|
|
78
|
+
input: `{"command": "echo Hello"}`,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
type: 'tool_call',
|
|
82
|
+
id: '2',
|
|
83
|
+
name: 'Bash',
|
|
84
|
+
input: `{"command": "echo World"}`,
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
// Record the last event emitted by the agent
|
|
89
|
+
let lastEvent: AgentEvent | null = null;
|
|
90
|
+
for await (const event of agent.replyStream({})) {
|
|
91
|
+
lastEvent = event;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
expect(lastEvent).toMatchObject({
|
|
95
|
+
type: EventType.REQUIRE_USER_CONFIRM,
|
|
96
|
+
toolCalls: [
|
|
97
|
+
{
|
|
98
|
+
type: 'tool_call',
|
|
99
|
+
id: '1',
|
|
100
|
+
name: 'Bash',
|
|
101
|
+
input: '{"command": "echo Hello"}',
|
|
102
|
+
awaitUserConfirmation: true,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
type: 'tool_call',
|
|
106
|
+
id: '2',
|
|
107
|
+
name: 'Bash',
|
|
108
|
+
input: '{"command": "echo World"}',
|
|
109
|
+
awaitUserConfirmation: true,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(await agent.toJSON()).toMatchObject({
|
|
115
|
+
replyId: expect.any(String),
|
|
116
|
+
confirmedToolCallIds: [],
|
|
117
|
+
curIter: 0,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Ensure the agent context state
|
|
121
|
+
expect(agent.context).toEqual([
|
|
122
|
+
{
|
|
123
|
+
content: [
|
|
124
|
+
{
|
|
125
|
+
id: '1',
|
|
126
|
+
input: '{"command": "echo Hello"}',
|
|
127
|
+
name: 'Bash',
|
|
128
|
+
type: 'tool_call',
|
|
129
|
+
awaitUserConfirmation: true,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: '2',
|
|
133
|
+
input: '{"command": "echo World"}',
|
|
134
|
+
name: 'Bash',
|
|
135
|
+
type: 'tool_call',
|
|
136
|
+
awaitUserConfirmation: true,
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
id: expect.any(String),
|
|
140
|
+
metadata: {},
|
|
141
|
+
name: 'Friday',
|
|
142
|
+
role: 'assistant',
|
|
143
|
+
timestamp: expect.any(String),
|
|
144
|
+
},
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
// Simulate user confirmation result for the first tool call
|
|
148
|
+
for await (const event of agent.replyStream({
|
|
149
|
+
event: {
|
|
150
|
+
id: 'xxx',
|
|
151
|
+
createdAt: new Date().toISOString(),
|
|
152
|
+
type: EventType.USER_CONFIRM_RESULT,
|
|
153
|
+
replyId: agent.replyId,
|
|
154
|
+
confirmResults: [
|
|
155
|
+
{
|
|
156
|
+
confirmed: true,
|
|
157
|
+
toolCall: {
|
|
158
|
+
type: 'tool_call',
|
|
159
|
+
id: '1',
|
|
160
|
+
name: 'Bash',
|
|
161
|
+
input: '{"command": "echo Hello"}',
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
} as UserConfirmResultEvent,
|
|
166
|
+
})) {
|
|
167
|
+
lastEvent = event;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Verify the agent still yields user confirmation for the second tool call
|
|
171
|
+
expect(lastEvent).toMatchObject({
|
|
172
|
+
type: EventType.REQUIRE_USER_CONFIRM,
|
|
173
|
+
replyId: expect.any(String),
|
|
174
|
+
toolCalls: [
|
|
175
|
+
{
|
|
176
|
+
type: 'tool_call',
|
|
177
|
+
id: '2',
|
|
178
|
+
name: 'Bash',
|
|
179
|
+
input: '{"command": "echo World"}',
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Verify the current agent context
|
|
185
|
+
expect(agent.context.map(msg => msg.content)).toEqual([
|
|
186
|
+
[
|
|
187
|
+
{
|
|
188
|
+
id: '1',
|
|
189
|
+
input: '{"command": "echo Hello"}',
|
|
190
|
+
name: 'Bash',
|
|
191
|
+
type: 'tool_call',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
id: '2',
|
|
195
|
+
input: '{"command": "echo World"}',
|
|
196
|
+
name: 'Bash',
|
|
197
|
+
type: 'tool_call',
|
|
198
|
+
awaitUserConfirmation: true,
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
id: '1',
|
|
202
|
+
name: 'Bash',
|
|
203
|
+
output: [
|
|
204
|
+
{
|
|
205
|
+
id: expect.any(String),
|
|
206
|
+
text: 'Hello\n',
|
|
207
|
+
type: 'text',
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
type: 'tool_result',
|
|
211
|
+
state: 'success',
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
]);
|
|
215
|
+
|
|
216
|
+
model.mockContent = [{ type: 'text', text: 'Finished', id: expect.any(String) }];
|
|
217
|
+
|
|
218
|
+
// Reject the second tool call by simulating user confirmation result
|
|
219
|
+
const res = agent.replyStream({
|
|
220
|
+
event: {
|
|
221
|
+
id: 'xxx',
|
|
222
|
+
createdAt: new Date().toISOString(),
|
|
223
|
+
type: EventType.USER_CONFIRM_RESULT,
|
|
224
|
+
replyId: agent.replyId,
|
|
225
|
+
confirmResults: [
|
|
226
|
+
{
|
|
227
|
+
confirmed: false,
|
|
228
|
+
toolCall: {
|
|
229
|
+
type: 'tool_call',
|
|
230
|
+
id: '2',
|
|
231
|
+
name: 'Bash',
|
|
232
|
+
input: '{"command": "echo World"}',
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
let replyMsg: Msg;
|
|
240
|
+
while (true) {
|
|
241
|
+
const { value, done } = await res.next();
|
|
242
|
+
if (done) {
|
|
243
|
+
replyMsg = value as Msg;
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
lastEvent = value;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Verify the lastEvent
|
|
250
|
+
expect(lastEvent).toMatchObject({
|
|
251
|
+
id: expect.any(String),
|
|
252
|
+
type: EventType.RUN_FINISHED,
|
|
253
|
+
createdAt: expect.any(String),
|
|
254
|
+
replyId: agent.replyId,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Verify the final agent reply msg
|
|
258
|
+
expect(replyMsg).toMatchObject({
|
|
259
|
+
content: [
|
|
260
|
+
{
|
|
261
|
+
id: expect.any(String),
|
|
262
|
+
type: 'text',
|
|
263
|
+
text: 'Finished',
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
id: expect.any(String),
|
|
267
|
+
metadata: {},
|
|
268
|
+
name: 'Friday',
|
|
269
|
+
role: 'assistant',
|
|
270
|
+
timestamp: expect.any(String),
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('external execution', async () => {
|
|
275
|
+
// Prepare tools and agent with external execution tools
|
|
276
|
+
const externalTool1 = {
|
|
277
|
+
name: 'ExternalTool1',
|
|
278
|
+
description: 'A tool that requires external execution',
|
|
279
|
+
inputSchema: {
|
|
280
|
+
type: 'object' as const,
|
|
281
|
+
properties: { query: { type: 'string' as const } },
|
|
282
|
+
},
|
|
283
|
+
// No call method means it requires external execution
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const externalTool2 = {
|
|
287
|
+
name: 'ExternalTool2',
|
|
288
|
+
description: 'Another tool that requires external execution',
|
|
289
|
+
inputSchema: {
|
|
290
|
+
type: 'object' as const,
|
|
291
|
+
properties: { data: { type: 'string' as const } },
|
|
292
|
+
},
|
|
293
|
+
// No call method means it requires external execution
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const toolkit = new Toolkit({
|
|
297
|
+
tools: [externalTool1, externalTool2],
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const model = new MockChatModel();
|
|
301
|
+
const agent = new Agent({
|
|
302
|
+
name: 'Friday',
|
|
303
|
+
sysPrompt: 'You are a helpful assistant named Friday.',
|
|
304
|
+
model: model,
|
|
305
|
+
toolkit,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// Set mock content to simulate model output with external tool calls
|
|
309
|
+
model.mockContent = [
|
|
310
|
+
{
|
|
311
|
+
type: 'tool_call',
|
|
312
|
+
id: '1',
|
|
313
|
+
name: 'ExternalTool1',
|
|
314
|
+
input: `{"query": "test query"}`,
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
type: 'tool_call',
|
|
318
|
+
id: '2',
|
|
319
|
+
name: 'ExternalTool2',
|
|
320
|
+
input: `{"data": "test data"}`,
|
|
321
|
+
},
|
|
322
|
+
];
|
|
323
|
+
|
|
324
|
+
// Record the last event emitted by the agent
|
|
325
|
+
let lastEvent: AgentEvent | null = null;
|
|
326
|
+
for await (const event of agent.replyStream({})) {
|
|
327
|
+
lastEvent = event;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Verify the agent emits REQUIRE_EXTERNAL_EXECUTION event
|
|
331
|
+
expect(lastEvent).toMatchObject({
|
|
332
|
+
type: EventType.REQUIRE_EXTERNAL_EXECUTION,
|
|
333
|
+
toolCalls: [
|
|
334
|
+
{
|
|
335
|
+
type: 'tool_call',
|
|
336
|
+
id: '1',
|
|
337
|
+
name: 'ExternalTool1',
|
|
338
|
+
input: '{"query": "test query"}',
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
type: 'tool_call',
|
|
342
|
+
id: '2',
|
|
343
|
+
name: 'ExternalTool2',
|
|
344
|
+
input: '{"data": "test data"}',
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// Verify agent state
|
|
350
|
+
expect(await agent.toJSON()).toMatchObject({
|
|
351
|
+
replyId: expect.any(String),
|
|
352
|
+
confirmedToolCallIds: [],
|
|
353
|
+
curIter: 0,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// Verify agent context
|
|
357
|
+
expect(agent.context).toEqual([
|
|
358
|
+
{
|
|
359
|
+
content: [
|
|
360
|
+
{
|
|
361
|
+
id: '1',
|
|
362
|
+
input: '{"query": "test query"}',
|
|
363
|
+
name: 'ExternalTool1',
|
|
364
|
+
type: 'tool_call',
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
id: '2',
|
|
368
|
+
input: '{"data": "test data"}',
|
|
369
|
+
name: 'ExternalTool2',
|
|
370
|
+
type: 'tool_call',
|
|
371
|
+
},
|
|
372
|
+
],
|
|
373
|
+
id: expect.any(String),
|
|
374
|
+
metadata: {},
|
|
375
|
+
name: 'Friday',
|
|
376
|
+
role: 'assistant',
|
|
377
|
+
timestamp: expect.any(String),
|
|
378
|
+
},
|
|
379
|
+
]);
|
|
380
|
+
|
|
381
|
+
// Provide execution result for the first tool call
|
|
382
|
+
for await (const event of agent.replyStream({
|
|
383
|
+
event: {
|
|
384
|
+
id: 'xxx',
|
|
385
|
+
createdAt: new Date().toISOString(),
|
|
386
|
+
type: EventType.EXTERNAL_EXECUTION_RESULT,
|
|
387
|
+
replyId: agent.replyId,
|
|
388
|
+
executionResults: [
|
|
389
|
+
{
|
|
390
|
+
type: 'tool_result',
|
|
391
|
+
id: '1',
|
|
392
|
+
name: 'ExternalTool1',
|
|
393
|
+
output: [
|
|
394
|
+
{
|
|
395
|
+
id: 'output-1',
|
|
396
|
+
type: 'text',
|
|
397
|
+
text: 'Result from ExternalTool1',
|
|
398
|
+
},
|
|
399
|
+
],
|
|
400
|
+
state: 'success',
|
|
401
|
+
},
|
|
402
|
+
],
|
|
403
|
+
},
|
|
404
|
+
})) {
|
|
405
|
+
lastEvent = event;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Verify the agent still requires external execution for the second tool
|
|
409
|
+
expect(lastEvent).toMatchObject({
|
|
410
|
+
type: EventType.REQUIRE_EXTERNAL_EXECUTION,
|
|
411
|
+
replyId: expect.any(String),
|
|
412
|
+
toolCalls: [
|
|
413
|
+
{
|
|
414
|
+
type: 'tool_call',
|
|
415
|
+
id: '2',
|
|
416
|
+
name: 'ExternalTool2',
|
|
417
|
+
input: '{"data": "test data"}',
|
|
418
|
+
},
|
|
419
|
+
],
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
// Verify the current agent context
|
|
423
|
+
expect(agent.context.map(msg => msg.content)).toEqual([
|
|
424
|
+
[
|
|
425
|
+
{
|
|
426
|
+
id: '1',
|
|
427
|
+
input: '{"query": "test query"}',
|
|
428
|
+
name: 'ExternalTool1',
|
|
429
|
+
type: 'tool_call',
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
id: '2',
|
|
433
|
+
input: '{"data": "test data"}',
|
|
434
|
+
name: 'ExternalTool2',
|
|
435
|
+
type: 'tool_call',
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
id: '1',
|
|
439
|
+
name: 'ExternalTool1',
|
|
440
|
+
output: [
|
|
441
|
+
{
|
|
442
|
+
id: expect.any(String),
|
|
443
|
+
text: 'Result from ExternalTool1',
|
|
444
|
+
type: 'text',
|
|
445
|
+
},
|
|
446
|
+
],
|
|
447
|
+
type: 'tool_result',
|
|
448
|
+
state: 'success',
|
|
449
|
+
},
|
|
450
|
+
],
|
|
451
|
+
]);
|
|
452
|
+
|
|
453
|
+
model.mockContent = [{ type: 'text', text: 'All tools executed', id: expect.any(String) }];
|
|
454
|
+
|
|
455
|
+
// Provide execution result for the second tool call
|
|
456
|
+
const res = agent.replyStream({
|
|
457
|
+
event: {
|
|
458
|
+
id: 'xxx',
|
|
459
|
+
createdAt: new Date().toISOString(),
|
|
460
|
+
type: EventType.EXTERNAL_EXECUTION_RESULT,
|
|
461
|
+
replyId: agent.replyId,
|
|
462
|
+
executionResults: [
|
|
463
|
+
{
|
|
464
|
+
type: 'tool_result',
|
|
465
|
+
id: '2',
|
|
466
|
+
name: 'ExternalTool2',
|
|
467
|
+
output: [
|
|
468
|
+
{
|
|
469
|
+
id: expect.any(String),
|
|
470
|
+
type: 'text',
|
|
471
|
+
text: 'Result from ExternalTool2',
|
|
472
|
+
},
|
|
473
|
+
],
|
|
474
|
+
state: 'success',
|
|
475
|
+
},
|
|
476
|
+
],
|
|
477
|
+
},
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
let replyMsg: Msg;
|
|
481
|
+
while (true) {
|
|
482
|
+
const { value, done } = await res.next();
|
|
483
|
+
if (done) {
|
|
484
|
+
replyMsg = value as Msg;
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
lastEvent = value;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Verify the lastEvent is RUN_FINISHED
|
|
491
|
+
expect(lastEvent).toMatchObject({
|
|
492
|
+
id: expect.any(String),
|
|
493
|
+
type: EventType.RUN_FINISHED,
|
|
494
|
+
createdAt: expect.any(String),
|
|
495
|
+
replyId: agent.replyId,
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
// Verify the final agent reply msg
|
|
499
|
+
expect(replyMsg).toMatchObject({
|
|
500
|
+
content: [
|
|
501
|
+
{
|
|
502
|
+
id: expect.any(String),
|
|
503
|
+
type: 'text',
|
|
504
|
+
text: 'All tools executed',
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
id: expect.any(String),
|
|
508
|
+
metadata: {},
|
|
509
|
+
name: 'Friday',
|
|
510
|
+
role: 'assistant',
|
|
511
|
+
timestamp: expect.any(String),
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
test('mixed tool calls', async () => {
|
|
516
|
+
// Create three tools: external execution, user confirm, and normal execution
|
|
517
|
+
const externalTool = {
|
|
518
|
+
name: 'ExternalTool',
|
|
519
|
+
description: 'A tool that requires external execution',
|
|
520
|
+
inputSchema: {
|
|
521
|
+
type: 'object' as const,
|
|
522
|
+
properties: { query: { type: 'string' as const } },
|
|
523
|
+
},
|
|
524
|
+
// No call method means it requires external execution
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
const confirmTool = {
|
|
528
|
+
name: 'ConfirmTool',
|
|
529
|
+
description: 'A tool that requires user confirmation',
|
|
530
|
+
inputSchema: {
|
|
531
|
+
type: 'object' as const,
|
|
532
|
+
properties: { action: { type: 'string' as const } },
|
|
533
|
+
},
|
|
534
|
+
requireUserConfirm: true,
|
|
535
|
+
call: async (input: { action: string }) => {
|
|
536
|
+
return `Executed action: ${input.action}`;
|
|
537
|
+
},
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
const normalTool = {
|
|
541
|
+
name: 'NormalTool',
|
|
542
|
+
description: 'A normal tool',
|
|
543
|
+
inputSchema: {
|
|
544
|
+
type: 'object' as const,
|
|
545
|
+
properties: { data: { type: 'string' as const } },
|
|
546
|
+
},
|
|
547
|
+
call: async (input: { data: string }) => {
|
|
548
|
+
return `Processed data: ${input.data}`;
|
|
549
|
+
},
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const toolkit = new Toolkit({
|
|
553
|
+
tools: [
|
|
554
|
+
externalTool,
|
|
555
|
+
confirmTool,
|
|
556
|
+
normalTool,
|
|
557
|
+
Bash(),
|
|
558
|
+
Glob(),
|
|
559
|
+
Grep(),
|
|
560
|
+
Read(),
|
|
561
|
+
Write(),
|
|
562
|
+
Edit(),
|
|
563
|
+
],
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
const model = new MockChatModel();
|
|
567
|
+
const agent = new Agent({
|
|
568
|
+
name: 'Friday',
|
|
569
|
+
sysPrompt: 'You are a helpful assistant named Friday.',
|
|
570
|
+
model: model,
|
|
571
|
+
toolkit,
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
// Set mock content to simulate model output with three different tool calls
|
|
575
|
+
model.mockContent = [
|
|
576
|
+
{
|
|
577
|
+
type: 'tool_call',
|
|
578
|
+
id: '1',
|
|
579
|
+
name: 'ExternalTool',
|
|
580
|
+
input: `{"query": "external query"}`,
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
type: 'tool_call',
|
|
584
|
+
id: '2',
|
|
585
|
+
name: 'ConfirmTool',
|
|
586
|
+
input: `{"action": "delete file"}`,
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
type: 'tool_call',
|
|
590
|
+
id: '3',
|
|
591
|
+
name: 'NormalTool',
|
|
592
|
+
input: `{"data": "normal data"}`,
|
|
593
|
+
},
|
|
594
|
+
];
|
|
595
|
+
|
|
596
|
+
// Record the last event emitted by the agent
|
|
597
|
+
let lastEvent: AgentEvent | null = null;
|
|
598
|
+
for await (const event of agent.replyStream({})) {
|
|
599
|
+
lastEvent = event;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Verify the agent emits REQUIRE_EXTERNAL_EXECUTION event for the first tool
|
|
603
|
+
expect(lastEvent).toMatchObject({
|
|
604
|
+
type: EventType.REQUIRE_EXTERNAL_EXECUTION,
|
|
605
|
+
toolCalls: [
|
|
606
|
+
{
|
|
607
|
+
type: 'tool_call',
|
|
608
|
+
id: '1',
|
|
609
|
+
name: 'ExternalTool',
|
|
610
|
+
input: '{"query": "external query"}',
|
|
611
|
+
},
|
|
612
|
+
],
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// Verify agent state
|
|
616
|
+
expect(await agent.toJSON()).toMatchObject({
|
|
617
|
+
replyId: expect.any(String),
|
|
618
|
+
confirmedToolCallIds: [],
|
|
619
|
+
curIter: 0,
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
// Provide execution result for the external tool
|
|
623
|
+
for await (const event of agent.replyStream({
|
|
624
|
+
event: {
|
|
625
|
+
id: 'xxx',
|
|
626
|
+
createdAt: new Date().toISOString(),
|
|
627
|
+
type: EventType.EXTERNAL_EXECUTION_RESULT,
|
|
628
|
+
replyId: agent.replyId,
|
|
629
|
+
executionResults: [
|
|
630
|
+
{
|
|
631
|
+
type: 'tool_result',
|
|
632
|
+
id: '1',
|
|
633
|
+
name: 'ExternalTool',
|
|
634
|
+
output: [
|
|
635
|
+
{
|
|
636
|
+
id: expect.any(String),
|
|
637
|
+
type: 'text',
|
|
638
|
+
text: 'External execution result',
|
|
639
|
+
},
|
|
640
|
+
],
|
|
641
|
+
state: 'success',
|
|
642
|
+
},
|
|
643
|
+
],
|
|
644
|
+
},
|
|
645
|
+
})) {
|
|
646
|
+
lastEvent = event;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Verify the agent now requires user confirmation for the second tool
|
|
650
|
+
expect(lastEvent).toMatchObject({
|
|
651
|
+
type: EventType.REQUIRE_USER_CONFIRM,
|
|
652
|
+
replyId: expect.any(String),
|
|
653
|
+
toolCalls: [
|
|
654
|
+
{
|
|
655
|
+
type: 'tool_call',
|
|
656
|
+
id: '2',
|
|
657
|
+
name: 'ConfirmTool',
|
|
658
|
+
input: '{"action": "delete file"}',
|
|
659
|
+
awaitUserConfirmation: true,
|
|
660
|
+
},
|
|
661
|
+
],
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
// Verify agent state after external execution
|
|
665
|
+
expect(await agent.toJSON()).toMatchObject({
|
|
666
|
+
replyId: expect.any(String),
|
|
667
|
+
confirmedToolCallIds: [],
|
|
668
|
+
curIter: 0,
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
// Update mock content to return final text response
|
|
672
|
+
model.mockContent = [
|
|
673
|
+
{
|
|
674
|
+
type: 'text',
|
|
675
|
+
text: 'All tools completed successfully',
|
|
676
|
+
id: expect.any(String),
|
|
677
|
+
},
|
|
678
|
+
];
|
|
679
|
+
|
|
680
|
+
// Provide user confirmation for the second tool
|
|
681
|
+
for await (const event of agent.replyStream({
|
|
682
|
+
event: {
|
|
683
|
+
id: 'xxx',
|
|
684
|
+
createdAt: new Date().toISOString(),
|
|
685
|
+
type: EventType.USER_CONFIRM_RESULT,
|
|
686
|
+
replyId: agent.replyId,
|
|
687
|
+
confirmResults: [
|
|
688
|
+
{
|
|
689
|
+
confirmed: true,
|
|
690
|
+
toolCall: {
|
|
691
|
+
type: 'tool_call',
|
|
692
|
+
id: '2',
|
|
693
|
+
name: 'ConfirmTool',
|
|
694
|
+
input: '{"action": "delete file"}',
|
|
695
|
+
},
|
|
696
|
+
},
|
|
697
|
+
],
|
|
698
|
+
},
|
|
699
|
+
})) {
|
|
700
|
+
lastEvent = event;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Verify the lastEvent is RUN_FINISHED
|
|
704
|
+
expect(lastEvent).toMatchObject({
|
|
705
|
+
id: expect.any(String),
|
|
706
|
+
type: EventType.RUN_FINISHED,
|
|
707
|
+
createdAt: expect.any(String),
|
|
708
|
+
replyId: agent.replyId,
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
// Verify the agent context includes all three tool calls, their results, and the final text
|
|
712
|
+
expect(agent.context.map(msg => msg.content)).toEqual([
|
|
713
|
+
[
|
|
714
|
+
{
|
|
715
|
+
id: '1',
|
|
716
|
+
input: '{"query": "external query"}',
|
|
717
|
+
name: 'ExternalTool',
|
|
718
|
+
type: 'tool_call',
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
id: '2',
|
|
722
|
+
input: '{"action": "delete file"}',
|
|
723
|
+
name: 'ConfirmTool',
|
|
724
|
+
type: 'tool_call',
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
id: '3',
|
|
728
|
+
input: '{"data": "normal data"}',
|
|
729
|
+
name: 'NormalTool',
|
|
730
|
+
type: 'tool_call',
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
id: '1',
|
|
734
|
+
name: 'ExternalTool',
|
|
735
|
+
output: [
|
|
736
|
+
{
|
|
737
|
+
id: expect.any(String),
|
|
738
|
+
text: 'External execution result',
|
|
739
|
+
type: 'text',
|
|
740
|
+
},
|
|
741
|
+
],
|
|
742
|
+
type: 'tool_result',
|
|
743
|
+
state: 'success',
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
id: '2',
|
|
747
|
+
name: 'ConfirmTool',
|
|
748
|
+
output: [
|
|
749
|
+
{
|
|
750
|
+
id: expect.any(String),
|
|
751
|
+
text: 'Executed action: delete file',
|
|
752
|
+
type: 'text',
|
|
753
|
+
},
|
|
754
|
+
],
|
|
755
|
+
type: 'tool_result',
|
|
756
|
+
state: 'success',
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
id: '3',
|
|
760
|
+
name: 'NormalTool',
|
|
761
|
+
output: [
|
|
762
|
+
{
|
|
763
|
+
id: expect.any(String),
|
|
764
|
+
text: 'Processed data: normal data',
|
|
765
|
+
type: 'text',
|
|
766
|
+
},
|
|
767
|
+
],
|
|
768
|
+
type: 'tool_result',
|
|
769
|
+
state: 'success',
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
id: expect.any(String),
|
|
773
|
+
type: 'text',
|
|
774
|
+
text: 'All tools completed successfully',
|
|
775
|
+
},
|
|
776
|
+
],
|
|
777
|
+
]);
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
test('a tool requires both external execution and user confirmation', async () => {
|
|
781
|
+
// Create two tools: one requires both external execution and user confirmation,
|
|
782
|
+
// another requires only user confirmation
|
|
783
|
+
const externalAndConfirmTool = {
|
|
784
|
+
name: 'ExternalAndConfirmTool',
|
|
785
|
+
description: 'A tool that requires both external execution and user confirmation',
|
|
786
|
+
inputSchema: {
|
|
787
|
+
type: 'object' as const,
|
|
788
|
+
properties: { command: { type: 'string' as const } },
|
|
789
|
+
},
|
|
790
|
+
requireUserConfirm: true,
|
|
791
|
+
// No call method means it requires external execution
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
const confirmOnlyTool = {
|
|
795
|
+
name: 'ConfirmOnlyTool',
|
|
796
|
+
description: 'A tool that requires only user confirmation',
|
|
797
|
+
inputSchema: {
|
|
798
|
+
type: 'object' as const,
|
|
799
|
+
properties: { action: { type: 'string' as const } },
|
|
800
|
+
},
|
|
801
|
+
requireUserConfirm: true,
|
|
802
|
+
call: async (input: { action: string }) => {
|
|
803
|
+
return `Executed action: ${input.action}`;
|
|
804
|
+
},
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
const toolkit = new Toolkit({
|
|
808
|
+
tools: [externalAndConfirmTool, confirmOnlyTool],
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
const model = new MockChatModel();
|
|
812
|
+
const agent = new Agent({
|
|
813
|
+
name: 'Friday',
|
|
814
|
+
sysPrompt: 'You are a helpful assistant named Friday.',
|
|
815
|
+
model: model,
|
|
816
|
+
toolkit,
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
// Set mock content to simulate model output with two tool calls
|
|
820
|
+
model.mockContent = [
|
|
821
|
+
{
|
|
822
|
+
type: 'tool_call',
|
|
823
|
+
id: '1',
|
|
824
|
+
name: 'ExternalAndConfirmTool',
|
|
825
|
+
input: `{"command": "rm -rf /"}`,
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
type: 'tool_call',
|
|
829
|
+
id: '2',
|
|
830
|
+
name: 'ConfirmOnlyTool',
|
|
831
|
+
input: `{"action": "delete database"}`,
|
|
832
|
+
},
|
|
833
|
+
];
|
|
834
|
+
|
|
835
|
+
// Record the last event emitted by the agent
|
|
836
|
+
let lastEvent: AgentEvent | null = null;
|
|
837
|
+
for await (const event of agent.replyStream({})) {
|
|
838
|
+
lastEvent = event;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// Verify the agent emits REQUIRE_USER_CONFIRM event for both tools
|
|
842
|
+
expect(lastEvent).toMatchObject({
|
|
843
|
+
type: EventType.REQUIRE_USER_CONFIRM,
|
|
844
|
+
toolCalls: [
|
|
845
|
+
{
|
|
846
|
+
type: 'tool_call',
|
|
847
|
+
id: '1',
|
|
848
|
+
name: 'ExternalAndConfirmTool',
|
|
849
|
+
input: '{"command": "rm -rf /"}',
|
|
850
|
+
awaitUserConfirmation: true,
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
type: 'tool_call',
|
|
854
|
+
id: '2',
|
|
855
|
+
name: 'ConfirmOnlyTool',
|
|
856
|
+
input: '{"action": "delete database"}',
|
|
857
|
+
awaitUserConfirmation: true,
|
|
858
|
+
},
|
|
859
|
+
],
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
// Verify agent state
|
|
863
|
+
expect(await agent.toJSON()).toMatchObject({
|
|
864
|
+
replyId: expect.any(String),
|
|
865
|
+
confirmedToolCallIds: [],
|
|
866
|
+
curIter: 0,
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
// Provide user confirmation for both tools
|
|
870
|
+
for await (const event of agent.replyStream({
|
|
871
|
+
event: {
|
|
872
|
+
id: 'xxx',
|
|
873
|
+
createdAt: new Date().toISOString(),
|
|
874
|
+
type: EventType.USER_CONFIRM_RESULT,
|
|
875
|
+
replyId: agent.replyId,
|
|
876
|
+
confirmResults: [
|
|
877
|
+
{
|
|
878
|
+
confirmed: true,
|
|
879
|
+
toolCall: {
|
|
880
|
+
type: 'tool_call',
|
|
881
|
+
id: '1',
|
|
882
|
+
name: 'ExternalAndConfirmTool',
|
|
883
|
+
input: '{"command": "rm -rf /"}',
|
|
884
|
+
},
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
confirmed: true,
|
|
888
|
+
toolCall: {
|
|
889
|
+
type: 'tool_call',
|
|
890
|
+
id: '2',
|
|
891
|
+
name: 'ConfirmOnlyTool',
|
|
892
|
+
input: '{"action": "delete database"}',
|
|
893
|
+
},
|
|
894
|
+
},
|
|
895
|
+
],
|
|
896
|
+
},
|
|
897
|
+
})) {
|
|
898
|
+
lastEvent = event;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// After user confirmation, the first tool requires external execution
|
|
902
|
+
expect(lastEvent).toMatchObject({
|
|
903
|
+
type: EventType.REQUIRE_EXTERNAL_EXECUTION,
|
|
904
|
+
replyId: expect.any(String),
|
|
905
|
+
toolCalls: [
|
|
906
|
+
{
|
|
907
|
+
type: 'tool_call',
|
|
908
|
+
id: '1',
|
|
909
|
+
name: 'ExternalAndConfirmTool',
|
|
910
|
+
input: '{"command": "rm -rf /"}',
|
|
911
|
+
},
|
|
912
|
+
],
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
// Verify agent state after user confirmation
|
|
916
|
+
expect(await agent.toJSON()).toMatchObject({
|
|
917
|
+
replyId: expect.any(String),
|
|
918
|
+
confirmedToolCallIds: ['1', '2'],
|
|
919
|
+
curIter: 0,
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
// Verify the current agent context
|
|
923
|
+
expect(agent.context.map(msg => msg.content)).toEqual([
|
|
924
|
+
[
|
|
925
|
+
{
|
|
926
|
+
id: '1',
|
|
927
|
+
input: '{"command": "rm -rf /"}',
|
|
928
|
+
name: 'ExternalAndConfirmTool',
|
|
929
|
+
type: 'tool_call',
|
|
930
|
+
},
|
|
931
|
+
{
|
|
932
|
+
id: '2',
|
|
933
|
+
input: '{"action": "delete database"}',
|
|
934
|
+
name: 'ConfirmOnlyTool',
|
|
935
|
+
type: 'tool_call',
|
|
936
|
+
},
|
|
937
|
+
],
|
|
938
|
+
]);
|
|
939
|
+
|
|
940
|
+
// Update mock content to return final text response
|
|
941
|
+
model.mockContent = [{ type: 'text', text: 'All operations completed', id: 'abc' }];
|
|
942
|
+
|
|
943
|
+
// Provide external execution result for the first tool
|
|
944
|
+
for await (const event of agent.replyStream({
|
|
945
|
+
event: {
|
|
946
|
+
id: 'xxx',
|
|
947
|
+
createdAt: new Date().toISOString(),
|
|
948
|
+
type: EventType.EXTERNAL_EXECUTION_RESULT,
|
|
949
|
+
replyId: agent.replyId,
|
|
950
|
+
executionResults: [
|
|
951
|
+
{
|
|
952
|
+
type: 'tool_result',
|
|
953
|
+
id: '1',
|
|
954
|
+
name: 'ExternalAndConfirmTool',
|
|
955
|
+
output: [
|
|
956
|
+
{
|
|
957
|
+
id: expect.any(String),
|
|
958
|
+
type: 'text',
|
|
959
|
+
text: 'External command executed',
|
|
960
|
+
},
|
|
961
|
+
],
|
|
962
|
+
state: 'success',
|
|
963
|
+
},
|
|
964
|
+
],
|
|
965
|
+
},
|
|
966
|
+
})) {
|
|
967
|
+
lastEvent = event;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// After external execution, the second tool should execute directly
|
|
971
|
+
// because it was already confirmed in the previous step
|
|
972
|
+
expect(lastEvent).toMatchObject({
|
|
973
|
+
id: expect.any(String),
|
|
974
|
+
type: EventType.RUN_FINISHED,
|
|
975
|
+
createdAt: expect.any(String),
|
|
976
|
+
replyId: agent.replyId,
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
// Verify the final agent context includes all tool calls, their results, and the final text
|
|
980
|
+
expect(agent.context.map(msg => msg.content)).toEqual([
|
|
981
|
+
[
|
|
982
|
+
{
|
|
983
|
+
id: '1',
|
|
984
|
+
input: '{"command": "rm -rf /"}',
|
|
985
|
+
name: 'ExternalAndConfirmTool',
|
|
986
|
+
type: 'tool_call',
|
|
987
|
+
},
|
|
988
|
+
{
|
|
989
|
+
id: '2',
|
|
990
|
+
input: '{"action": "delete database"}',
|
|
991
|
+
name: 'ConfirmOnlyTool',
|
|
992
|
+
type: 'tool_call',
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
id: '1',
|
|
996
|
+
name: 'ExternalAndConfirmTool',
|
|
997
|
+
output: [
|
|
998
|
+
{
|
|
999
|
+
id: expect.any(String),
|
|
1000
|
+
text: 'External command executed',
|
|
1001
|
+
type: 'text',
|
|
1002
|
+
},
|
|
1003
|
+
],
|
|
1004
|
+
type: 'tool_result',
|
|
1005
|
+
state: 'success',
|
|
1006
|
+
},
|
|
1007
|
+
{
|
|
1008
|
+
id: '2',
|
|
1009
|
+
name: 'ConfirmOnlyTool',
|
|
1010
|
+
output: [
|
|
1011
|
+
{
|
|
1012
|
+
id: expect.any(String),
|
|
1013
|
+
text: 'Executed action: delete database',
|
|
1014
|
+
type: 'text',
|
|
1015
|
+
},
|
|
1016
|
+
],
|
|
1017
|
+
type: 'tool_result',
|
|
1018
|
+
state: 'success',
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
id: expect.any(String),
|
|
1022
|
+
type: 'text',
|
|
1023
|
+
text: 'All operations completed',
|
|
1024
|
+
},
|
|
1025
|
+
],
|
|
1026
|
+
]);
|
|
1027
|
+
});
|
|
1028
|
+
});
|