@providerprotocol/agents 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/README.md +333 -6
- package/dist/checkpoint/index.d.ts +43 -0
- package/dist/checkpoint/index.js +64 -0
- package/dist/checkpoint/index.js.map +1 -0
- package/{src/execution/loop.ts → dist/chunk-4ESYN66B.js} +54 -162
- package/dist/chunk-4ESYN66B.js.map +1 -0
- package/dist/chunk-EKRXMSDX.js +8 -0
- package/dist/chunk-EKRXMSDX.js.map +1 -0
- package/dist/chunk-PHI5ULBV.js +427 -0
- package/dist/chunk-PHI5ULBV.js.map +1 -0
- package/dist/execution/index.d.ts +105 -0
- package/dist/execution/index.js +679 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/index-qsPwbY86.d.ts +65 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +218 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/index.d.ts +23 -0
- package/dist/middleware/index.js +82 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/thread-tree/index.d.ts +115 -0
- package/dist/thread-tree/index.js +4 -0
- package/dist/thread-tree/index.js.map +1 -0
- package/dist/types-2Vsthzyu.d.ts +163 -0
- package/dist/types-BhX9uD_d.d.ts +91 -0
- package/dist/types-DR02gtFv.d.ts +270 -0
- package/dist/types-NGQMdnaD.d.ts +65 -0
- package/package.json +40 -8
- package/.claude/settings.local.json +0 -27
- package/AGENTS.md +0 -681
- package/CLAUDE.md +0 -681
- package/bun.lock +0 -472
- package/eslint.config.js +0 -75
- package/index.ts +0 -1
- package/llms.md +0 -796
- package/specs/UAP-1.0.md +0 -2355
- package/src/agent/index.ts +0 -384
- package/src/agent/types.ts +0 -91
- package/src/checkpoint/file.ts +0 -126
- package/src/checkpoint/index.ts +0 -40
- package/src/checkpoint/types.ts +0 -95
- package/src/execution/index.ts +0 -37
- package/src/execution/plan.ts +0 -497
- package/src/execution/react.ts +0 -340
- package/src/execution/tool-ordering.ts +0 -186
- package/src/execution/types.ts +0 -315
- package/src/index.ts +0 -80
- package/src/middleware/index.ts +0 -7
- package/src/middleware/logging.ts +0 -123
- package/src/middleware/types.ts +0 -69
- package/src/state/index.ts +0 -301
- package/src/state/types.ts +0 -173
- package/src/thread-tree/index.ts +0 -249
- package/src/thread-tree/types.ts +0 -29
- package/src/utils/uuid.ts +0 -7
- package/tests/live/agent-anthropic.test.ts +0 -288
- package/tests/live/agent-strategy-hooks.test.ts +0 -268
- package/tests/live/checkpoint.test.ts +0 -243
- package/tests/live/execution-strategies.test.ts +0 -255
- package/tests/live/plan-strategy.test.ts +0 -160
- package/tests/live/subagent-events.live.test.ts +0 -249
- package/tests/live/thread-tree.test.ts +0 -186
- package/tests/unit/agent.test.ts +0 -703
- package/tests/unit/checkpoint.test.ts +0 -232
- package/tests/unit/execution/equivalence.test.ts +0 -402
- package/tests/unit/execution/loop.test.ts +0 -437
- package/tests/unit/execution/plan.test.ts +0 -590
- package/tests/unit/execution/react.test.ts +0 -604
- package/tests/unit/execution/subagent-events.test.ts +0 -235
- package/tests/unit/execution/tool-ordering.test.ts +0 -310
- package/tests/unit/middleware/logging.test.ts +0 -276
- package/tests/unit/state.test.ts +0 -573
- package/tests/unit/thread-tree.test.ts +0 -249
- package/tsconfig.json +0 -29
|
@@ -1,604 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, mock, beforeEach } from 'bun:test';
|
|
2
|
-
import { UserMessage, AssistantMessage } from '@providerprotocol/ai';
|
|
3
|
-
import type { Turn, LLMInstance, Tool, ToolCall, ToolExecution, TextBlock } from '@providerprotocol/ai';
|
|
4
|
-
import { react } from '../../../src/execution/react.ts';
|
|
5
|
-
import { AgentState } from '../../../src/state/index.ts';
|
|
6
|
-
import type { ExecutionContext } from '../../../src/execution/types.ts';
|
|
7
|
-
|
|
8
|
-
// Mock Turn factory
|
|
9
|
-
function createMockTurn(options: {
|
|
10
|
-
text?: string;
|
|
11
|
-
toolCalls?: ToolCall[];
|
|
12
|
-
toolExecutions?: ToolExecution[];
|
|
13
|
-
}): Turn {
|
|
14
|
-
const response = new AssistantMessage(options.text ?? 'Hello', options.toolCalls);
|
|
15
|
-
return {
|
|
16
|
-
response,
|
|
17
|
-
messages: [response],
|
|
18
|
-
toolExecutions: options.toolExecutions ?? [],
|
|
19
|
-
usage: {
|
|
20
|
-
inputTokens: 10,
|
|
21
|
-
outputTokens: 20,
|
|
22
|
-
totalTokens: 30,
|
|
23
|
-
},
|
|
24
|
-
cycles: 1,
|
|
25
|
-
} as unknown as Turn;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Mock LLM factory that returns different turns for reasoning and action phases
|
|
29
|
-
function createMockLLM(turnSequence: Turn[]): LLMInstance {
|
|
30
|
-
let callIndex = 0;
|
|
31
|
-
|
|
32
|
-
return {
|
|
33
|
-
generate: mock(async () => {
|
|
34
|
-
const turn = turnSequence[callIndex] ?? turnSequence[turnSequence.length - 1];
|
|
35
|
-
callIndex++;
|
|
36
|
-
return turn;
|
|
37
|
-
}),
|
|
38
|
-
stream: mock(() => {
|
|
39
|
-
const turn = turnSequence[callIndex] ?? turnSequence[turnSequence.length - 1];
|
|
40
|
-
callIndex++;
|
|
41
|
-
|
|
42
|
-
const events: Array<{ type: string; delta?: { text?: string } }> = [];
|
|
43
|
-
if (turn) {
|
|
44
|
-
events.push({ type: 'text_delta', delta: { text: turn.response.text } });
|
|
45
|
-
events.push({ type: 'message_stop' });
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
async *[Symbol.asyncIterator]() {
|
|
50
|
-
for (const event of events) {
|
|
51
|
-
yield event;
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
turn: Promise.resolve(turn),
|
|
55
|
-
};
|
|
56
|
-
}),
|
|
57
|
-
} as unknown as LLMInstance;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
describe('react() execution strategy', () => {
|
|
61
|
-
let state: AgentState;
|
|
62
|
-
let input: UserMessage;
|
|
63
|
-
let tools: Tool[];
|
|
64
|
-
|
|
65
|
-
beforeEach(() => {
|
|
66
|
-
state = AgentState.initial();
|
|
67
|
-
input = new UserMessage('Hello');
|
|
68
|
-
tools = [];
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe('execute()', () => {
|
|
72
|
-
test('returns turn and updated state', async () => {
|
|
73
|
-
// ReAct makes 2 LLM calls per step: reasoning + action
|
|
74
|
-
const reasoningTurn = createMockTurn({ text: 'I should respond with a greeting.' });
|
|
75
|
-
const actionTurn = createMockTurn({ text: 'Hi there!' });
|
|
76
|
-
const llm = createMockLLM([reasoningTurn, actionTurn]);
|
|
77
|
-
|
|
78
|
-
const strategy = react();
|
|
79
|
-
const context: ExecutionContext = {
|
|
80
|
-
agent: { id: 'test-agent' },
|
|
81
|
-
llm,
|
|
82
|
-
input,
|
|
83
|
-
state,
|
|
84
|
-
tools,
|
|
85
|
-
strategy: {},
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const result = await strategy.execute(context);
|
|
89
|
-
|
|
90
|
-
expect(result.turn).toBe(actionTurn);
|
|
91
|
-
expect(result.state.step).toBe(1);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test('calls reasoning and action phases', async () => {
|
|
95
|
-
const reasoningTurn = createMockTurn({ text: 'Thinking about the problem...' });
|
|
96
|
-
const actionTurn = createMockTurn({ text: 'Here is my answer.' });
|
|
97
|
-
const llm = createMockLLM([reasoningTurn, actionTurn]);
|
|
98
|
-
|
|
99
|
-
const strategy = react();
|
|
100
|
-
const context: ExecutionContext = {
|
|
101
|
-
agent: { id: 'test-agent' },
|
|
102
|
-
llm,
|
|
103
|
-
input,
|
|
104
|
-
state,
|
|
105
|
-
tools,
|
|
106
|
-
strategy: {},
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
await strategy.execute(context);
|
|
110
|
-
|
|
111
|
-
// 2 calls: reasoning phase + action phase
|
|
112
|
-
expect(llm.generate).toHaveBeenCalledTimes(2);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test('stores reasoning in state', async () => {
|
|
116
|
-
const reasoningTurn = createMockTurn({ text: 'My reasoning about this task.' });
|
|
117
|
-
const actionTurn = createMockTurn({ text: 'Action result.' });
|
|
118
|
-
const llm = createMockLLM([reasoningTurn, actionTurn]);
|
|
119
|
-
|
|
120
|
-
const strategy = react();
|
|
121
|
-
const context: ExecutionContext = {
|
|
122
|
-
agent: { id: 'test-agent' },
|
|
123
|
-
llm,
|
|
124
|
-
input,
|
|
125
|
-
state,
|
|
126
|
-
tools,
|
|
127
|
-
strategy: {},
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const result = await strategy.execute(context);
|
|
131
|
-
|
|
132
|
-
expect(result.state.reasoning).toHaveLength(1);
|
|
133
|
-
expect(result.state.reasoning[0]).toBe('My reasoning about this task.');
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
test('loops on tool calls', async () => {
|
|
137
|
-
const toolCall: ToolCall = {
|
|
138
|
-
toolCallId: 'call-1',
|
|
139
|
-
toolName: 'test_tool',
|
|
140
|
-
arguments: { arg: 'value' },
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
// Step 1: reasoning + action with tool call
|
|
144
|
-
const reasoning1 = createMockTurn({ text: 'I need to use a tool.' });
|
|
145
|
-
const action1 = createMockTurn({
|
|
146
|
-
text: 'Using tool...',
|
|
147
|
-
toolCalls: [toolCall],
|
|
148
|
-
toolExecutions: [{
|
|
149
|
-
toolCallId: 'call-1',
|
|
150
|
-
toolName: 'test_tool',
|
|
151
|
-
arguments: { arg: 'value' },
|
|
152
|
-
result: 'tool result',
|
|
153
|
-
duration: 100,
|
|
154
|
-
isError: false,
|
|
155
|
-
}],
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// Step 2: reasoning + final action
|
|
159
|
-
const reasoning2 = createMockTurn({ text: 'Now I can provide the answer.' });
|
|
160
|
-
const action2 = createMockTurn({ text: 'Final answer!' });
|
|
161
|
-
|
|
162
|
-
const llm = createMockLLM([reasoning1, action1, reasoning2, action2]);
|
|
163
|
-
|
|
164
|
-
const strategy = react();
|
|
165
|
-
const context: ExecutionContext = {
|
|
166
|
-
agent: { id: 'test-agent' },
|
|
167
|
-
llm,
|
|
168
|
-
input,
|
|
169
|
-
state,
|
|
170
|
-
tools,
|
|
171
|
-
strategy: {},
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
const result = await strategy.execute(context);
|
|
175
|
-
|
|
176
|
-
// 4 calls: (reasoning1 + action1) + (reasoning2 + action2)
|
|
177
|
-
expect(llm.generate).toHaveBeenCalledTimes(4);
|
|
178
|
-
expect(result.state.step).toBe(2);
|
|
179
|
-
expect(result.state.reasoning).toHaveLength(2);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
test('respects maxSteps', async () => {
|
|
183
|
-
const toolCall: ToolCall = {
|
|
184
|
-
toolCallId: 'call-1',
|
|
185
|
-
toolName: 'test_tool',
|
|
186
|
-
arguments: {},
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
// All turns have tool calls to force looping
|
|
190
|
-
const reasoningTurn = createMockTurn({ text: 'Thinking...' });
|
|
191
|
-
const actionTurn = createMockTurn({
|
|
192
|
-
text: 'Acting...',
|
|
193
|
-
toolCalls: [toolCall],
|
|
194
|
-
toolExecutions: [{
|
|
195
|
-
toolCallId: 'call-1',
|
|
196
|
-
toolName: 'test_tool',
|
|
197
|
-
arguments: {},
|
|
198
|
-
result: 'ok',
|
|
199
|
-
duration: 10,
|
|
200
|
-
isError: false,
|
|
201
|
-
}],
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
const llm = createMockLLM([
|
|
205
|
-
reasoningTurn, actionTurn,
|
|
206
|
-
reasoningTurn, actionTurn,
|
|
207
|
-
reasoningTurn, actionTurn,
|
|
208
|
-
reasoningTurn, actionTurn,
|
|
209
|
-
reasoningTurn, actionTurn,
|
|
210
|
-
]);
|
|
211
|
-
|
|
212
|
-
const strategy = react({ maxSteps: 2 });
|
|
213
|
-
const context: ExecutionContext = {
|
|
214
|
-
agent: { id: 'test-agent' },
|
|
215
|
-
llm,
|
|
216
|
-
input,
|
|
217
|
-
state,
|
|
218
|
-
tools,
|
|
219
|
-
strategy: {},
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
const result = await strategy.execute(context);
|
|
223
|
-
|
|
224
|
-
// 2 steps * 2 calls per step = 4 calls
|
|
225
|
-
expect(llm.generate).toHaveBeenCalledTimes(4);
|
|
226
|
-
expect(result.state.step).toBe(2);
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
test('defaults to Infinity maxSteps', () => {
|
|
230
|
-
const strategy = react();
|
|
231
|
-
expect(strategy.name).toBe('react');
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
test('calls onReason hook', async () => {
|
|
235
|
-
const reasoningTurn = createMockTurn({ text: 'My reasoning process.' });
|
|
236
|
-
const actionTurn = createMockTurn({ text: 'Done.' });
|
|
237
|
-
const llm = createMockLLM([reasoningTurn, actionTurn]);
|
|
238
|
-
const onReason = mock(() => {});
|
|
239
|
-
|
|
240
|
-
const strategy = react();
|
|
241
|
-
const context: ExecutionContext = {
|
|
242
|
-
agent: { id: 'test-agent' },
|
|
243
|
-
llm,
|
|
244
|
-
input,
|
|
245
|
-
state,
|
|
246
|
-
tools,
|
|
247
|
-
strategy: { onReason },
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
await strategy.execute(context);
|
|
251
|
-
|
|
252
|
-
expect(onReason).toHaveBeenCalledTimes(1);
|
|
253
|
-
expect(onReason).toHaveBeenCalledWith(1, 'My reasoning process.');
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
test('calls onStepStart hook', async () => {
|
|
257
|
-
const reasoningTurn = createMockTurn({ text: 'Thinking.' });
|
|
258
|
-
const actionTurn = createMockTurn({ text: 'Done.' });
|
|
259
|
-
const llm = createMockLLM([reasoningTurn, actionTurn]);
|
|
260
|
-
const onStepStart = mock(() => {});
|
|
261
|
-
|
|
262
|
-
const strategy = react();
|
|
263
|
-
const context: ExecutionContext = {
|
|
264
|
-
agent: { id: 'test-agent' },
|
|
265
|
-
llm,
|
|
266
|
-
input,
|
|
267
|
-
state,
|
|
268
|
-
tools,
|
|
269
|
-
strategy: { onStepStart },
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
await strategy.execute(context);
|
|
273
|
-
|
|
274
|
-
expect(onStepStart).toHaveBeenCalledTimes(1);
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
test('calls onStepEnd hook', async () => {
|
|
278
|
-
const reasoningTurn = createMockTurn({ text: 'Thinking.' });
|
|
279
|
-
const actionTurn = createMockTurn({ text: 'Done.' });
|
|
280
|
-
const llm = createMockLLM([reasoningTurn, actionTurn]);
|
|
281
|
-
const onStepEnd = mock(() => {});
|
|
282
|
-
|
|
283
|
-
const strategy = react();
|
|
284
|
-
const context: ExecutionContext = {
|
|
285
|
-
agent: { id: 'test-agent' },
|
|
286
|
-
llm,
|
|
287
|
-
input,
|
|
288
|
-
state,
|
|
289
|
-
tools,
|
|
290
|
-
strategy: { onStepEnd },
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
await strategy.execute(context);
|
|
294
|
-
|
|
295
|
-
expect(onStepEnd).toHaveBeenCalledTimes(1);
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
test('calls onAct hook on tool calls', async () => {
|
|
299
|
-
const toolCall: ToolCall = {
|
|
300
|
-
toolCallId: 'call-1',
|
|
301
|
-
toolName: 'test_tool',
|
|
302
|
-
arguments: {},
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
const reasoningTurn = createMockTurn({ text: 'Using tool.' });
|
|
306
|
-
const actionTurn = createMockTurn({
|
|
307
|
-
text: 'Called tool.',
|
|
308
|
-
toolCalls: [toolCall],
|
|
309
|
-
});
|
|
310
|
-
const reasoning2 = createMockTurn({ text: 'Done now.' });
|
|
311
|
-
const action2 = createMockTurn({ text: 'Final.' });
|
|
312
|
-
|
|
313
|
-
const llm = createMockLLM([reasoningTurn, actionTurn, reasoning2, action2]);
|
|
314
|
-
const onAct = mock(() => {});
|
|
315
|
-
|
|
316
|
-
const strategy = react();
|
|
317
|
-
const context: ExecutionContext = {
|
|
318
|
-
agent: { id: 'test-agent' },
|
|
319
|
-
llm,
|
|
320
|
-
input,
|
|
321
|
-
state,
|
|
322
|
-
tools,
|
|
323
|
-
strategy: { onAct },
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
await strategy.execute(context);
|
|
327
|
-
|
|
328
|
-
expect(onAct).toHaveBeenCalledWith(1, [toolCall]);
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
test('calls onObserve hook on tool executions', async () => {
|
|
332
|
-
const execution: ToolExecution = {
|
|
333
|
-
toolCallId: 'call-1',
|
|
334
|
-
toolName: 'test_tool',
|
|
335
|
-
arguments: {},
|
|
336
|
-
result: 'result',
|
|
337
|
-
duration: 100,
|
|
338
|
-
isError: false,
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
const reasoningTurn = createMockTurn({ text: 'Observing.' });
|
|
342
|
-
const actionTurn = createMockTurn({
|
|
343
|
-
text: 'Done.',
|
|
344
|
-
toolExecutions: [execution],
|
|
345
|
-
});
|
|
346
|
-
const llm = createMockLLM([reasoningTurn, actionTurn]);
|
|
347
|
-
const onObserve = mock(() => {});
|
|
348
|
-
|
|
349
|
-
const strategy = react();
|
|
350
|
-
const context: ExecutionContext = {
|
|
351
|
-
agent: { id: 'test-agent' },
|
|
352
|
-
llm,
|
|
353
|
-
input,
|
|
354
|
-
state,
|
|
355
|
-
tools,
|
|
356
|
-
strategy: { onObserve },
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
await strategy.execute(context);
|
|
360
|
-
|
|
361
|
-
expect(onObserve).toHaveBeenCalledWith(1, [execution]);
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
test('calls onComplete hook', async () => {
|
|
365
|
-
const reasoningTurn = createMockTurn({ text: 'Thinking.' });
|
|
366
|
-
const actionTurn = createMockTurn({ text: 'Done.' });
|
|
367
|
-
const llm = createMockLLM([reasoningTurn, actionTurn]);
|
|
368
|
-
const onComplete = mock(() => {});
|
|
369
|
-
|
|
370
|
-
const strategy = react();
|
|
371
|
-
const context: ExecutionContext = {
|
|
372
|
-
agent: { id: 'test-agent' },
|
|
373
|
-
llm,
|
|
374
|
-
input,
|
|
375
|
-
state,
|
|
376
|
-
tools,
|
|
377
|
-
strategy: { onComplete },
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
await strategy.execute(context);
|
|
381
|
-
|
|
382
|
-
expect(onComplete).toHaveBeenCalledTimes(1);
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
test('respects stopCondition', async () => {
|
|
386
|
-
const toolCall: ToolCall = {
|
|
387
|
-
toolCallId: 'call-1',
|
|
388
|
-
toolName: 'test_tool',
|
|
389
|
-
arguments: {},
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
const reasoningTurn = createMockTurn({ text: 'Thinking.' });
|
|
393
|
-
const actionTurn = createMockTurn({
|
|
394
|
-
text: 'Acting.',
|
|
395
|
-
toolCalls: [toolCall],
|
|
396
|
-
toolExecutions: [{
|
|
397
|
-
toolCallId: 'call-1',
|
|
398
|
-
toolName: 'test_tool',
|
|
399
|
-
arguments: {},
|
|
400
|
-
result: 'ok',
|
|
401
|
-
duration: 10,
|
|
402
|
-
isError: false,
|
|
403
|
-
}],
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
const llm = createMockLLM([
|
|
407
|
-
reasoningTurn, actionTurn,
|
|
408
|
-
reasoningTurn, actionTurn,
|
|
409
|
-
reasoningTurn, actionTurn,
|
|
410
|
-
]);
|
|
411
|
-
|
|
412
|
-
const strategy = react();
|
|
413
|
-
const context: ExecutionContext = {
|
|
414
|
-
agent: { id: 'test-agent' },
|
|
415
|
-
llm,
|
|
416
|
-
input,
|
|
417
|
-
state,
|
|
418
|
-
tools,
|
|
419
|
-
strategy: {
|
|
420
|
-
stopCondition: (s) => s.step >= 2,
|
|
421
|
-
},
|
|
422
|
-
};
|
|
423
|
-
|
|
424
|
-
const result = await strategy.execute(context);
|
|
425
|
-
|
|
426
|
-
expect(result.state.step).toBe(2);
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
test('uses custom reasoning prompt', async () => {
|
|
430
|
-
const reasoningTurn = createMockTurn({ text: 'Custom reasoning.' });
|
|
431
|
-
const actionTurn = createMockTurn({ text: 'Done.' });
|
|
432
|
-
|
|
433
|
-
let callCount = 0;
|
|
434
|
-
let reasoningCallMessages: unknown[] = [];
|
|
435
|
-
const llm = {
|
|
436
|
-
generate: mock(async (...args: unknown[]) => {
|
|
437
|
-
callCount++;
|
|
438
|
-
// First call is reasoning phase - capture those messages
|
|
439
|
-
if (callCount === 1) {
|
|
440
|
-
// The args[0] is the messages array passed to llm.generate
|
|
441
|
-
reasoningCallMessages = Array.isArray(args[0]) ? [...args[0]] : [];
|
|
442
|
-
return reasoningTurn;
|
|
443
|
-
}
|
|
444
|
-
return actionTurn;
|
|
445
|
-
}),
|
|
446
|
-
stream: mock(() => ({
|
|
447
|
-
async *[Symbol.asyncIterator]() {},
|
|
448
|
-
turn: Promise.resolve(actionTurn),
|
|
449
|
-
})),
|
|
450
|
-
} as unknown as LLMInstance;
|
|
451
|
-
|
|
452
|
-
const customPrompt = 'Think carefully about this specific problem.';
|
|
453
|
-
const strategy = react({ reasoningPrompt: customPrompt });
|
|
454
|
-
const context: ExecutionContext = {
|
|
455
|
-
agent: { id: 'test-agent' },
|
|
456
|
-
llm,
|
|
457
|
-
input,
|
|
458
|
-
state,
|
|
459
|
-
tools,
|
|
460
|
-
strategy: {},
|
|
461
|
-
};
|
|
462
|
-
|
|
463
|
-
await strategy.execute(context);
|
|
464
|
-
|
|
465
|
-
// Should have 2 calls: reasoning + action
|
|
466
|
-
expect(callCount).toBe(2);
|
|
467
|
-
|
|
468
|
-
// The reasoning phase messages should include the custom prompt
|
|
469
|
-
// The last message should be the custom reasoning prompt as a UserMessage
|
|
470
|
-
expect(reasoningCallMessages.length).toBeGreaterThan(0);
|
|
471
|
-
|
|
472
|
-
const lastMsg = reasoningCallMessages[reasoningCallMessages.length - 1];
|
|
473
|
-
expect(lastMsg).toBeInstanceOf(UserMessage);
|
|
474
|
-
|
|
475
|
-
// UserMessage.content is an array of content blocks in UPP format
|
|
476
|
-
const msgContent = (lastMsg as UserMessage).content;
|
|
477
|
-
// Content is array of blocks like [{ type: "text", text: "..." }]
|
|
478
|
-
const textBlock = msgContent.find(
|
|
479
|
-
(block): block is TextBlock => block.type === 'text',
|
|
480
|
-
);
|
|
481
|
-
expect(textBlock).toBeDefined();
|
|
482
|
-
expect(textBlock?.text).toBe(customPrompt);
|
|
483
|
-
});
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
describe('stream()', () => {
|
|
487
|
-
test('yields events and resolves result', async () => {
|
|
488
|
-
const reasoningTurn = createMockTurn({ text: 'Streaming reasoning.' });
|
|
489
|
-
const actionTurn = createMockTurn({ text: 'Streaming action.' });
|
|
490
|
-
const llm = createMockLLM([reasoningTurn, actionTurn]);
|
|
491
|
-
|
|
492
|
-
const strategy = react();
|
|
493
|
-
const context: ExecutionContext = {
|
|
494
|
-
agent: { id: 'test-agent' },
|
|
495
|
-
llm,
|
|
496
|
-
input,
|
|
497
|
-
state,
|
|
498
|
-
tools,
|
|
499
|
-
strategy: {},
|
|
500
|
-
};
|
|
501
|
-
|
|
502
|
-
const streamResult = strategy.stream(context);
|
|
503
|
-
const events: unknown[] = [];
|
|
504
|
-
|
|
505
|
-
for await (const event of streamResult) {
|
|
506
|
-
events.push(event);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
const result = await streamResult.result;
|
|
510
|
-
|
|
511
|
-
expect(events.length).toBeGreaterThan(0);
|
|
512
|
-
expect(result.turn).toBeDefined();
|
|
513
|
-
expect(result.state).toBeDefined();
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
test('emits reasoning event', async () => {
|
|
517
|
-
const reasoningTurn = createMockTurn({ text: 'My reasoning.' });
|
|
518
|
-
const actionTurn = createMockTurn({ text: 'Done.' });
|
|
519
|
-
const llm = createMockLLM([reasoningTurn, actionTurn]);
|
|
520
|
-
|
|
521
|
-
const strategy = react();
|
|
522
|
-
const context: ExecutionContext = {
|
|
523
|
-
agent: { id: 'test-agent' },
|
|
524
|
-
llm,
|
|
525
|
-
input,
|
|
526
|
-
state,
|
|
527
|
-
tools,
|
|
528
|
-
strategy: {},
|
|
529
|
-
};
|
|
530
|
-
|
|
531
|
-
const streamResult = strategy.stream(context);
|
|
532
|
-
const uapEvents: Array<{ source: string; uap?: { type: string; data?: { reasoning?: string } } }> = [];
|
|
533
|
-
|
|
534
|
-
for await (const event of streamResult) {
|
|
535
|
-
if (event.source === 'uap') {
|
|
536
|
-
uapEvents.push(event as typeof uapEvents[0]);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
await streamResult.result;
|
|
541
|
-
|
|
542
|
-
const reasoningEvent = uapEvents.find((e) => e.uap?.type === 'reasoning');
|
|
543
|
-
expect(reasoningEvent).toBeDefined();
|
|
544
|
-
expect(reasoningEvent?.uap?.data?.reasoning).toBe('My reasoning.');
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
test('emits step_start and step_end events', async () => {
|
|
548
|
-
const reasoningTurn = createMockTurn({ text: 'Thinking.' });
|
|
549
|
-
const actionTurn = createMockTurn({ text: 'Done.' });
|
|
550
|
-
const llm = createMockLLM([reasoningTurn, actionTurn]);
|
|
551
|
-
|
|
552
|
-
const strategy = react();
|
|
553
|
-
const context: ExecutionContext = {
|
|
554
|
-
agent: { id: 'test-agent' },
|
|
555
|
-
llm,
|
|
556
|
-
input,
|
|
557
|
-
state,
|
|
558
|
-
tools,
|
|
559
|
-
strategy: {},
|
|
560
|
-
};
|
|
561
|
-
|
|
562
|
-
const streamResult = strategy.stream(context);
|
|
563
|
-
const uapEvents: Array<{ source: string; uap?: { type: string } }> = [];
|
|
564
|
-
|
|
565
|
-
for await (const event of streamResult) {
|
|
566
|
-
if (event.source === 'uap') {
|
|
567
|
-
uapEvents.push(event as typeof uapEvents[0]);
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
await streamResult.result;
|
|
572
|
-
|
|
573
|
-
const stepStart = uapEvents.find((e) => e.uap?.type === 'step_start');
|
|
574
|
-
const stepEnd = uapEvents.find((e) => e.uap?.type === 'step_end');
|
|
575
|
-
|
|
576
|
-
expect(stepStart).toBeDefined();
|
|
577
|
-
expect(stepEnd).toBeDefined();
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
test('supports abort()', async () => {
|
|
581
|
-
const reasoningTurn = createMockTurn({ text: 'Thinking.' });
|
|
582
|
-
const actionTurn = createMockTurn({ text: 'Done.' });
|
|
583
|
-
const llm = createMockLLM([reasoningTurn, actionTurn]);
|
|
584
|
-
|
|
585
|
-
const strategy = react();
|
|
586
|
-
const context: ExecutionContext = {
|
|
587
|
-
agent: { id: 'test-agent' },
|
|
588
|
-
llm,
|
|
589
|
-
input,
|
|
590
|
-
state,
|
|
591
|
-
tools,
|
|
592
|
-
strategy: {},
|
|
593
|
-
};
|
|
594
|
-
|
|
595
|
-
const streamResult = strategy.stream(context);
|
|
596
|
-
|
|
597
|
-
// Abort immediately
|
|
598
|
-
streamResult.abort();
|
|
599
|
-
|
|
600
|
-
// The stream should handle abort gracefully
|
|
601
|
-
expect(typeof streamResult.abort).toBe('function');
|
|
602
|
-
});
|
|
603
|
-
});
|
|
604
|
-
});
|