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.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/package.json +95 -0
  4. package/src/agent.ts +1230 -0
  5. package/src/backends/composite.ts +273 -0
  6. package/src/backends/filesystem.ts +692 -0
  7. package/src/backends/index.ts +22 -0
  8. package/src/backends/local-sandbox.ts +175 -0
  9. package/src/backends/persistent.ts +593 -0
  10. package/src/backends/sandbox.ts +510 -0
  11. package/src/backends/state.ts +244 -0
  12. package/src/backends/utils.ts +287 -0
  13. package/src/checkpointer/file-saver.ts +98 -0
  14. package/src/checkpointer/index.ts +5 -0
  15. package/src/checkpointer/kv-saver.ts +82 -0
  16. package/src/checkpointer/memory-saver.ts +82 -0
  17. package/src/checkpointer/types.ts +125 -0
  18. package/src/cli/components/ApiKeyInput.tsx +300 -0
  19. package/src/cli/components/FilePreview.tsx +237 -0
  20. package/src/cli/components/Input.tsx +277 -0
  21. package/src/cli/components/Message.tsx +93 -0
  22. package/src/cli/components/ModelSelection.tsx +338 -0
  23. package/src/cli/components/SlashMenu.tsx +101 -0
  24. package/src/cli/components/StatusBar.tsx +89 -0
  25. package/src/cli/components/Subagent.tsx +91 -0
  26. package/src/cli/components/TodoList.tsx +133 -0
  27. package/src/cli/components/ToolApproval.tsx +70 -0
  28. package/src/cli/components/ToolCall.tsx +144 -0
  29. package/src/cli/components/ToolCallSummary.tsx +175 -0
  30. package/src/cli/components/Welcome.tsx +75 -0
  31. package/src/cli/components/index.ts +24 -0
  32. package/src/cli/hooks/index.ts +12 -0
  33. package/src/cli/hooks/useAgent.ts +933 -0
  34. package/src/cli/index.tsx +1066 -0
  35. package/src/cli/theme.ts +205 -0
  36. package/src/cli/utils/model-list.ts +365 -0
  37. package/src/constants/errors.ts +29 -0
  38. package/src/constants/limits.ts +195 -0
  39. package/src/index.ts +176 -0
  40. package/src/middleware/agent-memory.ts +330 -0
  41. package/src/prompts.ts +196 -0
  42. package/src/skills/index.ts +2 -0
  43. package/src/skills/load.ts +191 -0
  44. package/src/skills/types.ts +53 -0
  45. package/src/tools/execute.ts +167 -0
  46. package/src/tools/filesystem.ts +418 -0
  47. package/src/tools/index.ts +39 -0
  48. package/src/tools/subagent.ts +443 -0
  49. package/src/tools/todos.ts +101 -0
  50. package/src/tools/web.ts +567 -0
  51. package/src/types/backend.ts +177 -0
  52. package/src/types/core.ts +220 -0
  53. package/src/types/events.ts +429 -0
  54. package/src/types/index.ts +94 -0
  55. package/src/types/structured-output.ts +43 -0
  56. package/src/types/subagent.ts +96 -0
  57. package/src/types.ts +22 -0
  58. package/src/utils/approval.ts +213 -0
  59. package/src/utils/events.ts +416 -0
  60. package/src/utils/eviction.ts +181 -0
  61. package/src/utils/index.ts +34 -0
  62. package/src/utils/model-parser.ts +38 -0
  63. package/src/utils/patch-tool-calls.ts +233 -0
  64. package/src/utils/project-detection.ts +32 -0
  65. package/src/utils/summarization.ts +254 -0
