@providerprotocol/agents 0.0.1
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/.claude/settings.local.json +27 -0
- package/AGENTS.md +681 -0
- package/CLAUDE.md +681 -0
- package/README.md +15 -0
- package/bun.lock +472 -0
- package/eslint.config.js +75 -0
- package/index.ts +1 -0
- package/llms.md +796 -0
- package/package.json +37 -0
- package/specs/UAP-1.0.md +2355 -0
- package/src/agent/index.ts +384 -0
- package/src/agent/types.ts +91 -0
- package/src/checkpoint/file.ts +126 -0
- package/src/checkpoint/index.ts +40 -0
- package/src/checkpoint/types.ts +95 -0
- package/src/execution/index.ts +37 -0
- package/src/execution/loop.ts +310 -0
- package/src/execution/plan.ts +497 -0
- package/src/execution/react.ts +340 -0
- package/src/execution/tool-ordering.ts +186 -0
- package/src/execution/types.ts +315 -0
- package/src/index.ts +80 -0
- package/src/middleware/index.ts +7 -0
- package/src/middleware/logging.ts +123 -0
- package/src/middleware/types.ts +69 -0
- package/src/state/index.ts +301 -0
- package/src/state/types.ts +173 -0
- package/src/thread-tree/index.ts +249 -0
- package/src/thread-tree/types.ts +29 -0
- package/src/utils/uuid.ts +7 -0
- package/tests/live/agent-anthropic.test.ts +288 -0
- package/tests/live/agent-strategy-hooks.test.ts +268 -0
- package/tests/live/checkpoint.test.ts +243 -0
- package/tests/live/execution-strategies.test.ts +255 -0
- package/tests/live/plan-strategy.test.ts +160 -0
- package/tests/live/subagent-events.live.test.ts +249 -0
- package/tests/live/thread-tree.test.ts +186 -0
- package/tests/unit/agent.test.ts +703 -0
- package/tests/unit/checkpoint.test.ts +232 -0
- package/tests/unit/execution/equivalence.test.ts +402 -0
- package/tests/unit/execution/loop.test.ts +437 -0
- package/tests/unit/execution/plan.test.ts +590 -0
- package/tests/unit/execution/react.test.ts +604 -0
- package/tests/unit/execution/subagent-events.test.ts +235 -0
- package/tests/unit/execution/tool-ordering.test.ts +310 -0
- package/tests/unit/middleware/logging.test.ts +276 -0
- package/tests/unit/state.test.ts +573 -0
- package/tests/unit/thread-tree.test.ts +249 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { UserMessage, AssistantMessage } from '@providerprotocol/ai';
|
|
3
|
+
import type { Turn, Tool, ModelReference, LLMInstance } from '@providerprotocol/ai';
|
|
4
|
+
import { agent } from '../../src/agent/index.ts';
|
|
5
|
+
import { AgentState } from '../../src/state/index.ts';
|
|
6
|
+
import type { ExecutionStrategy } from '../../src/execution/types.ts';
|
|
7
|
+
import type { Middleware } from '../../src/middleware/types.ts';
|
|
8
|
+
|
|
9
|
+
// Mock model reference - use unknown cast for unit tests
|
|
10
|
+
const mockModel = {
|
|
11
|
+
provider: 'mock',
|
|
12
|
+
modelId: 'mock-model',
|
|
13
|
+
} as unknown as ModelReference;
|
|
14
|
+
|
|
15
|
+
// Mock Turn factory
|
|
16
|
+
function createMockTurn(text: string = 'Response'): Turn {
|
|
17
|
+
const response = new AssistantMessage(text);
|
|
18
|
+
return {
|
|
19
|
+
response,
|
|
20
|
+
messages: [response],
|
|
21
|
+
toolExecutions: [],
|
|
22
|
+
usage: {
|
|
23
|
+
inputTokens: 10,
|
|
24
|
+
outputTokens: 20,
|
|
25
|
+
totalTokens: 30,
|
|
26
|
+
},
|
|
27
|
+
cycles: 1,
|
|
28
|
+
} as unknown as Turn;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Mock LLM instance for testing
|
|
32
|
+
const mockLLM = {
|
|
33
|
+
generate: async () => createMockTurn(),
|
|
34
|
+
stream: () => ({
|
|
35
|
+
async *[Symbol.asyncIterator] () {},
|
|
36
|
+
turn: Promise.resolve(createMockTurn()),
|
|
37
|
+
}),
|
|
38
|
+
} as unknown as LLMInstance;
|
|
39
|
+
|
|
40
|
+
describe('agent()', () => {
|
|
41
|
+
describe('creation', () => {
|
|
42
|
+
test('creates agent with unique ID', () => {
|
|
43
|
+
// Create a custom execution strategy for testing
|
|
44
|
+
const mockStrategy: ExecutionStrategy = {
|
|
45
|
+
name: 'mock',
|
|
46
|
+
async execute() {
|
|
47
|
+
return {
|
|
48
|
+
turn: createMockTurn(),
|
|
49
|
+
state: AgentState.initial(),
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
stream() {
|
|
53
|
+
return {
|
|
54
|
+
async *[Symbol.asyncIterator] () {},
|
|
55
|
+
result: Promise.resolve({
|
|
56
|
+
turn: createMockTurn(),
|
|
57
|
+
state: AgentState.initial(),
|
|
58
|
+
}),
|
|
59
|
+
abort: () => {},
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const agent1 = agent({
|
|
65
|
+
model: mockModel,
|
|
66
|
+
execution: mockStrategy,
|
|
67
|
+
_llmInstance: mockLLM,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const agent2 = agent({
|
|
71
|
+
model: mockModel,
|
|
72
|
+
execution: mockStrategy,
|
|
73
|
+
_llmInstance: mockLLM,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
expect(agent1.id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
|
|
77
|
+
expect(agent1.id).not.toBe(agent2.id);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('stores model reference', () => {
|
|
81
|
+
const mockStrategy: ExecutionStrategy = {
|
|
82
|
+
name: 'mock',
|
|
83
|
+
async execute() {
|
|
84
|
+
return {
|
|
85
|
+
turn: createMockTurn(),
|
|
86
|
+
state: AgentState.initial(),
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
stream() {
|
|
90
|
+
return {
|
|
91
|
+
async *[Symbol.asyncIterator] () {},
|
|
92
|
+
result: Promise.resolve({
|
|
93
|
+
turn: createMockTurn(),
|
|
94
|
+
state: AgentState.initial(),
|
|
95
|
+
}),
|
|
96
|
+
abort: () => {},
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const a = agent({
|
|
102
|
+
model: mockModel,
|
|
103
|
+
execution: mockStrategy,
|
|
104
|
+
_llmInstance: mockLLM,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(a.model).toBe(mockModel);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('stores tools', () => {
|
|
111
|
+
const mockTool: Tool = {
|
|
112
|
+
name: 'test_tool',
|
|
113
|
+
description: 'A test tool',
|
|
114
|
+
parameters: { type: 'object', properties: {} },
|
|
115
|
+
run: async () => 'result',
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const mockStrategy: ExecutionStrategy = {
|
|
119
|
+
name: 'mock',
|
|
120
|
+
async execute() {
|
|
121
|
+
return {
|
|
122
|
+
turn: createMockTurn(),
|
|
123
|
+
state: AgentState.initial(),
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
stream() {
|
|
127
|
+
return {
|
|
128
|
+
async *[Symbol.asyncIterator] () {},
|
|
129
|
+
result: Promise.resolve({
|
|
130
|
+
turn: createMockTurn(),
|
|
131
|
+
state: AgentState.initial(),
|
|
132
|
+
}),
|
|
133
|
+
abort: () => {},
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const a = agent({
|
|
139
|
+
model: mockModel,
|
|
140
|
+
tools: [mockTool],
|
|
141
|
+
execution: mockStrategy,
|
|
142
|
+
_llmInstance: mockLLM,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(a.tools).toHaveLength(1);
|
|
146
|
+
expect(a.tools[0]).toBe(mockTool);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('stores system prompt', () => {
|
|
150
|
+
const mockStrategy: ExecutionStrategy = {
|
|
151
|
+
name: 'mock',
|
|
152
|
+
async execute() {
|
|
153
|
+
return {
|
|
154
|
+
turn: createMockTurn(),
|
|
155
|
+
state: AgentState.initial(),
|
|
156
|
+
};
|
|
157
|
+
},
|
|
158
|
+
stream() {
|
|
159
|
+
return {
|
|
160
|
+
async *[Symbol.asyncIterator] () {},
|
|
161
|
+
result: Promise.resolve({
|
|
162
|
+
turn: createMockTurn(),
|
|
163
|
+
state: AgentState.initial(),
|
|
164
|
+
}),
|
|
165
|
+
abort: () => {},
|
|
166
|
+
};
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const a = agent({
|
|
171
|
+
model: mockModel,
|
|
172
|
+
system: 'You are a helpful assistant.',
|
|
173
|
+
execution: mockStrategy,
|
|
174
|
+
_llmInstance: mockLLM,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
expect(a.system).toBe('You are a helpful assistant.');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('defaults to empty tools array', () => {
|
|
181
|
+
const mockStrategy: ExecutionStrategy = {
|
|
182
|
+
name: 'mock',
|
|
183
|
+
async execute() {
|
|
184
|
+
return {
|
|
185
|
+
turn: createMockTurn(),
|
|
186
|
+
state: AgentState.initial(),
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
stream() {
|
|
190
|
+
return {
|
|
191
|
+
async *[Symbol.asyncIterator] () {},
|
|
192
|
+
result: Promise.resolve({
|
|
193
|
+
turn: createMockTurn(),
|
|
194
|
+
state: AgentState.initial(),
|
|
195
|
+
}),
|
|
196
|
+
abort: () => {},
|
|
197
|
+
};
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const a = agent({
|
|
202
|
+
model: mockModel,
|
|
203
|
+
execution: mockStrategy,
|
|
204
|
+
_llmInstance: mockLLM,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(a.tools).toEqual([]);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('generate()', () => {
|
|
212
|
+
test('accepts string input', async () => {
|
|
213
|
+
let capturedInput: unknown;
|
|
214
|
+
|
|
215
|
+
const mockStrategy: ExecutionStrategy = {
|
|
216
|
+
name: 'mock',
|
|
217
|
+
async execute(ctx) {
|
|
218
|
+
capturedInput = ctx.input;
|
|
219
|
+
return {
|
|
220
|
+
turn: createMockTurn(),
|
|
221
|
+
state: ctx.state,
|
|
222
|
+
};
|
|
223
|
+
},
|
|
224
|
+
stream() {
|
|
225
|
+
return {
|
|
226
|
+
async *[Symbol.asyncIterator] () {},
|
|
227
|
+
result: Promise.resolve({
|
|
228
|
+
turn: createMockTurn(),
|
|
229
|
+
state: AgentState.initial(),
|
|
230
|
+
}),
|
|
231
|
+
abort: () => {},
|
|
232
|
+
};
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const a = agent({
|
|
237
|
+
model: mockModel,
|
|
238
|
+
execution: mockStrategy,
|
|
239
|
+
_llmInstance: mockLLM,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
await a.generate('Hello', AgentState.initial());
|
|
243
|
+
|
|
244
|
+
expect(capturedInput).toBeInstanceOf(UserMessage);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('accepts Message input', async () => {
|
|
248
|
+
let capturedInput: unknown;
|
|
249
|
+
const inputMessage = new UserMessage('Hello');
|
|
250
|
+
|
|
251
|
+
const mockStrategy: ExecutionStrategy = {
|
|
252
|
+
name: 'mock',
|
|
253
|
+
async execute(ctx) {
|
|
254
|
+
capturedInput = ctx.input;
|
|
255
|
+
return {
|
|
256
|
+
turn: createMockTurn(),
|
|
257
|
+
state: ctx.state,
|
|
258
|
+
};
|
|
259
|
+
},
|
|
260
|
+
stream() {
|
|
261
|
+
return {
|
|
262
|
+
async *[Symbol.asyncIterator] () {},
|
|
263
|
+
result: Promise.resolve({
|
|
264
|
+
turn: createMockTurn(),
|
|
265
|
+
state: AgentState.initial(),
|
|
266
|
+
}),
|
|
267
|
+
abort: () => {},
|
|
268
|
+
};
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const a = agent({
|
|
273
|
+
model: mockModel,
|
|
274
|
+
execution: mockStrategy,
|
|
275
|
+
_llmInstance: mockLLM,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
await a.generate(inputMessage, AgentState.initial());
|
|
279
|
+
|
|
280
|
+
expect(capturedInput).toBe(inputMessage);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test('returns turn and state', async () => {
|
|
284
|
+
const mockTurn = createMockTurn('Test response');
|
|
285
|
+
const mockState = AgentState.initial().withStep(1);
|
|
286
|
+
|
|
287
|
+
const mockStrategy: ExecutionStrategy = {
|
|
288
|
+
name: 'mock',
|
|
289
|
+
async execute() {
|
|
290
|
+
return {
|
|
291
|
+
turn: mockTurn,
|
|
292
|
+
state: mockState,
|
|
293
|
+
};
|
|
294
|
+
},
|
|
295
|
+
stream() {
|
|
296
|
+
return {
|
|
297
|
+
async *[Symbol.asyncIterator] () {},
|
|
298
|
+
result: Promise.resolve({
|
|
299
|
+
turn: mockTurn,
|
|
300
|
+
state: mockState,
|
|
301
|
+
}),
|
|
302
|
+
abort: () => {},
|
|
303
|
+
};
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const a = agent({
|
|
308
|
+
model: mockModel,
|
|
309
|
+
execution: mockStrategy,
|
|
310
|
+
_llmInstance: mockLLM,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const result = await a.generate('Hello', AgentState.initial());
|
|
314
|
+
|
|
315
|
+
expect(result.turn).toBe(mockTurn);
|
|
316
|
+
expect(result.state).toBe(mockState);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test('passes state to execution strategy', async () => {
|
|
320
|
+
let capturedState: AgentState | undefined;
|
|
321
|
+
const inputState = AgentState.initial()
|
|
322
|
+
.withStep(5)
|
|
323
|
+
.withMetadata('test', 'value');
|
|
324
|
+
|
|
325
|
+
const mockStrategy: ExecutionStrategy = {
|
|
326
|
+
name: 'mock',
|
|
327
|
+
async execute(ctx) {
|
|
328
|
+
capturedState = ctx.state;
|
|
329
|
+
return {
|
|
330
|
+
turn: createMockTurn(),
|
|
331
|
+
state: ctx.state,
|
|
332
|
+
};
|
|
333
|
+
},
|
|
334
|
+
stream() {
|
|
335
|
+
return {
|
|
336
|
+
async *[Symbol.asyncIterator] () {},
|
|
337
|
+
result: Promise.resolve({
|
|
338
|
+
turn: createMockTurn(),
|
|
339
|
+
state: AgentState.initial(),
|
|
340
|
+
}),
|
|
341
|
+
abort: () => {},
|
|
342
|
+
};
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const a = agent({
|
|
347
|
+
model: mockModel,
|
|
348
|
+
execution: mockStrategy,
|
|
349
|
+
_llmInstance: mockLLM,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
await a.generate('Hello', inputState);
|
|
353
|
+
|
|
354
|
+
expect(capturedState?.step).toBe(5);
|
|
355
|
+
expect(capturedState?.metadata).toEqual({ test: 'value' });
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
describe('ask()', () => {
|
|
360
|
+
test('preserves conversation history', async () => {
|
|
361
|
+
let callCount = 0;
|
|
362
|
+
|
|
363
|
+
const mockStrategy: ExecutionStrategy = {
|
|
364
|
+
name: 'mock',
|
|
365
|
+
async execute(ctx) {
|
|
366
|
+
callCount++;
|
|
367
|
+
return {
|
|
368
|
+
turn: createMockTurn(`Response ${callCount}`),
|
|
369
|
+
state: ctx.state.withStep(callCount),
|
|
370
|
+
};
|
|
371
|
+
},
|
|
372
|
+
stream() {
|
|
373
|
+
return {
|
|
374
|
+
async *[Symbol.asyncIterator] () {},
|
|
375
|
+
result: Promise.resolve({
|
|
376
|
+
turn: createMockTurn(),
|
|
377
|
+
state: AgentState.initial(),
|
|
378
|
+
}),
|
|
379
|
+
abort: () => {},
|
|
380
|
+
};
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const a = agent({
|
|
385
|
+
model: mockModel,
|
|
386
|
+
execution: mockStrategy,
|
|
387
|
+
_llmInstance: mockLLM,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
const state0 = AgentState.initial();
|
|
391
|
+
const result1 = await a.ask('First message', state0);
|
|
392
|
+
|
|
393
|
+
// State should have the input message added
|
|
394
|
+
expect(result1.state.messages.length).toBeGreaterThan(0);
|
|
395
|
+
|
|
396
|
+
const result2 = await a.ask('Second message', result1.state);
|
|
397
|
+
|
|
398
|
+
// State should have accumulated messages
|
|
399
|
+
expect(result2.state.messages.length).toBeGreaterThan(result1.state.messages.length);
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
describe('query()', () => {
|
|
404
|
+
test('is stateless', async () => {
|
|
405
|
+
const mockStrategy: ExecutionStrategy = {
|
|
406
|
+
name: 'mock',
|
|
407
|
+
async execute(ctx) {
|
|
408
|
+
// State should be fresh
|
|
409
|
+
expect(ctx.state.messages).toHaveLength(0);
|
|
410
|
+
expect(ctx.state.step).toBe(0);
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
turn: createMockTurn(),
|
|
414
|
+
state: ctx.state,
|
|
415
|
+
};
|
|
416
|
+
},
|
|
417
|
+
stream() {
|
|
418
|
+
return {
|
|
419
|
+
async *[Symbol.asyncIterator] () {},
|
|
420
|
+
result: Promise.resolve({
|
|
421
|
+
turn: createMockTurn(),
|
|
422
|
+
state: AgentState.initial(),
|
|
423
|
+
}),
|
|
424
|
+
abort: () => {},
|
|
425
|
+
};
|
|
426
|
+
},
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const a = agent({
|
|
430
|
+
model: mockModel,
|
|
431
|
+
execution: mockStrategy,
|
|
432
|
+
_llmInstance: mockLLM,
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
const turn = await a.query('Question');
|
|
436
|
+
|
|
437
|
+
expect(turn).toBeDefined();
|
|
438
|
+
expect(turn.response.text).toBe('Response');
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
test('returns only Turn, not state', async () => {
|
|
442
|
+
const mockStrategy: ExecutionStrategy = {
|
|
443
|
+
name: 'mock',
|
|
444
|
+
async execute(ctx) {
|
|
445
|
+
return {
|
|
446
|
+
turn: createMockTurn('Answer'),
|
|
447
|
+
state: ctx.state,
|
|
448
|
+
};
|
|
449
|
+
},
|
|
450
|
+
stream() {
|
|
451
|
+
return {
|
|
452
|
+
async *[Symbol.asyncIterator] () {},
|
|
453
|
+
result: Promise.resolve({
|
|
454
|
+
turn: createMockTurn(),
|
|
455
|
+
state: AgentState.initial(),
|
|
456
|
+
}),
|
|
457
|
+
abort: () => {},
|
|
458
|
+
};
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
const a = agent({
|
|
463
|
+
model: mockModel,
|
|
464
|
+
execution: mockStrategy,
|
|
465
|
+
_llmInstance: mockLLM,
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
const turn = await a.query('Question');
|
|
469
|
+
|
|
470
|
+
// Should return Turn, not GenerateResult
|
|
471
|
+
expect(turn.response).toBeDefined();
|
|
472
|
+
expect(turn.response.text).toBe('Answer');
|
|
473
|
+
expect((turn as unknown as { state?: unknown }).state).toBeUndefined();
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
describe('middleware', () => {
|
|
478
|
+
test('runs before middleware in order', async () => {
|
|
479
|
+
const order: string[] = [];
|
|
480
|
+
|
|
481
|
+
const mw1: Middleware = {
|
|
482
|
+
name: 'first',
|
|
483
|
+
async before(ctx) {
|
|
484
|
+
order.push('first-before');
|
|
485
|
+
return ctx;
|
|
486
|
+
},
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
const mw2: Middleware = {
|
|
490
|
+
name: 'second',
|
|
491
|
+
async before(ctx) {
|
|
492
|
+
order.push('second-before');
|
|
493
|
+
return ctx;
|
|
494
|
+
},
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
const mockStrategy: ExecutionStrategy = {
|
|
498
|
+
name: 'mock',
|
|
499
|
+
async execute(ctx) {
|
|
500
|
+
order.push('execute');
|
|
501
|
+
return {
|
|
502
|
+
turn: createMockTurn(),
|
|
503
|
+
state: ctx.state,
|
|
504
|
+
};
|
|
505
|
+
},
|
|
506
|
+
stream() {
|
|
507
|
+
return {
|
|
508
|
+
async *[Symbol.asyncIterator] () {},
|
|
509
|
+
result: Promise.resolve({
|
|
510
|
+
turn: createMockTurn(),
|
|
511
|
+
state: AgentState.initial(),
|
|
512
|
+
}),
|
|
513
|
+
abort: () => {},
|
|
514
|
+
};
|
|
515
|
+
},
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
const a = agent({
|
|
519
|
+
model: mockModel,
|
|
520
|
+
middleware: [mw1, mw2],
|
|
521
|
+
execution: mockStrategy,
|
|
522
|
+
_llmInstance: mockLLM,
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
await a.generate('Hello', AgentState.initial());
|
|
526
|
+
|
|
527
|
+
expect(order).toEqual(['first-before', 'second-before', 'execute']);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
test('runs after middleware in reverse order', async () => {
|
|
531
|
+
const order: string[] = [];
|
|
532
|
+
|
|
533
|
+
const mw1: Middleware = {
|
|
534
|
+
name: 'first',
|
|
535
|
+
async after(ctx, result) {
|
|
536
|
+
order.push('first-after');
|
|
537
|
+
return result;
|
|
538
|
+
},
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
const mw2: Middleware = {
|
|
542
|
+
name: 'second',
|
|
543
|
+
async after(ctx, result) {
|
|
544
|
+
order.push('second-after');
|
|
545
|
+
return result;
|
|
546
|
+
},
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
const mockStrategy: ExecutionStrategy = {
|
|
550
|
+
name: 'mock',
|
|
551
|
+
async execute(ctx) {
|
|
552
|
+
order.push('execute');
|
|
553
|
+
return {
|
|
554
|
+
turn: createMockTurn(),
|
|
555
|
+
state: ctx.state,
|
|
556
|
+
};
|
|
557
|
+
},
|
|
558
|
+
stream() {
|
|
559
|
+
return {
|
|
560
|
+
async *[Symbol.asyncIterator] () {},
|
|
561
|
+
result: Promise.resolve({
|
|
562
|
+
turn: createMockTurn(),
|
|
563
|
+
state: AgentState.initial(),
|
|
564
|
+
}),
|
|
565
|
+
abort: () => {},
|
|
566
|
+
};
|
|
567
|
+
},
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
const a = agent({
|
|
571
|
+
model: mockModel,
|
|
572
|
+
middleware: [mw1, mw2],
|
|
573
|
+
execution: mockStrategy,
|
|
574
|
+
_llmInstance: mockLLM,
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
await a.generate('Hello', AgentState.initial());
|
|
578
|
+
|
|
579
|
+
expect(order).toEqual(['execute', 'second-after', 'first-after']);
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
test('middleware can modify result', async () => {
|
|
583
|
+
const mw: Middleware = {
|
|
584
|
+
name: 'modifier',
|
|
585
|
+
async after(ctx, result) {
|
|
586
|
+
return {
|
|
587
|
+
...result,
|
|
588
|
+
state: result.state.withMetadata('modified', true),
|
|
589
|
+
};
|
|
590
|
+
},
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
const mockStrategy: ExecutionStrategy = {
|
|
594
|
+
name: 'mock',
|
|
595
|
+
async execute(ctx) {
|
|
596
|
+
return {
|
|
597
|
+
turn: createMockTurn(),
|
|
598
|
+
state: ctx.state,
|
|
599
|
+
};
|
|
600
|
+
},
|
|
601
|
+
stream() {
|
|
602
|
+
return {
|
|
603
|
+
async *[Symbol.asyncIterator] () {},
|
|
604
|
+
result: Promise.resolve({
|
|
605
|
+
turn: createMockTurn(),
|
|
606
|
+
state: AgentState.initial(),
|
|
607
|
+
}),
|
|
608
|
+
abort: () => {},
|
|
609
|
+
};
|
|
610
|
+
},
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
const a = agent({
|
|
614
|
+
model: mockModel,
|
|
615
|
+
middleware: [mw],
|
|
616
|
+
execution: mockStrategy,
|
|
617
|
+
_llmInstance: mockLLM,
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
const result = await a.generate('Hello', AgentState.initial());
|
|
621
|
+
|
|
622
|
+
expect(result.state.metadata).toEqual({ modified: true });
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
describe('stream()', () => {
|
|
627
|
+
test('returns AgentStreamResult', async () => {
|
|
628
|
+
const mockStrategy: ExecutionStrategy = {
|
|
629
|
+
name: 'mock',
|
|
630
|
+
async execute(ctx) {
|
|
631
|
+
return {
|
|
632
|
+
turn: createMockTurn(),
|
|
633
|
+
state: ctx.state,
|
|
634
|
+
};
|
|
635
|
+
},
|
|
636
|
+
stream() {
|
|
637
|
+
return {
|
|
638
|
+
async *[Symbol.asyncIterator] () {
|
|
639
|
+
yield { source: 'uap' as const, uap: { type: 'step_start' as const, step: 1, agentId: 'test', data: {} } };
|
|
640
|
+
},
|
|
641
|
+
result: Promise.resolve({
|
|
642
|
+
turn: createMockTurn(),
|
|
643
|
+
state: AgentState.initial(),
|
|
644
|
+
}),
|
|
645
|
+
abort: () => {},
|
|
646
|
+
};
|
|
647
|
+
},
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
const a = agent({
|
|
651
|
+
model: mockModel,
|
|
652
|
+
execution: mockStrategy,
|
|
653
|
+
_llmInstance: mockLLM,
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
const stream = a.stream('Hello', AgentState.initial());
|
|
657
|
+
|
|
658
|
+
expect(stream[Symbol.asyncIterator]).toBeDefined();
|
|
659
|
+
expect(stream.result).toBeInstanceOf(Promise);
|
|
660
|
+
expect(typeof stream.abort).toBe('function');
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
test('yields events from strategy', async () => {
|
|
664
|
+
const mockStrategy: ExecutionStrategy = {
|
|
665
|
+
name: 'mock',
|
|
666
|
+
async execute(ctx) {
|
|
667
|
+
return {
|
|
668
|
+
turn: createMockTurn(),
|
|
669
|
+
state: ctx.state,
|
|
670
|
+
};
|
|
671
|
+
},
|
|
672
|
+
stream() {
|
|
673
|
+
return {
|
|
674
|
+
async *[Symbol.asyncIterator] () {
|
|
675
|
+
yield { source: 'uap' as const, uap: { type: 'step_start' as const, step: 1, agentId: 'test', data: {} } };
|
|
676
|
+
yield { source: 'uap' as const, uap: { type: 'step_end' as const, step: 1, agentId: 'test', data: {} } };
|
|
677
|
+
},
|
|
678
|
+
result: Promise.resolve({
|
|
679
|
+
turn: createMockTurn(),
|
|
680
|
+
state: AgentState.initial(),
|
|
681
|
+
}),
|
|
682
|
+
abort: () => {},
|
|
683
|
+
};
|
|
684
|
+
},
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
const a = agent({
|
|
688
|
+
model: mockModel,
|
|
689
|
+
execution: mockStrategy,
|
|
690
|
+
_llmInstance: mockLLM,
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
const stream = a.stream('Hello', AgentState.initial());
|
|
694
|
+
const events: unknown[] = [];
|
|
695
|
+
|
|
696
|
+
for await (const event of stream) {
|
|
697
|
+
events.push(event);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
expect(events).toHaveLength(2);
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
});
|