@providerprotocol/agents 0.0.2 → 0.0.4
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/dist/checkpoint/index.d.ts +43 -0
- package/dist/checkpoint/index.js +73 -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-T47B3VAF.js +427 -0
- package/dist/chunk-T47B3VAF.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-BiyEVOnf.d.ts +65 -0
- package/dist/types-D1egxttz.d.ts +270 -0
- package/dist/types-DChRdQoX.d.ts +98 -0
- package/package.json +41 -9
- package/.claude/settings.local.json +0 -29
- 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,249 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Live tests for sub-agent event propagation.
|
|
3
|
-
*
|
|
4
|
-
* These tests verify that sub-agent events are correctly emitted
|
|
5
|
-
* when using the Task tool pattern.
|
|
6
|
-
*
|
|
7
|
-
* @see UAP-1.0 Spec Section 8.7
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { describe, test, expect } from 'bun:test';
|
|
11
|
-
import { agent, AgentState } from '../../src/index.ts';
|
|
12
|
-
import type {
|
|
13
|
-
SubagentEvent,
|
|
14
|
-
SubagentStartEvent,
|
|
15
|
-
SubagentEndEvent,
|
|
16
|
-
OnSubagentEvent,
|
|
17
|
-
} from '../../src/execution/types.ts';
|
|
18
|
-
import { anthropic } from '@providerprotocol/ai/anthropic';
|
|
19
|
-
import type { Tool } from '@providerprotocol/ai';
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Helper to create a simple sub-agent tool with event propagation.
|
|
23
|
-
*/
|
|
24
|
-
function createSubagentTool(options: {
|
|
25
|
-
name: string;
|
|
26
|
-
subagentType: string;
|
|
27
|
-
onSubagentEvent?: OnSubagentEvent;
|
|
28
|
-
}): Tool {
|
|
29
|
-
const { name, subagentType, onSubagentEvent } = options;
|
|
30
|
-
|
|
31
|
-
// Create a simple sub-agent
|
|
32
|
-
const subAgent = agent({
|
|
33
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
34
|
-
params: { max_tokens: 256 },
|
|
35
|
-
system: 'You are a helpful assistant. Be concise.',
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
name,
|
|
40
|
-
description: `A sub-agent that helps with tasks. Type: ${subagentType}`,
|
|
41
|
-
parameters: {
|
|
42
|
-
type: 'object' as const,
|
|
43
|
-
properties: {
|
|
44
|
-
task: {
|
|
45
|
-
type: 'string' as const,
|
|
46
|
-
description: 'The task to perform',
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
required: ['task'],
|
|
50
|
-
},
|
|
51
|
-
run: async (params: { task: string }, context?: { toolCallId?: string }) => {
|
|
52
|
-
const subagentId = `sub-${Date.now()}`;
|
|
53
|
-
const parentToolCallId = context?.toolCallId ?? `tool-${Date.now()}`;
|
|
54
|
-
const startTime = Date.now();
|
|
55
|
-
|
|
56
|
-
// Emit start event
|
|
57
|
-
onSubagentEvent?.({
|
|
58
|
-
type: 'subagent_start',
|
|
59
|
-
subagentId,
|
|
60
|
-
subagentType,
|
|
61
|
-
parentToolCallId,
|
|
62
|
-
prompt: params.task,
|
|
63
|
-
timestamp: startTime,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
// Stream the sub-agent for real-time events
|
|
68
|
-
const stream = subAgent.stream(params.task, AgentState.initial());
|
|
69
|
-
|
|
70
|
-
// Forward inner events
|
|
71
|
-
for await (const event of stream) {
|
|
72
|
-
onSubagentEvent?.({
|
|
73
|
-
type: 'subagent_event',
|
|
74
|
-
subagentId,
|
|
75
|
-
subagentType,
|
|
76
|
-
parentToolCallId,
|
|
77
|
-
innerEvent: event,
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const result = await stream.result;
|
|
82
|
-
const endTime = Date.now();
|
|
83
|
-
|
|
84
|
-
// Emit end event
|
|
85
|
-
onSubagentEvent?.({
|
|
86
|
-
type: 'subagent_end',
|
|
87
|
-
subagentId,
|
|
88
|
-
subagentType,
|
|
89
|
-
parentToolCallId,
|
|
90
|
-
success: true,
|
|
91
|
-
result: result.turn.response.text,
|
|
92
|
-
timestamp: endTime,
|
|
93
|
-
toolExecutions: result.turn.toolExecutions?.map((exec) => ({
|
|
94
|
-
toolName: exec.toolName,
|
|
95
|
-
arguments: exec.arguments as Record<string, unknown>,
|
|
96
|
-
result: typeof exec.result === 'string' ? exec.result : JSON.stringify(exec.result),
|
|
97
|
-
})),
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
return result.turn.response.text;
|
|
101
|
-
} catch (error) {
|
|
102
|
-
const endTime = Date.now();
|
|
103
|
-
|
|
104
|
-
// Emit error end event
|
|
105
|
-
onSubagentEvent?.({
|
|
106
|
-
type: 'subagent_end',
|
|
107
|
-
subagentId,
|
|
108
|
-
subagentType,
|
|
109
|
-
parentToolCallId,
|
|
110
|
-
success: false,
|
|
111
|
-
error: error instanceof Error ? error.message : String(error),
|
|
112
|
-
timestamp: endTime,
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
throw error;
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
describe('Sub-agent event propagation (live)', () => {
|
|
122
|
-
test('sub-agent tool emits start, inner events, and end events', async () => {
|
|
123
|
-
const receivedEvents: SubagentEvent[] = [];
|
|
124
|
-
|
|
125
|
-
const subagentTool = createSubagentTool({
|
|
126
|
-
name: 'helper',
|
|
127
|
-
subagentType: 'assistant',
|
|
128
|
-
onSubagentEvent: (event) => receivedEvents.push(event),
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// Call the tool directly
|
|
132
|
-
const result = await subagentTool.run({ task: 'Say hello' });
|
|
133
|
-
|
|
134
|
-
// Verify we received events
|
|
135
|
-
expect(receivedEvents.length).toBeGreaterThan(0);
|
|
136
|
-
|
|
137
|
-
// Check for start event
|
|
138
|
-
const startEvent = receivedEvents.find((e) => e.type === 'subagent_start') as SubagentStartEvent | undefined;
|
|
139
|
-
expect(startEvent).toBeDefined();
|
|
140
|
-
expect(startEvent?.subagentType).toBe('assistant');
|
|
141
|
-
expect(startEvent?.prompt).toBe('Say hello');
|
|
142
|
-
|
|
143
|
-
// Check for inner events (should have at least text deltas)
|
|
144
|
-
const innerEvents = receivedEvents.filter((e) => e.type === 'subagent_event');
|
|
145
|
-
expect(innerEvents.length).toBeGreaterThan(0);
|
|
146
|
-
|
|
147
|
-
// Check for end event
|
|
148
|
-
const endEvent = receivedEvents.find((e) => e.type === 'subagent_end') as SubagentEndEvent | undefined;
|
|
149
|
-
expect(endEvent).toBeDefined();
|
|
150
|
-
expect(endEvent?.success).toBe(true);
|
|
151
|
-
expect(endEvent?.result).toBeDefined();
|
|
152
|
-
|
|
153
|
-
// Result should match end event result
|
|
154
|
-
expect(result).toBe(endEvent?.result);
|
|
155
|
-
|
|
156
|
-
// Verify event order: start should come before end
|
|
157
|
-
const startIndex = receivedEvents.findIndex((e) => e.type === 'subagent_start');
|
|
158
|
-
const endIndex = receivedEvents.findIndex((e) => e.type === 'subagent_end');
|
|
159
|
-
expect(startIndex).toBeLessThan(endIndex);
|
|
160
|
-
|
|
161
|
-
// All events should have the same subagentId
|
|
162
|
-
const subagentId = startEvent?.subagentId;
|
|
163
|
-
expect(receivedEvents.every((e) => e.subagentId === subagentId)).toBe(true);
|
|
164
|
-
}, 30000);
|
|
165
|
-
|
|
166
|
-
test('sub-agent inner events include text deltas', async () => {
|
|
167
|
-
const receivedEvents: SubagentEvent[] = [];
|
|
168
|
-
|
|
169
|
-
const subagentTool = createSubagentTool({
|
|
170
|
-
name: 'helper',
|
|
171
|
-
subagentType: 'assistant',
|
|
172
|
-
onSubagentEvent: (event) => receivedEvents.push(event),
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
await subagentTool.run({ task: 'Count from 1 to 3' });
|
|
176
|
-
|
|
177
|
-
// Find text delta events
|
|
178
|
-
const textDeltas = receivedEvents.filter(
|
|
179
|
-
(e) => e.type === 'subagent_event' && e.innerEvent.source === 'upp' && e.innerEvent.upp?.type === 'text_delta'
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
// Should have some text deltas
|
|
183
|
-
expect(textDeltas.length).toBeGreaterThan(0);
|
|
184
|
-
}, 30000);
|
|
185
|
-
|
|
186
|
-
test('sub-agent events have consistent parentToolCallId', async () => {
|
|
187
|
-
const receivedEvents: SubagentEvent[] = [];
|
|
188
|
-
|
|
189
|
-
const subagentTool = createSubagentTool({
|
|
190
|
-
name: 'helper',
|
|
191
|
-
subagentType: 'assistant',
|
|
192
|
-
onSubagentEvent: (event) => receivedEvents.push(event),
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
// Call with explicit context
|
|
196
|
-
await (subagentTool.run as (params: { task: string }, context?: { toolCallId?: string }) => Promise<string>)(
|
|
197
|
-
{ task: 'Hello' },
|
|
198
|
-
{ toolCallId: 'custom-tool-id-123' }
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
// All events should have the same parentToolCallId
|
|
202
|
-
expect(receivedEvents.every((e) => e.parentToolCallId === 'custom-tool-id-123')).toBe(true);
|
|
203
|
-
}, 30000);
|
|
204
|
-
|
|
205
|
-
test('timestamps are monotonically increasing', async () => {
|
|
206
|
-
const receivedEvents: SubagentEvent[] = [];
|
|
207
|
-
|
|
208
|
-
const subagentTool = createSubagentTool({
|
|
209
|
-
name: 'helper',
|
|
210
|
-
subagentType: 'assistant',
|
|
211
|
-
onSubagentEvent: (event) => receivedEvents.push(event),
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
await subagentTool.run({ task: 'Hi' });
|
|
215
|
-
|
|
216
|
-
const startEvent = receivedEvents.find((e) => e.type === 'subagent_start') as SubagentStartEvent | undefined;
|
|
217
|
-
const endEvent = receivedEvents.find((e) => e.type === 'subagent_end') as SubagentEndEvent | undefined;
|
|
218
|
-
|
|
219
|
-
expect(startEvent?.timestamp).toBeDefined();
|
|
220
|
-
expect(endEvent?.timestamp).toBeDefined();
|
|
221
|
-
if (startEvent && endEvent) {
|
|
222
|
-
expect(endEvent.timestamp).toBeGreaterThanOrEqual(startEvent.timestamp);
|
|
223
|
-
}
|
|
224
|
-
}, 30000);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
describe('Sub-agent event type guards', () => {
|
|
228
|
-
test('can filter events by type', async () => {
|
|
229
|
-
const receivedEvents: SubagentEvent[] = [];
|
|
230
|
-
|
|
231
|
-
const subagentTool = createSubagentTool({
|
|
232
|
-
name: 'helper',
|
|
233
|
-
subagentType: 'assistant',
|
|
234
|
-
onSubagentEvent: (event) => receivedEvents.push(event),
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
await subagentTool.run({ task: 'Say one word' });
|
|
238
|
-
|
|
239
|
-
// Filter start events
|
|
240
|
-
const startEvents = receivedEvents.filter((e): e is SubagentStartEvent => e.type === 'subagent_start');
|
|
241
|
-
expect(startEvents).toHaveLength(1);
|
|
242
|
-
expect(startEvents[0]?.prompt).toBeDefined();
|
|
243
|
-
|
|
244
|
-
// Filter end events
|
|
245
|
-
const endEvents = receivedEvents.filter((e): e is SubagentEndEvent => e.type === 'subagent_end');
|
|
246
|
-
expect(endEvents).toHaveLength(1);
|
|
247
|
-
expect(typeof endEvents[0]?.success).toBe('boolean');
|
|
248
|
-
}, 30000);
|
|
249
|
-
});
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, setDefaultTimeout } from 'bun:test';
|
|
2
|
-
import { anthropic } from '@providerprotocol/ai/anthropic';
|
|
3
|
-
import { agent, ThreadTree } from '../../src/index.ts';
|
|
4
|
-
|
|
5
|
-
// Skip tests if no API key
|
|
6
|
-
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
|
7
|
-
|
|
8
|
-
// Increase timeout for live API tests (60 seconds)
|
|
9
|
-
setDefaultTimeout(60_000);
|
|
10
|
-
|
|
11
|
-
describe.skipIf(!ANTHROPIC_API_KEY)('ThreadTree (Live)', () => {
|
|
12
|
-
describe('basic branching', () => {
|
|
13
|
-
test('maintains separate contexts in branches', async () => {
|
|
14
|
-
const a = agent({
|
|
15
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
16
|
-
params: { max_tokens: 100 },
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
const tree = new ThreadTree();
|
|
20
|
-
|
|
21
|
-
// First conversation in root
|
|
22
|
-
const result1 = await a.ask('My name is Alice.', tree.history());
|
|
23
|
-
tree.current.state = result1.state;
|
|
24
|
-
|
|
25
|
-
// Create branch 1 from root
|
|
26
|
-
const branch1Id = tree.branch(tree.root.id, 'branch-1');
|
|
27
|
-
tree.checkout(branch1Id);
|
|
28
|
-
|
|
29
|
-
// In branch 1, say name is Bob
|
|
30
|
-
const branch1Result = await a.ask('Actually, my name is Bob.', tree.history());
|
|
31
|
-
tree.current.state = branch1Result.state;
|
|
32
|
-
|
|
33
|
-
// Create branch 2 from root (not from branch 1)
|
|
34
|
-
const branch2Id = tree.branch(tree.root.id, 'branch-2');
|
|
35
|
-
tree.checkout(branch2Id);
|
|
36
|
-
|
|
37
|
-
// In branch 2, say name is Charlie
|
|
38
|
-
const branch2Result = await a.ask('Actually, my name is Charlie.', tree.history());
|
|
39
|
-
tree.current.state = branch2Result.state;
|
|
40
|
-
|
|
41
|
-
// Verify branch 1 remembers Bob
|
|
42
|
-
tree.checkout(branch1Id);
|
|
43
|
-
const branch1Check = await a.ask('What is my name?', tree.history());
|
|
44
|
-
expect(branch1Check.turn.response.text.toLowerCase()).toContain('bob');
|
|
45
|
-
|
|
46
|
-
// Verify branch 2 remembers Charlie
|
|
47
|
-
tree.checkout(branch2Id);
|
|
48
|
-
const branch2Check = await a.ask('What is my name?', tree.history());
|
|
49
|
-
expect(branch2Check.turn.response.text.toLowerCase()).toContain('charlie');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('branch inherits parent state', async () => {
|
|
53
|
-
const a = agent({
|
|
54
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
55
|
-
params: { max_tokens: 100 },
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const tree = new ThreadTree();
|
|
59
|
-
|
|
60
|
-
// Establish context in root
|
|
61
|
-
const result1 = await a.ask('My favorite color is green.', tree.history());
|
|
62
|
-
tree.current.state = result1.state;
|
|
63
|
-
|
|
64
|
-
// Create branch
|
|
65
|
-
const branchId = tree.branch(tree.root.id, 'color-branch');
|
|
66
|
-
tree.checkout(branchId);
|
|
67
|
-
|
|
68
|
-
// Branch should remember the color from parent
|
|
69
|
-
const branchResult = await a.ask('What is my favorite color?', tree.history());
|
|
70
|
-
expect(branchResult.turn.response.text.toLowerCase()).toContain('green');
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
describe('serialization with live data', () => {
|
|
75
|
-
test('serializes and restores tree with conversation history', async () => {
|
|
76
|
-
const a = agent({
|
|
77
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
78
|
-
params: { max_tokens: 100 },
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
const tree = new ThreadTree();
|
|
82
|
-
|
|
83
|
-
// Build conversation
|
|
84
|
-
const result1 = await a.ask('Remember the number 42.', tree.history());
|
|
85
|
-
tree.current.state = result1.state;
|
|
86
|
-
|
|
87
|
-
// Create branch
|
|
88
|
-
const branchId = tree.branch(tree.root.id, 'number-branch');
|
|
89
|
-
tree.checkout(branchId);
|
|
90
|
-
|
|
91
|
-
const result2 = await a.ask('The number is now 99.', tree.history());
|
|
92
|
-
tree.current.state = result2.state;
|
|
93
|
-
|
|
94
|
-
// Serialize
|
|
95
|
-
const json = tree.toJSON();
|
|
96
|
-
|
|
97
|
-
// Restore
|
|
98
|
-
const restored = ThreadTree.fromJSON(json);
|
|
99
|
-
|
|
100
|
-
// Verify restored tree has correct structure
|
|
101
|
-
expect(restored.nodes.size).toBe(tree.nodes.size);
|
|
102
|
-
expect(restored.root.id).toBe(tree.root.id);
|
|
103
|
-
|
|
104
|
-
// Checkout branch and verify context
|
|
105
|
-
restored.checkout(branchId);
|
|
106
|
-
const checkResult = await a.ask('What number did I mention last?', restored.history());
|
|
107
|
-
expect(checkResult.turn.response.text).toContain('99');
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
describe('multiple branches', () => {
|
|
112
|
-
test('supports multiple simultaneous branches', async () => {
|
|
113
|
-
const a = agent({
|
|
114
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
115
|
-
params: { max_tokens: 100 },
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const tree = new ThreadTree();
|
|
119
|
-
|
|
120
|
-
// Setup base context
|
|
121
|
-
const result = await a.ask('I am thinking of a secret word.', tree.history());
|
|
122
|
-
tree.current.state = result.state;
|
|
123
|
-
|
|
124
|
-
// Create 3 branches with different secrets
|
|
125
|
-
const branch1Id = tree.branch(tree.root.id, 'branch-apple');
|
|
126
|
-
tree.checkout(branch1Id);
|
|
127
|
-
const r1 = await a.ask('The secret word is apple.', tree.history());
|
|
128
|
-
tree.current.state = r1.state;
|
|
129
|
-
|
|
130
|
-
const branch2Id = tree.branch(tree.root.id, 'branch-banana');
|
|
131
|
-
tree.checkout(branch2Id);
|
|
132
|
-
const r2 = await a.ask('The secret word is banana.', tree.history());
|
|
133
|
-
tree.current.state = r2.state;
|
|
134
|
-
|
|
135
|
-
const branch3Id = tree.branch(tree.root.id, 'branch-cherry');
|
|
136
|
-
tree.checkout(branch3Id);
|
|
137
|
-
const r3 = await a.ask('The secret word is cherry.', tree.history());
|
|
138
|
-
tree.current.state = r3.state;
|
|
139
|
-
|
|
140
|
-
// Verify leaves
|
|
141
|
-
const leaves = tree.getLeaves();
|
|
142
|
-
expect(leaves.length).toBe(3);
|
|
143
|
-
|
|
144
|
-
// Verify each branch has correct secret
|
|
145
|
-
tree.checkout(branch1Id);
|
|
146
|
-
const check1 = await a.ask('What is the secret word?', tree.history());
|
|
147
|
-
expect(check1.turn.response.text.toLowerCase()).toContain('apple');
|
|
148
|
-
|
|
149
|
-
tree.checkout(branch2Id);
|
|
150
|
-
const check2 = await a.ask('What is the secret word?', tree.history());
|
|
151
|
-
expect(check2.turn.response.text.toLowerCase()).toContain('banana');
|
|
152
|
-
|
|
153
|
-
tree.checkout(branch3Id);
|
|
154
|
-
const check3 = await a.ask('What is the secret word?', tree.history());
|
|
155
|
-
expect(check3.turn.response.text.toLowerCase()).toContain('cherry');
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
describe('branch naming', () => {
|
|
160
|
-
test('retrieves branches by name', async () => {
|
|
161
|
-
const a = agent({
|
|
162
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
163
|
-
params: { max_tokens: 50 },
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
const tree = new ThreadTree();
|
|
167
|
-
|
|
168
|
-
// Build initial state
|
|
169
|
-
const result = await a.ask('Hello', tree.history());
|
|
170
|
-
tree.current.state = result.state;
|
|
171
|
-
|
|
172
|
-
// Create named branches
|
|
173
|
-
tree.branch(tree.root.id, 'experiment-a');
|
|
174
|
-
tree.branch(tree.root.id, 'experiment-b');
|
|
175
|
-
tree.branch(tree.root.id, 'experiment-c');
|
|
176
|
-
|
|
177
|
-
const branches = tree.getBranches();
|
|
178
|
-
|
|
179
|
-
// Should have 4 branches (root + 3 named)
|
|
180
|
-
expect(branches.size).toBe(4);
|
|
181
|
-
expect([...branches.values()]).toContain('experiment-a');
|
|
182
|
-
expect([...branches.values()]).toContain('experiment-b');
|
|
183
|
-
expect([...branches.values()]).toContain('experiment-c');
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
});
|