@lleverage-ai/agent-sdk 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/README.md +141 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +528 -41
- package/dist/agent.js.map +1 -1
- package/dist/hooks.d.ts +28 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +40 -0
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware/apply.d.ts.map +1 -1
- package/dist/middleware/apply.js +8 -0
- package/dist/middleware/apply.js.map +1 -1
- package/dist/middleware/context.d.ts.map +1 -1
- package/dist/middleware/context.js +11 -0
- package/dist/middleware/context.js.map +1 -1
- package/dist/middleware/types.d.ts +8 -0
- package/dist/middleware/types.d.ts.map +1 -1
- package/dist/plugins/agent-teams/coordinator.d.ts +46 -0
- package/dist/plugins/agent-teams/coordinator.d.ts.map +1 -0
- package/dist/plugins/agent-teams/coordinator.js +255 -0
- package/dist/plugins/agent-teams/coordinator.js.map +1 -0
- package/dist/plugins/agent-teams/hooks.d.ts +29 -0
- package/dist/plugins/agent-teams/hooks.d.ts.map +1 -0
- package/dist/plugins/agent-teams/hooks.js +29 -0
- package/dist/plugins/agent-teams/hooks.js.map +1 -0
- package/dist/plugins/agent-teams/index.d.ts +59 -0
- package/dist/plugins/agent-teams/index.d.ts.map +1 -0
- package/dist/plugins/agent-teams/index.js +313 -0
- package/dist/plugins/agent-teams/index.js.map +1 -0
- package/dist/plugins/agent-teams/mermaid.d.ts +32 -0
- package/dist/plugins/agent-teams/mermaid.d.ts.map +1 -0
- package/dist/plugins/agent-teams/mermaid.js +66 -0
- package/dist/plugins/agent-teams/mermaid.js.map +1 -0
- package/dist/plugins/agent-teams/session-runner.d.ts +92 -0
- package/dist/plugins/agent-teams/session-runner.d.ts.map +1 -0
- package/dist/plugins/agent-teams/session-runner.js +166 -0
- package/dist/plugins/agent-teams/session-runner.js.map +1 -0
- package/dist/plugins/agent-teams/tools.d.ts +41 -0
- package/dist/plugins/agent-teams/tools.d.ts.map +1 -0
- package/dist/plugins/agent-teams/tools.js +289 -0
- package/dist/plugins/agent-teams/tools.js.map +1 -0
- package/dist/plugins/agent-teams/types.d.ts +164 -0
- package/dist/plugins/agent-teams/types.d.ts.map +1 -0
- package/dist/plugins/agent-teams/types.js +7 -0
- package/dist/plugins/agent-teams/types.js.map +1 -0
- package/dist/plugins.d.ts.map +1 -1
- package/dist/plugins.js +1 -0
- package/dist/plugins.js.map +1 -1
- package/dist/presets/production.d.ts.map +1 -1
- package/dist/presets/production.js +7 -7
- package/dist/presets/production.js.map +1 -1
- package/dist/prompt-builder/components.d.ts +149 -0
- package/dist/prompt-builder/components.d.ts.map +1 -0
- package/dist/prompt-builder/components.js +252 -0
- package/dist/prompt-builder/components.js.map +1 -0
- package/dist/prompt-builder/index.d.ts +248 -0
- package/dist/prompt-builder/index.d.ts.map +1 -0
- package/dist/prompt-builder/index.js +165 -0
- package/dist/prompt-builder/index.js.map +1 -0
- package/dist/task-manager.d.ts +15 -0
- package/dist/task-manager.d.ts.map +1 -1
- package/dist/task-manager.js +36 -0
- package/dist/task-manager.js.map +1 -1
- package/dist/testing/mock-agent.d.ts.map +1 -1
- package/dist/testing/mock-agent.js +6 -0
- package/dist/testing/mock-agent.js.map +1 -1
- package/dist/testing/recorder.d.ts.map +1 -1
- package/dist/testing/recorder.js +6 -0
- package/dist/testing/recorder.js.map +1 -1
- package/dist/tools/task.d.ts.map +1 -1
- package/dist/tools/task.js +6 -2
- package/dist/tools/task.js.map +1 -1
- package/dist/types.d.ts +178 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -13,6 +13,7 @@ import { createRetryLoopState, handleGenerationError, invokePreGenerateHooks, no
|
|
|
13
13
|
import { aggregatePermissionDecisions, extractUpdatedInput, extractUpdatedResult, invokeHooksWithTimeout, invokeMatchingHooks, } from "./hooks.js";
|
|
14
14
|
import { MCPManager } from "./mcp/manager.js";
|
|
15
15
|
import { applyMiddleware, mergeHooks, setupMiddleware } from "./middleware/index.js";
|
|
16
|
+
import { createDefaultPromptBuilder } from "./prompt-builder/components.js";
|
|
16
17
|
import { ACCEPT_EDITS_BLOCKED_PATTERNS } from "./security/index.js";
|
|
17
18
|
import { TaskManager } from "./task-manager.js";
|
|
18
19
|
import { coreToolsToToolSet, createCoreTools, createSearchToolsTool, createTaskOutputTool, createTaskTool, } from "./tools/factory.js";
|
|
@@ -41,6 +42,46 @@ class InterruptSignal extends Error {
|
|
|
41
42
|
function isInterruptSignal(error) {
|
|
42
43
|
return error instanceof InterruptSignal;
|
|
43
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Outermost tool wrapper that intercepts flow-control signals.
|
|
47
|
+
*
|
|
48
|
+
* When a tool throws `InterruptSignal`, this wrapper catches it before the AI
|
|
49
|
+
* SDK can, stores it in the shared `signalState`, and returns a placeholder
|
|
50
|
+
* string. Combined with a custom `stopWhen` condition, this cleanly stops
|
|
51
|
+
* generation and allows `generate()` to inspect `signalState` in the normal
|
|
52
|
+
* return path (not the catch block).
|
|
53
|
+
*
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
function wrapToolsWithSignalCatching(tools, signalState) {
|
|
57
|
+
const wrapped = {};
|
|
58
|
+
for (const [name, toolDef] of Object.entries(tools)) {
|
|
59
|
+
if (!toolDef.execute) {
|
|
60
|
+
wrapped[name] = toolDef;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const originalExecute = toolDef.execute;
|
|
64
|
+
wrapped[name] = {
|
|
65
|
+
...toolDef,
|
|
66
|
+
execute: async (input, options) => {
|
|
67
|
+
try {
|
|
68
|
+
return await originalExecute.call(toolDef, input, options);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (isInterruptSignal(error)) {
|
|
72
|
+
if (signalState.interrupt) {
|
|
73
|
+
throw error; // Already have a signal — let AI SDK handle this one
|
|
74
|
+
}
|
|
75
|
+
signalState.interrupt = error;
|
|
76
|
+
return "[Interrupt requested]";
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return wrapped;
|
|
84
|
+
}
|
|
44
85
|
/**
|
|
45
86
|
* File edit tool names that get auto-approved in acceptEdits mode.
|
|
46
87
|
* @internal
|
|
@@ -456,17 +497,21 @@ function wrapToolsWithHooks(tools, hookRegistration, agent, sessionId) {
|
|
|
456
497
|
return output;
|
|
457
498
|
}
|
|
458
499
|
catch (error) {
|
|
459
|
-
//
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
500
|
+
// Skip PostToolUseFailure for flow-control signals — these are not
|
|
501
|
+
// actual failures but intentional control flow (interrupt).
|
|
502
|
+
if (!isInterruptSignal(error)) {
|
|
503
|
+
// Invoke PostToolUseFailure hooks
|
|
504
|
+
if (hookRegistration?.PostToolUseFailure?.length) {
|
|
505
|
+
const failureInput = {
|
|
506
|
+
hook_event_name: "PostToolUseFailure",
|
|
507
|
+
session_id: sessionId,
|
|
508
|
+
cwd: process.cwd(),
|
|
509
|
+
tool_name: name,
|
|
510
|
+
tool_input: input,
|
|
511
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
512
|
+
};
|
|
513
|
+
await invokeMatchingHooks(hookRegistration.PostToolUseFailure, name, failureInput, toolUseId, agent);
|
|
514
|
+
}
|
|
470
515
|
}
|
|
471
516
|
throw error;
|
|
472
517
|
}
|
|
@@ -527,10 +572,21 @@ function isBackendFactory(value) {
|
|
|
527
572
|
*/
|
|
528
573
|
export function createAgent(options) {
|
|
529
574
|
const id = `agent-${++agentIdCounter}`;
|
|
575
|
+
// Validate mutually exclusive prompt options
|
|
576
|
+
if (options.systemPrompt !== undefined && options.promptBuilder) {
|
|
577
|
+
throw new Error("Cannot specify both systemPrompt and promptBuilder - they are mutually exclusive");
|
|
578
|
+
}
|
|
579
|
+
// Determine prompt mode
|
|
580
|
+
// - 'static': Use systemPrompt string directly
|
|
581
|
+
// - 'builder': Use PromptBuilder to generate dynamic prompts
|
|
582
|
+
const promptMode = options.systemPrompt !== undefined ? "static" : "builder";
|
|
583
|
+
// Get or create prompt builder
|
|
584
|
+
const promptBuilder = options.promptBuilder ?? (promptMode === "builder" ? createDefaultPromptBuilder() : undefined);
|
|
530
585
|
// Process middleware to get hooks (middleware hooks come before explicit hooks)
|
|
531
586
|
const middleware = options.middleware ?? [];
|
|
532
587
|
const middlewareHooks = applyMiddleware(middleware);
|
|
533
|
-
const
|
|
588
|
+
const pluginHooks = (options.plugins ?? []).filter((p) => p.hooks).map((p) => p.hooks);
|
|
589
|
+
const mergedHooks = mergeHooks(middlewareHooks, ...pluginHooks, options.hooks);
|
|
534
590
|
// Create options with merged hooks for all hook lookups
|
|
535
591
|
const effectiveHooks = mergedHooks;
|
|
536
592
|
// Permission mode (mutable for setPermissionMode)
|
|
@@ -561,6 +617,18 @@ export function createAgent(options) {
|
|
|
561
617
|
}
|
|
562
618
|
// Initialize task manager for background task tracking
|
|
563
619
|
const taskManager = new TaskManager();
|
|
620
|
+
// Background task completion options
|
|
621
|
+
const waitForBackgroundTasks = options.waitForBackgroundTasks ?? true;
|
|
622
|
+
const formatTaskCompletion = options.formatTaskCompletion ??
|
|
623
|
+
((task) => {
|
|
624
|
+
const command = task.metadata?.command ?? "unknown command";
|
|
625
|
+
return `[Background task completed: ${task.id}]\nCommand: ${command}\nOutput:\n${task.result ?? "(no output)"}`;
|
|
626
|
+
});
|
|
627
|
+
const formatTaskFailure = options.formatTaskFailure ??
|
|
628
|
+
((task) => {
|
|
629
|
+
const command = task.metadata?.command ?? "unknown command";
|
|
630
|
+
return `[Background task failed: ${task.id}]\nCommand: ${command}\nError: ${task.error ?? "Unknown error"}`;
|
|
631
|
+
});
|
|
564
632
|
// Determine plugin loading mode
|
|
565
633
|
// Track whether it was explicitly set to distinguish from default
|
|
566
634
|
const explicitPluginLoading = options.pluginLoading !== undefined;
|
|
@@ -811,10 +879,71 @@ export function createAgent(options) {
|
|
|
811
879
|
}
|
|
812
880
|
return filtered;
|
|
813
881
|
};
|
|
814
|
-
// Helper to
|
|
882
|
+
// Helper to build prompt context from current agent state
|
|
883
|
+
const buildPromptContext = (messages, threadId) => {
|
|
884
|
+
// Get filtered tools (respecting allowedTools/disallowedTools) so the prompt
|
|
885
|
+
// only advertises tools the agent will actually expose
|
|
886
|
+
const filteredTools = filterToolsByAllowed((() => {
|
|
887
|
+
const allTools = { ...coreTools };
|
|
888
|
+
Object.assign(allTools, runtimeTools);
|
|
889
|
+
Object.assign(allTools, mcpManager.getToolSet());
|
|
890
|
+
if (toolRegistry) {
|
|
891
|
+
Object.assign(allTools, toolRegistry.getLoadedTools());
|
|
892
|
+
}
|
|
893
|
+
return allTools;
|
|
894
|
+
})());
|
|
895
|
+
// Extract tool metadata for context
|
|
896
|
+
const toolsMetadata = Object.entries(filteredTools).map(([name, tool]) => ({
|
|
897
|
+
name,
|
|
898
|
+
description: tool.description ?? "",
|
|
899
|
+
}));
|
|
900
|
+
// Extract skills metadata from the skills array
|
|
901
|
+
const skillsMetadata = skills.map((skill) => ({
|
|
902
|
+
name: skill.name,
|
|
903
|
+
description: skill.description,
|
|
904
|
+
}));
|
|
905
|
+
// Extract plugins metadata
|
|
906
|
+
const pluginsMetadata = (options.plugins ?? []).map((plugin) => ({
|
|
907
|
+
name: plugin.name,
|
|
908
|
+
description: plugin.description ?? "",
|
|
909
|
+
}));
|
|
910
|
+
// Build backend info
|
|
911
|
+
const backendInfo = {
|
|
912
|
+
type: backend.constructor.name.toLowerCase().replace("backend", "") || "unknown",
|
|
913
|
+
hasExecuteCapability: hasExecuteCapability(backend),
|
|
914
|
+
rootDir: "rootDir" in backend ? backend.rootDir : undefined,
|
|
915
|
+
};
|
|
916
|
+
return {
|
|
917
|
+
tools: toolsMetadata.length > 0 ? toolsMetadata : undefined,
|
|
918
|
+
skills: skillsMetadata.length > 0 ? skillsMetadata : undefined,
|
|
919
|
+
plugins: pluginsMetadata.length > 0 ? pluginsMetadata : undefined,
|
|
920
|
+
backend: backendInfo,
|
|
921
|
+
state,
|
|
922
|
+
// Model ID extraction is not reliable across all LanguageModel types
|
|
923
|
+
// Users can access the full model via their custom context if needed
|
|
924
|
+
model: undefined,
|
|
925
|
+
maxSteps: options.maxSteps,
|
|
926
|
+
permissionMode,
|
|
927
|
+
currentMessages: messages,
|
|
928
|
+
threadId,
|
|
929
|
+
};
|
|
930
|
+
};
|
|
931
|
+
// Helper to get system prompt (either static or built from context)
|
|
932
|
+
const getSystemPrompt = (context) => {
|
|
933
|
+
if (promptMode === "static") {
|
|
934
|
+
return options.systemPrompt;
|
|
935
|
+
}
|
|
936
|
+
// Build prompt using prompt builder
|
|
937
|
+
return promptBuilder.build(context);
|
|
938
|
+
};
|
|
939
|
+
// Runtime tools added/removed dynamically by plugins at runtime
|
|
940
|
+
const runtimeTools = {};
|
|
941
|
+
// Helper to get current active tools (core + runtime + MCP + dynamically loaded from registry)
|
|
815
942
|
const getActiveToolSet = (threadId) => {
|
|
816
943
|
// Start with core tools
|
|
817
944
|
const allTools = { ...coreTools };
|
|
945
|
+
// Add runtime tools (added by plugins at runtime)
|
|
946
|
+
Object.assign(allTools, runtimeTools);
|
|
818
947
|
// Add MCP tools from plugin registrations
|
|
819
948
|
const mcpTools = mcpManager.getToolSet();
|
|
820
949
|
Object.assign(allTools, mcpTools);
|
|
@@ -843,6 +972,8 @@ export function createAgent(options) {
|
|
|
843
972
|
const getActiveToolSetWithStreaming = (streamingContext, threadId, step) => {
|
|
844
973
|
// Start with core tools
|
|
845
974
|
const allTools = { ...coreTools };
|
|
975
|
+
// Add runtime tools (added by plugins at runtime)
|
|
976
|
+
Object.assign(allTools, runtimeTools);
|
|
846
977
|
// Process plugins - invoke function-based tools with streaming context
|
|
847
978
|
for (const plugin of options.plugins ?? []) {
|
|
848
979
|
if (plugin.tools) {
|
|
@@ -1155,6 +1286,34 @@ export function createAgent(options) {
|
|
|
1155
1286
|
usage: step.usage,
|
|
1156
1287
|
}));
|
|
1157
1288
|
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Get the next actionable task prompt from the background task queue.
|
|
1291
|
+
*
|
|
1292
|
+
* Waits for a task to reach terminal state, skips already-consumed and killed
|
|
1293
|
+
* tasks, formats the result as a prompt, removes the task, and returns it.
|
|
1294
|
+
* Returns null when no more tasks need processing.
|
|
1295
|
+
*/
|
|
1296
|
+
async function getNextTaskPrompt() {
|
|
1297
|
+
while (taskManager.hasActiveTasks() || taskManager.hasTerminalTasks()) {
|
|
1298
|
+
const completedTask = await taskManager.waitForNextCompletion();
|
|
1299
|
+
// Dedup: skip if already consumed via task_output tool
|
|
1300
|
+
if (!taskManager.getTask(completedTask.id)) {
|
|
1301
|
+
continue;
|
|
1302
|
+
}
|
|
1303
|
+
// Skip killed tasks (user already knows)
|
|
1304
|
+
if (completedTask.status === "killed") {
|
|
1305
|
+
taskManager.removeTask(completedTask.id);
|
|
1306
|
+
continue;
|
|
1307
|
+
}
|
|
1308
|
+
// Format as follow-up prompt
|
|
1309
|
+
const prompt = completedTask.status === "completed"
|
|
1310
|
+
? formatTaskCompletion(completedTask)
|
|
1311
|
+
: formatTaskFailure(completedTask);
|
|
1312
|
+
taskManager.removeTask(completedTask.id);
|
|
1313
|
+
return prompt;
|
|
1314
|
+
}
|
|
1315
|
+
return null;
|
|
1316
|
+
}
|
|
1158
1317
|
const agent = {
|
|
1159
1318
|
id,
|
|
1160
1319
|
options,
|
|
@@ -1184,11 +1343,22 @@ export function createAgent(options) {
|
|
|
1184
1343
|
lastBuiltMessages = messages;
|
|
1185
1344
|
const maxSteps = options.maxSteps ?? 10;
|
|
1186
1345
|
const startStep = checkpoint?.step ?? 0;
|
|
1346
|
+
// Shared signal state: flow-control signals (interrupt) thrown by tools
|
|
1347
|
+
// are caught by the outermost wrapper and stored here. A custom
|
|
1348
|
+
// stopWhen condition stops generation after the current step completes.
|
|
1349
|
+
const signalState = {};
|
|
1187
1350
|
// Build initial params - use active tools (core + dynamically loaded + task)
|
|
1188
|
-
// Apply hooks AFTER adding task tool so task tool is also wrapped
|
|
1189
|
-
|
|
1351
|
+
// Apply hooks AFTER adding task tool so task tool is also wrapped.
|
|
1352
|
+
// Then wrap with signal catching as the outermost layer so that
|
|
1353
|
+
// InterruptSignal is intercepted before the AI SDK can catch it and
|
|
1354
|
+
// convert it to a tool-error result.
|
|
1355
|
+
const hookedTools = applyToolHooks(addTaskToolIfConfigured(getActiveToolSet(effectiveGenOptions.threadId)), effectiveGenOptions.threadId);
|
|
1356
|
+
const activeTools = wrapToolsWithSignalCatching(hookedTools, signalState);
|
|
1357
|
+
// Build prompt context and generate system prompt
|
|
1358
|
+
const promptContext = buildPromptContext(messages, effectiveGenOptions.threadId);
|
|
1359
|
+
const systemPrompt = getSystemPrompt(promptContext);
|
|
1190
1360
|
const initialParams = {
|
|
1191
|
-
system:
|
|
1361
|
+
system: systemPrompt,
|
|
1192
1362
|
messages,
|
|
1193
1363
|
tools: activeTools,
|
|
1194
1364
|
maxTokens: effectiveGenOptions.maxTokens,
|
|
@@ -1198,6 +1368,9 @@ export function createAgent(options) {
|
|
|
1198
1368
|
providerOptions: effectiveGenOptions.providerOptions,
|
|
1199
1369
|
headers: effectiveGenOptions.headers,
|
|
1200
1370
|
};
|
|
1371
|
+
// Stop condition: stop when an interrupt signal was caught, OR when
|
|
1372
|
+
// the step count reaches maxSteps (whichever comes first).
|
|
1373
|
+
const signalStopCondition = () => signalState.interrupt != null;
|
|
1201
1374
|
// Execute generation
|
|
1202
1375
|
const response = await generateText({
|
|
1203
1376
|
model: retryState.currentModel,
|
|
@@ -1208,13 +1381,53 @@ export function createAgent(options) {
|
|
|
1208
1381
|
temperature: initialParams.temperature,
|
|
1209
1382
|
stopSequences: initialParams.stopSequences,
|
|
1210
1383
|
abortSignal: initialParams.abortSignal,
|
|
1211
|
-
stopWhen: stepCountIs(maxSteps),
|
|
1384
|
+
stopWhen: [signalStopCondition, stepCountIs(maxSteps)],
|
|
1212
1385
|
// Passthrough AI SDK options
|
|
1213
1386
|
output: effectiveGenOptions.output,
|
|
1214
1387
|
// biome-ignore lint/suspicious/noExplicitAny: Type cast needed for AI SDK compatibility
|
|
1215
1388
|
providerOptions: initialParams.providerOptions,
|
|
1216
1389
|
headers: initialParams.headers,
|
|
1217
1390
|
});
|
|
1391
|
+
// Check for intercepted interrupt signal (cooperative path)
|
|
1392
|
+
if (signalState.interrupt) {
|
|
1393
|
+
const interrupt = signalState.interrupt.interrupt;
|
|
1394
|
+
// Save the interrupt to checkpoint
|
|
1395
|
+
if (effectiveGenOptions.threadId && options.checkpointer) {
|
|
1396
|
+
const checkpoint = await options.checkpointer.load(effectiveGenOptions.threadId);
|
|
1397
|
+
if (checkpoint) {
|
|
1398
|
+
const updatedCheckpoint = updateCheckpoint(checkpoint, {
|
|
1399
|
+
pendingInterrupt: interrupt,
|
|
1400
|
+
});
|
|
1401
|
+
await options.checkpointer.save(updatedCheckpoint);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
// Emit InterruptRequested hook
|
|
1405
|
+
const interruptRequestedHooks = effectiveHooks?.InterruptRequested ?? [];
|
|
1406
|
+
if (interruptRequestedHooks.length > 0) {
|
|
1407
|
+
const hookInput = {
|
|
1408
|
+
hook_event_name: "InterruptRequested",
|
|
1409
|
+
session_id: effectiveGenOptions.threadId ?? "default",
|
|
1410
|
+
cwd: process.cwd(),
|
|
1411
|
+
interrupt_id: interrupt.id,
|
|
1412
|
+
interrupt_type: interrupt.type,
|
|
1413
|
+
tool_call_id: interrupt.toolCallId,
|
|
1414
|
+
tool_name: interrupt.toolName,
|
|
1415
|
+
request: interrupt.request,
|
|
1416
|
+
};
|
|
1417
|
+
await invokeHooksWithTimeout(interruptRequestedHooks, hookInput, null, agent);
|
|
1418
|
+
}
|
|
1419
|
+
// Return interrupted result with partial results from the response
|
|
1420
|
+
const interruptedResult = {
|
|
1421
|
+
status: "interrupted",
|
|
1422
|
+
interrupt,
|
|
1423
|
+
partial: {
|
|
1424
|
+
text: response.text,
|
|
1425
|
+
steps: mapSteps(response.steps),
|
|
1426
|
+
usage: response.usage,
|
|
1427
|
+
},
|
|
1428
|
+
};
|
|
1429
|
+
return interruptedResult;
|
|
1430
|
+
}
|
|
1218
1431
|
// Only access output if an output schema was provided
|
|
1219
1432
|
// (accessing response.output throws AI_NoOutputGeneratedError otherwise)
|
|
1220
1433
|
let output;
|
|
@@ -1272,7 +1485,39 @@ export function createAgent(options) {
|
|
|
1272
1485
|
finalResult = updatedResult;
|
|
1273
1486
|
}
|
|
1274
1487
|
}
|
|
1275
|
-
|
|
1488
|
+
// --- Background task completion loop ---
|
|
1489
|
+
if (!waitForBackgroundTasks) {
|
|
1490
|
+
return finalResult;
|
|
1491
|
+
}
|
|
1492
|
+
// When checkpointing is active, the checkpoint already contains the
|
|
1493
|
+
// full conversation history (saved above). Passing explicit messages
|
|
1494
|
+
// would cause buildMessages() to load checkpoint messages AND append
|
|
1495
|
+
// the same messages again, causing duplication.
|
|
1496
|
+
const hasCheckpointing = !!(effectiveGenOptions.threadId && options.checkpointer);
|
|
1497
|
+
let lastResult = finalResult;
|
|
1498
|
+
let runningMessages = hasCheckpointing
|
|
1499
|
+
? []
|
|
1500
|
+
: [...messages, { role: "assistant", content: finalResult.text }];
|
|
1501
|
+
let followUpPrompt = await getNextTaskPrompt();
|
|
1502
|
+
while (followUpPrompt !== null) {
|
|
1503
|
+
lastResult = await agent.generate({
|
|
1504
|
+
...genOptions,
|
|
1505
|
+
prompt: followUpPrompt,
|
|
1506
|
+
messages: hasCheckpointing ? undefined : runningMessages,
|
|
1507
|
+
});
|
|
1508
|
+
if (lastResult.status === "interrupted") {
|
|
1509
|
+
return lastResult;
|
|
1510
|
+
}
|
|
1511
|
+
if (!hasCheckpointing) {
|
|
1512
|
+
runningMessages = [
|
|
1513
|
+
...runningMessages,
|
|
1514
|
+
{ role: "user", content: followUpPrompt },
|
|
1515
|
+
{ role: "assistant", content: lastResult.text },
|
|
1516
|
+
];
|
|
1517
|
+
}
|
|
1518
|
+
followUpPrompt = await getNextTaskPrompt();
|
|
1519
|
+
}
|
|
1520
|
+
return lastResult;
|
|
1276
1521
|
}
|
|
1277
1522
|
catch (error) {
|
|
1278
1523
|
// Check if this is an InterruptSignal (new interrupt system)
|
|
@@ -1451,11 +1696,18 @@ export function createAgent(options) {
|
|
|
1451
1696
|
const { messages, checkpoint } = await buildMessages(effectiveGenOptions);
|
|
1452
1697
|
const maxSteps = options.maxSteps ?? 10;
|
|
1453
1698
|
const startStep = checkpoint?.step ?? 0;
|
|
1699
|
+
// Signal state for cooperative signal catching in streaming mode
|
|
1700
|
+
const signalState = {};
|
|
1454
1701
|
// Build initial params - use active tools (core + dynamically loaded + task)
|
|
1455
|
-
// Apply hooks AFTER adding task tool so task tool is also wrapped
|
|
1456
|
-
|
|
1702
|
+
// Apply hooks AFTER adding task tool so task tool is also wrapped.
|
|
1703
|
+
// Then wrap with signal catching as the outermost layer.
|
|
1704
|
+
const hookedTools = applyToolHooks(addTaskToolIfConfigured(getActiveToolSet(effectiveGenOptions.threadId)), effectiveGenOptions.threadId);
|
|
1705
|
+
const activeTools = wrapToolsWithSignalCatching(hookedTools, signalState);
|
|
1706
|
+
// Build prompt context and generate system prompt
|
|
1707
|
+
const promptContext = buildPromptContext(messages, effectiveGenOptions.threadId);
|
|
1708
|
+
const systemPrompt = getSystemPrompt(promptContext);
|
|
1457
1709
|
const initialParams = {
|
|
1458
|
-
system:
|
|
1710
|
+
system: systemPrompt,
|
|
1459
1711
|
messages,
|
|
1460
1712
|
tools: activeTools,
|
|
1461
1713
|
maxTokens: effectiveGenOptions.maxTokens,
|
|
@@ -1465,6 +1717,9 @@ export function createAgent(options) {
|
|
|
1465
1717
|
providerOptions: effectiveGenOptions.providerOptions,
|
|
1466
1718
|
headers: effectiveGenOptions.headers,
|
|
1467
1719
|
};
|
|
1720
|
+
// Stop condition: stop when an interrupt signal was caught, OR when
|
|
1721
|
+
// the step count reaches maxSteps.
|
|
1722
|
+
const signalStopCondition = () => signalState.interrupt != null;
|
|
1468
1723
|
// Execute stream
|
|
1469
1724
|
const response = streamText({
|
|
1470
1725
|
model: retryState.currentModel,
|
|
@@ -1475,7 +1730,7 @@ export function createAgent(options) {
|
|
|
1475
1730
|
temperature: initialParams.temperature,
|
|
1476
1731
|
stopSequences: initialParams.stopSequences,
|
|
1477
1732
|
abortSignal: initialParams.abortSignal,
|
|
1478
|
-
stopWhen: stepCountIs(maxSteps),
|
|
1733
|
+
stopWhen: [signalStopCondition, stepCountIs(maxSteps)],
|
|
1479
1734
|
// Passthrough AI SDK options
|
|
1480
1735
|
output: genOptions.output,
|
|
1481
1736
|
// biome-ignore lint/suspicious/noExplicitAny: Type cast needed for AI SDK compatibility
|
|
@@ -1559,7 +1814,36 @@ export function createAgent(options) {
|
|
|
1559
1814
|
await invokeHooksWithTimeout(postGenerateHooks, postGenerateInput, null, agent);
|
|
1560
1815
|
// Note: updatedResult is not applied for streaming since the stream has already been sent
|
|
1561
1816
|
}
|
|
1562
|
-
//
|
|
1817
|
+
// --- Background task completion loop ---
|
|
1818
|
+
if (!waitForBackgroundTasks || signalState.interrupt) {
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
const hasCheckpointing = !!(effectiveGenOptions.threadId && options.checkpointer);
|
|
1822
|
+
let currentMessages = hasCheckpointing
|
|
1823
|
+
? []
|
|
1824
|
+
: [...messages, { role: "assistant", content: text }];
|
|
1825
|
+
let followUpPrompt = await getNextTaskPrompt();
|
|
1826
|
+
while (followUpPrompt !== null) {
|
|
1827
|
+
const followUpGen = agent.stream({
|
|
1828
|
+
...genOptions,
|
|
1829
|
+
prompt: followUpPrompt,
|
|
1830
|
+
messages: hasCheckpointing ? undefined : currentMessages,
|
|
1831
|
+
});
|
|
1832
|
+
let followUpText = "";
|
|
1833
|
+
for await (const part of followUpGen) {
|
|
1834
|
+
yield part;
|
|
1835
|
+
if (part.type === "text-delta")
|
|
1836
|
+
followUpText += part.text;
|
|
1837
|
+
}
|
|
1838
|
+
if (!hasCheckpointing) {
|
|
1839
|
+
currentMessages = [
|
|
1840
|
+
...currentMessages,
|
|
1841
|
+
{ role: "user", content: followUpPrompt },
|
|
1842
|
+
{ role: "assistant", content: followUpText },
|
|
1843
|
+
];
|
|
1844
|
+
}
|
|
1845
|
+
followUpPrompt = await getNextTaskPrompt();
|
|
1846
|
+
}
|
|
1563
1847
|
return;
|
|
1564
1848
|
}
|
|
1565
1849
|
catch (error) {
|
|
@@ -1609,11 +1893,18 @@ export function createAgent(options) {
|
|
|
1609
1893
|
const { messages, checkpoint } = await buildMessages(effectiveGenOptions);
|
|
1610
1894
|
const maxSteps = options.maxSteps ?? 10;
|
|
1611
1895
|
const startStep = checkpoint?.step ?? 0;
|
|
1896
|
+
// Signal state for cooperative signal catching in streaming mode
|
|
1897
|
+
const signalState = {};
|
|
1612
1898
|
// Build initial params - use active tools (core + dynamically loaded + task)
|
|
1613
|
-
// Apply hooks AFTER adding task tool so task tool is also wrapped
|
|
1614
|
-
|
|
1899
|
+
// Apply hooks AFTER adding task tool so task tool is also wrapped.
|
|
1900
|
+
// Then wrap with signal catching as the outermost layer.
|
|
1901
|
+
const hookedTools = applyToolHooks(addTaskToolIfConfigured(getActiveToolSet(effectiveGenOptions.threadId)), effectiveGenOptions.threadId);
|
|
1902
|
+
const activeTools = wrapToolsWithSignalCatching(hookedTools, signalState);
|
|
1903
|
+
// Build prompt context and generate system prompt
|
|
1904
|
+
const promptContext = buildPromptContext(messages, effectiveGenOptions.threadId);
|
|
1905
|
+
const systemPrompt = getSystemPrompt(promptContext);
|
|
1615
1906
|
const initialParams = {
|
|
1616
|
-
system:
|
|
1907
|
+
system: systemPrompt,
|
|
1617
1908
|
messages,
|
|
1618
1909
|
tools: activeTools,
|
|
1619
1910
|
maxTokens: effectiveGenOptions.maxTokens,
|
|
@@ -1623,11 +1914,18 @@ export function createAgent(options) {
|
|
|
1623
1914
|
providerOptions: effectiveGenOptions.providerOptions,
|
|
1624
1915
|
headers: effectiveGenOptions.headers,
|
|
1625
1916
|
};
|
|
1917
|
+
// Capture currentModel for use in the callback closure
|
|
1918
|
+
const modelToUse = retryState.currentModel;
|
|
1919
|
+
// Stop condition: stop when an interrupt signal was caught, OR when
|
|
1920
|
+
// the step count reaches maxSteps.
|
|
1921
|
+
const signalStopCondition = () => signalState.interrupt != null;
|
|
1626
1922
|
// Track step count for incremental checkpointing
|
|
1627
1923
|
let currentStepCount = 0;
|
|
1628
|
-
// Execute
|
|
1924
|
+
// Execute streamText OUTSIDE createUIMessageStream so errors propagate
|
|
1925
|
+
// to the retry loop (if streamText throws synchronously on creation,
|
|
1926
|
+
// e.g. rate limit, the catch block handles retry/fallback).
|
|
1629
1927
|
const result = streamText({
|
|
1630
|
-
model:
|
|
1928
|
+
model: modelToUse,
|
|
1631
1929
|
system: initialParams.system,
|
|
1632
1930
|
messages: initialParams.messages,
|
|
1633
1931
|
tools: initialParams.tools,
|
|
@@ -1635,7 +1933,7 @@ export function createAgent(options) {
|
|
|
1635
1933
|
temperature: initialParams.temperature,
|
|
1636
1934
|
stopSequences: initialParams.stopSequences,
|
|
1637
1935
|
abortSignal: initialParams.abortSignal,
|
|
1638
|
-
stopWhen: stepCountIs(maxSteps),
|
|
1936
|
+
stopWhen: [signalStopCondition, stepCountIs(maxSteps)],
|
|
1639
1937
|
// Passthrough AI SDK options
|
|
1640
1938
|
output: effectiveGenOptions.output,
|
|
1641
1939
|
// biome-ignore lint/suspicious/noExplicitAny: Type cast needed for AI SDK compatibility
|
|
@@ -1698,10 +1996,93 @@ export function createAgent(options) {
|
|
|
1698
1996
|
}
|
|
1699
1997
|
},
|
|
1700
1998
|
});
|
|
1701
|
-
//
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1999
|
+
// Use createUIMessageStream to control stream lifecycle for background task follow-ups
|
|
2000
|
+
const stream = createUIMessageStream({
|
|
2001
|
+
execute: async ({ writer }) => {
|
|
2002
|
+
// Merge initial generation into the stream
|
|
2003
|
+
writer.merge(result.toUIMessageStream());
|
|
2004
|
+
// Wait for initial generation to complete to get final text
|
|
2005
|
+
const text = await result.text;
|
|
2006
|
+
// --- Background task completion loop ---
|
|
2007
|
+
if (waitForBackgroundTasks) {
|
|
2008
|
+
// Track accumulated steps for checkpoint saves
|
|
2009
|
+
const initialSteps = await result.steps;
|
|
2010
|
+
let accumulatedStepCount = initialSteps.length;
|
|
2011
|
+
let currentMessages = [
|
|
2012
|
+
...messages,
|
|
2013
|
+
{ role: "assistant", content: text },
|
|
2014
|
+
];
|
|
2015
|
+
let followUpPrompt = await getNextTaskPrompt();
|
|
2016
|
+
while (followUpPrompt !== null) {
|
|
2017
|
+
// Stream follow-up generation into the same writer
|
|
2018
|
+
const followUpResult = streamText({
|
|
2019
|
+
model: modelToUse,
|
|
2020
|
+
system: initialParams.system,
|
|
2021
|
+
messages: [
|
|
2022
|
+
...currentMessages,
|
|
2023
|
+
{ role: "user", content: followUpPrompt },
|
|
2024
|
+
],
|
|
2025
|
+
tools: initialParams.tools,
|
|
2026
|
+
maxOutputTokens: initialParams.maxTokens,
|
|
2027
|
+
temperature: initialParams.temperature,
|
|
2028
|
+
stopSequences: initialParams.stopSequences,
|
|
2029
|
+
abortSignal: initialParams.abortSignal,
|
|
2030
|
+
stopWhen: [signalStopCondition, stepCountIs(maxSteps)],
|
|
2031
|
+
// biome-ignore lint/suspicious/noExplicitAny: Type cast needed for AI SDK compatibility
|
|
2032
|
+
providerOptions: initialParams.providerOptions,
|
|
2033
|
+
headers: initialParams.headers,
|
|
2034
|
+
});
|
|
2035
|
+
writer.merge(followUpResult.toUIMessageStream());
|
|
2036
|
+
const followUpText = await followUpResult.text;
|
|
2037
|
+
currentMessages = [
|
|
2038
|
+
...currentMessages,
|
|
2039
|
+
{ role: "user", content: followUpPrompt },
|
|
2040
|
+
{ role: "assistant", content: followUpText },
|
|
2041
|
+
];
|
|
2042
|
+
// --- Post-completion bookkeeping for follow-ups ---
|
|
2043
|
+
const followUpSteps = await followUpResult.steps;
|
|
2044
|
+
accumulatedStepCount += followUpSteps.length;
|
|
2045
|
+
// Checkpoint save
|
|
2046
|
+
if (effectiveGenOptions.threadId && options.checkpointer) {
|
|
2047
|
+
await saveCheckpoint(effectiveGenOptions.threadId, currentMessages, startStep + accumulatedStepCount);
|
|
2048
|
+
}
|
|
2049
|
+
// Context manager update
|
|
2050
|
+
const followUpUsage = await followUpResult.usage;
|
|
2051
|
+
if (options.contextManager?.updateUsage && followUpUsage) {
|
|
2052
|
+
options.contextManager.updateUsage({
|
|
2053
|
+
inputTokens: followUpUsage.inputTokens,
|
|
2054
|
+
outputTokens: followUpUsage.outputTokens,
|
|
2055
|
+
totalTokens: followUpUsage.totalTokens,
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
// PostGenerate hooks
|
|
2059
|
+
const followUpPostGenerateHooks = effectiveHooks?.PostGenerate ?? [];
|
|
2060
|
+
if (followUpPostGenerateHooks.length > 0) {
|
|
2061
|
+
const followUpFinishReason = await followUpResult.finishReason;
|
|
2062
|
+
const followUpHookResult = {
|
|
2063
|
+
status: "complete",
|
|
2064
|
+
text: followUpText,
|
|
2065
|
+
usage: followUpUsage,
|
|
2066
|
+
finishReason: followUpFinishReason,
|
|
2067
|
+
output: undefined,
|
|
2068
|
+
steps: mapSteps(followUpSteps),
|
|
2069
|
+
};
|
|
2070
|
+
const followUpPostGenerateInput = {
|
|
2071
|
+
hook_event_name: "PostGenerate",
|
|
2072
|
+
session_id: effectiveGenOptions.threadId ?? "default",
|
|
2073
|
+
cwd: process.cwd(),
|
|
2074
|
+
options: effectiveGenOptions,
|
|
2075
|
+
result: followUpHookResult,
|
|
2076
|
+
};
|
|
2077
|
+
await invokeHooksWithTimeout(followUpPostGenerateHooks, followUpPostGenerateInput, null, agent);
|
|
2078
|
+
}
|
|
2079
|
+
followUpPrompt = await getNextTaskPrompt();
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
},
|
|
2083
|
+
});
|
|
2084
|
+
// Convert the stream to a Response
|
|
2085
|
+
return createUIMessageStreamResponse({ stream });
|
|
1705
2086
|
}
|
|
1706
2087
|
catch (error) {
|
|
1707
2088
|
// Normalize error to AgentError
|
|
@@ -1740,11 +2121,18 @@ export function createAgent(options) {
|
|
|
1740
2121
|
const { messages, checkpoint } = await buildMessages(effectiveGenOptions);
|
|
1741
2122
|
const maxSteps = options.maxSteps ?? 10;
|
|
1742
2123
|
const startStep = checkpoint?.step ?? 0;
|
|
2124
|
+
// Signal state for cooperative signal catching in streaming mode
|
|
2125
|
+
const signalState = {};
|
|
1743
2126
|
// Build initial params - use active tools (core + dynamically loaded + task)
|
|
1744
|
-
// Apply hooks AFTER adding task tool so task tool is also wrapped
|
|
1745
|
-
|
|
2127
|
+
// Apply hooks AFTER adding task tool so task tool is also wrapped.
|
|
2128
|
+
// Then wrap with signal catching as the outermost layer.
|
|
2129
|
+
const hookedTools = applyToolHooks(addTaskToolIfConfigured(getActiveToolSet(effectiveGenOptions.threadId)), effectiveGenOptions.threadId);
|
|
2130
|
+
const activeTools = wrapToolsWithSignalCatching(hookedTools, signalState);
|
|
2131
|
+
// Build prompt context and generate system prompt
|
|
2132
|
+
const promptContext = buildPromptContext(messages, effectiveGenOptions.threadId);
|
|
2133
|
+
const systemPrompt = getSystemPrompt(promptContext);
|
|
1746
2134
|
const initialParams = {
|
|
1747
|
-
system:
|
|
2135
|
+
system: systemPrompt,
|
|
1748
2136
|
messages,
|
|
1749
2137
|
tools: activeTools,
|
|
1750
2138
|
maxTokens: effectiveGenOptions.maxTokens,
|
|
@@ -1756,6 +2144,9 @@ export function createAgent(options) {
|
|
|
1756
2144
|
};
|
|
1757
2145
|
// Track step count for incremental checkpointing
|
|
1758
2146
|
let currentStepCount = 0;
|
|
2147
|
+
// Stop condition: stop when an interrupt signal was caught, OR when
|
|
2148
|
+
// the step count reaches maxSteps.
|
|
2149
|
+
const signalStopCondition = () => signalState.interrupt != null;
|
|
1759
2150
|
// Execute stream
|
|
1760
2151
|
const result = streamText({
|
|
1761
2152
|
model: retryState.currentModel,
|
|
@@ -1766,7 +2157,7 @@ export function createAgent(options) {
|
|
|
1766
2157
|
temperature: initialParams.temperature,
|
|
1767
2158
|
stopSequences: initialParams.stopSequences,
|
|
1768
2159
|
abortSignal: initialParams.abortSignal,
|
|
1769
|
-
stopWhen: stepCountIs(maxSteps),
|
|
2160
|
+
stopWhen: [signalStopCondition, stepCountIs(maxSteps)],
|
|
1770
2161
|
// Passthrough AI SDK options
|
|
1771
2162
|
output: effectiveGenOptions.output,
|
|
1772
2163
|
// biome-ignore lint/suspicious/noExplicitAny: Type cast needed for AI SDK compatibility
|
|
@@ -1889,12 +2280,19 @@ export function createAgent(options) {
|
|
|
1889
2280
|
}
|
|
1890
2281
|
// Create streaming context for tools
|
|
1891
2282
|
const streamingContext = { writer };
|
|
2283
|
+
// Signal state for cooperative signal catching in streaming mode
|
|
2284
|
+
const signalState = {};
|
|
1892
2285
|
// Build tools with streaming context and task tool
|
|
1893
|
-
// Apply hooks AFTER adding task tool so task tool is also wrapped
|
|
1894
|
-
|
|
2286
|
+
// Apply hooks AFTER adding task tool so task tool is also wrapped.
|
|
2287
|
+
// Then wrap with signal catching as the outermost layer.
|
|
2288
|
+
const hookedStreamingTools = applyToolHooks(addTaskToolIfConfigured(getActiveToolSetWithStreaming(streamingContext, effectiveGenOptions.threadId), streamingContext), effectiveGenOptions.threadId);
|
|
2289
|
+
const streamingTools = wrapToolsWithSignalCatching(hookedStreamingTools, signalState);
|
|
2290
|
+
// Build prompt context and generate system prompt
|
|
2291
|
+
const promptContext = buildPromptContext(messages, effectiveGenOptions.threadId);
|
|
2292
|
+
const systemPrompt = getSystemPrompt(promptContext);
|
|
1895
2293
|
// Build initial params with streaming-aware tools
|
|
1896
2294
|
const initialParams = {
|
|
1897
|
-
system:
|
|
2295
|
+
system: systemPrompt,
|
|
1898
2296
|
messages,
|
|
1899
2297
|
tools: streamingTools,
|
|
1900
2298
|
maxTokens: effectiveGenOptions.maxTokens,
|
|
@@ -1906,6 +2304,9 @@ export function createAgent(options) {
|
|
|
1906
2304
|
};
|
|
1907
2305
|
// Track step count for incremental checkpointing
|
|
1908
2306
|
let currentStepCount = 0;
|
|
2307
|
+
// Stop condition: stop when a flow-control signal was caught, OR when
|
|
2308
|
+
// the step count reaches maxSteps.
|
|
2309
|
+
const signalStopCondition = () => signalState.interrupt != null;
|
|
1909
2310
|
// Execute stream
|
|
1910
2311
|
const result = streamText({
|
|
1911
2312
|
model: modelToUse,
|
|
@@ -1916,7 +2317,7 @@ export function createAgent(options) {
|
|
|
1916
2317
|
temperature: initialParams.temperature,
|
|
1917
2318
|
stopSequences: initialParams.stopSequences,
|
|
1918
2319
|
abortSignal: initialParams.abortSignal,
|
|
1919
|
-
stopWhen: stepCountIs(maxSteps),
|
|
2320
|
+
stopWhen: [signalStopCondition, stepCountIs(maxSteps)],
|
|
1920
2321
|
// Passthrough AI SDK options
|
|
1921
2322
|
output: effectiveGenOptions.output,
|
|
1922
2323
|
// biome-ignore lint/suspicious/noExplicitAny: Type cast needed for AI SDK compatibility
|
|
@@ -1984,6 +2385,84 @@ export function createAgent(options) {
|
|
|
1984
2385
|
});
|
|
1985
2386
|
// Merge the streamText output into the UI message stream
|
|
1986
2387
|
writer.merge(result.toUIMessageStream());
|
|
2388
|
+
// Wait for initial generation to complete to get final text
|
|
2389
|
+
const text = await result.text;
|
|
2390
|
+
// --- Background task completion loop ---
|
|
2391
|
+
if (waitForBackgroundTasks) {
|
|
2392
|
+
// Track accumulated steps for checkpoint saves
|
|
2393
|
+
const initialSteps = await result.steps;
|
|
2394
|
+
let accumulatedStepCount = initialSteps.length;
|
|
2395
|
+
let currentMessages = [
|
|
2396
|
+
...messages,
|
|
2397
|
+
{ role: "assistant", content: text },
|
|
2398
|
+
];
|
|
2399
|
+
let followUpPrompt = await getNextTaskPrompt();
|
|
2400
|
+
while (followUpPrompt !== null) {
|
|
2401
|
+
// Stream follow-up generation into the same writer
|
|
2402
|
+
const followUpResult = streamText({
|
|
2403
|
+
model: modelToUse,
|
|
2404
|
+
system: initialParams.system,
|
|
2405
|
+
messages: [
|
|
2406
|
+
...currentMessages,
|
|
2407
|
+
{ role: "user", content: followUpPrompt },
|
|
2408
|
+
],
|
|
2409
|
+
tools: initialParams.tools,
|
|
2410
|
+
maxOutputTokens: initialParams.maxTokens,
|
|
2411
|
+
temperature: initialParams.temperature,
|
|
2412
|
+
stopSequences: initialParams.stopSequences,
|
|
2413
|
+
abortSignal: initialParams.abortSignal,
|
|
2414
|
+
stopWhen: [signalStopCondition, stepCountIs(maxSteps)],
|
|
2415
|
+
// biome-ignore lint/suspicious/noExplicitAny: Type cast needed for AI SDK compatibility
|
|
2416
|
+
providerOptions: initialParams.providerOptions,
|
|
2417
|
+
headers: initialParams.headers,
|
|
2418
|
+
});
|
|
2419
|
+
writer.merge(followUpResult.toUIMessageStream());
|
|
2420
|
+
const followUpText = await followUpResult.text;
|
|
2421
|
+
currentMessages = [
|
|
2422
|
+
...currentMessages,
|
|
2423
|
+
{ role: "user", content: followUpPrompt },
|
|
2424
|
+
{ role: "assistant", content: followUpText },
|
|
2425
|
+
];
|
|
2426
|
+
// --- Post-completion bookkeeping for follow-ups ---
|
|
2427
|
+
const followUpSteps = await followUpResult.steps;
|
|
2428
|
+
accumulatedStepCount += followUpSteps.length;
|
|
2429
|
+
// Checkpoint save
|
|
2430
|
+
if (effectiveGenOptions.threadId && options.checkpointer) {
|
|
2431
|
+
await saveCheckpoint(effectiveGenOptions.threadId, currentMessages, startStep + accumulatedStepCount);
|
|
2432
|
+
}
|
|
2433
|
+
// Context manager update
|
|
2434
|
+
const followUpUsage = await followUpResult.usage;
|
|
2435
|
+
if (options.contextManager?.updateUsage && followUpUsage) {
|
|
2436
|
+
options.contextManager.updateUsage({
|
|
2437
|
+
inputTokens: followUpUsage.inputTokens,
|
|
2438
|
+
outputTokens: followUpUsage.outputTokens,
|
|
2439
|
+
totalTokens: followUpUsage.totalTokens,
|
|
2440
|
+
});
|
|
2441
|
+
}
|
|
2442
|
+
// PostGenerate hooks
|
|
2443
|
+
const followUpPostGenerateHooks = effectiveHooks?.PostGenerate ?? [];
|
|
2444
|
+
if (followUpPostGenerateHooks.length > 0) {
|
|
2445
|
+
const followUpFinishReason = await followUpResult.finishReason;
|
|
2446
|
+
const followUpHookResult = {
|
|
2447
|
+
status: "complete",
|
|
2448
|
+
text: followUpText,
|
|
2449
|
+
usage: followUpUsage,
|
|
2450
|
+
finishReason: followUpFinishReason,
|
|
2451
|
+
output: undefined,
|
|
2452
|
+
steps: mapSteps(followUpSteps),
|
|
2453
|
+
};
|
|
2454
|
+
const followUpPostGenerateInput = {
|
|
2455
|
+
hook_event_name: "PostGenerate",
|
|
2456
|
+
session_id: effectiveGenOptions.threadId ?? "default",
|
|
2457
|
+
cwd: process.cwd(),
|
|
2458
|
+
options: effectiveGenOptions,
|
|
2459
|
+
result: followUpHookResult,
|
|
2460
|
+
};
|
|
2461
|
+
await invokeHooksWithTimeout(followUpPostGenerateHooks, followUpPostGenerateInput, null, agent);
|
|
2462
|
+
}
|
|
2463
|
+
followUpPrompt = await getNextTaskPrompt();
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
1987
2466
|
},
|
|
1988
2467
|
});
|
|
1989
2468
|
// Convert the stream to a Response
|
|
@@ -2013,6 +2492,14 @@ export function createAgent(options) {
|
|
|
2013
2492
|
getActiveTools() {
|
|
2014
2493
|
return getActiveToolSet();
|
|
2015
2494
|
},
|
|
2495
|
+
addRuntimeTools(tools) {
|
|
2496
|
+
Object.assign(runtimeTools, tools);
|
|
2497
|
+
},
|
|
2498
|
+
removeRuntimeTools(toolNames) {
|
|
2499
|
+
for (const name of toolNames) {
|
|
2500
|
+
delete runtimeTools[name];
|
|
2501
|
+
}
|
|
2502
|
+
},
|
|
2016
2503
|
loadTools(toolNames) {
|
|
2017
2504
|
if (!toolRegistry) {
|
|
2018
2505
|
// No registry in eager mode - all tools already loaded
|