@ank1015/providers 0.0.1 → 0.0.3
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/LICENSE +21 -0
- package/README.md +93 -383
- package/dist/agent/conversation.d.ts +97 -0
- package/dist/agent/conversation.d.ts.map +1 -0
- package/dist/agent/conversation.js +328 -0
- package/dist/agent/conversation.js.map +1 -0
- package/dist/agent/runner.d.ts +37 -0
- package/dist/agent/runner.d.ts.map +1 -0
- package/dist/agent/runner.js +169 -0
- package/dist/agent/runner.js.map +1 -0
- package/dist/agent/tools/calculate.d.ts +15 -0
- package/dist/agent/tools/calculate.d.ts.map +1 -0
- package/dist/agent/tools/calculate.js +23 -0
- package/dist/agent/tools/calculate.js.map +1 -0
- package/dist/agent/tools/get-current-time.d.ts +15 -0
- package/dist/agent/tools/get-current-time.d.ts.map +1 -0
- package/dist/agent/tools/get-current-time.js +38 -0
- package/dist/agent/tools/get-current-time.js.map +1 -0
- package/dist/agent/tools/index.d.ts +3 -0
- package/dist/agent/tools/index.d.ts.map +1 -0
- package/dist/agent/tools/index.js +3 -0
- package/dist/agent/tools/index.js.map +1 -0
- package/dist/agent/types.d.ts +53 -31
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js +1 -2
- package/dist/agent/utils.d.ts +14 -0
- package/dist/agent/utils.d.ts.map +1 -0
- package/dist/agent/utils.js +59 -0
- package/dist/agent/utils.js.map +1 -0
- package/dist/index.d.ts +16 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -28
- package/dist/index.js.map +1 -1
- package/dist/llm.d.ts +15 -0
- package/dist/llm.d.ts.map +1 -0
- package/dist/llm.js +92 -0
- package/dist/llm.js.map +1 -0
- package/dist/models.d.ts +8 -1
- package/dist/models.d.ts.map +1 -1
- package/dist/models.generated.d.ts +25 -112
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/models.generated.js +72 -227
- package/dist/models.generated.js.map +1 -1
- package/dist/models.js +30 -32
- package/dist/models.js.map +1 -1
- package/dist/providers/google/complete.d.ts +3 -0
- package/dist/providers/google/complete.d.ts.map +1 -0
- package/dist/providers/google/complete.js +53 -0
- package/dist/providers/google/complete.js.map +1 -0
- package/dist/providers/google/index.d.ts +6 -0
- package/dist/providers/google/index.d.ts.map +1 -0
- package/dist/providers/google/index.js +6 -0
- package/dist/providers/google/index.js.map +1 -0
- package/dist/providers/google/stream.d.ts +3 -0
- package/dist/providers/google/stream.d.ts.map +1 -0
- package/dist/providers/{google.js → google/stream.js} +67 -231
- package/dist/providers/google/stream.js.map +1 -0
- package/dist/providers/google/types.d.ts +8 -0
- package/dist/providers/google/types.d.ts.map +1 -0
- package/dist/providers/google/types.js +2 -0
- package/dist/providers/google/types.js.map +1 -0
- package/dist/providers/google/utils.d.ts +30 -0
- package/dist/providers/google/utils.d.ts.map +1 -0
- package/dist/providers/google/utils.js +354 -0
- package/dist/providers/google/utils.js.map +1 -0
- package/dist/providers/openai/complete.d.ts +3 -0
- package/dist/providers/openai/complete.d.ts.map +1 -0
- package/dist/providers/openai/complete.js +57 -0
- package/dist/providers/openai/complete.js.map +1 -0
- package/dist/providers/openai/index.d.ts +4 -0
- package/dist/providers/openai/index.d.ts.map +1 -0
- package/dist/providers/openai/index.js +4 -0
- package/dist/providers/openai/index.js.map +1 -0
- package/dist/providers/openai/stream.d.ts +3 -0
- package/dist/providers/openai/stream.d.ts.map +1 -0
- package/dist/providers/{openai.js → openai/stream.js} +74 -152
- package/dist/providers/openai/stream.js.map +1 -0
- package/dist/providers/openai/types.d.ts +8 -0
- package/dist/providers/openai/types.d.ts.map +1 -0
- package/dist/providers/openai/types.js +2 -0
- package/dist/providers/openai/types.js.map +1 -0
- package/dist/providers/openai/utils.d.ts +13 -0
- package/dist/providers/openai/utils.d.ts.map +1 -0
- package/dist/providers/openai/utils.js +285 -0
- package/dist/providers/openai/utils.js.map +1 -0
- package/dist/types.d.ts +95 -87
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -9
- package/dist/types.js.map +1 -1
- package/dist/utils/event-stream.d.ts +2 -2
- package/dist/utils/event-stream.d.ts.map +1 -1
- package/dist/utils/event-stream.js +2 -7
- package/dist/utils/event-stream.js.map +1 -1
- package/dist/utils/json-parse.js +3 -6
- package/dist/utils/json-parse.js.map +1 -1
- package/dist/utils/overflow.d.ts +51 -0
- package/dist/utils/overflow.d.ts.map +1 -0
- package/dist/utils/overflow.js +106 -0
- package/dist/utils/overflow.js.map +1 -0
- package/dist/utils/sanitize-unicode.js +1 -4
- package/dist/utils/sanitize-unicode.js.map +1 -1
- package/dist/utils/uuid.d.ts +6 -0
- package/dist/utils/uuid.d.ts.map +1 -0
- package/dist/utils/uuid.js +9 -0
- package/dist/utils/uuid.js.map +1 -0
- package/dist/utils/validation.d.ts +10 -3
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +20 -12
- package/dist/utils/validation.js.map +1 -1
- package/package.json +47 -8
- package/biome.json +0 -43
- package/dist/agent/agent-loop.d.ts +0 -5
- package/dist/agent/agent-loop.d.ts.map +0 -1
- package/dist/agent/agent-loop.js +0 -219
- package/dist/agent/agent-loop.js.map +0 -1
- package/dist/providers/convert.d.ts +0 -6
- package/dist/providers/convert.d.ts.map +0 -1
- package/dist/providers/convert.js +0 -207
- package/dist/providers/convert.js.map +0 -1
- package/dist/providers/google.d.ts +0 -26
- package/dist/providers/google.d.ts.map +0 -1
- package/dist/providers/google.js.map +0 -1
- package/dist/providers/openai.d.ts +0 -17
- package/dist/providers/openai.d.ts.map +0 -1
- package/dist/providers/openai.js.map +0 -1
- package/dist/stream.d.ts +0 -4
- package/dist/stream.d.ts.map +0 -1
- package/dist/stream.js +0 -40
- package/dist/stream.js.map +0 -1
- package/dist/test-google-agent-loop.d.ts +0 -2
- package/dist/test-google-agent-loop.d.ts.map +0 -1
- package/dist/test-google-agent-loop.js +0 -186
- package/dist/test-google-agent-loop.js.map +0 -1
- package/dist/test-google.d.ts +0 -2
- package/dist/test-google.d.ts.map +0 -1
- package/dist/test-google.js +0 -41
- package/dist/test-google.js.map +0 -1
- package/src/agent/agent-loop.ts +0 -275
- package/src/agent/types.ts +0 -80
- package/src/index.ts +0 -72
- package/src/models.generated.ts +0 -314
- package/src/models.ts +0 -45
- package/src/providers/convert.ts +0 -222
- package/src/providers/google.ts +0 -496
- package/src/providers/openai.ts +0 -437
- package/src/stream.ts +0 -60
- package/src/types.ts +0 -198
- package/src/utils/event-stream.ts +0 -60
- package/src/utils/json-parse.ts +0 -28
- package/src/utils/sanitize-unicode.ts +0 -25
- package/src/utils/validation.ts +0 -69
- package/test/core/agent-loop.test.ts +0 -958
- package/test/core/stream.test.ts +0 -409
- package/test/data/red-circle.png +0 -0
- package/test/data/superintelligentwill.pdf +0 -0
- package/test/edge-cases/general.test.ts +0 -565
- package/test/integration/e2e.test.ts +0 -530
- package/test/models/cost.test.ts +0 -499
- package/test/models/registry.test.ts +0 -298
- package/test/providers/convert.test.ts +0 -846
- package/test/providers/google-schema.test.ts +0 -666
- package/test/providers/google-stream.test.ts +0 -369
- package/test/providers/openai-stream.test.ts +0 -251
- package/test/utils/event-stream.test.ts +0 -289
- package/test/utils/json-parse.test.ts +0 -344
- package/test/utils/sanitize-unicode.test.ts +0 -329
- package/test/utils/validation.test.ts +0 -614
- package/tsconfig.json +0 -21
- package/vitest.config.ts +0 -9
|
@@ -1,958 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { Type } from '@sinclair/typebox';
|
|
3
|
-
import { agentLoop } from '../../src/agent/agent-loop';
|
|
4
|
-
import { AgentContext, AgentLoopConfig, AgentTool, AgentEvent } from '../../src/agent/types';
|
|
5
|
-
import { UserMessage, AssistantMessage, NativeOpenAIMessage, Model } from '../../src/types';
|
|
6
|
-
import * as streamModule from '../../src/stream';
|
|
7
|
-
|
|
8
|
-
// Mock the stream module
|
|
9
|
-
vi.mock('../../src/stream');
|
|
10
|
-
|
|
11
|
-
// Clear mocks before each test
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
vi.clearAllMocks();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
const mockModel: Model<'openai'> = {
|
|
17
|
-
id: 'test-model',
|
|
18
|
-
name: 'Test Model',
|
|
19
|
-
api: 'openai',
|
|
20
|
-
baseUrl: 'https://api.openai.com',
|
|
21
|
-
reasoning: false,
|
|
22
|
-
input: ['text'],
|
|
23
|
-
cost: { input: 0.01, output: 0.03, cacheRead: 0, cacheWrite: 0 },
|
|
24
|
-
contextWindow: 128000,
|
|
25
|
-
maxTokens: 4096,
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
// Helper to create mock stream
|
|
29
|
-
function createMockStream(events: any[], result: any) {
|
|
30
|
-
const eventStream = {
|
|
31
|
-
async *[Symbol.asyncIterator]() {
|
|
32
|
-
for (const event of events) {
|
|
33
|
-
yield event;
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
result: async () => result,
|
|
37
|
-
};
|
|
38
|
-
return eventStream as any;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Helper to create mock assistant message
|
|
42
|
-
function createMockAssistantMessage(
|
|
43
|
-
content: AssistantMessage['content'],
|
|
44
|
-
stopReason: AssistantMessage['stopReason'] = 'stop'
|
|
45
|
-
): AssistantMessage {
|
|
46
|
-
return {
|
|
47
|
-
role: 'assistant',
|
|
48
|
-
content,
|
|
49
|
-
api: 'openai',
|
|
50
|
-
model: 'test-model',
|
|
51
|
-
usage: {
|
|
52
|
-
input: 10,
|
|
53
|
-
output: 5,
|
|
54
|
-
cacheRead: 0,
|
|
55
|
-
cacheWrite: 0,
|
|
56
|
-
totalTokens: 15,
|
|
57
|
-
cost: {
|
|
58
|
-
input: 0.0001,
|
|
59
|
-
output: 0.00015,
|
|
60
|
-
cacheRead: 0,
|
|
61
|
-
cacheWrite: 0,
|
|
62
|
-
total: 0.00025,
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
stopReason,
|
|
66
|
-
timestamp: Date.now(),
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Helper to create native message
|
|
71
|
-
function createNativeMessage(assistantMessage: AssistantMessage): NativeOpenAIMessage {
|
|
72
|
-
return {
|
|
73
|
-
role: 'assistant',
|
|
74
|
-
_provider: 'openai',
|
|
75
|
-
message: {
|
|
76
|
-
output: [],
|
|
77
|
-
id: 'resp_123',
|
|
78
|
-
object: "response",
|
|
79
|
-
created_at: 1740855869,
|
|
80
|
-
output_text: '',
|
|
81
|
-
status: "completed",
|
|
82
|
-
incomplete_details: null,
|
|
83
|
-
parallel_tool_calls: false,
|
|
84
|
-
error: null,
|
|
85
|
-
instructions: null,
|
|
86
|
-
max_output_tokens: null,
|
|
87
|
-
model: "gpt-4o-mini-2024-07-18",
|
|
88
|
-
user: undefined,
|
|
89
|
-
metadata: {},
|
|
90
|
-
previous_response_id: null,
|
|
91
|
-
temperature: 1,
|
|
92
|
-
text: {},
|
|
93
|
-
tool_choice: "auto",
|
|
94
|
-
tools: [],
|
|
95
|
-
top_p: 1,
|
|
96
|
-
truncation: "disabled",
|
|
97
|
-
usage: { input_tokens: 10, output_tokens: 5, total_tokens: 15, input_tokens_details: {cached_tokens: 0}, output_tokens_details: {reasoning_tokens: 0} },
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
describe('agentLoop - Basic Flow', () => {
|
|
103
|
-
it('should handle single turn without tool calls', async () => {
|
|
104
|
-
const prompt: UserMessage = {
|
|
105
|
-
role: 'user',
|
|
106
|
-
content: [{ type: 'text', content: 'Hello!' }],
|
|
107
|
-
timestamp: Date.now(),
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const context: AgentContext = {
|
|
111
|
-
messages: [],
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const assistantMessage = createMockAssistantMessage([
|
|
115
|
-
{ type: 'text', text: 'Hello! How can I help you?' },
|
|
116
|
-
]);
|
|
117
|
-
|
|
118
|
-
const nativeMessage = createNativeMessage(assistantMessage);
|
|
119
|
-
|
|
120
|
-
const mockStream = createMockStream(
|
|
121
|
-
[
|
|
122
|
-
{ type: 'start', partial: assistantMessage },
|
|
123
|
-
{ type: 'text_start', contentIndex: 0, partial: assistantMessage },
|
|
124
|
-
{ type: 'text_delta', contentIndex: 0, delta: 'Hello!', partial: assistantMessage },
|
|
125
|
-
{ type: 'text_end', contentIndex: 0, content: 'Hello! How can I help you?', partial: assistantMessage },
|
|
126
|
-
{ type: 'done', reason: 'stop', message: assistantMessage },
|
|
127
|
-
],
|
|
128
|
-
nativeMessage
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
vi.spyOn(streamModule, 'stream').mockReturnValue(mockStream);
|
|
132
|
-
|
|
133
|
-
const config: AgentLoopConfig<'openai'> = {
|
|
134
|
-
model: mockModel,
|
|
135
|
-
providerOptions: {},
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const eventStream = agentLoop(prompt, context, config);
|
|
139
|
-
const events: AgentEvent[] = [];
|
|
140
|
-
|
|
141
|
-
for await (const event of eventStream) {
|
|
142
|
-
events.push(event);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const messages = await eventStream.result();
|
|
146
|
-
|
|
147
|
-
// Verify events
|
|
148
|
-
expect(events).toContainEqual({ type: 'agent_start' });
|
|
149
|
-
expect(events).toContainEqual({ type: 'turn_start' });
|
|
150
|
-
expect(events).toContainEqual(expect.objectContaining({ type: 'message_start' }));
|
|
151
|
-
expect(events).toContainEqual(expect.objectContaining({ type: 'message_end' }));
|
|
152
|
-
expect(events).toContainEqual(expect.objectContaining({ type: 'turn_end' }));
|
|
153
|
-
expect(events).toContainEqual(expect.objectContaining({ type: 'agent_end', status: 'completed' }));
|
|
154
|
-
|
|
155
|
-
// Verify messages
|
|
156
|
-
expect(messages).toHaveLength(2); // prompt + assistant response
|
|
157
|
-
expect(messages[0]).toEqual(prompt);
|
|
158
|
-
expect(messages[1]).toEqual(nativeMessage);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('should add initial prompt to messages', async () => {
|
|
162
|
-
const prompt: UserMessage = {
|
|
163
|
-
role: 'user',
|
|
164
|
-
content: [{ type: 'text', content: 'Test prompt' }],
|
|
165
|
-
timestamp: Date.now(),
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const context: AgentContext = {
|
|
169
|
-
messages: [],
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
const assistantMessage = createMockAssistantMessage([
|
|
173
|
-
{ type: 'text', text: 'Response' },
|
|
174
|
-
]);
|
|
175
|
-
|
|
176
|
-
const mockStream = createMockStream(
|
|
177
|
-
[
|
|
178
|
-
{ type: 'start', partial: assistantMessage },
|
|
179
|
-
{ type: 'done', reason: 'stop', message: assistantMessage },
|
|
180
|
-
],
|
|
181
|
-
createNativeMessage(assistantMessage)
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
vi.spyOn(streamModule, 'stream').mockReturnValue(mockStream);
|
|
185
|
-
|
|
186
|
-
const config: AgentLoopConfig<'openai'> = {
|
|
187
|
-
model: mockModel,
|
|
188
|
-
providerOptions: {},
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
const eventStream = agentLoop(prompt, context, config);
|
|
192
|
-
const messages = await eventStream.result();
|
|
193
|
-
|
|
194
|
-
expect(messages[0]).toEqual(prompt);
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('should emit agent start and end events', async () => {
|
|
198
|
-
const prompt: UserMessage = {
|
|
199
|
-
role: 'user',
|
|
200
|
-
content: [{ type: 'text', content: 'Hello' }],
|
|
201
|
-
timestamp: Date.now(),
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
const context: AgentContext = {
|
|
205
|
-
messages: [],
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
const assistantMessage = createMockAssistantMessage([
|
|
209
|
-
{ type: 'text', text: 'Hi!' },
|
|
210
|
-
]);
|
|
211
|
-
|
|
212
|
-
const mockStream = createMockStream(
|
|
213
|
-
[
|
|
214
|
-
{ type: 'start', partial: assistantMessage },
|
|
215
|
-
{ type: 'done', reason: 'stop', message: assistantMessage },
|
|
216
|
-
],
|
|
217
|
-
createNativeMessage(assistantMessage)
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
vi.spyOn(streamModule, 'stream').mockReturnValue(mockStream);
|
|
221
|
-
|
|
222
|
-
const config: AgentLoopConfig<'openai'> = {
|
|
223
|
-
model: mockModel,
|
|
224
|
-
providerOptions: {},
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
const eventStream = agentLoop(prompt, context, config);
|
|
228
|
-
const events: AgentEvent[] = [];
|
|
229
|
-
|
|
230
|
-
for await (const event of eventStream) {
|
|
231
|
-
events.push(event);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
expect(events[0]).toEqual({ type: 'agent_start' });
|
|
235
|
-
expect(events[events.length - 1]).toMatchObject({
|
|
236
|
-
type: 'agent_end',
|
|
237
|
-
status: 'completed',
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
it('should emit turn start and end events', async () => {
|
|
242
|
-
const prompt: UserMessage = {
|
|
243
|
-
role: 'user',
|
|
244
|
-
content: [{ type: 'text', content: 'Hello' }],
|
|
245
|
-
timestamp: Date.now(),
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
const context: AgentContext = {
|
|
249
|
-
messages: [],
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
const assistantMessage = createMockAssistantMessage([
|
|
253
|
-
{ type: 'text', text: 'Hi!' },
|
|
254
|
-
]);
|
|
255
|
-
|
|
256
|
-
const mockStream = createMockStream(
|
|
257
|
-
[
|
|
258
|
-
{ type: 'start', partial: assistantMessage },
|
|
259
|
-
{ type: 'done', reason: 'stop', message: assistantMessage },
|
|
260
|
-
],
|
|
261
|
-
createNativeMessage(assistantMessage)
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
vi.spyOn(streamModule, 'stream').mockReturnValue(mockStream);
|
|
265
|
-
|
|
266
|
-
const config: AgentLoopConfig<'openai'> = {
|
|
267
|
-
model: mockModel,
|
|
268
|
-
providerOptions: {},
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
const eventStream = agentLoop(prompt, context, config);
|
|
272
|
-
const events: AgentEvent[] = [];
|
|
273
|
-
|
|
274
|
-
for await (const event of eventStream) {
|
|
275
|
-
events.push(event);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const turnStarts = events.filter(e => e.type === 'turn_start');
|
|
279
|
-
const turnEnds = events.filter(e => e.type === 'turn_end');
|
|
280
|
-
|
|
281
|
-
expect(turnStarts).toHaveLength(1);
|
|
282
|
-
expect(turnEnds).toHaveLength(1);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
it('should preserve message order', async () => {
|
|
286
|
-
const prompt: UserMessage = {
|
|
287
|
-
role: 'user',
|
|
288
|
-
content: [{ type: 'text', content: 'Hello' }],
|
|
289
|
-
timestamp: Date.now(),
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
const context: AgentContext = {
|
|
293
|
-
messages: [],
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
const assistantMessage = createMockAssistantMessage([
|
|
297
|
-
{ type: 'text', text: 'Response' },
|
|
298
|
-
]);
|
|
299
|
-
|
|
300
|
-
const mockStream = createMockStream(
|
|
301
|
-
[
|
|
302
|
-
{ type: 'start', partial: assistantMessage },
|
|
303
|
-
{ type: 'done', reason: 'stop', message: assistantMessage },
|
|
304
|
-
],
|
|
305
|
-
createNativeMessage(assistantMessage)
|
|
306
|
-
);
|
|
307
|
-
|
|
308
|
-
vi.spyOn(streamModule, 'stream').mockReturnValue(mockStream);
|
|
309
|
-
|
|
310
|
-
const config: AgentLoopConfig<'openai'> = {
|
|
311
|
-
model: mockModel,
|
|
312
|
-
providerOptions: {},
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
const eventStream = agentLoop(prompt, context, config);
|
|
316
|
-
const messages = await eventStream.result();
|
|
317
|
-
|
|
318
|
-
expect(messages[0].role).toBe('user');
|
|
319
|
-
expect(messages[1].role).toBe('assistant');
|
|
320
|
-
});
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
describe('agentLoop - Tool Execution', () => {
|
|
324
|
-
it('should execute single tool call', async () => {
|
|
325
|
-
const calculatorTool: AgentTool = {
|
|
326
|
-
name: 'calculator',
|
|
327
|
-
description: 'Perform calculations',
|
|
328
|
-
label: 'Calculator',
|
|
329
|
-
parameters: Type.Object({
|
|
330
|
-
expression: Type.String(),
|
|
331
|
-
}),
|
|
332
|
-
async execute(toolCallId, params) {
|
|
333
|
-
return {
|
|
334
|
-
content: [{ type: 'text', content: `Result: ${eval((params as any).expression)}` }],
|
|
335
|
-
details: { result: eval((params as any).expression) },
|
|
336
|
-
};
|
|
337
|
-
},
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
const prompt: UserMessage = {
|
|
341
|
-
role: 'user',
|
|
342
|
-
content: [{ type: 'text', content: 'Calculate 2 + 2' }],
|
|
343
|
-
timestamp: Date.now(),
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
const context: AgentContext = {
|
|
347
|
-
messages: [],
|
|
348
|
-
tools: [calculatorTool],
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
// First turn - assistant calls tool
|
|
352
|
-
const assistantMessage1 = createMockAssistantMessage(
|
|
353
|
-
[
|
|
354
|
-
{
|
|
355
|
-
type: 'toolCall',
|
|
356
|
-
name: 'calculator',
|
|
357
|
-
arguments: { expression: '2 + 2' },
|
|
358
|
-
id: 'call_123',
|
|
359
|
-
},
|
|
360
|
-
],
|
|
361
|
-
'toolUse'
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
// Second turn - assistant responds with result
|
|
365
|
-
const assistantMessage2 = createMockAssistantMessage([
|
|
366
|
-
{ type: 'text', text: 'The result is 4' },
|
|
367
|
-
]);
|
|
368
|
-
|
|
369
|
-
let callCount = 0;
|
|
370
|
-
vi.spyOn(streamModule, 'stream').mockImplementation(() => {
|
|
371
|
-
callCount++;
|
|
372
|
-
if (callCount === 1) {
|
|
373
|
-
return createMockStream(
|
|
374
|
-
[
|
|
375
|
-
{ type: 'start', partial: assistantMessage1 },
|
|
376
|
-
{ type: 'done', reason: 'toolUse', message: assistantMessage1 },
|
|
377
|
-
],
|
|
378
|
-
createNativeMessage(assistantMessage1)
|
|
379
|
-
);
|
|
380
|
-
} else {
|
|
381
|
-
return createMockStream(
|
|
382
|
-
[
|
|
383
|
-
{ type: 'start', partial: assistantMessage2 },
|
|
384
|
-
{ type: 'done', reason: 'stop', message: assistantMessage2 },
|
|
385
|
-
],
|
|
386
|
-
createNativeMessage(assistantMessage2)
|
|
387
|
-
);
|
|
388
|
-
}
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
const config: AgentLoopConfig<'openai'> = {
|
|
392
|
-
model: mockModel,
|
|
393
|
-
providerOptions: {},
|
|
394
|
-
};
|
|
395
|
-
|
|
396
|
-
const eventStream = agentLoop(prompt, context, config);
|
|
397
|
-
const events: AgentEvent[] = [];
|
|
398
|
-
|
|
399
|
-
for await (const event of eventStream) {
|
|
400
|
-
events.push(event);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const toolExecutionStarts = events.filter(e => e.type === 'tool_execution_start');
|
|
404
|
-
const toolExecutionEnds = events.filter(e => e.type === 'tool_execution_end');
|
|
405
|
-
|
|
406
|
-
expect(toolExecutionStarts).toHaveLength(1);
|
|
407
|
-
expect(toolExecutionEnds).toHaveLength(1);
|
|
408
|
-
expect(toolExecutionStarts[0]).toMatchObject({
|
|
409
|
-
type: 'tool_execution_start',
|
|
410
|
-
toolName: 'calculator',
|
|
411
|
-
toolCallId: 'call_123',
|
|
412
|
-
});
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
it('should add tool result to context', async () => {
|
|
416
|
-
const tool: AgentTool = {
|
|
417
|
-
name: 'test_tool',
|
|
418
|
-
description: 'Test tool',
|
|
419
|
-
label: 'Test',
|
|
420
|
-
parameters: Type.Object({}),
|
|
421
|
-
async execute() {
|
|
422
|
-
return {
|
|
423
|
-
content: [{ type: 'text', content: 'Tool result' }],
|
|
424
|
-
details: {},
|
|
425
|
-
};
|
|
426
|
-
},
|
|
427
|
-
};
|
|
428
|
-
|
|
429
|
-
const prompt: UserMessage = {
|
|
430
|
-
role: 'user',
|
|
431
|
-
content: [{ type: 'text', content: 'Use the tool' }],
|
|
432
|
-
timestamp: Date.now(),
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
const context: AgentContext = {
|
|
436
|
-
messages: [],
|
|
437
|
-
tools: [tool],
|
|
438
|
-
};
|
|
439
|
-
|
|
440
|
-
const assistantMessage1 = createMockAssistantMessage(
|
|
441
|
-
[{ type: 'toolCall', name: 'test_tool', arguments: {}, id: 'call_1' }],
|
|
442
|
-
'toolUse'
|
|
443
|
-
);
|
|
444
|
-
|
|
445
|
-
const assistantMessage2 = createMockAssistantMessage([
|
|
446
|
-
{ type: 'text', text: 'Done' },
|
|
447
|
-
]);
|
|
448
|
-
|
|
449
|
-
let callCount = 0;
|
|
450
|
-
vi.spyOn(streamModule, 'stream').mockImplementation(() => {
|
|
451
|
-
callCount++;
|
|
452
|
-
if (callCount === 1) {
|
|
453
|
-
return createMockStream(
|
|
454
|
-
[{ type: 'done', reason: 'toolUse', message: assistantMessage1 }],
|
|
455
|
-
createNativeMessage(assistantMessage1)
|
|
456
|
-
);
|
|
457
|
-
} else {
|
|
458
|
-
return createMockStream(
|
|
459
|
-
[{ type: 'done', reason: 'stop', message: assistantMessage2 }],
|
|
460
|
-
createNativeMessage(assistantMessage2)
|
|
461
|
-
);
|
|
462
|
-
}
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
const config: AgentLoopConfig<'openai'> = {
|
|
466
|
-
model: mockModel,
|
|
467
|
-
providerOptions: {},
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
const eventStream = agentLoop(prompt, context, config);
|
|
471
|
-
const messages = await eventStream.result();
|
|
472
|
-
|
|
473
|
-
// Should have: prompt, assistant1, tool result, assistant2
|
|
474
|
-
expect(messages).toHaveLength(4);
|
|
475
|
-
expect(messages[2].role).toBe('toolResult');
|
|
476
|
-
expect((messages[2] as any).toolName).toBe('test_tool');
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
it('should handle tool not found error', async () => {
|
|
480
|
-
const prompt: UserMessage = {
|
|
481
|
-
role: 'user',
|
|
482
|
-
content: [{ type: 'text', content: 'Use unknown tool' }],
|
|
483
|
-
timestamp: Date.now(),
|
|
484
|
-
};
|
|
485
|
-
|
|
486
|
-
const context: AgentContext = {
|
|
487
|
-
messages: [],
|
|
488
|
-
tools: [], // No tools defined
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
const assistantMessage1 = createMockAssistantMessage(
|
|
492
|
-
[{ type: 'toolCall', name: 'unknown_tool', arguments: {}, id: 'call_1' }],
|
|
493
|
-
'toolUse'
|
|
494
|
-
);
|
|
495
|
-
|
|
496
|
-
const assistantMessage2 = createMockAssistantMessage([
|
|
497
|
-
{ type: 'text', text: 'Error handled' },
|
|
498
|
-
]);
|
|
499
|
-
|
|
500
|
-
let callCount = 0;
|
|
501
|
-
vi.spyOn(streamModule, 'stream').mockImplementation(() => {
|
|
502
|
-
callCount++;
|
|
503
|
-
if (callCount === 1) {
|
|
504
|
-
return createMockStream(
|
|
505
|
-
[{ type: 'done', reason: 'toolUse', message: assistantMessage1 }],
|
|
506
|
-
createNativeMessage(assistantMessage1)
|
|
507
|
-
);
|
|
508
|
-
} else {
|
|
509
|
-
return createMockStream(
|
|
510
|
-
[{ type: 'done', reason: 'stop', message: assistantMessage2 }],
|
|
511
|
-
createNativeMessage(assistantMessage2)
|
|
512
|
-
);
|
|
513
|
-
}
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
const config: AgentLoopConfig<'openai'> = {
|
|
517
|
-
model: mockModel,
|
|
518
|
-
providerOptions: {},
|
|
519
|
-
};
|
|
520
|
-
|
|
521
|
-
const eventStream = agentLoop(prompt, context, config);
|
|
522
|
-
const events: AgentEvent[] = [];
|
|
523
|
-
|
|
524
|
-
for await (const event of eventStream) {
|
|
525
|
-
events.push(event);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
const toolExecutionEnd = events.find(e => e.type === 'tool_execution_end') as any;
|
|
529
|
-
expect(toolExecutionEnd.isError).toBe(true);
|
|
530
|
-
expect(toolExecutionEnd.result).toContain('not found');
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
it('should handle tool execution error', async () => {
|
|
534
|
-
const failingTool: AgentTool = {
|
|
535
|
-
name: 'failing_tool',
|
|
536
|
-
description: 'A tool that fails',
|
|
537
|
-
label: 'Failing',
|
|
538
|
-
parameters: Type.Object({}),
|
|
539
|
-
async execute() {
|
|
540
|
-
throw new Error('Tool execution failed');
|
|
541
|
-
},
|
|
542
|
-
};
|
|
543
|
-
|
|
544
|
-
const prompt: UserMessage = {
|
|
545
|
-
role: 'user',
|
|
546
|
-
content: [{ type: 'text', content: 'Use failing tool' }],
|
|
547
|
-
timestamp: Date.now(),
|
|
548
|
-
};
|
|
549
|
-
|
|
550
|
-
const context: AgentContext = {
|
|
551
|
-
messages: [],
|
|
552
|
-
tools: [failingTool],
|
|
553
|
-
};
|
|
554
|
-
|
|
555
|
-
const assistantMessage1 = createMockAssistantMessage(
|
|
556
|
-
[{ type: 'toolCall', name: 'failing_tool', arguments: {}, id: 'call_1' }],
|
|
557
|
-
'toolUse'
|
|
558
|
-
);
|
|
559
|
-
|
|
560
|
-
const assistantMessage2 = createMockAssistantMessage([
|
|
561
|
-
{ type: 'text', text: 'Error handled' },
|
|
562
|
-
]);
|
|
563
|
-
|
|
564
|
-
let callCount = 0;
|
|
565
|
-
vi.spyOn(streamModule, 'stream').mockImplementation(() => {
|
|
566
|
-
callCount++;
|
|
567
|
-
if (callCount === 1) {
|
|
568
|
-
return createMockStream(
|
|
569
|
-
[{ type: 'done', reason: 'toolUse', message: assistantMessage1 }],
|
|
570
|
-
createNativeMessage(assistantMessage1)
|
|
571
|
-
);
|
|
572
|
-
} else {
|
|
573
|
-
return createMockStream(
|
|
574
|
-
[{ type: 'done', reason: 'stop', message: assistantMessage2 }],
|
|
575
|
-
createNativeMessage(assistantMessage2)
|
|
576
|
-
);
|
|
577
|
-
}
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
const config: AgentLoopConfig<'openai'> = {
|
|
581
|
-
model: mockModel,
|
|
582
|
-
providerOptions: {},
|
|
583
|
-
};
|
|
584
|
-
|
|
585
|
-
const eventStream = agentLoop(prompt, context, config);
|
|
586
|
-
const events: AgentEvent[] = [];
|
|
587
|
-
|
|
588
|
-
for await (const event of eventStream) {
|
|
589
|
-
events.push(event);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
const toolExecutionEnd = events.find(e => e.type === 'tool_execution_end') as any;
|
|
593
|
-
expect(toolExecutionEnd.isError).toBe(true);
|
|
594
|
-
expect(toolExecutionEnd.result).toBe('Tool execution failed');
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
it('should create tool result with error details', async () => {
|
|
598
|
-
const failingTool: AgentTool = {
|
|
599
|
-
name: 'error_tool',
|
|
600
|
-
description: 'Tool with error',
|
|
601
|
-
label: 'Error',
|
|
602
|
-
parameters: Type.Object({}),
|
|
603
|
-
async execute() {
|
|
604
|
-
const error = new Error('Custom error');
|
|
605
|
-
error.name = 'CustomError';
|
|
606
|
-
throw error;
|
|
607
|
-
},
|
|
608
|
-
};
|
|
609
|
-
|
|
610
|
-
const prompt: UserMessage = {
|
|
611
|
-
role: 'user',
|
|
612
|
-
content: [{ type: 'text', content: 'Test' }],
|
|
613
|
-
timestamp: Date.now(),
|
|
614
|
-
};
|
|
615
|
-
|
|
616
|
-
const context: AgentContext = {
|
|
617
|
-
messages: [],
|
|
618
|
-
tools: [failingTool],
|
|
619
|
-
};
|
|
620
|
-
|
|
621
|
-
const assistantMessage1 = createMockAssistantMessage(
|
|
622
|
-
[{ type: 'toolCall', name: 'error_tool', arguments: {}, id: 'call_1' }],
|
|
623
|
-
'toolUse'
|
|
624
|
-
);
|
|
625
|
-
|
|
626
|
-
const assistantMessage2 = createMockAssistantMessage([
|
|
627
|
-
{ type: 'text', text: 'Done' },
|
|
628
|
-
]);
|
|
629
|
-
|
|
630
|
-
let callCount = 0;
|
|
631
|
-
vi.spyOn(streamModule, 'stream').mockImplementation(() => {
|
|
632
|
-
callCount++;
|
|
633
|
-
if (callCount === 1) {
|
|
634
|
-
return createMockStream(
|
|
635
|
-
[{ type: 'done', reason: 'toolUse', message: assistantMessage1 }],
|
|
636
|
-
createNativeMessage(assistantMessage1)
|
|
637
|
-
);
|
|
638
|
-
} else {
|
|
639
|
-
return createMockStream(
|
|
640
|
-
[{ type: 'done', reason: 'stop', message: assistantMessage2 }],
|
|
641
|
-
createNativeMessage(assistantMessage2)
|
|
642
|
-
);
|
|
643
|
-
}
|
|
644
|
-
});
|
|
645
|
-
|
|
646
|
-
const config: AgentLoopConfig<'openai'> = {
|
|
647
|
-
model: mockModel,
|
|
648
|
-
providerOptions: {},
|
|
649
|
-
};
|
|
650
|
-
|
|
651
|
-
const eventStream = agentLoop(prompt, context, config);
|
|
652
|
-
const messages = await eventStream.result();
|
|
653
|
-
|
|
654
|
-
const toolResult = messages.find(m => m.role === 'toolResult') as any;
|
|
655
|
-
expect(toolResult.isError).toBe(true);
|
|
656
|
-
expect(toolResult.error).toMatchObject({
|
|
657
|
-
message: 'Custom error',
|
|
658
|
-
name: 'CustomError',
|
|
659
|
-
});
|
|
660
|
-
expect(toolResult.error.stack).toBeDefined();
|
|
661
|
-
});
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
describe('agentLoop - Multi-turn Conversation', () => {
|
|
665
|
-
it('should handle multiple tool-using turns', async () => {
|
|
666
|
-
const tool: AgentTool = {
|
|
667
|
-
name: 'count',
|
|
668
|
-
description: 'Count',
|
|
669
|
-
label: 'Count',
|
|
670
|
-
parameters: Type.Object({}),
|
|
671
|
-
async execute() {
|
|
672
|
-
return {
|
|
673
|
-
content: [{ type: 'text', content: 'counted' }],
|
|
674
|
-
details: {},
|
|
675
|
-
};
|
|
676
|
-
},
|
|
677
|
-
};
|
|
678
|
-
|
|
679
|
-
const prompt: UserMessage = {
|
|
680
|
-
role: 'user',
|
|
681
|
-
content: [{ type: 'text', content: 'Count twice' }],
|
|
682
|
-
timestamp: Date.now(),
|
|
683
|
-
};
|
|
684
|
-
|
|
685
|
-
const context: AgentContext = {
|
|
686
|
-
messages: [],
|
|
687
|
-
tools: [tool],
|
|
688
|
-
};
|
|
689
|
-
|
|
690
|
-
const messages = [
|
|
691
|
-
createMockAssistantMessage(
|
|
692
|
-
[{ type: 'toolCall', name: 'count', arguments: {}, id: 'call_1' }],
|
|
693
|
-
'toolUse'
|
|
694
|
-
),
|
|
695
|
-
createMockAssistantMessage(
|
|
696
|
-
[{ type: 'toolCall', name: 'count', arguments: {}, id: 'call_2' }],
|
|
697
|
-
'toolUse'
|
|
698
|
-
),
|
|
699
|
-
createMockAssistantMessage([{ type: 'text', text: 'Done counting twice' }]),
|
|
700
|
-
];
|
|
701
|
-
|
|
702
|
-
let callCount = 0;
|
|
703
|
-
vi.spyOn(streamModule, 'stream').mockImplementation(() => {
|
|
704
|
-
const msg = messages[callCount];
|
|
705
|
-
callCount++;
|
|
706
|
-
return createMockStream(
|
|
707
|
-
[{ type: 'done', reason: msg.stopReason, message: msg }],
|
|
708
|
-
createNativeMessage(msg)
|
|
709
|
-
);
|
|
710
|
-
});
|
|
711
|
-
|
|
712
|
-
const config: AgentLoopConfig<'openai'> = {
|
|
713
|
-
model: mockModel,
|
|
714
|
-
providerOptions: {},
|
|
715
|
-
};
|
|
716
|
-
|
|
717
|
-
const eventStream = agentLoop(prompt, context, config);
|
|
718
|
-
const result = await eventStream.result();
|
|
719
|
-
|
|
720
|
-
// prompt + assistant1 + tool1 + assistant2 + tool2 + assistant3 = 6
|
|
721
|
-
expect(result).toHaveLength(6);
|
|
722
|
-
expect(streamModule.stream).toHaveBeenCalledTimes(3);
|
|
723
|
-
});
|
|
724
|
-
|
|
725
|
-
it('should accumulate context across turns', async () => {
|
|
726
|
-
const tool: AgentTool = {
|
|
727
|
-
name: 'test',
|
|
728
|
-
description: 'Test',
|
|
729
|
-
label: 'Test',
|
|
730
|
-
parameters: Type.Object({}),
|
|
731
|
-
async execute() {
|
|
732
|
-
return {
|
|
733
|
-
content: [{ type: 'text', content: 'result' }],
|
|
734
|
-
details: {},
|
|
735
|
-
};
|
|
736
|
-
},
|
|
737
|
-
};
|
|
738
|
-
|
|
739
|
-
const prompt: UserMessage = {
|
|
740
|
-
role: 'user',
|
|
741
|
-
content: [{ type: 'text', content: 'Test' }],
|
|
742
|
-
timestamp: Date.now(),
|
|
743
|
-
};
|
|
744
|
-
|
|
745
|
-
const context: AgentContext = {
|
|
746
|
-
messages: [],
|
|
747
|
-
tools: [tool],
|
|
748
|
-
};
|
|
749
|
-
|
|
750
|
-
const assistantMessage1 = createMockAssistantMessage(
|
|
751
|
-
[{ type: 'toolCall', name: 'test', arguments: {}, id: 'call_1' }],
|
|
752
|
-
'toolUse'
|
|
753
|
-
);
|
|
754
|
-
|
|
755
|
-
const assistantMessage2 = createMockAssistantMessage([
|
|
756
|
-
{ type: 'text', text: 'Final response' },
|
|
757
|
-
]);
|
|
758
|
-
|
|
759
|
-
const streamSpy = vi.spyOn(streamModule, 'stream');
|
|
760
|
-
|
|
761
|
-
// Use mockReturnValueOnce for sequential calls
|
|
762
|
-
streamSpy
|
|
763
|
-
.mockReturnValueOnce(
|
|
764
|
-
createMockStream(
|
|
765
|
-
[{ type: 'done', reason: 'toolUse', message: assistantMessage1 }],
|
|
766
|
-
createNativeMessage(assistantMessage1)
|
|
767
|
-
)
|
|
768
|
-
)
|
|
769
|
-
.mockReturnValueOnce(
|
|
770
|
-
createMockStream(
|
|
771
|
-
[{ type: 'done', reason: 'stop', message: assistantMessage2 }],
|
|
772
|
-
createNativeMessage(assistantMessage2)
|
|
773
|
-
)
|
|
774
|
-
);
|
|
775
|
-
|
|
776
|
-
const config: AgentLoopConfig<'openai'> = {
|
|
777
|
-
model: mockModel,
|
|
778
|
-
providerOptions: {},
|
|
779
|
-
};
|
|
780
|
-
|
|
781
|
-
const eventStream = agentLoop(prompt, context, config);
|
|
782
|
-
await eventStream.result();
|
|
783
|
-
|
|
784
|
-
// Verify the stream was called twice
|
|
785
|
-
expect(streamSpy).toHaveBeenCalledTimes(2);
|
|
786
|
-
|
|
787
|
-
// Verify first call had prompt only
|
|
788
|
-
expect(streamSpy.mock.calls[0][1].messages).toHaveLength(1);
|
|
789
|
-
|
|
790
|
-
// Verify second call had prompt + assistant1 + tool result
|
|
791
|
-
expect(streamSpy.mock.calls[1][1].messages).toHaveLength(3);
|
|
792
|
-
});
|
|
793
|
-
});
|
|
794
|
-
|
|
795
|
-
describe('agentLoop - Error Handling', () => {
|
|
796
|
-
it('should handle abort signal during streaming', async () => {
|
|
797
|
-
const prompt: UserMessage = {
|
|
798
|
-
role: 'user',
|
|
799
|
-
content: [{ type: 'text', content: 'Test' }],
|
|
800
|
-
timestamp: Date.now(),
|
|
801
|
-
};
|
|
802
|
-
|
|
803
|
-
const context: AgentContext = {
|
|
804
|
-
messages: [],
|
|
805
|
-
};
|
|
806
|
-
|
|
807
|
-
const assistantMessage = createMockAssistantMessage([], 'aborted');
|
|
808
|
-
|
|
809
|
-
const mockStream = createMockStream(
|
|
810
|
-
[{ type: 'error', reason: 'aborted', error: assistantMessage }],
|
|
811
|
-
createNativeMessage(assistantMessage)
|
|
812
|
-
);
|
|
813
|
-
|
|
814
|
-
vi.spyOn(streamModule, 'stream').mockReturnValue(mockStream);
|
|
815
|
-
|
|
816
|
-
const config: AgentLoopConfig<'openai'> = {
|
|
817
|
-
model: mockModel,
|
|
818
|
-
providerOptions: {},
|
|
819
|
-
};
|
|
820
|
-
|
|
821
|
-
const abortController = new AbortController();
|
|
822
|
-
const eventStream = agentLoop(prompt, context, config, abortController.signal);
|
|
823
|
-
|
|
824
|
-
const events: AgentEvent[] = [];
|
|
825
|
-
for await (const event of eventStream) {
|
|
826
|
-
events.push(event);
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
expect(events).toContainEqual(
|
|
830
|
-
expect.objectContaining({ type: 'agent_end', status: 'aborted' })
|
|
831
|
-
);
|
|
832
|
-
});
|
|
833
|
-
|
|
834
|
-
it('should handle error stop reason', async () => {
|
|
835
|
-
const prompt: UserMessage = {
|
|
836
|
-
role: 'user',
|
|
837
|
-
content: [{ type: 'text', content: 'Test' }],
|
|
838
|
-
timestamp: Date.now(),
|
|
839
|
-
};
|
|
840
|
-
|
|
841
|
-
const context: AgentContext = {
|
|
842
|
-
messages: [],
|
|
843
|
-
};
|
|
844
|
-
|
|
845
|
-
const assistantMessage = createMockAssistantMessage([], 'error');
|
|
846
|
-
assistantMessage.errorMessage = 'API Error';
|
|
847
|
-
|
|
848
|
-
const mockStream = createMockStream(
|
|
849
|
-
[{ type: 'error', reason: 'error', error: assistantMessage }],
|
|
850
|
-
createNativeMessage(assistantMessage)
|
|
851
|
-
);
|
|
852
|
-
|
|
853
|
-
vi.spyOn(streamModule, 'stream').mockReturnValue(mockStream);
|
|
854
|
-
|
|
855
|
-
const config: AgentLoopConfig<'openai'> = {
|
|
856
|
-
model: mockModel,
|
|
857
|
-
providerOptions: {},
|
|
858
|
-
};
|
|
859
|
-
|
|
860
|
-
const eventStream = agentLoop(prompt, context, config);
|
|
861
|
-
const events: AgentEvent[] = [];
|
|
862
|
-
|
|
863
|
-
for await (const event of eventStream) {
|
|
864
|
-
events.push(event);
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
expect(events).toContainEqual(
|
|
868
|
-
expect.objectContaining({ type: 'agent_end', status: 'error' })
|
|
869
|
-
);
|
|
870
|
-
});
|
|
871
|
-
});
|
|
872
|
-
|
|
873
|
-
describe('agentLoop - Message Events', () => {
|
|
874
|
-
it('should emit message_start and message_end for prompt', async () => {
|
|
875
|
-
const prompt: UserMessage = {
|
|
876
|
-
role: 'user',
|
|
877
|
-
content: [{ type: 'text', content: 'Hello' }],
|
|
878
|
-
timestamp: Date.now(),
|
|
879
|
-
};
|
|
880
|
-
|
|
881
|
-
const context: AgentContext = {
|
|
882
|
-
messages: [],
|
|
883
|
-
};
|
|
884
|
-
|
|
885
|
-
const assistantMessage = createMockAssistantMessage([
|
|
886
|
-
{ type: 'text', text: 'Hi' },
|
|
887
|
-
]);
|
|
888
|
-
|
|
889
|
-
const mockStream = createMockStream(
|
|
890
|
-
[{ type: 'done', reason: 'stop', message: assistantMessage }],
|
|
891
|
-
createNativeMessage(assistantMessage)
|
|
892
|
-
);
|
|
893
|
-
|
|
894
|
-
vi.spyOn(streamModule, 'stream').mockReturnValue(mockStream);
|
|
895
|
-
|
|
896
|
-
const config: AgentLoopConfig<'openai'> = {
|
|
897
|
-
model: mockModel,
|
|
898
|
-
providerOptions: {},
|
|
899
|
-
};
|
|
900
|
-
|
|
901
|
-
const eventStream = agentLoop(prompt, context, config);
|
|
902
|
-
const events: AgentEvent[] = [];
|
|
903
|
-
|
|
904
|
-
for await (const event of eventStream) {
|
|
905
|
-
events.push(event);
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
const messageStarts = events.filter(e => e.type === 'message_start');
|
|
909
|
-
const messageEnds = events.filter(e => e.type === 'message_end');
|
|
910
|
-
|
|
911
|
-
expect(messageStarts.length).toBeGreaterThan(0);
|
|
912
|
-
expect(messageEnds.length).toBeGreaterThan(0);
|
|
913
|
-
});
|
|
914
|
-
|
|
915
|
-
it('should emit message_update events during streaming', async () => {
|
|
916
|
-
const prompt: UserMessage = {
|
|
917
|
-
role: 'user',
|
|
918
|
-
content: [{ type: 'text', content: 'Hello' }],
|
|
919
|
-
timestamp: Date.now(),
|
|
920
|
-
};
|
|
921
|
-
|
|
922
|
-
const context: AgentContext = {
|
|
923
|
-
messages: [],
|
|
924
|
-
};
|
|
925
|
-
|
|
926
|
-
const assistantMessage = createMockAssistantMessage([
|
|
927
|
-
{ type: 'text', text: 'Response' },
|
|
928
|
-
]);
|
|
929
|
-
|
|
930
|
-
const mockStream = createMockStream(
|
|
931
|
-
[
|
|
932
|
-
{ type: 'start', partial: assistantMessage },
|
|
933
|
-
{ type: 'text_start', contentIndex: 0, partial: assistantMessage },
|
|
934
|
-
{ type: 'text_delta', contentIndex: 0, delta: 'R', partial: assistantMessage },
|
|
935
|
-
{ type: 'text_delta', contentIndex: 0, delta: 'e', partial: assistantMessage },
|
|
936
|
-
{ type: 'done', reason: 'stop', message: assistantMessage },
|
|
937
|
-
],
|
|
938
|
-
createNativeMessage(assistantMessage)
|
|
939
|
-
);
|
|
940
|
-
|
|
941
|
-
vi.spyOn(streamModule, 'stream').mockReturnValue(mockStream);
|
|
942
|
-
|
|
943
|
-
const config: AgentLoopConfig<'openai'> = {
|
|
944
|
-
model: mockModel,
|
|
945
|
-
providerOptions: {},
|
|
946
|
-
};
|
|
947
|
-
|
|
948
|
-
const eventStream = agentLoop(prompt, context, config);
|
|
949
|
-
const events: AgentEvent[] = [];
|
|
950
|
-
|
|
951
|
-
for await (const event of eventStream) {
|
|
952
|
-
events.push(event);
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
const messageUpdates = events.filter(e => e.type === 'message_update');
|
|
956
|
-
expect(messageUpdates.length).toBeGreaterThan(0);
|
|
957
|
-
});
|
|
958
|
-
});
|