stratus-sdk 0.7.6 → 0.9.0
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/azure/chat-completions-model.d.ts.map +1 -1
- package/dist/azure/chat-completions-model.js +10 -0
- package/dist/azure/chat-completions-model.js.map +1 -1
- package/dist/azure/responses-model.d.ts.map +1 -1
- package/dist/azure/responses-model.js +72 -7
- package/dist/azure/responses-model.js.map +1 -1
- package/dist/core/builtin-tools.d.ts +11 -0
- package/dist/core/builtin-tools.d.ts.map +1 -1
- package/dist/core/builtin-tools.js +26 -0
- package/dist/core/builtin-tools.js.map +1 -1
- package/dist/core/errors.d.ts +5 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +10 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/guardrails.d.ts +26 -2
- package/dist/core/guardrails.d.ts.map +1 -1
- package/dist/core/guardrails.js +22 -6
- package/dist/core/guardrails.js.map +1 -1
- package/dist/core/handoff.d.ts +18 -1
- package/dist/core/handoff.d.ts.map +1 -1
- package/dist/core/handoff.js +8 -1
- package/dist/core/handoff.js.map +1 -1
- package/dist/core/hooks.d.ts +65 -1
- package/dist/core/hooks.d.ts.map +1 -1
- package/dist/core/index.d.ts +9 -9
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/model.d.ts +4 -0
- package/dist/core/model.d.ts.map +1 -1
- package/dist/core/result.d.ts +7 -0
- package/dist/core/result.d.ts.map +1 -1
- package/dist/core/result.js +8 -0
- package/dist/core/result.js.map +1 -1
- package/dist/core/run.d.ts +35 -5
- package/dist/core/run.d.ts.map +1 -1
- package/dist/core/run.js +252 -32
- package/dist/core/run.js.map +1 -1
- package/dist/core/session.d.ts +15 -2
- package/dist/core/session.d.ts.map +1 -1
- package/dist/core/session.js +22 -5
- package/dist/core/session.js.map +1 -1
- package/dist/core/todo.d.ts +13 -39
- package/dist/core/todo.d.ts.map +1 -1
- package/dist/core/tool.d.ts +6 -0
- package/dist/core/tool.d.ts.map +1 -1
- package/dist/core/tool.js +2 -0
- package/dist/core/tool.js.map +1 -1
- package/dist/core/types.d.ts +13 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/utils/zod.d.ts +2 -2
- package/dist/core/utils/zod.d.ts.map +1 -1
- package/dist/core/utils/zod.js +4 -70
- package/dist/core/utils/zod.js.map +1 -1
- package/package.json +3 -3
package/dist/core/run.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RunContext } from "./context";
|
|
2
|
-
import { MaxBudgetExceededError, MaxTurnsExceededError, OutputParseError, RunAbortedError, StratusError, } from "./errors";
|
|
3
|
-
import { runInputGuardrails, runOutputGuardrails } from "./guardrails";
|
|
2
|
+
import { MaxBudgetExceededError, MaxTurnsExceededError, OutputParseError, RunAbortedError, StratusError, ToolTimeoutError, } from "./errors";
|
|
3
|
+
import { runInputGuardrails, runOutputGuardrails, runToolInputGuardrails, runToolOutputGuardrails, } from "./guardrails";
|
|
4
4
|
import { handoffToDefinition } from "./handoff";
|
|
5
5
|
import { isHostedTool, isFunctionTool } from "./hosted-tool";
|
|
6
6
|
import { subagentToDefinition, subagentToTool } from "./subagent";
|
|
@@ -70,6 +70,33 @@ async function resolveAfterToolCallHook(hook, params) {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
+
/** Check if a tool/handoff isEnabled field resolves to true */
|
|
74
|
+
async function checkEnabled(isEnabled, context) {
|
|
75
|
+
if (isEnabled === undefined)
|
|
76
|
+
return true;
|
|
77
|
+
if (typeof isEnabled === "boolean")
|
|
78
|
+
return isEnabled;
|
|
79
|
+
return isEnabled(context);
|
|
80
|
+
}
|
|
81
|
+
/** Execute a tool with optional timeout */
|
|
82
|
+
async function executeWithTimeout(fn, timeout, toolName) {
|
|
83
|
+
if (!timeout)
|
|
84
|
+
return fn();
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
const timer = setTimeout(() => {
|
|
87
|
+
reject(new ToolTimeoutError(toolName, timeout));
|
|
88
|
+
}, timeout);
|
|
89
|
+
Promise.resolve(fn())
|
|
90
|
+
.then((result) => {
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
resolve(result);
|
|
93
|
+
})
|
|
94
|
+
.catch((error) => {
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
reject(error);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
73
100
|
function checkAborted(signal) {
|
|
74
101
|
if (signal?.aborted) {
|
|
75
102
|
throw new RunAbortedError();
|
|
@@ -90,6 +117,11 @@ function checkBudget(ctx, maxBudgetUsd) {
|
|
|
90
117
|
throw new MaxBudgetExceededError(maxBudgetUsd, ctx.totalCostUsd);
|
|
91
118
|
}
|
|
92
119
|
}
|
|
120
|
+
function formatToolError(toolName, error, formatter) {
|
|
121
|
+
if (formatter)
|
|
122
|
+
return formatter(toolName, error);
|
|
123
|
+
return `Error executing tool "${toolName}": ${getErrorMessage(error)}`;
|
|
124
|
+
}
|
|
93
125
|
export async function run(agent, input, options) {
|
|
94
126
|
validateBudgetOptions(options);
|
|
95
127
|
const model = options?.model ?? agent.model;
|
|
@@ -103,24 +135,30 @@ export async function run(agent, input, options) {
|
|
|
103
135
|
const maxBudgetUsd = options?.maxBudgetUsd;
|
|
104
136
|
const ctx = new RunContext(options?.context);
|
|
105
137
|
const trace = getCurrentTrace();
|
|
138
|
+
const runHooks = options?.runHooks;
|
|
139
|
+
const toolErrorFmt = options?.toolErrorFormatter;
|
|
140
|
+
const callModelInputFilter = options?.callModelInputFilter;
|
|
141
|
+
const toolInputGuardrails = options?.toolInputGuardrails ?? [];
|
|
142
|
+
const toolOutputGuardrails = options?.toolOutputGuardrails ?? [];
|
|
106
143
|
// Fire beforeRun hook on the entry agent
|
|
107
144
|
const inputText = typeof input === "string" ? input : extractUserText(input);
|
|
108
145
|
if (agent.hooks.beforeRun) {
|
|
109
146
|
await agent.hooks.beforeRun({ agent, input: inputText, context: ctx.context });
|
|
110
147
|
}
|
|
111
148
|
// Run input guardrails on the starting agent
|
|
149
|
+
let inputGuardrailResults = [];
|
|
112
150
|
if (agent.inputGuardrails.length > 0) {
|
|
113
151
|
if (trace) {
|
|
114
152
|
const span = trace.startSpan("input_guardrails", "guardrail");
|
|
115
153
|
try {
|
|
116
|
-
await runInputGuardrails(agent.inputGuardrails, inputText, ctx.context);
|
|
154
|
+
inputGuardrailResults = await runInputGuardrails(agent.inputGuardrails, inputText, ctx.context);
|
|
117
155
|
}
|
|
118
156
|
finally {
|
|
119
157
|
trace.endSpan(span);
|
|
120
158
|
}
|
|
121
159
|
}
|
|
122
160
|
else {
|
|
123
|
-
await runInputGuardrails(agent.inputGuardrails, inputText, ctx.context);
|
|
161
|
+
inputGuardrailResults = await runInputGuardrails(agent.inputGuardrails, inputText, ctx.context);
|
|
124
162
|
}
|
|
125
163
|
}
|
|
126
164
|
const messages = [];
|
|
@@ -137,16 +175,33 @@ export async function run(agent, input, options) {
|
|
|
137
175
|
}
|
|
138
176
|
let lastFinishReason;
|
|
139
177
|
let lastResponseId;
|
|
178
|
+
// Fire run-level onAgentStart
|
|
179
|
+
if (runHooks?.onAgentStart) {
|
|
180
|
+
await runHooks.onAgentStart({ agent: currentAgent, context: ctx.context });
|
|
181
|
+
}
|
|
140
182
|
for (let turn = 0; turn < maxTurns; turn++) {
|
|
141
183
|
checkAborted(signal);
|
|
142
|
-
const toolDefs = buildToolDefs(currentAgent);
|
|
143
|
-
|
|
184
|
+
const toolDefs = await buildToolDefs(currentAgent, ctx.context);
|
|
185
|
+
let request = {
|
|
144
186
|
messages,
|
|
145
187
|
tools: toolDefs.length > 0 ? toolDefs : undefined,
|
|
146
|
-
modelSettings: currentAgent.modelSettings
|
|
188
|
+
modelSettings: currentAgent.modelSettings
|
|
189
|
+
? applyResetToolChoice(currentAgent.modelSettings, turn, options?.resetToolChoice)
|
|
190
|
+
: undefined,
|
|
147
191
|
responseFormat: currentAgent.getResponseFormat(),
|
|
148
192
|
previousResponseId: lastResponseId,
|
|
149
193
|
};
|
|
194
|
+
// Apply callModelInputFilter
|
|
195
|
+
if (callModelInputFilter) {
|
|
196
|
+
request = callModelInputFilter({ agent: currentAgent, request, context: ctx.context });
|
|
197
|
+
}
|
|
198
|
+
// Fire onLlmStart hooks
|
|
199
|
+
if (currentAgent.hooks.onLlmStart) {
|
|
200
|
+
await currentAgent.hooks.onLlmStart({ agent: currentAgent, messages, context: ctx.context });
|
|
201
|
+
}
|
|
202
|
+
if (runHooks?.onLlmStart) {
|
|
203
|
+
await runHooks.onLlmStart({ agent: currentAgent, request, context: ctx.context });
|
|
204
|
+
}
|
|
150
205
|
let response;
|
|
151
206
|
if (trace) {
|
|
152
207
|
const span = trace.startSpan(`model_call:${currentAgent.name}`, "model_call", {
|
|
@@ -168,6 +223,14 @@ export async function run(agent, input, options) {
|
|
|
168
223
|
else {
|
|
169
224
|
response = await model.getResponse(request, { signal });
|
|
170
225
|
}
|
|
226
|
+
// Fire onLlmEnd hooks
|
|
227
|
+
const llmEndInfo = { content: response.content, toolCallCount: response.toolCalls.length };
|
|
228
|
+
if (currentAgent.hooks.onLlmEnd) {
|
|
229
|
+
await currentAgent.hooks.onLlmEnd({ agent: currentAgent, response: llmEndInfo, context: ctx.context });
|
|
230
|
+
}
|
|
231
|
+
if (runHooks?.onLlmEnd) {
|
|
232
|
+
await runHooks.onLlmEnd({ agent: currentAgent, response: llmEndInfo, context: ctx.context });
|
|
233
|
+
}
|
|
171
234
|
checkAborted(signal);
|
|
172
235
|
lastFinishReason = response.finishReason;
|
|
173
236
|
if (response.responseId)
|
|
@@ -196,12 +259,16 @@ export async function run(agent, input, options) {
|
|
|
196
259
|
};
|
|
197
260
|
messages.push(assistantMsg);
|
|
198
261
|
if (response.toolCalls.length === 0) {
|
|
199
|
-
|
|
262
|
+
// Fire run-level onAgentEnd
|
|
263
|
+
if (runHooks?.onAgentEnd) {
|
|
264
|
+
await runHooks.onAgentEnd({ agent: currentAgent, output: response.content ?? "", context: ctx.context });
|
|
265
|
+
}
|
|
266
|
+
return buildFinalResult(agent, currentAgent, messages, ctx, trace, lastFinishReason, lastResponseId, inputGuardrailResults);
|
|
200
267
|
}
|
|
201
|
-
const { toolMessages, handoffAgent } = await executeToolCallsWithHandoffs(currentAgent, ctx, response.toolCalls, trace, signal);
|
|
268
|
+
const { toolMessages, handoffAgent } = await executeToolCallsWithHandoffs(currentAgent, ctx, response.toolCalls, trace, signal, toolErrorFmt, runHooks, toolInputGuardrails, toolOutputGuardrails);
|
|
202
269
|
messages.push(...toolMessages);
|
|
203
270
|
// Check toolUseBehavior — should we stop instead of calling the LLM again?
|
|
204
|
-
if (shouldStopAfterToolCalls(currentAgent, response.toolCalls)) {
|
|
271
|
+
if (await shouldStopAfterToolCalls(currentAgent, response.toolCalls, toolMessages)) {
|
|
205
272
|
const toolOutput = toolMessages.map((m) => m.content).join("\n");
|
|
206
273
|
return new RunResult({
|
|
207
274
|
output: toolOutput,
|
|
@@ -212,6 +279,7 @@ export async function run(agent, input, options) {
|
|
|
212
279
|
numTurns: ctx.numTurns,
|
|
213
280
|
totalCostUsd: ctx.totalCostUsd,
|
|
214
281
|
responseId: lastResponseId,
|
|
282
|
+
inputGuardrailResults,
|
|
215
283
|
});
|
|
216
284
|
}
|
|
217
285
|
if (handoffAgent) {
|
|
@@ -238,10 +306,25 @@ export async function run(agent, input, options) {
|
|
|
238
306
|
}
|
|
239
307
|
}
|
|
240
308
|
if (allowHandoff) {
|
|
309
|
+
// Fire run-level onAgentEnd for current agent
|
|
310
|
+
if (runHooks?.onAgentEnd) {
|
|
311
|
+
await runHooks.onAgentEnd({ agent: currentAgent, output: response.content ?? "", context: ctx.context });
|
|
312
|
+
}
|
|
313
|
+
// Fire run-level onHandoff
|
|
314
|
+
if (runHooks?.onHandoff) {
|
|
315
|
+
await runHooks.onHandoff({ fromAgent: currentAgent, toAgent: handoffAgent, context: ctx.context });
|
|
316
|
+
}
|
|
241
317
|
if (trace) {
|
|
242
318
|
const span = trace.startSpan(`handoff:${currentAgent.name}->${handoffAgent.name}`, "handoff", { fromAgent: currentAgent.name, toAgent: handoffAgent.name });
|
|
243
319
|
trace.endSpan(span);
|
|
244
320
|
}
|
|
321
|
+
// Apply handoff inputFilter if present
|
|
322
|
+
const matchedHandoff = currentAgent.handoffs.find((h) => h.agent === handoffAgent || h.agent.name === handoffAgent.name);
|
|
323
|
+
if (matchedHandoff?.inputFilter) {
|
|
324
|
+
const filtered = matchedHandoff.inputFilter({ history: [...messages] });
|
|
325
|
+
messages.length = 0;
|
|
326
|
+
messages.push(...filtered);
|
|
327
|
+
}
|
|
245
328
|
currentAgent = handoffAgent;
|
|
246
329
|
// Replace system message with new agent's prompt
|
|
247
330
|
const newSystemPrompt = await currentAgent.getSystemPrompt(ctx.context);
|
|
@@ -257,6 +340,10 @@ export async function run(agent, input, options) {
|
|
|
257
340
|
else if (systemIdx >= 0) {
|
|
258
341
|
messages.splice(systemIdx, 1);
|
|
259
342
|
}
|
|
343
|
+
// Fire run-level onAgentStart for new agent
|
|
344
|
+
if (runHooks?.onAgentStart) {
|
|
345
|
+
await runHooks.onAgentStart({ agent: currentAgent, context: ctx.context });
|
|
346
|
+
}
|
|
260
347
|
}
|
|
261
348
|
}
|
|
262
349
|
}
|
|
@@ -268,6 +355,15 @@ export async function run(agent, input, options) {
|
|
|
268
355
|
reason: "max_turns",
|
|
269
356
|
});
|
|
270
357
|
}
|
|
358
|
+
// Check for error handler
|
|
359
|
+
if (options?.errorHandlers?.maxTurns) {
|
|
360
|
+
return options.errorHandlers.maxTurns({
|
|
361
|
+
agent: currentAgent,
|
|
362
|
+
messages,
|
|
363
|
+
context: ctx.context,
|
|
364
|
+
maxTurns,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
271
367
|
throw new MaxTurnsExceededError(maxTurns);
|
|
272
368
|
}
|
|
273
369
|
export function stream(agent, input, options) {
|
|
@@ -294,24 +390,30 @@ async function* streamInternal(agent, input, options, resolveResult, rejectResul
|
|
|
294
390
|
const maxBudgetUsd = options?.maxBudgetUsd;
|
|
295
391
|
const ctx = new RunContext(options?.context);
|
|
296
392
|
const trace = getCurrentTrace();
|
|
393
|
+
const runHooks = options?.runHooks;
|
|
394
|
+
const toolErrorFmt = options?.toolErrorFormatter;
|
|
395
|
+
const callModelInputFilter = options?.callModelInputFilter;
|
|
396
|
+
const toolInputGuardrails = options?.toolInputGuardrails ?? [];
|
|
397
|
+
const toolOutputGuardrails = options?.toolOutputGuardrails ?? [];
|
|
297
398
|
// Fire beforeRun hook on the entry agent
|
|
298
399
|
const inputText = typeof input === "string" ? input : extractUserText(input);
|
|
299
400
|
if (agent.hooks.beforeRun) {
|
|
300
401
|
await agent.hooks.beforeRun({ agent, input: inputText, context: ctx.context });
|
|
301
402
|
}
|
|
302
403
|
// Run input guardrails on the starting agent
|
|
404
|
+
let inputGuardrailResults = [];
|
|
303
405
|
if (agent.inputGuardrails.length > 0) {
|
|
304
406
|
if (trace) {
|
|
305
407
|
const span = trace.startSpan("input_guardrails", "guardrail");
|
|
306
408
|
try {
|
|
307
|
-
await runInputGuardrails(agent.inputGuardrails, inputText, ctx.context);
|
|
409
|
+
inputGuardrailResults = await runInputGuardrails(agent.inputGuardrails, inputText, ctx.context);
|
|
308
410
|
}
|
|
309
411
|
finally {
|
|
310
412
|
trace.endSpan(span);
|
|
311
413
|
}
|
|
312
414
|
}
|
|
313
415
|
else {
|
|
314
|
-
await runInputGuardrails(agent.inputGuardrails, inputText, ctx.context);
|
|
416
|
+
inputGuardrailResults = await runInputGuardrails(agent.inputGuardrails, inputText, ctx.context);
|
|
315
417
|
}
|
|
316
418
|
}
|
|
317
419
|
const messages = [];
|
|
@@ -328,16 +430,33 @@ async function* streamInternal(agent, input, options, resolveResult, rejectResul
|
|
|
328
430
|
}
|
|
329
431
|
let lastFinishReason;
|
|
330
432
|
let lastResponseId;
|
|
433
|
+
// Fire run-level onAgentStart
|
|
434
|
+
if (runHooks?.onAgentStart) {
|
|
435
|
+
await runHooks.onAgentStart({ agent: currentAgent, context: ctx.context });
|
|
436
|
+
}
|
|
331
437
|
for (let turn = 0; turn < maxTurns; turn++) {
|
|
332
438
|
checkAborted(signal);
|
|
333
|
-
const toolDefs = buildToolDefs(currentAgent);
|
|
334
|
-
|
|
439
|
+
const toolDefs = await buildToolDefs(currentAgent, ctx.context);
|
|
440
|
+
let request = {
|
|
335
441
|
messages,
|
|
336
442
|
tools: toolDefs.length > 0 ? toolDefs : undefined,
|
|
337
|
-
modelSettings: currentAgent.modelSettings
|
|
443
|
+
modelSettings: currentAgent.modelSettings
|
|
444
|
+
? applyResetToolChoice(currentAgent.modelSettings, turn, options?.resetToolChoice)
|
|
445
|
+
: undefined,
|
|
338
446
|
responseFormat: currentAgent.getResponseFormat(),
|
|
339
447
|
previousResponseId: lastResponseId,
|
|
340
448
|
};
|
|
449
|
+
// Apply callModelInputFilter
|
|
450
|
+
if (callModelInputFilter) {
|
|
451
|
+
request = callModelInputFilter({ agent: currentAgent, request, context: ctx.context });
|
|
452
|
+
}
|
|
453
|
+
// Fire onLlmStart hooks
|
|
454
|
+
if (currentAgent.hooks.onLlmStart) {
|
|
455
|
+
await currentAgent.hooks.onLlmStart({ agent: currentAgent, messages, context: ctx.context });
|
|
456
|
+
}
|
|
457
|
+
if (runHooks?.onLlmStart) {
|
|
458
|
+
await runHooks.onLlmStart({ agent: currentAgent, request, context: ctx.context });
|
|
459
|
+
}
|
|
341
460
|
let finalResponse;
|
|
342
461
|
let gotDone = false;
|
|
343
462
|
for await (const event of model.getStreamedResponse(request, { signal })) {
|
|
@@ -350,6 +469,14 @@ async function* streamInternal(agent, input, options, resolveResult, rejectResul
|
|
|
350
469
|
if (!gotDone) {
|
|
351
470
|
throw new StratusError("Stream ended without a done event");
|
|
352
471
|
}
|
|
472
|
+
// Fire onLlmEnd hooks
|
|
473
|
+
const llmEndInfo = { content: finalResponse.content, toolCallCount: finalResponse.toolCalls.length };
|
|
474
|
+
if (currentAgent.hooks.onLlmEnd) {
|
|
475
|
+
await currentAgent.hooks.onLlmEnd({ agent: currentAgent, response: llmEndInfo, context: ctx.context });
|
|
476
|
+
}
|
|
477
|
+
if (runHooks?.onLlmEnd) {
|
|
478
|
+
await runHooks.onLlmEnd({ agent: currentAgent, response: llmEndInfo, context: ctx.context });
|
|
479
|
+
}
|
|
353
480
|
checkAborted(signal);
|
|
354
481
|
lastFinishReason = finalResponse.finishReason;
|
|
355
482
|
if (finalResponse.responseId)
|
|
@@ -380,14 +507,17 @@ async function* streamInternal(agent, input, options, resolveResult, rejectResul
|
|
|
380
507
|
};
|
|
381
508
|
messages.push(assistantMsg);
|
|
382
509
|
if (finalResponse.toolCalls.length === 0) {
|
|
383
|
-
|
|
510
|
+
if (runHooks?.onAgentEnd) {
|
|
511
|
+
await runHooks.onAgentEnd({ agent: currentAgent, output: finalResponse.content ?? "", context: ctx.context });
|
|
512
|
+
}
|
|
513
|
+
const result = await buildFinalResult(agent, currentAgent, messages, ctx, trace, lastFinishReason, lastResponseId, inputGuardrailResults);
|
|
384
514
|
resolveResult(result);
|
|
385
515
|
return;
|
|
386
516
|
}
|
|
387
|
-
const { toolMessages, handoffAgent } = await executeToolCallsWithHandoffs(currentAgent, ctx, finalResponse.toolCalls, trace, signal);
|
|
517
|
+
const { toolMessages, handoffAgent } = await executeToolCallsWithHandoffs(currentAgent, ctx, finalResponse.toolCalls, trace, signal, toolErrorFmt, runHooks, toolInputGuardrails, toolOutputGuardrails);
|
|
388
518
|
messages.push(...toolMessages);
|
|
389
519
|
// Check toolUseBehavior
|
|
390
|
-
if (shouldStopAfterToolCalls(currentAgent, finalResponse.toolCalls)) {
|
|
520
|
+
if (await shouldStopAfterToolCalls(currentAgent, finalResponse.toolCalls, toolMessages)) {
|
|
391
521
|
const toolOutput = toolMessages.map((m) => m.content).join("\n");
|
|
392
522
|
resolveResult(new RunResult({
|
|
393
523
|
output: toolOutput,
|
|
@@ -398,6 +528,7 @@ async function* streamInternal(agent, input, options, resolveResult, rejectResul
|
|
|
398
528
|
numTurns: ctx.numTurns,
|
|
399
529
|
totalCostUsd: ctx.totalCostUsd,
|
|
400
530
|
responseId: lastResponseId,
|
|
531
|
+
inputGuardrailResults,
|
|
401
532
|
}));
|
|
402
533
|
return;
|
|
403
534
|
}
|
|
@@ -423,6 +554,19 @@ async function* streamInternal(agent, input, options, resolveResult, rejectResul
|
|
|
423
554
|
}
|
|
424
555
|
}
|
|
425
556
|
if (allowHandoff) {
|
|
557
|
+
if (runHooks?.onAgentEnd) {
|
|
558
|
+
await runHooks.onAgentEnd({ agent: currentAgent, output: finalResponse.content ?? "", context: ctx.context });
|
|
559
|
+
}
|
|
560
|
+
if (runHooks?.onHandoff) {
|
|
561
|
+
await runHooks.onHandoff({ fromAgent: currentAgent, toAgent: handoffAgent, context: ctx.context });
|
|
562
|
+
}
|
|
563
|
+
// Apply handoff inputFilter if present
|
|
564
|
+
const matchedHandoff = currentAgent.handoffs.find((h) => h.agent === handoffAgent || h.agent.name === handoffAgent.name);
|
|
565
|
+
if (matchedHandoff?.inputFilter) {
|
|
566
|
+
const filtered = matchedHandoff.inputFilter({ history: [...messages] });
|
|
567
|
+
messages.length = 0;
|
|
568
|
+
messages.push(...filtered);
|
|
569
|
+
}
|
|
426
570
|
currentAgent = handoffAgent;
|
|
427
571
|
const newSystemPrompt = await currentAgent.getSystemPrompt(ctx.context);
|
|
428
572
|
const systemIdx = messages.findIndex((m) => m.role === "system");
|
|
@@ -437,6 +581,9 @@ async function* streamInternal(agent, input, options, resolveResult, rejectResul
|
|
|
437
581
|
else if (systemIdx >= 0) {
|
|
438
582
|
messages.splice(systemIdx, 1);
|
|
439
583
|
}
|
|
584
|
+
if (runHooks?.onAgentStart) {
|
|
585
|
+
await runHooks.onAgentStart({ agent: currentAgent, context: ctx.context });
|
|
586
|
+
}
|
|
440
587
|
}
|
|
441
588
|
}
|
|
442
589
|
}
|
|
@@ -448,6 +595,16 @@ async function* streamInternal(agent, input, options, resolveResult, rejectResul
|
|
|
448
595
|
reason: "max_turns",
|
|
449
596
|
});
|
|
450
597
|
}
|
|
598
|
+
// Check for error handler
|
|
599
|
+
if (options?.errorHandlers?.maxTurns) {
|
|
600
|
+
resolveResult(await options.errorHandlers.maxTurns({
|
|
601
|
+
agent: currentAgent,
|
|
602
|
+
messages,
|
|
603
|
+
context: ctx.context,
|
|
604
|
+
maxTurns,
|
|
605
|
+
}));
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
451
608
|
throw new MaxTurnsExceededError(maxTurns);
|
|
452
609
|
}
|
|
453
610
|
catch (error) {
|
|
@@ -455,22 +612,23 @@ async function* streamInternal(agent, input, options, resolveResult, rejectResul
|
|
|
455
612
|
throw error;
|
|
456
613
|
}
|
|
457
614
|
}
|
|
458
|
-
async function buildFinalResult(entryAgent, currentAgent, messages, ctx, trace, finishReason, responseId) {
|
|
615
|
+
async function buildFinalResult(entryAgent, currentAgent, messages, ctx, trace, finishReason, responseId, inputGuardrailResults) {
|
|
459
616
|
const lastMessage = messages[messages.length - 1];
|
|
460
617
|
const rawOutput = lastMessage && lastMessage.role === "assistant" ? (lastMessage.content ?? "") : "";
|
|
461
618
|
// Run output guardrails on the current (possibly handed-off) agent
|
|
619
|
+
let outputGuardrailResults = [];
|
|
462
620
|
if (currentAgent.outputGuardrails.length > 0) {
|
|
463
621
|
if (trace) {
|
|
464
622
|
const span = trace.startSpan("output_guardrails", "guardrail");
|
|
465
623
|
try {
|
|
466
|
-
await runOutputGuardrails(currentAgent.outputGuardrails, rawOutput, ctx.context);
|
|
624
|
+
outputGuardrailResults = await runOutputGuardrails(currentAgent.outputGuardrails, rawOutput, ctx.context);
|
|
467
625
|
}
|
|
468
626
|
finally {
|
|
469
627
|
trace.endSpan(span);
|
|
470
628
|
}
|
|
471
629
|
}
|
|
472
630
|
else {
|
|
473
|
-
await runOutputGuardrails(currentAgent.outputGuardrails, rawOutput, ctx.context);
|
|
631
|
+
outputGuardrailResults = await runOutputGuardrails(currentAgent.outputGuardrails, rawOutput, ctx.context);
|
|
474
632
|
}
|
|
475
633
|
}
|
|
476
634
|
// Parse structured output if outputType is set
|
|
@@ -494,6 +652,8 @@ async function buildFinalResult(entryAgent, currentAgent, messages, ctx, trace,
|
|
|
494
652
|
numTurns: ctx.numTurns,
|
|
495
653
|
totalCostUsd: ctx.totalCostUsd,
|
|
496
654
|
responseId,
|
|
655
|
+
inputGuardrailResults,
|
|
656
|
+
outputGuardrailResults,
|
|
497
657
|
});
|
|
498
658
|
// Fire afterRun hook on the entry agent
|
|
499
659
|
if (entryAgent.hooks.afterRun) {
|
|
@@ -501,13 +661,16 @@ async function buildFinalResult(entryAgent, currentAgent, messages, ctx, trace,
|
|
|
501
661
|
}
|
|
502
662
|
return result;
|
|
503
663
|
}
|
|
504
|
-
function buildToolDefs(agent) {
|
|
664
|
+
async function buildToolDefs(agent, context) {
|
|
505
665
|
const defs = [];
|
|
506
666
|
for (const t of agent.tools) {
|
|
507
667
|
if (isHostedTool(t)) {
|
|
508
668
|
defs.push(t.definition);
|
|
509
669
|
}
|
|
510
670
|
else {
|
|
671
|
+
// Check isEnabled for function tools
|
|
672
|
+
if (!(await checkEnabled(t.isEnabled, context)))
|
|
673
|
+
continue;
|
|
511
674
|
defs.push(toolToDefinition(t));
|
|
512
675
|
}
|
|
513
676
|
}
|
|
@@ -515,6 +678,9 @@ function buildToolDefs(agent) {
|
|
|
515
678
|
defs.push(subagentToDefinition(sa));
|
|
516
679
|
}
|
|
517
680
|
for (const h of agent.handoffs) {
|
|
681
|
+
// Check isEnabled for handoffs
|
|
682
|
+
if (!(await checkEnabled(h.isEnabled, context)))
|
|
683
|
+
continue;
|
|
518
684
|
defs.push(handoffToDefinition(h));
|
|
519
685
|
}
|
|
520
686
|
return defs;
|
|
@@ -537,18 +703,36 @@ function extractUserText(messages) {
|
|
|
537
703
|
}
|
|
538
704
|
return texts.join("\n");
|
|
539
705
|
}
|
|
540
|
-
function shouldStopAfterToolCalls(agent, toolCalls) {
|
|
541
|
-
|
|
706
|
+
async function shouldStopAfterToolCalls(agent, toolCalls, toolMessages) {
|
|
707
|
+
const behavior = agent.toolUseBehavior;
|
|
708
|
+
if (behavior === "run_llm_again")
|
|
542
709
|
return false;
|
|
543
|
-
if (
|
|
710
|
+
if (behavior === "stop_on_first_tool")
|
|
544
711
|
return true;
|
|
545
|
-
if ("
|
|
546
|
-
|
|
712
|
+
if (typeof behavior === "function") {
|
|
713
|
+
// Custom function variant
|
|
714
|
+
const results = toolCalls.map((tc, i) => ({
|
|
715
|
+
toolName: tc.function.name,
|
|
716
|
+
result: toolMessages[i]?.content ?? "",
|
|
717
|
+
}));
|
|
718
|
+
return behavior(results);
|
|
719
|
+
}
|
|
720
|
+
if (typeof behavior === "object" && "stopAtToolNames" in behavior) {
|
|
721
|
+
const stopNames = new Set(behavior.stopAtToolNames);
|
|
547
722
|
return toolCalls.some((tc) => stopNames.has(tc.function.name));
|
|
548
723
|
}
|
|
549
724
|
return false;
|
|
550
725
|
}
|
|
551
|
-
|
|
726
|
+
function applyResetToolChoice(settings, turn, resetToolChoice) {
|
|
727
|
+
if (!resetToolChoice || turn === 0)
|
|
728
|
+
return settings;
|
|
729
|
+
// After the first turn, reset tool_choice to "auto" to prevent infinite loops
|
|
730
|
+
if (settings.toolChoice && settings.toolChoice !== "auto") {
|
|
731
|
+
return { ...settings, toolChoice: "auto" };
|
|
732
|
+
}
|
|
733
|
+
return settings;
|
|
734
|
+
}
|
|
735
|
+
async function executeToolCallsWithHandoffs(agent, ctx, toolCalls, trace, signal, toolErrorFmt, runHooks, toolInputGuardrails, toolOutputGuardrails) {
|
|
552
736
|
let handoffAgent;
|
|
553
737
|
// Build O(1) lookup maps
|
|
554
738
|
const handoffsByName = new Map(agent.handoffs.map((h) => [h.toolName, h]));
|
|
@@ -597,6 +781,10 @@ async function executeToolCallsWithHandoffs(agent, ctx, toolCalls, trace, signal
|
|
|
597
781
|
context: ctx.context,
|
|
598
782
|
});
|
|
599
783
|
}
|
|
784
|
+
// Fire run-level onToolStart
|
|
785
|
+
if (runHooks?.onToolStart) {
|
|
786
|
+
await runHooks.onToolStart({ agent, toolName: tcName, context: ctx.context });
|
|
787
|
+
}
|
|
600
788
|
let result;
|
|
601
789
|
if (trace) {
|
|
602
790
|
const span = trace.startSpan(`subagent:${matchedSubagent.agent.name}`, "subagent", { toolName: tcName });
|
|
@@ -619,6 +807,10 @@ async function executeToolCallsWithHandoffs(agent, ctx, toolCalls, trace, signal
|
|
|
619
807
|
context: ctx.context,
|
|
620
808
|
});
|
|
621
809
|
}
|
|
810
|
+
// Fire run-level onToolEnd
|
|
811
|
+
if (runHooks?.onToolEnd) {
|
|
812
|
+
await runHooks.onToolEnd({ agent, toolName: tcName, result, context: ctx.context });
|
|
813
|
+
}
|
|
622
814
|
await resolveAfterToolCallHook(agent.hooks.afterToolCall, {
|
|
623
815
|
agent,
|
|
624
816
|
toolCall: fullToolCall,
|
|
@@ -635,7 +827,7 @@ async function executeToolCallsWithHandoffs(agent, ctx, toolCalls, trace, signal
|
|
|
635
827
|
return {
|
|
636
828
|
role: "tool",
|
|
637
829
|
tool_call_id: tc.id,
|
|
638
|
-
content:
|
|
830
|
+
content: formatToolError(matchedSubagent.agent.name, error, toolErrorFmt),
|
|
639
831
|
};
|
|
640
832
|
}
|
|
641
833
|
}
|
|
@@ -667,6 +859,22 @@ async function executeToolCallsWithHandoffs(agent, ctx, toolCalls, trace, signal
|
|
|
667
859
|
if (decision?.decision === "modify") {
|
|
668
860
|
params = decision.modifiedParams;
|
|
669
861
|
}
|
|
862
|
+
// Run tool input guardrails
|
|
863
|
+
if (toolInputGuardrails && toolInputGuardrails.length > 0) {
|
|
864
|
+
const guardrailResults = await runToolInputGuardrails(toolInputGuardrails, tcName, params, ctx.context);
|
|
865
|
+
const tripped = guardrailResults.find((r) => r.result.tripwireTriggered);
|
|
866
|
+
if (tripped) {
|
|
867
|
+
return {
|
|
868
|
+
role: "tool",
|
|
869
|
+
tool_call_id: tc.id,
|
|
870
|
+
content: `Tool input guardrail "${tripped.guardrailName}" blocked execution of "${tcName}"`,
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
// Fire run-level onToolStart
|
|
875
|
+
if (runHooks?.onToolStart) {
|
|
876
|
+
await runHooks.onToolStart({ agent, toolName: tcName, context: ctx.context });
|
|
877
|
+
}
|
|
670
878
|
checkAborted(signal);
|
|
671
879
|
let result;
|
|
672
880
|
if (trace) {
|
|
@@ -674,14 +882,26 @@ async function executeToolCallsWithHandoffs(agent, ctx, toolCalls, trace, signal
|
|
|
674
882
|
toolName: tcName,
|
|
675
883
|
});
|
|
676
884
|
try {
|
|
677
|
-
result = await tool.execute(ctx.context, params, { signal });
|
|
885
|
+
result = await executeWithTimeout(() => tool.execute(ctx.context, params, { signal }), tool.timeout, tcName);
|
|
678
886
|
}
|
|
679
887
|
finally {
|
|
680
888
|
trace.endSpan(span);
|
|
681
889
|
}
|
|
682
890
|
}
|
|
683
891
|
else {
|
|
684
|
-
result = await tool.execute(ctx.context, params, { signal });
|
|
892
|
+
result = await executeWithTimeout(() => tool.execute(ctx.context, params, { signal }), tool.timeout, tcName);
|
|
893
|
+
}
|
|
894
|
+
// Run tool output guardrails
|
|
895
|
+
if (toolOutputGuardrails && toolOutputGuardrails.length > 0) {
|
|
896
|
+
const guardrailResults = await runToolOutputGuardrails(toolOutputGuardrails, tcName, result, ctx.context);
|
|
897
|
+
const tripped = guardrailResults.find((r) => r.result.tripwireTriggered);
|
|
898
|
+
if (tripped) {
|
|
899
|
+
result = `Tool output guardrail "${tripped.guardrailName}" flagged the output of "${tcName}"`;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
// Fire run-level onToolEnd
|
|
903
|
+
if (runHooks?.onToolEnd) {
|
|
904
|
+
await runHooks.onToolEnd({ agent, toolName: tcName, result, context: ctx.context });
|
|
685
905
|
}
|
|
686
906
|
// Fire afterToolCall hook
|
|
687
907
|
await resolveAfterToolCallHook(agent.hooks.afterToolCall, {
|
|
@@ -700,7 +920,7 @@ async function executeToolCallsWithHandoffs(agent, ctx, toolCalls, trace, signal
|
|
|
700
920
|
return {
|
|
701
921
|
role: "tool",
|
|
702
922
|
tool_call_id: tc.id,
|
|
703
|
-
content:
|
|
923
|
+
content: formatToolError(tcName, error, toolErrorFmt),
|
|
704
924
|
};
|
|
705
925
|
}
|
|
706
926
|
}));
|