@providerprotocol/agents 0.0.2 → 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/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 -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,235 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for sub-agent event types.
|
|
3
|
-
*
|
|
4
|
-
* @see UAP-1.0 Spec Section 8.7
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { describe, test, expect } from 'bun:test';
|
|
8
|
-
import type {
|
|
9
|
-
SubagentEvent,
|
|
10
|
-
SubagentStartEvent,
|
|
11
|
-
SubagentInnerEvent,
|
|
12
|
-
SubagentEndEvent,
|
|
13
|
-
OnSubagentEvent,
|
|
14
|
-
AgentStreamEvent,
|
|
15
|
-
} from '../../../src/execution/types.ts';
|
|
16
|
-
|
|
17
|
-
describe('SubagentEvent types', () => {
|
|
18
|
-
describe('SubagentStartEvent', () => {
|
|
19
|
-
test('should have correct structure', () => {
|
|
20
|
-
const event: SubagentStartEvent = {
|
|
21
|
-
type: 'subagent_start',
|
|
22
|
-
subagentId: 'sub-123',
|
|
23
|
-
subagentType: 'explorer',
|
|
24
|
-
parentToolCallId: 'tool-456',
|
|
25
|
-
prompt: 'Find all TypeScript files',
|
|
26
|
-
timestamp: Date.now(),
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
expect(event.type).toBe('subagent_start');
|
|
30
|
-
expect(event.subagentId).toBe('sub-123');
|
|
31
|
-
expect(event.subagentType).toBe('explorer');
|
|
32
|
-
expect(event.parentToolCallId).toBe('tool-456');
|
|
33
|
-
expect(event.prompt).toBe('Find all TypeScript files');
|
|
34
|
-
expect(typeof event.timestamp).toBe('number');
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
describe('SubagentInnerEvent', () => {
|
|
39
|
-
test('should wrap inner AgentStreamEvent', () => {
|
|
40
|
-
const innerEvent: AgentStreamEvent = {
|
|
41
|
-
source: 'upp',
|
|
42
|
-
upp: {
|
|
43
|
-
type: 'text_delta',
|
|
44
|
-
index: 0,
|
|
45
|
-
delta: { text: 'Hello' },
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const event: SubagentInnerEvent = {
|
|
50
|
-
type: 'subagent_event',
|
|
51
|
-
subagentId: 'sub-123',
|
|
52
|
-
subagentType: 'explorer',
|
|
53
|
-
parentToolCallId: 'tool-456',
|
|
54
|
-
innerEvent,
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
expect(event.type).toBe('subagent_event');
|
|
58
|
-
expect(event.innerEvent.source).toBe('upp');
|
|
59
|
-
expect(event.innerEvent.upp?.type).toBe('text_delta');
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('should wrap UAP events from sub-agent', () => {
|
|
63
|
-
const innerEvent: AgentStreamEvent = {
|
|
64
|
-
source: 'uap',
|
|
65
|
-
uap: {
|
|
66
|
-
type: 'step_start',
|
|
67
|
-
step: 1,
|
|
68
|
-
agentId: 'sub-123',
|
|
69
|
-
data: { iteration: 1 },
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const event: SubagentInnerEvent = {
|
|
74
|
-
type: 'subagent_event',
|
|
75
|
-
subagentId: 'sub-123',
|
|
76
|
-
subagentType: 'planner',
|
|
77
|
-
parentToolCallId: 'tool-789',
|
|
78
|
-
innerEvent,
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
expect(event.innerEvent.source).toBe('uap');
|
|
82
|
-
expect(event.innerEvent.uap?.type).toBe('step_start');
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
describe('SubagentEndEvent', () => {
|
|
87
|
-
test('should have correct structure for successful completion', () => {
|
|
88
|
-
const event: SubagentEndEvent = {
|
|
89
|
-
type: 'subagent_end',
|
|
90
|
-
subagentId: 'sub-123',
|
|
91
|
-
subagentType: 'explorer',
|
|
92
|
-
parentToolCallId: 'tool-456',
|
|
93
|
-
success: true,
|
|
94
|
-
result: 'Found 42 TypeScript files',
|
|
95
|
-
timestamp: Date.now(),
|
|
96
|
-
toolExecutions: [
|
|
97
|
-
{
|
|
98
|
-
toolName: 'Glob',
|
|
99
|
-
arguments: { pattern: '**/*.ts' },
|
|
100
|
-
result: 'file1.ts\nfile2.ts',
|
|
101
|
-
},
|
|
102
|
-
],
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
expect(event.type).toBe('subagent_end');
|
|
106
|
-
expect(event.success).toBe(true);
|
|
107
|
-
expect(event.result).toBe('Found 42 TypeScript files');
|
|
108
|
-
expect(event.error).toBeUndefined();
|
|
109
|
-
expect(event.toolExecutions).toHaveLength(1);
|
|
110
|
-
expect(event.toolExecutions?.[0]?.toolName).toBe('Glob');
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test('should have correct structure for failed completion', () => {
|
|
114
|
-
const event: SubagentEndEvent = {
|
|
115
|
-
type: 'subagent_end',
|
|
116
|
-
subagentId: 'sub-123',
|
|
117
|
-
subagentType: 'explorer',
|
|
118
|
-
parentToolCallId: 'tool-456',
|
|
119
|
-
success: false,
|
|
120
|
-
error: 'Rate limit exceeded',
|
|
121
|
-
timestamp: Date.now(),
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
expect(event.type).toBe('subagent_end');
|
|
125
|
-
expect(event.success).toBe(false);
|
|
126
|
-
expect(event.error).toBe('Rate limit exceeded');
|
|
127
|
-
expect(event.result).toBeUndefined();
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
describe('SubagentEvent union type', () => {
|
|
132
|
-
test('should discriminate by type field', () => {
|
|
133
|
-
const events: SubagentEvent[] = [
|
|
134
|
-
{
|
|
135
|
-
type: 'subagent_start',
|
|
136
|
-
subagentId: 'sub-1',
|
|
137
|
-
subagentType: 'explorer',
|
|
138
|
-
parentToolCallId: 'tool-1',
|
|
139
|
-
prompt: 'test',
|
|
140
|
-
timestamp: 1000,
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
type: 'subagent_event',
|
|
144
|
-
subagentId: 'sub-1',
|
|
145
|
-
subagentType: 'explorer',
|
|
146
|
-
parentToolCallId: 'tool-1',
|
|
147
|
-
innerEvent: { source: 'upp', upp: { type: 'text_delta', index: 0, delta: { text: 'hi' } } },
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
type: 'subagent_end',
|
|
151
|
-
subagentId: 'sub-1',
|
|
152
|
-
subagentType: 'explorer',
|
|
153
|
-
parentToolCallId: 'tool-1',
|
|
154
|
-
success: true,
|
|
155
|
-
timestamp: 2000,
|
|
156
|
-
},
|
|
157
|
-
];
|
|
158
|
-
|
|
159
|
-
// Type narrowing should work correctly
|
|
160
|
-
for (const event of events) {
|
|
161
|
-
switch (event.type) {
|
|
162
|
-
case 'subagent_start':
|
|
163
|
-
expect(event.prompt).toBeDefined();
|
|
164
|
-
break;
|
|
165
|
-
case 'subagent_event':
|
|
166
|
-
expect(event.innerEvent).toBeDefined();
|
|
167
|
-
break;
|
|
168
|
-
case 'subagent_end':
|
|
169
|
-
expect(typeof event.success).toBe('boolean');
|
|
170
|
-
break;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
describe('OnSubagentEvent callback', () => {
|
|
177
|
-
test('should be callable with any SubagentEvent', () => {
|
|
178
|
-
const receivedEvents: SubagentEvent[] = [];
|
|
179
|
-
|
|
180
|
-
const callback: OnSubagentEvent = (event) => {
|
|
181
|
-
receivedEvents.push(event);
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
// Emit start event
|
|
185
|
-
callback({
|
|
186
|
-
type: 'subagent_start',
|
|
187
|
-
subagentId: 'sub-1',
|
|
188
|
-
subagentType: 'explorer',
|
|
189
|
-
parentToolCallId: 'tool-1',
|
|
190
|
-
prompt: 'test',
|
|
191
|
-
timestamp: Date.now(),
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// Emit inner event
|
|
195
|
-
callback({
|
|
196
|
-
type: 'subagent_event',
|
|
197
|
-
subagentId: 'sub-1',
|
|
198
|
-
subagentType: 'explorer',
|
|
199
|
-
parentToolCallId: 'tool-1',
|
|
200
|
-
innerEvent: { source: 'upp', upp: { type: 'message_stop', index: 0, delta: {} } },
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// Emit end event
|
|
204
|
-
callback({
|
|
205
|
-
type: 'subagent_end',
|
|
206
|
-
subagentId: 'sub-1',
|
|
207
|
-
subagentType: 'explorer',
|
|
208
|
-
parentToolCallId: 'tool-1',
|
|
209
|
-
success: true,
|
|
210
|
-
result: 'done',
|
|
211
|
-
timestamp: Date.now(),
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
expect(receivedEvents).toHaveLength(3);
|
|
215
|
-
expect(receivedEvents[0]?.type).toBe('subagent_start');
|
|
216
|
-
expect(receivedEvents[1]?.type).toBe('subagent_event');
|
|
217
|
-
expect(receivedEvents[2]?.type).toBe('subagent_end');
|
|
218
|
-
});
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
describe('UAPEventType includes subagent events', () => {
|
|
223
|
-
test('subagent event types are valid UAPEventType values', () => {
|
|
224
|
-
// These should compile without errors
|
|
225
|
-
const types: import('../../../src/execution/types.ts').UAPEventType[] = [
|
|
226
|
-
'subagent_start',
|
|
227
|
-
'subagent_event',
|
|
228
|
-
'subagent_end',
|
|
229
|
-
];
|
|
230
|
-
|
|
231
|
-
expect(types).toContain('subagent_start');
|
|
232
|
-
expect(types).toContain('subagent_event');
|
|
233
|
-
expect(types).toContain('subagent_end');
|
|
234
|
-
});
|
|
235
|
-
});
|
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
import type { ToolCall } from '@providerprotocol/ai';
|
|
3
|
-
import {
|
|
4
|
-
orderToolCalls,
|
|
5
|
-
hasToolDependencies,
|
|
6
|
-
hasCallDependencies,
|
|
7
|
-
} from '../../../src/execution/tool-ordering.ts';
|
|
8
|
-
import type {
|
|
9
|
-
ToolWithDependencies,
|
|
10
|
-
OrderedToolCall,
|
|
11
|
-
} from '../../../src/execution/types.ts';
|
|
12
|
-
|
|
13
|
-
// Helper to create a mock tool
|
|
14
|
-
function createTool(
|
|
15
|
-
name: string,
|
|
16
|
-
options?: { sequential?: boolean; dependsOn?: string[] },
|
|
17
|
-
): ToolWithDependencies {
|
|
18
|
-
return {
|
|
19
|
-
name,
|
|
20
|
-
description: `Tool ${name}`,
|
|
21
|
-
parameters: { type: 'object', properties: {} },
|
|
22
|
-
run: async () => 'result',
|
|
23
|
-
sequential: options?.sequential,
|
|
24
|
-
dependsOn: options?.dependsOn,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Helper to create a mock tool call
|
|
29
|
-
function createToolCall(
|
|
30
|
-
toolName: string,
|
|
31
|
-
id?: string,
|
|
32
|
-
after?: string[],
|
|
33
|
-
): ToolCall | OrderedToolCall {
|
|
34
|
-
const call: OrderedToolCall = {
|
|
35
|
-
toolCallId: id ?? `call-${toolName}`,
|
|
36
|
-
toolName,
|
|
37
|
-
arguments: {},
|
|
38
|
-
};
|
|
39
|
-
if (after) {
|
|
40
|
-
call.after = after;
|
|
41
|
-
}
|
|
42
|
-
return call;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
describe('orderToolCalls()', () => {
|
|
46
|
-
test('returns empty array for no tool calls', () => {
|
|
47
|
-
const groups = orderToolCalls([], []);
|
|
48
|
-
expect(groups).toEqual([]);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test('groups independent tools together', () => {
|
|
52
|
-
const tools = [
|
|
53
|
-
createTool('tool_a'),
|
|
54
|
-
createTool('tool_b'),
|
|
55
|
-
createTool('tool_c'),
|
|
56
|
-
];
|
|
57
|
-
|
|
58
|
-
const calls = [
|
|
59
|
-
createToolCall('tool_a'),
|
|
60
|
-
createToolCall('tool_b'),
|
|
61
|
-
createToolCall('tool_c'),
|
|
62
|
-
];
|
|
63
|
-
|
|
64
|
-
const groups = orderToolCalls(calls, tools);
|
|
65
|
-
|
|
66
|
-
expect(groups).toHaveLength(1);
|
|
67
|
-
const firstGroup = groups[0];
|
|
68
|
-
expect(firstGroup).toBeDefined();
|
|
69
|
-
if (firstGroup) {
|
|
70
|
-
expect(firstGroup.calls).toHaveLength(3);
|
|
71
|
-
expect(firstGroup.isBarrier).toBe(false);
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test('sequential tool creates barrier', () => {
|
|
76
|
-
const tools = [
|
|
77
|
-
createTool('read', { sequential: true }),
|
|
78
|
-
createTool('process'),
|
|
79
|
-
];
|
|
80
|
-
|
|
81
|
-
const calls = [
|
|
82
|
-
createToolCall('read'),
|
|
83
|
-
createToolCall('process'),
|
|
84
|
-
];
|
|
85
|
-
|
|
86
|
-
const groups = orderToolCalls(calls, tools);
|
|
87
|
-
|
|
88
|
-
// read should be in its own group as a barrier
|
|
89
|
-
expect(groups.length).toBeGreaterThanOrEqual(1);
|
|
90
|
-
const readGroup = groups.find((g) => g.calls.some((c) => c.toolName === 'read'));
|
|
91
|
-
expect(readGroup?.isBarrier).toBe(true);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test('dependsOn creates ordering', () => {
|
|
95
|
-
const tools = [
|
|
96
|
-
createTool('read'),
|
|
97
|
-
createTool('write', { dependsOn: ['read'] }),
|
|
98
|
-
];
|
|
99
|
-
|
|
100
|
-
const calls = [
|
|
101
|
-
createToolCall('read'),
|
|
102
|
-
createToolCall('write'),
|
|
103
|
-
];
|
|
104
|
-
|
|
105
|
-
const groups = orderToolCalls(calls, tools);
|
|
106
|
-
|
|
107
|
-
// Should have at least 2 groups due to dependency
|
|
108
|
-
expect(groups.length).toBeGreaterThanOrEqual(2);
|
|
109
|
-
|
|
110
|
-
// Find read and write group indices
|
|
111
|
-
const readGroupIndex = groups.findIndex(
|
|
112
|
-
(g) => g.calls.some((c) => c.toolName === 'read'),
|
|
113
|
-
);
|
|
114
|
-
const writeGroupIndex = groups.findIndex(
|
|
115
|
-
(g) => g.calls.some((c) => c.toolName === 'write'),
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
// read should come before write
|
|
119
|
-
expect(readGroupIndex).toBeLessThan(writeGroupIndex);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test('model-driven dependencies (after field)', () => {
|
|
123
|
-
const tools = [
|
|
124
|
-
createTool('tool_a'),
|
|
125
|
-
createTool('tool_b'),
|
|
126
|
-
];
|
|
127
|
-
|
|
128
|
-
const calls = [
|
|
129
|
-
createToolCall('tool_a', 'call-1'),
|
|
130
|
-
createToolCall('tool_b', 'call-2', ['call-1']), // depends on call-1
|
|
131
|
-
] as OrderedToolCall[];
|
|
132
|
-
|
|
133
|
-
const groups = orderToolCalls(calls, tools);
|
|
134
|
-
|
|
135
|
-
// Should have at least 2 groups due to dependency
|
|
136
|
-
expect(groups.length).toBeGreaterThanOrEqual(2);
|
|
137
|
-
|
|
138
|
-
// Find group indices
|
|
139
|
-
const aGroupIndex = groups.findIndex(
|
|
140
|
-
(g) => g.calls.some((c) => c.toolCallId === 'call-1'),
|
|
141
|
-
);
|
|
142
|
-
const bGroupIndex = groups.findIndex(
|
|
143
|
-
(g) => g.calls.some((c) => c.toolCallId === 'call-2'),
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
// call-1 should come before call-2
|
|
147
|
-
expect(aGroupIndex).toBeLessThan(bGroupIndex);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
test('complex dependency chain', () => {
|
|
151
|
-
// A -> B -> C (chain dependency)
|
|
152
|
-
const tools = [
|
|
153
|
-
createTool('tool_a'),
|
|
154
|
-
createTool('tool_b', { dependsOn: ['tool_a'] }),
|
|
155
|
-
createTool('tool_c', { dependsOn: ['tool_b'] }),
|
|
156
|
-
];
|
|
157
|
-
|
|
158
|
-
const calls = [
|
|
159
|
-
createToolCall('tool_a'),
|
|
160
|
-
createToolCall('tool_b'),
|
|
161
|
-
createToolCall('tool_c'),
|
|
162
|
-
];
|
|
163
|
-
|
|
164
|
-
const groups = orderToolCalls(calls, tools);
|
|
165
|
-
|
|
166
|
-
// Should have 3 groups, one for each tool
|
|
167
|
-
expect(groups).toHaveLength(3);
|
|
168
|
-
|
|
169
|
-
// Verify order
|
|
170
|
-
expect(groups[0]?.calls[0]?.toolName).toBe('tool_a');
|
|
171
|
-
expect(groups[1]?.calls[0]?.toolName).toBe('tool_b');
|
|
172
|
-
expect(groups[2]?.calls[0]?.toolName).toBe('tool_c');
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
test('parallel tools with shared dependency', () => {
|
|
176
|
-
// D depends on both A and B (which can run in parallel)
|
|
177
|
-
const tools = [
|
|
178
|
-
createTool('tool_a'),
|
|
179
|
-
createTool('tool_b'),
|
|
180
|
-
createTool('tool_d', { dependsOn: ['tool_a', 'tool_b'] }),
|
|
181
|
-
];
|
|
182
|
-
|
|
183
|
-
const calls = [
|
|
184
|
-
createToolCall('tool_a'),
|
|
185
|
-
createToolCall('tool_b'),
|
|
186
|
-
createToolCall('tool_d'),
|
|
187
|
-
];
|
|
188
|
-
|
|
189
|
-
const groups = orderToolCalls(calls, tools);
|
|
190
|
-
|
|
191
|
-
// First group should have A and B (parallel)
|
|
192
|
-
// Second group should have D
|
|
193
|
-
expect(groups.length).toBeGreaterThanOrEqual(2);
|
|
194
|
-
|
|
195
|
-
const firstGroup = groups[0];
|
|
196
|
-
expect(firstGroup).toBeDefined();
|
|
197
|
-
if (firstGroup) {
|
|
198
|
-
const hasA = firstGroup.calls.some((c) => c.toolName === 'tool_a');
|
|
199
|
-
const hasB = firstGroup.calls.some((c) => c.toolName === 'tool_b');
|
|
200
|
-
expect(hasA).toBe(true);
|
|
201
|
-
expect(hasB).toBe(true);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const dGroupIndex = groups.findIndex(
|
|
205
|
-
(g) => g.calls.some((c) => c.toolName === 'tool_d'),
|
|
206
|
-
);
|
|
207
|
-
expect(dGroupIndex).toBe(groups.length - 1); // D should be last
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
test('handles unknown tools gracefully', () => {
|
|
211
|
-
const tools = [createTool('known')];
|
|
212
|
-
const calls = [
|
|
213
|
-
createToolCall('known'),
|
|
214
|
-
createToolCall('unknown'),
|
|
215
|
-
];
|
|
216
|
-
|
|
217
|
-
// Should not throw
|
|
218
|
-
const groups = orderToolCalls(calls, tools);
|
|
219
|
-
expect(groups.length).toBeGreaterThan(0);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
test('handles cyclic dependencies by executing remaining items', () => {
|
|
223
|
-
// A depends on B, B depends on A (cycle)
|
|
224
|
-
const tools = [
|
|
225
|
-
createTool('tool_a', { dependsOn: ['tool_b'] }),
|
|
226
|
-
createTool('tool_b', { dependsOn: ['tool_a'] }),
|
|
227
|
-
];
|
|
228
|
-
|
|
229
|
-
const calls = [
|
|
230
|
-
createToolCall('tool_a'),
|
|
231
|
-
createToolCall('tool_b'),
|
|
232
|
-
];
|
|
233
|
-
|
|
234
|
-
// Should not hang - should execute items despite cycle
|
|
235
|
-
const groups = orderToolCalls(calls, tools);
|
|
236
|
-
expect(groups.length).toBeGreaterThan(0);
|
|
237
|
-
|
|
238
|
-
// All calls should be included
|
|
239
|
-
const allCalls = groups.flatMap((g) => g.calls);
|
|
240
|
-
expect(allCalls).toHaveLength(2);
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
describe('hasToolDependencies()', () => {
|
|
245
|
-
test('returns false for tools without dependencies', () => {
|
|
246
|
-
const tools = [
|
|
247
|
-
createTool('tool_a'),
|
|
248
|
-
createTool('tool_b'),
|
|
249
|
-
];
|
|
250
|
-
|
|
251
|
-
expect(hasToolDependencies(tools)).toBe(false);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
test('returns true for sequential tool', () => {
|
|
255
|
-
const tools = [
|
|
256
|
-
createTool('tool_a'),
|
|
257
|
-
createTool('tool_b', { sequential: true }),
|
|
258
|
-
];
|
|
259
|
-
|
|
260
|
-
expect(hasToolDependencies(tools)).toBe(true);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
test('returns true for tool with dependsOn', () => {
|
|
264
|
-
const tools = [
|
|
265
|
-
createTool('tool_a'),
|
|
266
|
-
createTool('tool_b', { dependsOn: ['tool_a'] }),
|
|
267
|
-
];
|
|
268
|
-
|
|
269
|
-
expect(hasToolDependencies(tools)).toBe(true);
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
test('returns false for empty array', () => {
|
|
273
|
-
expect(hasToolDependencies([])).toBe(false);
|
|
274
|
-
});
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
describe('hasCallDependencies()', () => {
|
|
278
|
-
test('returns false for calls without after field', () => {
|
|
279
|
-
const calls = [
|
|
280
|
-
createToolCall('tool_a'),
|
|
281
|
-
createToolCall('tool_b'),
|
|
282
|
-
];
|
|
283
|
-
|
|
284
|
-
expect(hasCallDependencies(calls)).toBe(false);
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
test('returns true for call with after field', () => {
|
|
288
|
-
const calls = [
|
|
289
|
-
createToolCall('tool_a', 'call-1'),
|
|
290
|
-
createToolCall('tool_b', 'call-2', ['call-1']),
|
|
291
|
-
] as OrderedToolCall[];
|
|
292
|
-
|
|
293
|
-
expect(hasCallDependencies(calls)).toBe(true);
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
test('returns false for empty array', () => {
|
|
297
|
-
expect(hasCallDependencies([])).toBe(false);
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
test('returns false for empty after array', () => {
|
|
301
|
-
const call: OrderedToolCall = {
|
|
302
|
-
toolCallId: 'call-1',
|
|
303
|
-
toolName: 'tool_a',
|
|
304
|
-
arguments: {},
|
|
305
|
-
after: [],
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
expect(hasCallDependencies([call])).toBe(false);
|
|
309
|
-
});
|
|
310
|
-
});
|