@@ -0,0 +1,443 @@
1
+ /**
2
+ * Subagent tool for task delegation using AI SDK v6 ToolLoopAgent.
3
+ */
4
+
5
+ import { tool, ToolLoopAgent, stepCountIs, Output, type ToolSet, type LanguageModel } from "ai";
6
+ import { z } from "zod";
7
+ import type {
8
+ SubAgent,
9
+ DeepAgentState,
10
+ BackendProtocol,
11
+ BackendFactory,
12
+ EventCallback,
13
+ InterruptOnConfig,
14
+ CreateDeepAgentParams,
15
+ BuiltinToolCreator,
16
+ SubagentToolConfig,
17
+ } from "../types";
18
+ import { applyInterruptConfig } from "../utils/approval";
19
+ import {
20
+ DEFAULT_SUBAGENT_MAX_STEPS,
21
+ DEFAULT_TIMEOUT_SECONDS,
22
+ } from "../constants/limits";
23
+ import {
24
+ getTaskToolDescription,
25
+ DEFAULT_GENERAL_PURPOSE_DESCRIPTION,
26
+ DEFAULT_SUBAGENT_PROMPT,
27
+ TODO_SYSTEM_PROMPT,
28
+ FILESYSTEM_SYSTEM_PROMPT,
29
+ BASE_PROMPT,
30
+ } from "../prompts";
31
+ import {
32
+ createSubagentStartEvent,
33
+ createSubagentStepEvent,
34
+ createSubagentFinishEvent,
35
+ } from "../utils/events";
36
+ import { createTodosTool } from "./todos";
37
+ import { createFilesystemTools } from "./filesystem";
38
+ import {
39
+ createWebSearchTool,
40
+ createHttpRequestTool,
41
+ createFetchUrlTool,
42
+ } from "./web";
43
+ import {
44
+ createLsTool,
45
+ createReadFileTool,
46
+ createWriteFileTool,
47
+ createEditFileTool,
48
+ createGlobTool,
49
+ createGrepTool,
50
+ } from "./filesystem";
51
+ import { createExecuteTool } from "./execute";
52
+
53
+ // ============================================================================
54
+ // Helper Functions for Builtin Tool Instantiation
55
+ // ============================================================================
56
+
57
+ /**
58
+ * Check if a value is a builtin tool creator function.
59
+ */
60
+ function isBuiltinToolCreator(value: any): value is BuiltinToolCreator {
61
+ return typeof value === "function" && (
62
+ value === createWebSearchTool ||
63
+ value === createHttpRequestTool ||
64
+ value === createFetchUrlTool ||
65
+ value === createLsTool ||
66
+ value === createReadFileTool ||
67
+ value === createWriteFileTool ||
68
+ value === createEditFileTool ||
69
+ value === createGlobTool ||
70
+ value === createGrepTool ||
71
+ value === createTodosTool ||
72
+ value === createExecuteTool
73
+ );
74
+ }
75
+
76
+ /**
77
+ * Instantiate a builtin tool creator with the given context.
78
+ */
79
+ function instantiateBuiltinTool(
80
+ creator: BuiltinToolCreator,
81
+ state: DeepAgentState,
82
+ options: {
83
+ backend?: BackendProtocol | BackendFactory;
84
+ onEvent?: EventCallback;
85
+ toolResultEvictionLimit?: number;
86
+ }
87
+ ): ToolSet {
88
+ const { backend, onEvent, toolResultEvictionLimit } = options;
89
+
90
+ // Web tools - require API key and timeout defaults
91
+ const tavilyApiKey = process.env.TAVILY_API_KEY || "";
92
+ const defaultTimeout = DEFAULT_TIMEOUT_SECONDS;
93
+
94
+ if (creator === createWebSearchTool) {
95
+ if (!tavilyApiKey) {
96
+ console.warn("web_search tool requested but TAVILY_API_KEY not set");
97
+ return {};
98
+ }
99
+ return {
100
+ web_search: createWebSearchTool(state, { backend, onEvent, toolResultEvictionLimit, tavilyApiKey }),
101
+ };
102
+ }
103
+ if (creator === createHttpRequestTool) {
104
+ return {
105
+ http_request: createHttpRequestTool(state, { backend, onEvent, toolResultEvictionLimit, defaultTimeout }),
106
+ };
107
+ }
108
+ if (creator === createFetchUrlTool) {
109
+ return {
110
+ fetch_url: createFetchUrlTool(state, { backend, onEvent, toolResultEvictionLimit, defaultTimeout }),
111
+ };
112
+ }
113
+
114
+ // Filesystem tools
115
+ if (creator === createLsTool) {
116
+ return {
117
+ ls: createLsTool(state, backend!, onEvent),
118
+ };
119
+ }
120
+ if (creator === createReadFileTool) {
121
+ return {
122
+ read_file: createReadFileTool(state, backend!, toolResultEvictionLimit, onEvent),
123
+ };
124
+ }
125
+ if (creator === createWriteFileTool) {
126
+ return {
127
+ write_file: createWriteFileTool(state, backend!, onEvent),
128
+ };
129
+ }
130
+ if (creator === createEditFileTool) {
131
+ return {
132
+ edit_file: createEditFileTool(state, backend!, onEvent),
133
+ };
134
+ }
135
+ if (creator === createGlobTool) {
136
+ return {
137
+ glob: createGlobTool(state, backend!, onEvent),
138
+ };
139
+ }
140
+ if (creator === createGrepTool) {
141
+ return {
142
+ grep: createGrepTool(state, backend!, toolResultEvictionLimit, onEvent),
143
+ };
144
+ }
145
+
146
+ // Utility tools
147
+ if (creator === createTodosTool) {
148
+ return {
149
+ write_todos: createTodosTool(state, onEvent),
150
+ };
151
+ }
152
+ if (creator === createExecuteTool) {
153
+ // Execute tool requires special handling - needs a sandbox backend
154
+ throw new Error("execute tool cannot be used via selective tools - it requires a SandboxBackendProtocol");
155
+ }
156
+
157
+ throw new Error(`Unknown builtin tool creator: ${creator}`);
158
+ }
159
+
160
+ /**
161
+ * Process subagent tool configuration (array or ToolSet) into a ToolSet.
162
+ */
163
+ function processSubagentTools(
164
+ toolConfig: ToolSet | SubagentToolConfig[] | undefined,
165
+ state: DeepAgentState,
166
+ options: {
167
+ backend?: BackendProtocol | BackendFactory;
168
+ onEvent?: EventCallback;
169
+ toolResultEvictionLimit?: number;
170
+ }
171
+ ): ToolSet {
172
+ if (!toolConfig) {
173
+ return {};
174
+ }
175
+
176
+ // If it's already a ToolSet object, return as-is
177
+ if (!Array.isArray(toolConfig)) {
178
+ return toolConfig;
179
+ }
180
+
181
+ // Process array of SubagentToolConfig items
182
+ let result: ToolSet = {};
183
+ for (const item of toolConfig) {
184
+ if (isBuiltinToolCreator(item)) {
185
+ // Instantiate builtin tool creator
186
+ const instantiated = instantiateBuiltinTool(item, state, options);
187
+ result = { ...result, ...instantiated };
188
+ } else if (typeof item === "object" && item !== null) {
189
+ // Assume it's a ToolSet object
190
+ result = { ...result, ...item };
191
+ }
192
+ // Silently skip invalid items
193
+ }
194
+
195
+ return result;
196
+ }
197
+
198
+ /**
199
+ * Options for creating the subagent tool.
200
+ */
201
+ export interface CreateSubagentToolOptions {
202
+ /** Default model for subagents (AI SDK LanguageModel instance) */
203
+ defaultModel: LanguageModel;
204
+ /** Default tools available to all subagents */
205
+ defaultTools?: ToolSet;
206
+ /** List of custom subagent specifications */
207
+ subagents?: SubAgent[];
208
+ /** Whether to include the general-purpose agent */
209
+ includeGeneralPurposeAgent?: boolean;
210
+ /** Backend for filesystem operations */
211
+ backend?: BackendProtocol | BackendFactory;
212
+ /** Custom description for the task tool */
213
+ taskDescription?: string | null;
214
+ /** Optional callback for emitting events */
215
+ onEvent?: EventCallback;
216
+ /** Interrupt config to pass to subagents */
217
+ interruptOn?: InterruptOnConfig;
218
+ /** Parent agent options to pass through to subagents */
219
+ parentGenerationOptions?: CreateDeepAgentParams["generationOptions"];
220
+ parentAdvancedOptions?: CreateDeepAgentParams["advancedOptions"];
221
+ }
222
+
223
+ /**
224
+ * Build the system prompt for a subagent.
225
+ */
226
+ function buildSubagentSystemPrompt(customPrompt: string): string {
227
+ return `${customPrompt}
228
+
229
+ ${BASE_PROMPT}
230
+
231
+ ${TODO_SYSTEM_PROMPT}
232
+
233
+ ${FILESYSTEM_SYSTEM_PROMPT}`;
234
+ }
235
+
236
+ /**
237
+ * Create the task tool for spawning subagents using ToolLoopAgent.
238
+ */
239
+ export function createSubagentTool(
240
+ state: DeepAgentState,
241
+ options: CreateSubagentToolOptions
242
+ ) {
243
+ const {
244
+ defaultModel,
245
+ defaultTools = {},
246
+ subagents = [],
247
+ includeGeneralPurposeAgent = true,
248
+ backend,
249
+ taskDescription = null,
250
+ onEvent,
251
+ interruptOn,
252
+ parentGenerationOptions,
253
+ parentAdvancedOptions,
254
+ } = options;
255
+
256
+ // Build subagent registry (store raw tool config, process during execution)
257
+ const subagentRegistry: Record<
258
+ string,
259
+ {
260
+ systemPrompt: string;
261
+ toolConfig: ToolSet | SubagentToolConfig[] | undefined;
262
+ model: LanguageModel;
263
+ output?: { schema: z.ZodType<any>; description?: string };
264
+ }
265
+ > = {};
266
+ const subagentDescriptions: string[] = [];
267
+
268
+ // Add general-purpose agent if enabled
269
+ if (includeGeneralPurposeAgent) {
270
+ subagentRegistry["general-purpose"] = {
271
+ systemPrompt: buildSubagentSystemPrompt(DEFAULT_SUBAGENT_PROMPT),
272
+ toolConfig: defaultTools,
273
+ model: defaultModel,
274
+ };
275
+ subagentDescriptions.push(
276
+ `- general-purpose: ${DEFAULT_GENERAL_PURPOSE_DESCRIPTION}`
277
+ );
278
+ }
279
+
280
+ // Add custom subagents (store raw tool config)
281
+ for (const subagent of subagents) {
282
+ subagentRegistry[subagent.name] = {
283
+ systemPrompt: buildSubagentSystemPrompt(subagent.systemPrompt),
284
+ toolConfig: subagent.tools || defaultTools,
285
+ model: subagent.model || defaultModel,
286
+ output: subagent.output,
287
+ };
288
+ subagentDescriptions.push(`- ${subagent.name}: ${subagent.description}`);
289
+ }
290
+
291
+ const finalTaskDescription =
292
+ taskDescription || getTaskToolDescription(subagentDescriptions);
293
+
294
+ return tool({
295
+ description: finalTaskDescription,
296
+ inputSchema: z.object({
297
+ description: z
298
+ .string()
299
+ .describe("The task to execute with the selected agent"),
300
+ subagent_type: z
301
+ .string()
302
+ .describe(
303
+ `Name of the agent to use. Available: ${Object.keys(subagentRegistry).join(", ")}`
304
+ ),
305
+ }),
306
+ execute: async ({ description, subagent_type }) => {
307
+ // Validate subagent type
308
+ if (!(subagent_type in subagentRegistry)) {
309
+ const allowedTypes = Object.keys(subagentRegistry)
310
+ .map((k) => `\`${k}\``)
311
+ .join(", ");
312
+ return `Error: invoked agent of type ${subagent_type}, the only allowed types are ${allowedTypes}`;
313
+ }
314
+
315
+ const subagentConfig = subagentRegistry[subagent_type]!;
316
+
317
+ // Find the subagent spec to get its specific options
318
+ const subagentSpec = subagents.find((sa) => sa.name === subagent_type);
319
+ const subagentInterruptOn = subagentSpec?.interruptOn ?? interruptOn;
320
+
321
+ // Merge options: subagent-specific options override parent options
322
+ const mergedGenerationOptions = {
323
+ ...parentGenerationOptions,
324
+ ...subagentSpec?.generationOptions,
325
+ };
326
+
327
+ const mergedAdvancedOptions = {
328
+ ...parentAdvancedOptions,
329
+ ...subagentSpec?.advancedOptions,
330
+ };
331
+
332
+ // Emit subagent start event
333
+ if (onEvent) {
334
+ onEvent(createSubagentStartEvent(subagent_type, description));
335
+ }
336
+
337
+ // Create a fresh state for the subagent (shares files but have own todos)
338
+ const subagentState: DeepAgentState = {
339
+ todos: [],
340
+ files: state.files, // Share files with parent
341
+ };
342
+
343
+ // Process subagent tool configuration (handles both arrays and ToolSet objects)
344
+ const customTools = processSubagentTools(
345
+ subagentConfig.toolConfig,
346
+ subagentState,
347
+ { backend, onEvent }
348
+ );
349
+
350
+ // Build default tools (todos + filesystem) that all subagents get
351
+ const todosTool = createTodosTool(subagentState, onEvent);
352
+ const filesystemTools = createFilesystemTools(subagentState, backend, onEvent);
353
+
354
+ // Combine default tools with custom tools
355
+ // Custom tools come last so they can override defaults if needed
356
+ let allTools: ToolSet = {
357
+ write_todos: todosTool,
358
+ ...filesystemTools,
359
+ ...customTools,
360
+ };
361
+
362
+ // Apply interruptOn config - use subagent's own config if provided, otherwise parent's
363
+ allTools = applyInterruptConfig(allTools, subagentInterruptOn);
364
+
365
+ try {
366
+ // Create and run a ToolLoopAgent for the subagent
367
+ const subagentSettings: any = {
368
+ model: subagentConfig.model,
369
+ instructions: subagentConfig.systemPrompt,
370
+ tools: allTools,
371
+ stopWhen: stepCountIs(DEFAULT_SUBAGENT_MAX_STEPS), // Enforce max steps limit for subagents
372
+ // Pass output configuration if subagent has one using AI SDK Output helper
373
+ ...(subagentConfig.output ? { output: Output.object(subagentConfig.output) } : {}),
374
+ };
375
+
376
+ // Add merged generation options
377
+ if (Object.keys(mergedGenerationOptions).length > 0) {
378
+ Object.assign(subagentSettings, mergedGenerationOptions);
379
+ }
380
+
381
+ // Add merged advanced options (excluding toolChoice and activeTools as per plan)
382
+ if (mergedAdvancedOptions) {
383
+ const { toolChoice, activeTools, ...safeAdvancedOptions } = mergedAdvancedOptions;
384
+ Object.assign(subagentSettings, safeAdvancedOptions);
385
+ }
386
+
387
+ // Track subagent step count for events
388
+ let subagentStepCount = 0;
389
+
390
+ // Add onStepFinish callback to settings to capture steps
391
+ subagentSettings.onStepFinish = async ({ toolCalls, toolResults }: { toolCalls: any[]; toolResults: any[] }) => {
392
+ // Emit subagent step event with tool calls
393
+ if (onEvent && toolCalls && toolCalls.length > 0) {
394
+ // Map tool calls with their results
395
+ const toolCallsWithResults = toolCalls.map((tc: any, index: number) => ({
396
+ toolName: tc.toolName,
397
+ args: tc.args,
398
+ result: toolResults[index],
399
+ }));
400
+
401
+ onEvent(createSubagentStepEvent(subagentStepCount++, toolCallsWithResults));
402
+ }
403
+ };
404
+
405
+ const subagentAgent = new ToolLoopAgent(subagentSettings);
406
+
407
+ const result = await subagentAgent.generate({
408
+ prompt: description,
409
+ });
410
+
411
+ // Merge any file changes back to parent state
412
+ state.files = { ...state.files, ...subagentState.files };
413
+
414
+ const resultText = result.text || "Task completed successfully.";
415
+
416
+ // Format output for parent agent
417
+ let formattedResult = resultText;
418
+
419
+ // If subagent has structured output, include it in the response
420
+ if (subagentConfig.output && 'output' in result && result.output) {
421
+ formattedResult = `${resultText}\n\n[Structured Output]\n${JSON.stringify(result.output, null, 2)}`;
422
+ }
423
+
424
+ // Emit subagent finish event
425
+ if (onEvent) {
426
+ onEvent(createSubagentFinishEvent(subagent_type, formattedResult));
427
+ }
428
+
429
+ return formattedResult;
430
+ } catch (error: unknown) {
431
+ const err = error as Error;
432
+ const errorMessage = `Error executing subagent: ${err.message}`;
433
+
434
+ // Emit subagent finish event with error
435
+ if (onEvent) {
436
+ onEvent(createSubagentFinishEvent(subagent_type, errorMessage));
437
+ }
438
+
439
+ return errorMessage;
440
+ }
441
+ },
442
+ });
443
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Todo list tool for task planning and tracking.
3
+ */
4
+
5
+ import { tool } from "ai";
6
+ import { z } from "zod";
7
+ import type { DeepAgentState, TodoItem, EventCallback } from "../types";
8
+ import { createTodosChangedEvent } from "../utils/events";
9
+
10
+ const TodoItemSchema = z.object({
11
+ id: z.string().describe("Unique identifier for the todo item"),
12
+ content: z
13
+ .string()
14
+ .max(100)
15
+ .describe("The description/content of the todo item (max 100 chars)"),
16
+ status: z
17
+ .enum(["pending", "in_progress", "completed", "cancelled"])
18
+ .describe("The current status of the todo item"),
19
+ });
20
+
21
+ /**
22
+ * Create the write_todos tool for task planning.
23
+ * @param state - The shared agent state
24
+ * @param onEvent - Optional callback for emitting events
25
+ */
26
+ export function createTodosTool(state: DeepAgentState, onEvent?: EventCallback) {
27
+ return tool({
28
+ description: `Manage and plan tasks using a structured todo list. Use this tool for:
29
+ - Complex multi-step tasks (3+ steps)
30
+ - After receiving new instructions - capture requirements
31
+ - When starting tasks - mark as in_progress (only one at a time)
32
+ - After completing tasks - mark complete immediately
33
+
34
+ Task states: pending, in_progress, completed, cancelled
35
+
36
+ When merge=true, updates are merged with existing todos by id.
37
+ When merge=false, the new todos replace all existing todos.`,
38
+ inputSchema: z.object({
39
+ todos: z
40
+ .array(TodoItemSchema)
41
+ .min(1)
42
+ .describe("Array of todo items to write"),
43
+ merge: z
44
+ .boolean()
45
+ .default(true)
46
+ .describe(
47
+ "Whether to merge with existing todos (true) or replace all (false)"
48
+ ),
49
+ }),
50
+ execute: async ({ todos, merge }) => {
51
+ if (merge) {
52
+ // Merge by id
53
+ const existingMap = new Map<string, TodoItem>();
54
+ for (const todo of state.todos) {
55
+ existingMap.set(todo.id, todo);
56
+ }
57
+
58
+ for (const newTodo of todos) {
59
+ const existing = existingMap.get(newTodo.id);
60
+ if (existing) {
61
+ // Update existing todo
62
+ existingMap.set(newTodo.id, {
63
+ ...existing,
64
+ ...newTodo,
65
+ });
66
+ } else {
67
+ // Add new todo
68
+ existingMap.set(newTodo.id, newTodo);
69
+ }
70
+ }
71
+
72
+ state.todos = Array.from(existingMap.values());
73
+ } else {
74
+ // Replace all
75
+ state.todos = todos;
76
+ }
77
+
78
+ // Emit event if callback provided
79
+ if (onEvent) {
80
+ onEvent(createTodosChangedEvent([...state.todos]));
81
+ }
82
+
83
+ // Format current todo list for response
84
+ const todoList = state.todos
85
+ .map((t) => `- [${t.status}] ${t.id}: ${t.content}`)
86
+ .join("\n");
87
+
88
+ return `Todo list updated successfully.\n\nCurrent todos:\n${todoList}`;
89
+ },
90
+ });
91
+ }
92
+
93
+ // ============================================================================
94
+ // Individual Tool Reference
95
+ // ============================================================================
96
+
97
+ /**
98
+ * Individual builtin tool reference for selective subagent configuration.
99
+ * This is a reference to the creator function, not an instance.
100
+ */
101
+ export const write_todos = createTodosTool;