deepagentsdk 0.9.2
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/README.md +159 -0
- package/package.json +95 -0
- package/src/agent.ts +1230 -0
- package/src/backends/composite.ts +273 -0
- package/src/backends/filesystem.ts +692 -0
- package/src/backends/index.ts +22 -0
- package/src/backends/local-sandbox.ts +175 -0
- package/src/backends/persistent.ts +593 -0
- package/src/backends/sandbox.ts +510 -0
- package/src/backends/state.ts +244 -0
- package/src/backends/utils.ts +287 -0
- package/src/checkpointer/file-saver.ts +98 -0
- package/src/checkpointer/index.ts +5 -0
- package/src/checkpointer/kv-saver.ts +82 -0
- package/src/checkpointer/memory-saver.ts +82 -0
- package/src/checkpointer/types.ts +125 -0
- package/src/cli/components/ApiKeyInput.tsx +300 -0
- package/src/cli/components/FilePreview.tsx +237 -0
- package/src/cli/components/Input.tsx +277 -0
- package/src/cli/components/Message.tsx +93 -0
- package/src/cli/components/ModelSelection.tsx +338 -0
- package/src/cli/components/SlashMenu.tsx +101 -0
- package/src/cli/components/StatusBar.tsx +89 -0
- package/src/cli/components/Subagent.tsx +91 -0
- package/src/cli/components/TodoList.tsx +133 -0
- package/src/cli/components/ToolApproval.tsx +70 -0
- package/src/cli/components/ToolCall.tsx +144 -0
- package/src/cli/components/ToolCallSummary.tsx +175 -0
- package/src/cli/components/Welcome.tsx +75 -0
- package/src/cli/components/index.ts +24 -0
- package/src/cli/hooks/index.ts +12 -0
- package/src/cli/hooks/useAgent.ts +933 -0
- package/src/cli/index.tsx +1066 -0
- package/src/cli/theme.ts +205 -0
- package/src/cli/utils/model-list.ts +365 -0
- package/src/constants/errors.ts +29 -0
- package/src/constants/limits.ts +195 -0
- package/src/index.ts +176 -0
- package/src/middleware/agent-memory.ts +330 -0
- package/src/prompts.ts +196 -0
- package/src/skills/index.ts +2 -0
- package/src/skills/load.ts +191 -0
- package/src/skills/types.ts +53 -0
- package/src/tools/execute.ts +167 -0
- package/src/tools/filesystem.ts +418 -0
- package/src/tools/index.ts +39 -0
- package/src/tools/subagent.ts +443 -0
- package/src/tools/todos.ts +101 -0
- package/src/tools/web.ts +567 -0
- package/src/types/backend.ts +177 -0
- package/src/types/core.ts +220 -0
- package/src/types/events.ts +429 -0
- package/src/types/index.ts +94 -0
- package/src/types/structured-output.ts +43 -0
- package/src/types/subagent.ts +96 -0
- package/src/types.ts +22 -0
- package/src/utils/approval.ts +213 -0
- package/src/utils/events.ts +416 -0
- package/src/utils/eviction.ts +181 -0
- package/src/utils/index.ts +34 -0
- package/src/utils/model-parser.ts +38 -0
- package/src/utils/patch-tool-calls.ts +233 -0
- package/src/utils/project-detection.ts +32 -0
- package/src/utils/summarization.ts +254 -0
package/src/agent.ts
ADDED
|
@@ -0,0 +1,1230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep Agent implementation using Vercel AI SDK v6 ToolLoopAgent.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
ToolLoopAgent,
|
|
7
|
+
stepCountIs,
|
|
8
|
+
generateText,
|
|
9
|
+
streamText,
|
|
10
|
+
wrapLanguageModel,
|
|
11
|
+
Output,
|
|
12
|
+
type ToolSet,
|
|
13
|
+
type StopCondition,
|
|
14
|
+
type LanguageModel,
|
|
15
|
+
type LanguageModelMiddleware,
|
|
16
|
+
type ToolLoopAgentSettings,
|
|
17
|
+
} from "ai";
|
|
18
|
+
import type { LanguageModelV3 } from "@ai-sdk/provider";
|
|
19
|
+
import type { z } from "zod";
|
|
20
|
+
import { DEFAULT_MAX_STEPS } from "./constants/limits";
|
|
21
|
+
import {
|
|
22
|
+
createCheckpointSavedEvent,
|
|
23
|
+
createCheckpointLoadedEvent,
|
|
24
|
+
} from "./utils/events";
|
|
25
|
+
import type {
|
|
26
|
+
CreateDeepAgentParams,
|
|
27
|
+
DeepAgentState,
|
|
28
|
+
BackendProtocol,
|
|
29
|
+
BackendFactory,
|
|
30
|
+
DeepAgentEvent,
|
|
31
|
+
ErrorEvent as DeepAgentErrorEvent,
|
|
32
|
+
CheckpointLoadedEvent,
|
|
33
|
+
EventCallback,
|
|
34
|
+
StreamWithEventsOptions,
|
|
35
|
+
ModelMessage,
|
|
36
|
+
SandboxBackendProtocol,
|
|
37
|
+
InterruptOnConfig,
|
|
38
|
+
PrepareStepFunction,
|
|
39
|
+
} from "./types";
|
|
40
|
+
import type { BaseCheckpointSaver, Checkpoint, InterruptData } from "./checkpointer/types";
|
|
41
|
+
import { isSandboxBackend } from "./types";
|
|
42
|
+
import {
|
|
43
|
+
BASE_PROMPT,
|
|
44
|
+
TODO_SYSTEM_PROMPT,
|
|
45
|
+
FILESYSTEM_SYSTEM_PROMPT,
|
|
46
|
+
TASK_SYSTEM_PROMPT,
|
|
47
|
+
EXECUTE_SYSTEM_PROMPT,
|
|
48
|
+
buildSkillsPrompt,
|
|
49
|
+
} from "./prompts";
|
|
50
|
+
import { createTodosTool } from "./tools/todos";
|
|
51
|
+
import { createFilesystemTools } from "./tools/filesystem";
|
|
52
|
+
import { createSubagentTool } from "./tools/subagent";
|
|
53
|
+
import { createExecuteTool } from "./tools/execute";
|
|
54
|
+
import { createWebTools } from "./tools/web";
|
|
55
|
+
import { StateBackend } from "./backends/state";
|
|
56
|
+
import { patchToolCalls } from "./utils/patch-tool-calls";
|
|
57
|
+
import { summarizeIfNeeded } from "./utils/summarization";
|
|
58
|
+
import { applyInterruptConfig, wrapToolsWithApproval, type ApprovalCallback } from "./utils/approval";
|
|
59
|
+
import type { SummarizationConfig } from "./types";
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Build the full system prompt from components.
|
|
63
|
+
*/
|
|
64
|
+
function buildSystemPrompt(
|
|
65
|
+
customPrompt?: string,
|
|
66
|
+
hasSubagents?: boolean,
|
|
67
|
+
hasSandbox?: boolean,
|
|
68
|
+
skills?: Array<{ name: string; description: string; path: string }>
|
|
69
|
+
): string {
|
|
70
|
+
const parts = [
|
|
71
|
+
customPrompt || "",
|
|
72
|
+
BASE_PROMPT,
|
|
73
|
+
TODO_SYSTEM_PROMPT,
|
|
74
|
+
FILESYSTEM_SYSTEM_PROMPT,
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
if (hasSandbox) {
|
|
78
|
+
parts.push(EXECUTE_SYSTEM_PROMPT);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (hasSubagents) {
|
|
82
|
+
parts.push(TASK_SYSTEM_PROMPT);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Add skills prompt if skills loaded
|
|
86
|
+
if (skills && skills.length > 0) {
|
|
87
|
+
parts.push(buildSkillsPrompt(skills));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return parts.filter(Boolean).join("\n\n");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Deep Agent wrapper class that provides generate() and stream() methods.
|
|
95
|
+
* Uses ToolLoopAgent from AI SDK v6 for the agent loop.
|
|
96
|
+
*/
|
|
97
|
+
export class DeepAgent {
|
|
98
|
+
private model: LanguageModel;
|
|
99
|
+
private systemPrompt: string;
|
|
100
|
+
private userTools: ToolSet;
|
|
101
|
+
private maxSteps: number;
|
|
102
|
+
private backend: BackendProtocol | BackendFactory;
|
|
103
|
+
private subagentOptions: {
|
|
104
|
+
defaultModel: LanguageModel;
|
|
105
|
+
defaultTools: ToolSet;
|
|
106
|
+
subagents: CreateDeepAgentParams["subagents"];
|
|
107
|
+
includeGeneralPurposeAgent: boolean;
|
|
108
|
+
};
|
|
109
|
+
private toolResultEvictionLimit?: number;
|
|
110
|
+
private enablePromptCaching: boolean;
|
|
111
|
+
private summarizationConfig?: SummarizationConfig;
|
|
112
|
+
private hasSandboxBackend: boolean;
|
|
113
|
+
private interruptOn?: InterruptOnConfig;
|
|
114
|
+
private checkpointer?: BaseCheckpointSaver;
|
|
115
|
+
private skillsMetadata: Array<{ name: string; description: string; path: string }> = [];
|
|
116
|
+
private outputConfig?: { schema: z.ZodType<any>; description?: string };
|
|
117
|
+
|
|
118
|
+
// AI SDK ToolLoopAgent passthrough options
|
|
119
|
+
private loopControl?: CreateDeepAgentParams["loopControl"];
|
|
120
|
+
private generationOptions?: CreateDeepAgentParams["generationOptions"];
|
|
121
|
+
private advancedOptions?: CreateDeepAgentParams["advancedOptions"];
|
|
122
|
+
|
|
123
|
+
constructor(params: CreateDeepAgentParams) {
|
|
124
|
+
const {
|
|
125
|
+
model,
|
|
126
|
+
middleware,
|
|
127
|
+
tools = {},
|
|
128
|
+
systemPrompt,
|
|
129
|
+
subagents = [],
|
|
130
|
+
backend,
|
|
131
|
+
maxSteps = DEFAULT_MAX_STEPS,
|
|
132
|
+
includeGeneralPurposeAgent = true,
|
|
133
|
+
toolResultEvictionLimit,
|
|
134
|
+
enablePromptCaching = false,
|
|
135
|
+
summarization,
|
|
136
|
+
interruptOn,
|
|
137
|
+
checkpointer,
|
|
138
|
+
skillsDir,
|
|
139
|
+
agentId,
|
|
140
|
+
output,
|
|
141
|
+
loopControl,
|
|
142
|
+
generationOptions,
|
|
143
|
+
advancedOptions,
|
|
144
|
+
} = params;
|
|
145
|
+
|
|
146
|
+
// Wrap model with middleware if provided
|
|
147
|
+
if (middleware) {
|
|
148
|
+
const middlewares = Array.isArray(middleware)
|
|
149
|
+
? middleware
|
|
150
|
+
: [middleware];
|
|
151
|
+
|
|
152
|
+
this.model = wrapLanguageModel({
|
|
153
|
+
model: model as LanguageModelV3, // Cast required since DeepAgent accepts LanguageModel
|
|
154
|
+
middleware: middlewares,
|
|
155
|
+
}) as LanguageModel;
|
|
156
|
+
} else {
|
|
157
|
+
this.model = model;
|
|
158
|
+
}
|
|
159
|
+
this.maxSteps = maxSteps;
|
|
160
|
+
this.backend =
|
|
161
|
+
backend || ((state: DeepAgentState) => new StateBackend(state));
|
|
162
|
+
this.toolResultEvictionLimit = toolResultEvictionLimit;
|
|
163
|
+
this.enablePromptCaching = enablePromptCaching;
|
|
164
|
+
this.summarizationConfig = summarization;
|
|
165
|
+
this.interruptOn = interruptOn;
|
|
166
|
+
this.checkpointer = checkpointer;
|
|
167
|
+
this.outputConfig = output;
|
|
168
|
+
|
|
169
|
+
// Store AI SDK passthrough options
|
|
170
|
+
this.loopControl = loopControl;
|
|
171
|
+
this.generationOptions = generationOptions;
|
|
172
|
+
this.advancedOptions = advancedOptions;
|
|
173
|
+
|
|
174
|
+
// Load skills - prefer agentId over legacy skillsDir
|
|
175
|
+
if (agentId) {
|
|
176
|
+
// Show deprecation warning if skillsDir is also provided
|
|
177
|
+
if (skillsDir) {
|
|
178
|
+
console.warn(
|
|
179
|
+
'[DeepAgent] agentId parameter takes precedence over skillsDir. ' +
|
|
180
|
+
'skillsDir is deprecated and will be ignored.'
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this.loadSkills({ agentId }).catch(error => {
|
|
185
|
+
console.warn('[DeepAgent] Failed to load skills:', error);
|
|
186
|
+
});
|
|
187
|
+
} else if (skillsDir) {
|
|
188
|
+
// Legacy mode: use skillsDir
|
|
189
|
+
this.loadSkills({ skillsDir }).catch(error => {
|
|
190
|
+
console.warn('[DeepAgent] Failed to load skills:', error);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Check if backend is a sandbox (supports execute)
|
|
195
|
+
// For factory functions, we can't know until runtime, so we check if it's an instance
|
|
196
|
+
this.hasSandboxBackend = typeof backend !== "function" && backend !== undefined && isSandboxBackend(backend);
|
|
197
|
+
|
|
198
|
+
// Determine if we have subagents
|
|
199
|
+
const hasSubagents =
|
|
200
|
+
includeGeneralPurposeAgent || (subagents && subagents.length > 0);
|
|
201
|
+
|
|
202
|
+
this.systemPrompt = buildSystemPrompt(systemPrompt, hasSubagents, this.hasSandboxBackend, this.skillsMetadata);
|
|
203
|
+
|
|
204
|
+
// Store user-provided tools
|
|
205
|
+
this.userTools = tools;
|
|
206
|
+
|
|
207
|
+
// Store subagent options for later use
|
|
208
|
+
this.subagentOptions = {
|
|
209
|
+
defaultModel: model,
|
|
210
|
+
defaultTools: tools,
|
|
211
|
+
subagents,
|
|
212
|
+
includeGeneralPurposeAgent,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Create core tools (todos and filesystem).
|
|
218
|
+
* @private
|
|
219
|
+
*/
|
|
220
|
+
private createCoreTools(state: DeepAgentState, onEvent?: EventCallback): ToolSet {
|
|
221
|
+
const todosTool = createTodosTool(state, onEvent);
|
|
222
|
+
const filesystemTools = createFilesystemTools(state, {
|
|
223
|
+
backend: this.backend,
|
|
224
|
+
onEvent,
|
|
225
|
+
toolResultEvictionLimit: this.toolResultEvictionLimit,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
write_todos: todosTool,
|
|
230
|
+
...filesystemTools,
|
|
231
|
+
...this.userTools,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Create web tools if TAVILY_API_KEY is available.
|
|
237
|
+
* @private
|
|
238
|
+
*/
|
|
239
|
+
private createWebToolSet(state: DeepAgentState, onEvent?: EventCallback): ToolSet {
|
|
240
|
+
const webTools = createWebTools(state, {
|
|
241
|
+
backend: this.backend,
|
|
242
|
+
onEvent,
|
|
243
|
+
toolResultEvictionLimit: this.toolResultEvictionLimit,
|
|
244
|
+
});
|
|
245
|
+
return webTools;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Create execute tool if backend is a sandbox.
|
|
250
|
+
* @private
|
|
251
|
+
*/
|
|
252
|
+
private createExecuteToolSet(onEvent?: EventCallback): ToolSet {
|
|
253
|
+
if (!this.hasSandboxBackend) {
|
|
254
|
+
return {};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const sandboxBackend = this.backend as SandboxBackendProtocol;
|
|
258
|
+
return {
|
|
259
|
+
execute: createExecuteTool({
|
|
260
|
+
backend: sandboxBackend,
|
|
261
|
+
onEvent,
|
|
262
|
+
}),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Create subagent tool if configured.
|
|
268
|
+
* @private
|
|
269
|
+
*/
|
|
270
|
+
private createSubagentToolSet(state: DeepAgentState, onEvent?: EventCallback): ToolSet {
|
|
271
|
+
if (
|
|
272
|
+
!this.subagentOptions.includeGeneralPurposeAgent &&
|
|
273
|
+
(!this.subagentOptions.subagents || this.subagentOptions.subagents.length === 0)
|
|
274
|
+
) {
|
|
275
|
+
return {};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const subagentTool = createSubagentTool(state, {
|
|
279
|
+
defaultModel: this.subagentOptions.defaultModel,
|
|
280
|
+
defaultTools: this.userTools,
|
|
281
|
+
subagents: this.subagentOptions.subagents,
|
|
282
|
+
includeGeneralPurposeAgent: this.subagentOptions.includeGeneralPurposeAgent,
|
|
283
|
+
backend: this.backend,
|
|
284
|
+
onEvent,
|
|
285
|
+
interruptOn: this.interruptOn,
|
|
286
|
+
parentGenerationOptions: this.generationOptions,
|
|
287
|
+
parentAdvancedOptions: this.advancedOptions,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
return { task: subagentTool };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Create all tools for the agent, combining core, web, execute, and subagent tools.
|
|
295
|
+
* @private
|
|
296
|
+
*/
|
|
297
|
+
private createTools(state: DeepAgentState, onEvent?: EventCallback): ToolSet {
|
|
298
|
+
// Start with core tools (todos, filesystem, user tools)
|
|
299
|
+
let allTools = this.createCoreTools(state, onEvent);
|
|
300
|
+
|
|
301
|
+
// Add web tools if available
|
|
302
|
+
const webTools = this.createWebToolSet(state, onEvent);
|
|
303
|
+
if (Object.keys(webTools).length > 0) {
|
|
304
|
+
allTools = { ...allTools, ...webTools };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Add execute tool if sandbox backend
|
|
308
|
+
const executeTools = this.createExecuteToolSet(onEvent);
|
|
309
|
+
if (Object.keys(executeTools).length > 0) {
|
|
310
|
+
allTools = { ...allTools, ...executeTools };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Add subagent tool if configured
|
|
314
|
+
const subagentTools = this.createSubagentToolSet(state, onEvent);
|
|
315
|
+
if (Object.keys(subagentTools).length > 0) {
|
|
316
|
+
allTools = { ...allTools, ...subagentTools };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Apply interruptOn configuration to tools
|
|
320
|
+
allTools = applyInterruptConfig(allTools, this.interruptOn);
|
|
321
|
+
|
|
322
|
+
return allTools;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Build stop conditions with maxSteps safety limit.
|
|
327
|
+
* Combines user-provided stop conditions with the maxSteps limit.
|
|
328
|
+
*/
|
|
329
|
+
private buildStopConditions(maxSteps?: number): StopCondition<any>[] {
|
|
330
|
+
const conditions: StopCondition<any>[] = [];
|
|
331
|
+
|
|
332
|
+
// Always add maxSteps safety limit
|
|
333
|
+
conditions.push(stepCountIs(maxSteps ?? this.maxSteps));
|
|
334
|
+
|
|
335
|
+
// Add user-provided stop conditions
|
|
336
|
+
if (this.loopControl?.stopWhen) {
|
|
337
|
+
if (Array.isArray(this.loopControl.stopWhen)) {
|
|
338
|
+
conditions.push(...this.loopControl.stopWhen);
|
|
339
|
+
} else {
|
|
340
|
+
conditions.push(this.loopControl.stopWhen);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return conditions;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Build agent settings by combining passthrough options with defaults.
|
|
349
|
+
*/
|
|
350
|
+
private buildAgentSettings(onEvent?: EventCallback) {
|
|
351
|
+
const settings: any = {
|
|
352
|
+
model: this.model,
|
|
353
|
+
instructions: this.systemPrompt,
|
|
354
|
+
tools: undefined, // Will be set by caller
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
// Add generation options if provided
|
|
358
|
+
if (this.generationOptions) {
|
|
359
|
+
Object.assign(settings, this.generationOptions);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Add advanced options if provided
|
|
363
|
+
if (this.advancedOptions) {
|
|
364
|
+
Object.assign(settings, this.advancedOptions);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Add composed loop control callbacks if provided
|
|
368
|
+
if (this.loopControl) {
|
|
369
|
+
if (this.loopControl.prepareStep) {
|
|
370
|
+
settings.prepareStep = this.composePrepareStep(this.loopControl.prepareStep);
|
|
371
|
+
}
|
|
372
|
+
if (this.loopControl.onStepFinish) {
|
|
373
|
+
settings.onStepFinish = this.composeOnStepFinish(this.loopControl.onStepFinish);
|
|
374
|
+
}
|
|
375
|
+
if (this.loopControl.onFinish) {
|
|
376
|
+
settings.onFinish = this.composeOnFinish(this.loopControl.onFinish);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Add output configuration if provided using AI SDK Output helper
|
|
381
|
+
if (this.outputConfig) {
|
|
382
|
+
settings.output = Output.object(this.outputConfig);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return settings;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Create a ToolLoopAgent for a given state.
|
|
390
|
+
* @param state - The shared agent state
|
|
391
|
+
* @param maxSteps - Optional max steps override
|
|
392
|
+
* @param onEvent - Optional callback for emitting events
|
|
393
|
+
*/
|
|
394
|
+
private createAgent(state: DeepAgentState, maxSteps?: number, onEvent?: EventCallback) {
|
|
395
|
+
const tools = this.createTools(state, onEvent);
|
|
396
|
+
const settings = this.buildAgentSettings(onEvent);
|
|
397
|
+
const stopConditions = this.buildStopConditions(maxSteps);
|
|
398
|
+
|
|
399
|
+
return new ToolLoopAgent({
|
|
400
|
+
...settings,
|
|
401
|
+
tools,
|
|
402
|
+
stopWhen: stopConditions,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Load skills from directory asynchronously.
|
|
408
|
+
* Supports both legacy skillsDir and new agentId modes.
|
|
409
|
+
*/
|
|
410
|
+
private async loadSkills(options: { skillsDir?: string; agentId?: string }) {
|
|
411
|
+
const { listSkills } = await import("./skills/load");
|
|
412
|
+
|
|
413
|
+
const skills = await listSkills(
|
|
414
|
+
options.agentId
|
|
415
|
+
? { agentId: options.agentId }
|
|
416
|
+
: { projectSkillsDir: options.skillsDir }
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
this.skillsMetadata = skills.map(s => ({
|
|
420
|
+
name: s.name,
|
|
421
|
+
description: s.description,
|
|
422
|
+
path: s.path,
|
|
423
|
+
}));
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Generate a response (non-streaming).
|
|
428
|
+
*/
|
|
429
|
+
async generate(options: { prompt: string; maxSteps?: number }) {
|
|
430
|
+
// Create fresh state for this invocation
|
|
431
|
+
const state: DeepAgentState = {
|
|
432
|
+
todos: [],
|
|
433
|
+
files: {},
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
const agent = this.createAgent(state, options.maxSteps);
|
|
437
|
+
const result = await agent.generate({ prompt: options.prompt });
|
|
438
|
+
|
|
439
|
+
// Return result with state attached
|
|
440
|
+
// Note: We attach state as a property to preserve getters on result
|
|
441
|
+
Object.defineProperty(result, 'state', {
|
|
442
|
+
value: state,
|
|
443
|
+
enumerable: true,
|
|
444
|
+
writable: false,
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
return result as typeof result & { state: DeepAgentState };
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Stream a response.
|
|
452
|
+
*/
|
|
453
|
+
async stream(options: { prompt: string; maxSteps?: number }) {
|
|
454
|
+
// Create fresh state for this invocation
|
|
455
|
+
const state: DeepAgentState = {
|
|
456
|
+
todos: [],
|
|
457
|
+
files: {},
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const agent = this.createAgent(state, options.maxSteps);
|
|
461
|
+
const result = await agent.stream({ prompt: options.prompt });
|
|
462
|
+
|
|
463
|
+
// Return result with state attached
|
|
464
|
+
// Note: We attach state as a property to preserve getters on result
|
|
465
|
+
Object.defineProperty(result, 'state', {
|
|
466
|
+
value: state,
|
|
467
|
+
enumerable: true,
|
|
468
|
+
writable: false,
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
return result as typeof result & { state: DeepAgentState };
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Generate with an existing state (for continuing conversations).
|
|
476
|
+
*/
|
|
477
|
+
async generateWithState(options: {
|
|
478
|
+
prompt: string;
|
|
479
|
+
state: DeepAgentState;
|
|
480
|
+
maxSteps?: number;
|
|
481
|
+
}) {
|
|
482
|
+
const agent = this.createAgent(options.state, options.maxSteps);
|
|
483
|
+
const result = await agent.generate({ prompt: options.prompt });
|
|
484
|
+
|
|
485
|
+
// Return result with state attached
|
|
486
|
+
// Note: We attach state as a property to preserve getters on result
|
|
487
|
+
Object.defineProperty(result, 'state', {
|
|
488
|
+
value: options.state,
|
|
489
|
+
enumerable: true,
|
|
490
|
+
writable: false,
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
return result as typeof result & { state: DeepAgentState };
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Get the underlying ToolLoopAgent for advanced usage.
|
|
498
|
+
* This allows using AI SDK's createAgentUIStream and other utilities.
|
|
499
|
+
*/
|
|
500
|
+
getAgent(state?: DeepAgentState) {
|
|
501
|
+
const agentState = state || { todos: [], files: {} };
|
|
502
|
+
return this.createAgent(agentState);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Stream a response with real-time events.
|
|
507
|
+
* This is an async generator that yields DeepAgentEvent objects.
|
|
508
|
+
*
|
|
509
|
+
* Supports conversation history via the `messages` option for multi-turn conversations.
|
|
510
|
+
*
|
|
511
|
+
* @example
|
|
512
|
+
* ```typescript
|
|
513
|
+
* // Single turn
|
|
514
|
+
* for await (const event of agent.streamWithEvents({ prompt: "..." })) {
|
|
515
|
+
* switch (event.type) {
|
|
516
|
+
* case 'text':
|
|
517
|
+
* process.stdout.write(event.text);
|
|
518
|
+
* break;
|
|
519
|
+
* case 'done':
|
|
520
|
+
* // event.messages contains the updated conversation history
|
|
521
|
+
* console.log('Messages:', event.messages);
|
|
522
|
+
* break;
|
|
523
|
+
* }
|
|
524
|
+
* }
|
|
525
|
+
*
|
|
526
|
+
* // Multi-turn conversation
|
|
527
|
+
* let messages = [];
|
|
528
|
+
* for await (const event of agent.streamWithEvents({ prompt: "Hello", messages })) {
|
|
529
|
+
* if (event.type === 'done') {
|
|
530
|
+
* messages = event.messages; // Save for next turn
|
|
531
|
+
* }
|
|
532
|
+
* }
|
|
533
|
+
* for await (const event of agent.streamWithEvents({ prompt: "Follow up", messages })) {
|
|
534
|
+
* // Agent now has context from previous turn
|
|
535
|
+
* }
|
|
536
|
+
* ```
|
|
537
|
+
*/
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Compose user's onStepFinish callback with DeepAgent's internal checkpointing logic.
|
|
541
|
+
* User callback executes first, errors are caught to prevent breaking checkpointing.
|
|
542
|
+
*/
|
|
543
|
+
private composeOnStepFinish(userOnStepFinish?: ToolLoopAgentSettings['onStepFinish']) {
|
|
544
|
+
return async (params: any) => {
|
|
545
|
+
// Execute user callback first if provided
|
|
546
|
+
if (userOnStepFinish) {
|
|
547
|
+
try {
|
|
548
|
+
await userOnStepFinish(params);
|
|
549
|
+
} catch (error) {
|
|
550
|
+
// Log error but don't let it break DeepAgent's internal logic
|
|
551
|
+
console.error("[DeepAgent] User onStepFinish callback failed:", error);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// TODO: Add DeepAgent's internal checkpointing logic here
|
|
556
|
+
// This will be implemented when we migrate from streamText to ToolLoopAgent
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Compose user's onFinish callback with DeepAgent's internal cleanup logic.
|
|
562
|
+
*/
|
|
563
|
+
private composeOnFinish(userOnFinish?: ToolLoopAgentSettings['onFinish']) {
|
|
564
|
+
return async (params: any) => {
|
|
565
|
+
// Execute user callback first if provided
|
|
566
|
+
if (userOnFinish) {
|
|
567
|
+
try {
|
|
568
|
+
await userOnFinish(params);
|
|
569
|
+
} catch (error) {
|
|
570
|
+
console.error("[DeepAgent] User onFinish callback failed:", error);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// TODO: Add DeepAgent's internal cleanup logic here
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Compose user's prepareStep callback with DeepAgent's internal step preparation.
|
|
580
|
+
* Returns a function typed as `any` to avoid AI SDK's strict toolName inference.
|
|
581
|
+
*/
|
|
582
|
+
private composePrepareStep(userPrepareStep?: PrepareStepFunction): any {
|
|
583
|
+
return async (params: any) => {
|
|
584
|
+
// Execute user callback first if provided
|
|
585
|
+
if (userPrepareStep) {
|
|
586
|
+
try {
|
|
587
|
+
const result = await userPrepareStep(params);
|
|
588
|
+
// Merge user's prepareStep result with DeepAgent's requirements
|
|
589
|
+
return {
|
|
590
|
+
...result,
|
|
591
|
+
// TODO: Add DeepAgent's internal step preparation here
|
|
592
|
+
};
|
|
593
|
+
} catch (error) {
|
|
594
|
+
console.error("[DeepAgent] User prepareStep callback failed:", error);
|
|
595
|
+
return params; // Return original params on error
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return params;
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Build streamText options with callbacks for step tracking and checkpointing.
|
|
605
|
+
*
|
|
606
|
+
* @private
|
|
607
|
+
*/
|
|
608
|
+
private buildStreamTextOptions(
|
|
609
|
+
inputMessages: ModelMessage[],
|
|
610
|
+
tools: ToolSet,
|
|
611
|
+
options: StreamWithEventsOptions,
|
|
612
|
+
state: DeepAgentState,
|
|
613
|
+
baseStep: number,
|
|
614
|
+
pendingInterrupt: InterruptData | undefined,
|
|
615
|
+
eventQueue: DeepAgentEvent[],
|
|
616
|
+
stepNumberRef: { value: number }
|
|
617
|
+
): Parameters<typeof streamText>[0] {
|
|
618
|
+
const { threadId } = options;
|
|
619
|
+
|
|
620
|
+
const streamOptions: Parameters<typeof streamText>[0] = {
|
|
621
|
+
model: this.model,
|
|
622
|
+
messages: inputMessages,
|
|
623
|
+
tools,
|
|
624
|
+
stopWhen: this.buildStopConditions(options.maxSteps),
|
|
625
|
+
abortSignal: options.abortSignal,
|
|
626
|
+
onStepFinish: async ({ toolCalls, toolResults }) => {
|
|
627
|
+
// Call user's onStepFinish first if provided
|
|
628
|
+
if (this.loopControl?.onStepFinish) {
|
|
629
|
+
const composedOnStepFinish = this.composeOnStepFinish(this.loopControl.onStepFinish);
|
|
630
|
+
await composedOnStepFinish({ toolCalls, toolResults });
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Then execute DeepAgent's checkpointing logic
|
|
634
|
+
stepNumberRef.value++;
|
|
635
|
+
const cumulativeStep = baseStep + stepNumberRef.value;
|
|
636
|
+
|
|
637
|
+
// Emit step finish event (relative step number)
|
|
638
|
+
const stepEvent: DeepAgentEvent = {
|
|
639
|
+
type: "step-finish",
|
|
640
|
+
stepNumber: stepNumberRef.value,
|
|
641
|
+
toolCalls: toolCalls.map((tc, i) => ({
|
|
642
|
+
toolName: tc.toolName,
|
|
643
|
+
args: "input" in tc ? tc.input : undefined,
|
|
644
|
+
result: toolResults[i] ? ("output" in toolResults[i] ? toolResults[i].output : undefined) : undefined,
|
|
645
|
+
})),
|
|
646
|
+
};
|
|
647
|
+
eventQueue.push(stepEvent);
|
|
648
|
+
|
|
649
|
+
// Save checkpoint if configured
|
|
650
|
+
if (threadId && this.checkpointer) {
|
|
651
|
+
// Get current messages state - we need to track messages as they're built
|
|
652
|
+
// For now, we'll save with the input messages (will be updated after assistant response)
|
|
653
|
+
const checkpoint: Checkpoint = {
|
|
654
|
+
threadId,
|
|
655
|
+
step: cumulativeStep, // Cumulative step number
|
|
656
|
+
messages: inputMessages, // Current messages before assistant response
|
|
657
|
+
state: { ...state },
|
|
658
|
+
interrupt: pendingInterrupt,
|
|
659
|
+
createdAt: new Date().toISOString(),
|
|
660
|
+
updatedAt: new Date().toISOString(),
|
|
661
|
+
};
|
|
662
|
+
await this.checkpointer.save(checkpoint);
|
|
663
|
+
|
|
664
|
+
eventQueue.push(createCheckpointSavedEvent(threadId, cumulativeStep));
|
|
665
|
+
}
|
|
666
|
+
},
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
// Add generation options if provided
|
|
670
|
+
if (this.generationOptions) {
|
|
671
|
+
Object.assign(streamOptions, this.generationOptions);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Add advanced options if provided
|
|
675
|
+
if (this.advancedOptions) {
|
|
676
|
+
Object.assign(streamOptions, this.advancedOptions);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Add output configuration if provided using AI SDK Output helper
|
|
680
|
+
if (this.outputConfig) {
|
|
681
|
+
streamOptions.output = Output.object(this.outputConfig);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Add composed loop control callbacks if provided
|
|
685
|
+
if (this.loopControl) {
|
|
686
|
+
if (this.loopControl.prepareStep) {
|
|
687
|
+
streamOptions.prepareStep = this.composePrepareStep(this.loopControl.prepareStep);
|
|
688
|
+
}
|
|
689
|
+
if (this.loopControl.onFinish) {
|
|
690
|
+
streamOptions.onFinish = this.composeOnFinish(this.loopControl.onFinish);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Add system prompt with optional caching for Anthropic models
|
|
695
|
+
if (this.enablePromptCaching) {
|
|
696
|
+
// Use messages format with cache control for Anthropic
|
|
697
|
+
streamOptions.messages = [
|
|
698
|
+
{
|
|
699
|
+
role: "system",
|
|
700
|
+
content: this.systemPrompt,
|
|
701
|
+
providerOptions: {
|
|
702
|
+
anthropic: { cacheControl: { type: "ephemeral" } },
|
|
703
|
+
},
|
|
704
|
+
} as ModelMessage,
|
|
705
|
+
...inputMessages,
|
|
706
|
+
];
|
|
707
|
+
} else {
|
|
708
|
+
// Use standard system prompt
|
|
709
|
+
streamOptions.system = this.systemPrompt;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
return streamOptions;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Build message array from options, handling validation and priority logic.
|
|
717
|
+
* Priority: explicit messages > prompt > checkpoint history.
|
|
718
|
+
*
|
|
719
|
+
* @private
|
|
720
|
+
*/
|
|
721
|
+
private async buildMessageArray(
|
|
722
|
+
options: StreamWithEventsOptions,
|
|
723
|
+
patchedHistory: ModelMessage[]
|
|
724
|
+
): Promise<{
|
|
725
|
+
messages: ModelMessage[];
|
|
726
|
+
patchedHistory: ModelMessage[];
|
|
727
|
+
error?: DeepAgentErrorEvent;
|
|
728
|
+
shouldReturnEmpty?: boolean;
|
|
729
|
+
}> {
|
|
730
|
+
const { resume } = options;
|
|
731
|
+
|
|
732
|
+
// Validation: require either prompt, messages, resume, or threadId
|
|
733
|
+
if (!options.prompt && !options.messages && !resume && !options.threadId) {
|
|
734
|
+
return {
|
|
735
|
+
messages: [],
|
|
736
|
+
patchedHistory,
|
|
737
|
+
error: {
|
|
738
|
+
type: "error",
|
|
739
|
+
error: new Error("Either 'prompt', 'messages', 'resume', or 'threadId' is required"),
|
|
740
|
+
},
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Build messages with priority: explicit messages > prompt > checkpoint
|
|
745
|
+
let userMessages: ModelMessage[] = [];
|
|
746
|
+
let shouldUseCheckpointHistory = true;
|
|
747
|
+
|
|
748
|
+
if (options.messages && options.messages.length > 0) {
|
|
749
|
+
// Use explicit messages array (preferred)
|
|
750
|
+
userMessages = options.messages;
|
|
751
|
+
shouldUseCheckpointHistory = false; // Explicit messages replace checkpoint history
|
|
752
|
+
|
|
753
|
+
// Emit deprecation warning for prompt if also provided
|
|
754
|
+
if (options.prompt && process.env.NODE_ENV !== 'production') {
|
|
755
|
+
console.warn('prompt parameter is deprecated when messages are provided, using messages instead');
|
|
756
|
+
}
|
|
757
|
+
} else if (options.messages) {
|
|
758
|
+
// Empty messages array provided - clear checkpoint history and treat as reset
|
|
759
|
+
shouldUseCheckpointHistory = false;
|
|
760
|
+
patchedHistory = []; // Clear checkpoint history
|
|
761
|
+
|
|
762
|
+
// According to priority logic, even empty messages take precedence over prompt
|
|
763
|
+
// This means prompt is ignored even if messages is empty
|
|
764
|
+
if (options.prompt && process.env.NODE_ENV !== 'production') {
|
|
765
|
+
console.warn('prompt parameter is deprecated when empty messages are provided, prompt ignored');
|
|
766
|
+
}
|
|
767
|
+
// Empty messages case will be handled by validation below
|
|
768
|
+
} else if (options.prompt) {
|
|
769
|
+
// Convert prompt to message for backward compatibility
|
|
770
|
+
userMessages = [{ role: "user", content: options.prompt } as ModelMessage];
|
|
771
|
+
|
|
772
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
773
|
+
console.warn('prompt parameter is deprecated, use messages instead');
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
// If neither messages nor prompt provided, use checkpoint history only
|
|
777
|
+
|
|
778
|
+
// Load checkpoint messages if available and not replaced by explicit messages
|
|
779
|
+
if (shouldUseCheckpointHistory && patchedHistory.length > 0) {
|
|
780
|
+
// Patch any dangling tool calls in the history first
|
|
781
|
+
patchedHistory = patchToolCalls(patchedHistory);
|
|
782
|
+
|
|
783
|
+
// Apply summarization if enabled and needed
|
|
784
|
+
if (this.summarizationConfig?.enabled && patchedHistory.length > 0) {
|
|
785
|
+
const summarizationResult = await summarizeIfNeeded(patchedHistory, {
|
|
786
|
+
model: this.summarizationConfig.model || this.model,
|
|
787
|
+
tokenThreshold: this.summarizationConfig.tokenThreshold,
|
|
788
|
+
keepMessages: this.summarizationConfig.keepMessages,
|
|
789
|
+
generationOptions: this.generationOptions,
|
|
790
|
+
advancedOptions: this.advancedOptions,
|
|
791
|
+
});
|
|
792
|
+
patchedHistory = summarizationResult.messages;
|
|
793
|
+
}
|
|
794
|
+
} else if (!shouldUseCheckpointHistory) {
|
|
795
|
+
// Explicit messages replace checkpoint history - clear patchedHistory
|
|
796
|
+
patchedHistory = [];
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Handle empty messages case
|
|
800
|
+
const hasEmptyMessages = options.messages && options.messages.length === 0;
|
|
801
|
+
const hasValidInput = userMessages.length > 0 || patchedHistory.length > 0;
|
|
802
|
+
|
|
803
|
+
// Special case: empty messages with no checkpoint history
|
|
804
|
+
if (hasEmptyMessages && !hasValidInput && !resume) {
|
|
805
|
+
// This is a "no-op" case - return done immediately with empty messages
|
|
806
|
+
return {
|
|
807
|
+
messages: [],
|
|
808
|
+
patchedHistory,
|
|
809
|
+
shouldReturnEmpty: true,
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Check if we have valid input: either user messages or checkpoint history
|
|
814
|
+
if (!hasValidInput && !resume) {
|
|
815
|
+
return {
|
|
816
|
+
messages: [],
|
|
817
|
+
patchedHistory,
|
|
818
|
+
error: {
|
|
819
|
+
type: "error",
|
|
820
|
+
error: new Error("No valid input: provide either non-empty messages, prompt, or threadId with existing checkpoint"),
|
|
821
|
+
},
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const inputMessages: ModelMessage[] = [
|
|
826
|
+
...patchedHistory,
|
|
827
|
+
...userMessages,
|
|
828
|
+
];
|
|
829
|
+
|
|
830
|
+
return { messages: inputMessages, patchedHistory };
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Load checkpoint context if threadId is provided.
|
|
835
|
+
* Handles checkpoint restoration and resume from interrupt.
|
|
836
|
+
*
|
|
837
|
+
* @private
|
|
838
|
+
*/
|
|
839
|
+
private async loadCheckpointContext(
|
|
840
|
+
options: StreamWithEventsOptions
|
|
841
|
+
): Promise<{
|
|
842
|
+
state: DeepAgentState;
|
|
843
|
+
patchedHistory: ModelMessage[];
|
|
844
|
+
currentStep: number;
|
|
845
|
+
pendingInterrupt: InterruptData | undefined;
|
|
846
|
+
checkpointEvent?: CheckpointLoadedEvent;
|
|
847
|
+
}> {
|
|
848
|
+
const { threadId, resume } = options;
|
|
849
|
+
let state: DeepAgentState = options.state || { todos: [], files: {} };
|
|
850
|
+
let patchedHistory: ModelMessage[] = [];
|
|
851
|
+
let currentStep = 0;
|
|
852
|
+
let pendingInterrupt: InterruptData | undefined;
|
|
853
|
+
let checkpointEvent: CheckpointLoadedEvent | undefined;
|
|
854
|
+
|
|
855
|
+
if (threadId && this.checkpointer) {
|
|
856
|
+
const checkpoint = await this.checkpointer.load(threadId);
|
|
857
|
+
if (checkpoint) {
|
|
858
|
+
state = checkpoint.state;
|
|
859
|
+
patchedHistory = checkpoint.messages;
|
|
860
|
+
currentStep = checkpoint.step;
|
|
861
|
+
pendingInterrupt = checkpoint.interrupt;
|
|
862
|
+
|
|
863
|
+
checkpointEvent = createCheckpointLoadedEvent(
|
|
864
|
+
threadId,
|
|
865
|
+
checkpoint.step,
|
|
866
|
+
checkpoint.messages.length
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Handle resume from interrupt
|
|
872
|
+
if (resume && pendingInterrupt) {
|
|
873
|
+
const decision = resume.decisions[0];
|
|
874
|
+
if (decision?.type === 'approve') {
|
|
875
|
+
pendingInterrupt = undefined;
|
|
876
|
+
} else {
|
|
877
|
+
pendingInterrupt = undefined;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
return { state, patchedHistory, currentStep, pendingInterrupt, checkpointEvent };
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
async *streamWithEvents(
|
|
885
|
+
options: StreamWithEventsOptions
|
|
886
|
+
): AsyncGenerator<DeepAgentEvent, void, unknown> {
|
|
887
|
+
const { threadId, resume } = options;
|
|
888
|
+
|
|
889
|
+
// Load checkpoint context (state, history, step tracking)
|
|
890
|
+
const context = await this.loadCheckpointContext(options);
|
|
891
|
+
const { state, currentStep, pendingInterrupt, checkpointEvent } = context;
|
|
892
|
+
let patchedHistory = context.patchedHistory; // Mutable - may be reassigned during message building
|
|
893
|
+
|
|
894
|
+
// Yield checkpoint-loaded event if checkpoint was restored
|
|
895
|
+
if (checkpointEvent) {
|
|
896
|
+
yield checkpointEvent;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// Build message array with validation and priority logic
|
|
900
|
+
const messageResult = await this.buildMessageArray(options, patchedHistory);
|
|
901
|
+
|
|
902
|
+
// Handle error cases
|
|
903
|
+
if (messageResult.error) {
|
|
904
|
+
yield messageResult.error;
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// Handle empty messages no-op case
|
|
909
|
+
if (messageResult.shouldReturnEmpty) {
|
|
910
|
+
yield {
|
|
911
|
+
type: "done",
|
|
912
|
+
text: "",
|
|
913
|
+
messages: [],
|
|
914
|
+
state,
|
|
915
|
+
};
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// Extract results
|
|
920
|
+
const inputMessages = messageResult.messages;
|
|
921
|
+
patchedHistory = messageResult.patchedHistory;
|
|
922
|
+
|
|
923
|
+
// Event queue for collecting events from tool executions
|
|
924
|
+
const eventQueue: DeepAgentEvent[] = [];
|
|
925
|
+
const stepNumberRef = { value: 0 }; // Mutable reference for stepNumber
|
|
926
|
+
const baseStep = currentStep; // Cumulative step from checkpoint
|
|
927
|
+
|
|
928
|
+
// Event callback that tools will use to emit events
|
|
929
|
+
const onEvent: EventCallback = (event) => {
|
|
930
|
+
eventQueue.push(event);
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
// Create tools with event callback
|
|
934
|
+
let tools = this.createTools(state, onEvent);
|
|
935
|
+
|
|
936
|
+
// Wrap tools with approval checking if interruptOn is configured and callback provided
|
|
937
|
+
// This intercepts tool execution and requests approval before running
|
|
938
|
+
const hasInterruptOn = !!this.interruptOn;
|
|
939
|
+
const hasApprovalCallback = !!options.onApprovalRequest;
|
|
940
|
+
|
|
941
|
+
if (hasInterruptOn && hasApprovalCallback) {
|
|
942
|
+
tools = wrapToolsWithApproval(tools, this.interruptOn, options.onApprovalRequest);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
try {
|
|
946
|
+
// Build streamText options with callbacks
|
|
947
|
+
const streamOptions = this.buildStreamTextOptions(
|
|
948
|
+
inputMessages,
|
|
949
|
+
tools,
|
|
950
|
+
options,
|
|
951
|
+
state,
|
|
952
|
+
baseStep,
|
|
953
|
+
pendingInterrupt,
|
|
954
|
+
eventQueue,
|
|
955
|
+
stepNumberRef
|
|
956
|
+
);
|
|
957
|
+
|
|
958
|
+
// Use streamText with messages array for conversation history
|
|
959
|
+
const result = streamText(streamOptions);
|
|
960
|
+
|
|
961
|
+
// Yield step start event
|
|
962
|
+
yield { type: "step-start", stepNumber: 1 };
|
|
963
|
+
|
|
964
|
+
// Stream text chunks
|
|
965
|
+
for await (const chunk of result.textStream) {
|
|
966
|
+
// First, yield any queued events from tool executions
|
|
967
|
+
while (eventQueue.length > 0) {
|
|
968
|
+
const event = eventQueue.shift()!;
|
|
969
|
+
yield event;
|
|
970
|
+
|
|
971
|
+
// If a step finished, yield the next step start
|
|
972
|
+
if (event.type === "step-finish") {
|
|
973
|
+
yield { type: "step-start", stepNumber: event.stepNumber + 1 };
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// Then yield the text chunk
|
|
978
|
+
if (chunk) {
|
|
979
|
+
yield { type: "text", text: chunk };
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// Yield any remaining queued events
|
|
984
|
+
while (eventQueue.length > 0) {
|
|
985
|
+
yield eventQueue.shift()!;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Get the final text
|
|
989
|
+
const finalText = await result.text;
|
|
990
|
+
|
|
991
|
+
// Build updated messages array with assistant response
|
|
992
|
+
// Only include assistant message if there's actual content (avoid empty text blocks)
|
|
993
|
+
const updatedMessages: ModelMessage[] = [
|
|
994
|
+
...inputMessages,
|
|
995
|
+
...(finalText ? [{ role: "assistant", content: finalText } as ModelMessage] : []),
|
|
996
|
+
];
|
|
997
|
+
|
|
998
|
+
// Extract output if present (from ToolLoopAgent's native output parsing)
|
|
999
|
+
const output = 'output' in result ? (result as { output: unknown }).output : undefined;
|
|
1000
|
+
|
|
1001
|
+
// Yield done event with updated messages
|
|
1002
|
+
yield {
|
|
1003
|
+
type: "done",
|
|
1004
|
+
state,
|
|
1005
|
+
text: finalText,
|
|
1006
|
+
messages: updatedMessages,
|
|
1007
|
+
...(output !== undefined ? { output } : {}),
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
// Save final checkpoint after done event
|
|
1011
|
+
if (threadId && this.checkpointer) {
|
|
1012
|
+
const finalCheckpoint: Checkpoint = {
|
|
1013
|
+
threadId,
|
|
1014
|
+
step: baseStep + stepNumberRef.value, // Cumulative step number
|
|
1015
|
+
messages: updatedMessages,
|
|
1016
|
+
state,
|
|
1017
|
+
createdAt: new Date().toISOString(),
|
|
1018
|
+
updatedAt: new Date().toISOString(),
|
|
1019
|
+
};
|
|
1020
|
+
await this.checkpointer.save(finalCheckpoint);
|
|
1021
|
+
|
|
1022
|
+
// Emit checkpoint-saved event for final checkpoint
|
|
1023
|
+
yield createCheckpointSavedEvent(threadId, baseStep + stepNumberRef.value);
|
|
1024
|
+
}
|
|
1025
|
+
} catch (error) {
|
|
1026
|
+
// Yield error event
|
|
1027
|
+
yield {
|
|
1028
|
+
type: "error",
|
|
1029
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* Stream with a simple callback interface.
|
|
1036
|
+
* This is a convenience wrapper around streamWithEvents.
|
|
1037
|
+
*/
|
|
1038
|
+
async streamWithCallback(
|
|
1039
|
+
options: StreamWithEventsOptions,
|
|
1040
|
+
onEvent: EventCallback
|
|
1041
|
+
): Promise<{ state: DeepAgentState; text?: string; messages?: ModelMessage[] }> {
|
|
1042
|
+
let finalState: DeepAgentState = options.state || { todos: [], files: {} };
|
|
1043
|
+
let finalText: string | undefined;
|
|
1044
|
+
let finalMessages: ModelMessage[] | undefined;
|
|
1045
|
+
|
|
1046
|
+
for await (const event of this.streamWithEvents(options)) {
|
|
1047
|
+
onEvent(event);
|
|
1048
|
+
|
|
1049
|
+
if (event.type === "done") {
|
|
1050
|
+
finalState = event.state;
|
|
1051
|
+
finalText = event.text;
|
|
1052
|
+
finalMessages = event.messages;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
return { state: finalState, text: finalText, messages: finalMessages };
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Create a Deep Agent with planning, filesystem, and subagent capabilities.
|
|
1062
|
+
*
|
|
1063
|
+
* @param params - Configuration object for the Deep Agent
|
|
1064
|
+
* @param params.model - **Required.** AI SDK LanguageModel instance (e.g., `anthropic('claude-sonnet-4-20250514')`, `openai('gpt-4o')`)
|
|
1065
|
+
* @param params.systemPrompt - Optional custom system prompt for the agent
|
|
1066
|
+
* @param params.tools - Optional custom tools to add to the agent (AI SDK ToolSet)
|
|
1067
|
+
* @param params.subagents - Optional array of specialized subagent configurations for task delegation
|
|
1068
|
+
* @param params.backend - Optional backend for filesystem operations (default: StateBackend for in-memory storage)
|
|
1069
|
+
* @param params.maxSteps - Optional maximum number of steps for the agent loop (default: 100)
|
|
1070
|
+
* @param params.includeGeneralPurposeAgent - Optional flag to include general-purpose subagent (default: true)
|
|
1071
|
+
* @param params.toolResultEvictionLimit - Optional token limit before evicting large tool results to filesystem (default: disabled)
|
|
1072
|
+
* @param params.enablePromptCaching - Optional flag to enable prompt caching for improved performance (Anthropic only, default: false)
|
|
1073
|
+
* @param params.summarization - Optional summarization configuration for automatic conversation summarization
|
|
1074
|
+
* @returns A configured DeepAgent instance
|
|
1075
|
+
*
|
|
1076
|
+
* @see {@link CreateDeepAgentParams} for detailed parameter types
|
|
1077
|
+
*
|
|
1078
|
+
* @example Basic usage
|
|
1079
|
+
* ```typescript
|
|
1080
|
+
* import { createDeepAgent } from 'deepagentsdk';
|
|
1081
|
+
* import { anthropic } from '@ai-sdk/anthropic';
|
|
1082
|
+
*
|
|
1083
|
+
* const agent = createDeepAgent({
|
|
1084
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
1085
|
+
* systemPrompt: 'You are a research assistant...',
|
|
1086
|
+
* });
|
|
1087
|
+
*
|
|
1088
|
+
* const result = await agent.generate({
|
|
1089
|
+
* prompt: 'Research the topic and write a report',
|
|
1090
|
+
* });
|
|
1091
|
+
* ```
|
|
1092
|
+
*
|
|
1093
|
+
* @example With custom tools
|
|
1094
|
+
* ```typescript
|
|
1095
|
+
* import { tool } from 'ai';
|
|
1096
|
+
* import { z } from 'zod';
|
|
1097
|
+
*
|
|
1098
|
+
* const customTool = tool({
|
|
1099
|
+
* description: 'Get current time',
|
|
1100
|
+
* inputSchema: z.object({}),
|
|
1101
|
+
* execute: async () => new Date().toISOString(),
|
|
1102
|
+
* });
|
|
1103
|
+
*
|
|
1104
|
+
* const agent = createDeepAgent({
|
|
1105
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
1106
|
+
* tools: { get_time: customTool },
|
|
1107
|
+
* });
|
|
1108
|
+
* ```
|
|
1109
|
+
*
|
|
1110
|
+
* @example With subagents
|
|
1111
|
+
* ```typescript
|
|
1112
|
+
* const agent = createDeepAgent({
|
|
1113
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
1114
|
+
* subagents: [{
|
|
1115
|
+
* name: 'research-agent',
|
|
1116
|
+
* description: 'Specialized for research tasks',
|
|
1117
|
+
* systemPrompt: 'You are a research specialist...',
|
|
1118
|
+
* }],
|
|
1119
|
+
* });
|
|
1120
|
+
* ```
|
|
1121
|
+
*
|
|
1122
|
+
* @example With StateBackend (default, explicit)
|
|
1123
|
+
* ```typescript
|
|
1124
|
+
* import { StateBackend } from 'deepagentsdk';
|
|
1125
|
+
*
|
|
1126
|
+
* const state = { todos: [], files: {} };
|
|
1127
|
+
* const agent = createDeepAgent({
|
|
1128
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
1129
|
+
* backend: new StateBackend(state), // Ephemeral in-memory storage
|
|
1130
|
+
* });
|
|
1131
|
+
* ```
|
|
1132
|
+
*
|
|
1133
|
+
* @example With FilesystemBackend
|
|
1134
|
+
* ```typescript
|
|
1135
|
+
* import { FilesystemBackend } from 'deepagentsdk';
|
|
1136
|
+
*
|
|
1137
|
+
* const agent = createDeepAgent({
|
|
1138
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
1139
|
+
* backend: new FilesystemBackend({ rootDir: './workspace' }), // Persist to disk
|
|
1140
|
+
* });
|
|
1141
|
+
* ```
|
|
1142
|
+
*
|
|
1143
|
+
* @example With PersistentBackend
|
|
1144
|
+
* ```typescript
|
|
1145
|
+
* import { PersistentBackend, InMemoryStore } from 'deepagentsdk';
|
|
1146
|
+
*
|
|
1147
|
+
* const store = new InMemoryStore();
|
|
1148
|
+
* const agent = createDeepAgent({
|
|
1149
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
1150
|
+
* backend: new PersistentBackend({ store, namespace: 'project-1' }), // Cross-session persistence
|
|
1151
|
+
* });
|
|
1152
|
+
* ```
|
|
1153
|
+
*
|
|
1154
|
+
* @example With CompositeBackend
|
|
1155
|
+
* ```typescript
|
|
1156
|
+
* import { CompositeBackend, FilesystemBackend, StateBackend } from 'deepagentsdk';
|
|
1157
|
+
*
|
|
1158
|
+
* const state = { todos: [], files: {} };
|
|
1159
|
+
* const agent = createDeepAgent({
|
|
1160
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
1161
|
+
* backend: new CompositeBackend(
|
|
1162
|
+
* new StateBackend(state),
|
|
1163
|
+
* { '/persistent/': new FilesystemBackend({ rootDir: './persistent' }) }
|
|
1164
|
+
* ), // Route files by path prefix
|
|
1165
|
+
* });
|
|
1166
|
+
* ```
|
|
1167
|
+
*
|
|
1168
|
+
* @example With middleware for logging and caching
|
|
1169
|
+
* ```typescript
|
|
1170
|
+
* import { createDeepAgent } from 'deepagentsdk';
|
|
1171
|
+
* import { anthropic } from '@ai-sdk/anthropic';
|
|
1172
|
+
*
|
|
1173
|
+
* const loggingMiddleware = {
|
|
1174
|
+
* wrapGenerate: async ({ doGenerate, params }) => {
|
|
1175
|
+
* console.log('Model called with:', params.prompt);
|
|
1176
|
+
* const result = await doGenerate();
|
|
1177
|
+
* console.log('Model returned:', result.text);
|
|
1178
|
+
* return result;
|
|
1179
|
+
* },
|
|
1180
|
+
* };
|
|
1181
|
+
*
|
|
1182
|
+
* const agent = createDeepAgent({
|
|
1183
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
1184
|
+
* middleware: [loggingMiddleware],
|
|
1185
|
+
* });
|
|
1186
|
+
* ```
|
|
1187
|
+
*
|
|
1188
|
+
* @example With middleware factory for context access
|
|
1189
|
+
* ```typescript
|
|
1190
|
+
* import { FilesystemBackend } from 'deepagentsdk';
|
|
1191
|
+
*
|
|
1192
|
+
* function createContextMiddleware(backend: BackendProtocol) {
|
|
1193
|
+
* return {
|
|
1194
|
+
* wrapGenerate: async ({ doGenerate }) => {
|
|
1195
|
+
* const state = await backend.read('state');
|
|
1196
|
+
* const result = await doGenerate();
|
|
1197
|
+
* await backend.write('state', { ...state, lastCall: result });
|
|
1198
|
+
* return result;
|
|
1199
|
+
* },
|
|
1200
|
+
* };
|
|
1201
|
+
* }
|
|
1202
|
+
*
|
|
1203
|
+
* const backend = new FilesystemBackend({ rootDir: './workspace' });
|
|
1204
|
+
* const agent = createDeepAgent({
|
|
1205
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
1206
|
+
* backend,
|
|
1207
|
+
* middleware: createContextMiddleware(backend),
|
|
1208
|
+
* });
|
|
1209
|
+
* ```
|
|
1210
|
+
*
|
|
1211
|
+
* @example With performance optimizations
|
|
1212
|
+
* ```typescript
|
|
1213
|
+
* const agent = createDeepAgent({
|
|
1214
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
1215
|
+
* enablePromptCaching: true,
|
|
1216
|
+
* toolResultEvictionLimit: 20000,
|
|
1217
|
+
* summarization: {
|
|
1218
|
+
* enabled: true,
|
|
1219
|
+
* tokenThreshold: 170000,
|
|
1220
|
+
* keepMessages: 6,
|
|
1221
|
+
* },
|
|
1222
|
+
* });
|
|
1223
|
+
* ```
|
|
1224
|
+
*/
|
|
1225
|
+
export function createDeepAgent(params: CreateDeepAgentParams): DeepAgent {
|
|
1226
|
+
return new DeepAgent(params);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// Re-export useful AI SDK v6 primitives
|
|
1230
|
+
export { ToolLoopAgent, stepCountIs, hasToolCall } from "ai";
|