@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
package/src/execution/react.ts
DELETED
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
import type { Turn, StreamEvent } from '@providerprotocol/ai';
|
|
2
|
-
import { UserMessage } from '@providerprotocol/ai';
|
|
3
|
-
import type {
|
|
4
|
-
ExecutionStrategy,
|
|
5
|
-
ExecutionContext,
|
|
6
|
-
ExecutionResult,
|
|
7
|
-
ReactOptions,
|
|
8
|
-
AgentStreamResult,
|
|
9
|
-
AgentStreamEvent,
|
|
10
|
-
} from './types.ts';
|
|
11
|
-
|
|
12
|
-
const DEFAULT_REACT_OPTIONS: Required<ReactOptions> = {
|
|
13
|
-
maxSteps: Infinity,
|
|
14
|
-
reasoningPrompt: 'Think step by step about what you need to do next. Consider the current state, what tools are available, and what action would be most helpful.',
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Create a ReAct (Reason-Act-Observe) execution strategy.
|
|
19
|
-
*
|
|
20
|
-
* Behavior:
|
|
21
|
-
* 1. Reason: LLM outputs reasoning about what to do next
|
|
22
|
-
* 2. Act: LLM selects and executes tool(s)
|
|
23
|
-
* 3. Observe: Tool results are formatted as observations
|
|
24
|
-
* 4. Repeat until stop condition, no more actions, or maxSteps
|
|
25
|
-
*
|
|
26
|
-
* @param options - ReAct configuration options
|
|
27
|
-
* @returns ExecutionStrategy
|
|
28
|
-
*/
|
|
29
|
-
export function react(options: ReactOptions = {}): ExecutionStrategy {
|
|
30
|
-
const opts = { ...DEFAULT_REACT_OPTIONS, ...options };
|
|
31
|
-
|
|
32
|
-
return {
|
|
33
|
-
name: 'react',
|
|
34
|
-
|
|
35
|
-
async execute(context: ExecutionContext): Promise<ExecutionResult> {
|
|
36
|
-
const { llm, input, state, strategy, signal } = context;
|
|
37
|
-
|
|
38
|
-
// Add input message to state and set agentId in metadata
|
|
39
|
-
// This ensures checkpoints include the full conversation
|
|
40
|
-
let currentState = state
|
|
41
|
-
.withMessage(input)
|
|
42
|
-
.withMetadata('agentId', context.agent.id);
|
|
43
|
-
let step = 0;
|
|
44
|
-
let finalTurn: Turn | undefined;
|
|
45
|
-
|
|
46
|
-
// Messages for LLM generation (includes input we just added)
|
|
47
|
-
const messages = [...currentState.messages];
|
|
48
|
-
|
|
49
|
-
while (true) {
|
|
50
|
-
step++;
|
|
51
|
-
currentState = currentState.withStep(step);
|
|
52
|
-
|
|
53
|
-
if (signal?.aborted) {
|
|
54
|
-
throw new Error('Execution aborted');
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
strategy.onStepStart?.(step, currentState);
|
|
58
|
-
|
|
59
|
-
// REASON PHASE: Ask LLM to think about what to do
|
|
60
|
-
const reasoningMessages = [
|
|
61
|
-
...messages,
|
|
62
|
-
new UserMessage(opts.reasoningPrompt),
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
const reasoningTurn = await llm.generate(reasoningMessages);
|
|
66
|
-
|
|
67
|
-
const reasoning = reasoningTurn.response.text;
|
|
68
|
-
currentState = currentState.withReasoning(reasoning);
|
|
69
|
-
strategy.onReason?.(step, reasoning);
|
|
70
|
-
|
|
71
|
-
// Add reasoning to conversation
|
|
72
|
-
messages.push(...reasoningTurn.messages);
|
|
73
|
-
|
|
74
|
-
// ACT PHASE: Execute with tools
|
|
75
|
-
const actionPrompt = new UserMessage(
|
|
76
|
-
'Based on your reasoning, take the appropriate action. Use tools if needed, or provide a final answer.',
|
|
77
|
-
);
|
|
78
|
-
messages.push(actionPrompt);
|
|
79
|
-
|
|
80
|
-
const actionTurn = await llm.generate(messages);
|
|
81
|
-
finalTurn = actionTurn;
|
|
82
|
-
|
|
83
|
-
// Update messages with action response
|
|
84
|
-
messages.push(...actionTurn.messages);
|
|
85
|
-
currentState = currentState.withMessages(actionTurn.messages);
|
|
86
|
-
|
|
87
|
-
// Handle tool calls
|
|
88
|
-
if (actionTurn.response.hasToolCalls) {
|
|
89
|
-
strategy.onAct?.(step, actionTurn.response.toolCalls ?? []);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// OBSERVE PHASE: Process tool results
|
|
93
|
-
if (actionTurn.toolExecutions && actionTurn.toolExecutions.length > 0) {
|
|
94
|
-
strategy.onObserve?.(step, actionTurn.toolExecutions);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
strategy.onStepEnd?.(step, { turn: actionTurn, state: currentState });
|
|
98
|
-
|
|
99
|
-
// Check stop conditions
|
|
100
|
-
const shouldStop = await strategy.stopCondition?.(currentState);
|
|
101
|
-
if (shouldStop) {
|
|
102
|
-
break;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// No more tool calls means we're done
|
|
106
|
-
if (!actionTurn.response.hasToolCalls) {
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Check step limit
|
|
111
|
-
if (opts.maxSteps !== Infinity && step >= opts.maxSteps) {
|
|
112
|
-
break;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (!finalTurn) {
|
|
117
|
-
throw new Error('No turn generated');
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Include sessionId in state metadata if checkpointing is enabled
|
|
121
|
-
let finalState = currentState;
|
|
122
|
-
if (context.sessionId) {
|
|
123
|
-
finalState = currentState.withMetadata('sessionId', context.sessionId);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const result: ExecutionResult = {
|
|
127
|
-
turn: finalTurn,
|
|
128
|
-
state: finalState,
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
strategy.onComplete?.(result);
|
|
132
|
-
|
|
133
|
-
return result;
|
|
134
|
-
},
|
|
135
|
-
|
|
136
|
-
stream(context: ExecutionContext): AgentStreamResult {
|
|
137
|
-
const { llm, input, state, strategy, signal } = context;
|
|
138
|
-
const agentId = context.agent.id;
|
|
139
|
-
|
|
140
|
-
let aborted = false;
|
|
141
|
-
const abortController = new AbortController();
|
|
142
|
-
|
|
143
|
-
if (signal) {
|
|
144
|
-
signal.addEventListener('abort', () => abortController.abort());
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
let resolveResult: (result: ExecutionResult) => void;
|
|
148
|
-
let rejectResult: (error: Error) => void;
|
|
149
|
-
|
|
150
|
-
const resultPromise = new Promise<ExecutionResult>((resolve, reject) => {
|
|
151
|
-
resolveResult = resolve;
|
|
152
|
-
rejectResult = reject;
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
async function* generateEvents(): AsyncGenerator<AgentStreamEvent> {
|
|
156
|
-
// Add input message to state and set agentId in metadata
|
|
157
|
-
// This ensures checkpoints include the full conversation
|
|
158
|
-
let currentState = state
|
|
159
|
-
.withMessage(input)
|
|
160
|
-
.withMetadata('agentId', context.agent.id);
|
|
161
|
-
let step = 0;
|
|
162
|
-
let finalTurn: Turn | undefined;
|
|
163
|
-
|
|
164
|
-
// Messages for LLM generation (includes input we just added)
|
|
165
|
-
const messages = [...currentState.messages];
|
|
166
|
-
|
|
167
|
-
try {
|
|
168
|
-
while (!aborted) {
|
|
169
|
-
step++;
|
|
170
|
-
currentState = currentState.withStep(step);
|
|
171
|
-
|
|
172
|
-
if (abortController.signal.aborted) {
|
|
173
|
-
throw new Error('Execution aborted');
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
strategy.onStepStart?.(step, currentState);
|
|
177
|
-
|
|
178
|
-
yield {
|
|
179
|
-
source: 'uap',
|
|
180
|
-
uap: {
|
|
181
|
-
type: 'step_start',
|
|
182
|
-
step,
|
|
183
|
-
agentId,
|
|
184
|
-
data: { phase: 'reasoning' },
|
|
185
|
-
},
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
// REASON PHASE
|
|
189
|
-
const reasoningMessages = [
|
|
190
|
-
...messages,
|
|
191
|
-
new UserMessage(opts.reasoningPrompt),
|
|
192
|
-
];
|
|
193
|
-
|
|
194
|
-
const reasoningStream = llm.stream(reasoningMessages);
|
|
195
|
-
let reasoningText = '';
|
|
196
|
-
|
|
197
|
-
for await (const event of reasoningStream as AsyncIterable<StreamEvent>) {
|
|
198
|
-
if (abortController.signal.aborted) {
|
|
199
|
-
throw new Error('Execution aborted');
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
yield { source: 'upp', upp: event };
|
|
203
|
-
|
|
204
|
-
if (event.type === 'text_delta' && event.delta.text) {
|
|
205
|
-
reasoningText += event.delta.text;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const reasoningTurn = await reasoningStream.turn;
|
|
210
|
-
currentState = currentState.withReasoning(reasoningText || reasoningTurn.response.text);
|
|
211
|
-
strategy.onReason?.(step, reasoningText || reasoningTurn.response.text);
|
|
212
|
-
|
|
213
|
-
yield {
|
|
214
|
-
source: 'uap',
|
|
215
|
-
uap: {
|
|
216
|
-
type: 'reasoning',
|
|
217
|
-
step,
|
|
218
|
-
agentId,
|
|
219
|
-
data: { reasoning: reasoningText || reasoningTurn.response.text },
|
|
220
|
-
},
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
messages.push(...reasoningTurn.messages);
|
|
224
|
-
|
|
225
|
-
// ACT PHASE
|
|
226
|
-
const actionPrompt = new UserMessage(
|
|
227
|
-
'Based on your reasoning, take the appropriate action. Use tools if needed, or provide a final answer.',
|
|
228
|
-
);
|
|
229
|
-
messages.push(actionPrompt);
|
|
230
|
-
|
|
231
|
-
const actionStream = llm.stream(messages);
|
|
232
|
-
|
|
233
|
-
for await (const event of actionStream as AsyncIterable<StreamEvent>) {
|
|
234
|
-
if (abortController.signal.aborted) {
|
|
235
|
-
throw new Error('Execution aborted');
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
yield { source: 'upp', upp: event };
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const actionTurn = await actionStream.turn;
|
|
242
|
-
finalTurn = actionTurn;
|
|
243
|
-
|
|
244
|
-
messages.push(...actionTurn.messages);
|
|
245
|
-
currentState = currentState.withMessages(actionTurn.messages);
|
|
246
|
-
|
|
247
|
-
if (actionTurn.response.hasToolCalls) {
|
|
248
|
-
strategy.onAct?.(step, actionTurn.response.toolCalls ?? []);
|
|
249
|
-
|
|
250
|
-
yield {
|
|
251
|
-
source: 'uap',
|
|
252
|
-
uap: {
|
|
253
|
-
type: 'action',
|
|
254
|
-
step,
|
|
255
|
-
agentId,
|
|
256
|
-
data: { toolCalls: actionTurn.response.toolCalls },
|
|
257
|
-
},
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
if (actionTurn.toolExecutions && actionTurn.toolExecutions.length > 0) {
|
|
262
|
-
strategy.onObserve?.(step, actionTurn.toolExecutions);
|
|
263
|
-
|
|
264
|
-
yield {
|
|
265
|
-
source: 'uap',
|
|
266
|
-
uap: {
|
|
267
|
-
type: 'observation',
|
|
268
|
-
step,
|
|
269
|
-
agentId,
|
|
270
|
-
data: { observations: actionTurn.toolExecutions },
|
|
271
|
-
},
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
strategy.onStepEnd?.(step, { turn: actionTurn, state: currentState });
|
|
276
|
-
|
|
277
|
-
yield {
|
|
278
|
-
source: 'uap',
|
|
279
|
-
uap: {
|
|
280
|
-
type: 'step_end',
|
|
281
|
-
step,
|
|
282
|
-
agentId,
|
|
283
|
-
data: { phase: 'complete' },
|
|
284
|
-
},
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
const shouldStop = await strategy.stopCondition?.(currentState);
|
|
288
|
-
if (shouldStop) {
|
|
289
|
-
break;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (!actionTurn.response.hasToolCalls) {
|
|
293
|
-
break;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (opts.maxSteps !== Infinity && step >= opts.maxSteps) {
|
|
297
|
-
break;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (!finalTurn) {
|
|
302
|
-
throw new Error('No turn generated');
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Include sessionId in state metadata if checkpointing is enabled
|
|
306
|
-
let finalState = currentState;
|
|
307
|
-
if (context.sessionId) {
|
|
308
|
-
finalState = currentState.withMetadata('sessionId', context.sessionId);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const result: ExecutionResult = {
|
|
312
|
-
turn: finalTurn,
|
|
313
|
-
state: finalState,
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
strategy.onComplete?.(result);
|
|
317
|
-
resolveResult(result);
|
|
318
|
-
} catch (error) {
|
|
319
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
320
|
-
strategy.onError?.(err, currentState);
|
|
321
|
-
rejectResult(err);
|
|
322
|
-
throw err;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const iterator = generateEvents();
|
|
327
|
-
|
|
328
|
-
return {
|
|
329
|
-
[Symbol.asyncIterator]() {
|
|
330
|
-
return iterator;
|
|
331
|
-
},
|
|
332
|
-
result: resultPromise,
|
|
333
|
-
abort() {
|
|
334
|
-
aborted = true;
|
|
335
|
-
abortController.abort();
|
|
336
|
-
},
|
|
337
|
-
};
|
|
338
|
-
},
|
|
339
|
-
};
|
|
340
|
-
}
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import type { Tool, ToolCall } from '@providerprotocol/ai';
|
|
2
|
-
import type { ToolWithDependencies, OrderedToolCall } from './types.ts';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Execution group - a set of tool calls that can execute in parallel.
|
|
6
|
-
*/
|
|
7
|
-
export interface ExecutionGroup {
|
|
8
|
-
/** Tool calls in this group */
|
|
9
|
-
calls: ToolCall[];
|
|
10
|
-
/** Whether this group contains a sequential tool (acts as barrier) */
|
|
11
|
-
isBarrier: boolean;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Build a map of tool definitions by name for quick lookup.
|
|
16
|
-
*/
|
|
17
|
-
function buildToolMap(tools: Tool[]): Map<string, ToolWithDependencies> {
|
|
18
|
-
const map = new Map<string, ToolWithDependencies>();
|
|
19
|
-
for (const tool of tools) {
|
|
20
|
-
map.set(tool.name, tool as ToolWithDependencies);
|
|
21
|
-
}
|
|
22
|
-
return map;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Check if a tool call has an explicit dependency via model hint.
|
|
27
|
-
*/
|
|
28
|
-
function getModelDependencies(call: ToolCall): string[] {
|
|
29
|
-
const orderedCall = call as OrderedToolCall;
|
|
30
|
-
return orderedCall.after ?? [];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Order tool calls into execution groups respecting dependencies.
|
|
35
|
-
*
|
|
36
|
-
* This function takes a list of tool calls and the available tools,
|
|
37
|
-
* then groups them for execution while respecting:
|
|
38
|
-
* 1. Tool-level `sequential` flag (creates execution barriers)
|
|
39
|
-
* 2. Tool-level `dependsOn` array (tool must wait for named tools)
|
|
40
|
-
* 3. Model-driven `after` array on tool calls (call must wait for specific calls)
|
|
41
|
-
*
|
|
42
|
-
* @param toolCalls - Tool calls from the model response
|
|
43
|
-
* @param tools - Tool definitions (may include dependency options)
|
|
44
|
-
* @returns Ordered array of execution groups
|
|
45
|
-
*
|
|
46
|
-
* @example
|
|
47
|
-
* ```typescript
|
|
48
|
-
* const groups = orderToolCalls(turn.response.toolCalls, agent.tools);
|
|
49
|
-
*
|
|
50
|
-
* for (const group of groups) {
|
|
51
|
-
* if (group.isBarrier) {
|
|
52
|
-
* // Execute sequentially
|
|
53
|
-
* for (const call of group.calls) {
|
|
54
|
-
* await executeTool(call);
|
|
55
|
-
* }
|
|
56
|
-
* } else {
|
|
57
|
-
* // Execute in parallel
|
|
58
|
-
* await Promise.all(group.calls.map(executeTool));
|
|
59
|
-
* }
|
|
60
|
-
* }
|
|
61
|
-
* ```
|
|
62
|
-
*/
|
|
63
|
-
export function orderToolCalls(
|
|
64
|
-
toolCalls: ToolCall[],
|
|
65
|
-
tools: Tool[],
|
|
66
|
-
): ExecutionGroup[] {
|
|
67
|
-
if (toolCalls.length === 0) {
|
|
68
|
-
return [];
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const toolMap = buildToolMap(tools);
|
|
72
|
-
const groups: ExecutionGroup[] = [];
|
|
73
|
-
|
|
74
|
-
// Track completed tool calls and tool names
|
|
75
|
-
const completedCallIds = new Set<string>();
|
|
76
|
-
const completedToolNames = new Set<string>();
|
|
77
|
-
|
|
78
|
-
// Create a queue of pending calls
|
|
79
|
-
const pending = [...toolCalls];
|
|
80
|
-
|
|
81
|
-
while (pending.length > 0) {
|
|
82
|
-
const readyForExecution: ToolCall[] = [];
|
|
83
|
-
let hasSequential = false;
|
|
84
|
-
|
|
85
|
-
// Find all calls that can execute now
|
|
86
|
-
const stillPending: ToolCall[] = [];
|
|
87
|
-
|
|
88
|
-
for (const call of pending) {
|
|
89
|
-
const tool = toolMap.get(call.toolName);
|
|
90
|
-
const toolDependsOn = tool?.dependsOn ?? [];
|
|
91
|
-
const modelDependsOn = getModelDependencies(call);
|
|
92
|
-
|
|
93
|
-
// Check if tool-level dependencies are satisfied
|
|
94
|
-
const toolDepsOk = toolDependsOn.every(
|
|
95
|
-
(depName) => completedToolNames.has(depName),
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
// Check if model-level dependencies are satisfied
|
|
99
|
-
const modelDepsOk = modelDependsOn.every(
|
|
100
|
-
(depId) => completedCallIds.has(depId),
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
if (toolDepsOk && modelDepsOk) {
|
|
104
|
-
readyForExecution.push(call);
|
|
105
|
-
if (tool?.sequential) {
|
|
106
|
-
hasSequential = true;
|
|
107
|
-
}
|
|
108
|
-
} else {
|
|
109
|
-
stillPending.push(call);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// If nothing is ready but we have pending items, there's a cycle
|
|
114
|
-
if (readyForExecution.length === 0 && stillPending.length > 0) {
|
|
115
|
-
// Break the cycle by executing remaining items
|
|
116
|
-
// This is a fallback - ideally dependencies should be acyclic
|
|
117
|
-
groups.push({
|
|
118
|
-
calls: stillPending,
|
|
119
|
-
isBarrier: false,
|
|
120
|
-
});
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// If we have sequential tools, they form a barrier
|
|
125
|
-
// Process them one at a time
|
|
126
|
-
if (hasSequential) {
|
|
127
|
-
for (const call of readyForExecution) {
|
|
128
|
-
const tool = toolMap.get(call.toolName);
|
|
129
|
-
groups.push({
|
|
130
|
-
calls: [call],
|
|
131
|
-
isBarrier: tool?.sequential ?? false,
|
|
132
|
-
});
|
|
133
|
-
completedCallIds.add(call.toolCallId);
|
|
134
|
-
completedToolNames.add(call.toolName);
|
|
135
|
-
}
|
|
136
|
-
} else {
|
|
137
|
-
// Non-sequential tools can be grouped for parallel execution
|
|
138
|
-
groups.push({
|
|
139
|
-
calls: readyForExecution,
|
|
140
|
-
isBarrier: false,
|
|
141
|
-
});
|
|
142
|
-
for (const call of readyForExecution) {
|
|
143
|
-
completedCallIds.add(call.toolCallId);
|
|
144
|
-
completedToolNames.add(call.toolName);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Update pending list
|
|
149
|
-
pending.length = 0;
|
|
150
|
-
pending.push(...stillPending);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return groups;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Check if any tools have execution dependencies defined.
|
|
158
|
-
*
|
|
159
|
-
* @param tools - Tool definitions to check
|
|
160
|
-
* @returns true if any tool has sequential or dependsOn set
|
|
161
|
-
*/
|
|
162
|
-
export function hasToolDependencies(tools: Tool[]): boolean {
|
|
163
|
-
for (const tool of tools) {
|
|
164
|
-
const t = tool as ToolWithDependencies;
|
|
165
|
-
if (t.sequential || (t.dependsOn && t.dependsOn.length > 0)) {
|
|
166
|
-
return true;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return false;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Check if any tool calls have model-driven dependencies.
|
|
174
|
-
*
|
|
175
|
-
* @param toolCalls - Tool calls to check
|
|
176
|
-
* @returns true if any call has the `after` field set
|
|
177
|
-
*/
|
|
178
|
-
export function hasCallDependencies(toolCalls: ToolCall[]): boolean {
|
|
179
|
-
for (const call of toolCalls) {
|
|
180
|
-
const ordered = call as OrderedToolCall;
|
|
181
|
-
if (ordered.after && ordered.after.length > 0) {
|
|
182
|
-
return true;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return false;
|
|
186
|
-
}
|