deepagentsdk 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/adapters/elements/index.cjs +324 -0
  2. package/dist/adapters/elements/index.cjs.map +1 -0
  3. package/dist/adapters/elements/index.d.cts +212 -0
  4. package/dist/adapters/elements/index.d.mts +212 -0
  5. package/dist/adapters/elements/index.mjs +320 -0
  6. package/dist/adapters/elements/index.mjs.map +1 -0
  7. package/dist/agent-CrH-He58.mjs +2974 -0
  8. package/dist/agent-CrH-He58.mjs.map +1 -0
  9. package/dist/agent-Cuks-Idh.cjs +3396 -0
  10. package/dist/agent-Cuks-Idh.cjs.map +1 -0
  11. package/dist/chunk-CbDLau6x.cjs +34 -0
  12. package/dist/cli/index.cjs +3162 -0
  13. package/dist/cli/index.cjs.map +1 -0
  14. package/dist/cli/index.d.cts +1 -0
  15. package/dist/cli/index.d.mts +1 -0
  16. package/dist/cli/index.mjs +3120 -0
  17. package/dist/cli/index.mjs.map +1 -0
  18. package/dist/file-saver-BJCqMIb5.mjs +655 -0
  19. package/dist/file-saver-BJCqMIb5.mjs.map +1 -0
  20. package/dist/file-saver-C6O2LAvg.cjs +679 -0
  21. package/dist/file-saver-C6O2LAvg.cjs.map +1 -0
  22. package/dist/index.cjs +1471 -0
  23. package/dist/index.cjs.map +1 -0
  24. package/dist/index.d.cts +1581 -0
  25. package/dist/index.d.mts +1581 -0
  26. package/dist/index.mjs +1371 -0
  27. package/dist/index.mjs.map +1 -0
  28. package/dist/load-79a2H4m0.cjs +163 -0
  29. package/dist/load-79a2H4m0.cjs.map +1 -0
  30. package/dist/load-94gjHorc.mjs +3 -0
  31. package/dist/load-B6CA5js_.mjs +142 -0
  32. package/dist/load-B6CA5js_.mjs.map +1 -0
  33. package/dist/load-C2qVmZMp.cjs +3 -0
  34. package/dist/types-4g9UvXal.d.mts +1151 -0
  35. package/dist/types-IulnvhFg.d.cts +1151 -0
  36. package/package.json +26 -12
  37. package/src/adapters/elements/index.ts +0 -27
  38. package/src/adapters/elements/messageAdapter.ts +0 -165
  39. package/src/adapters/elements/statusAdapter.ts +0 -39
  40. package/src/adapters/elements/types.ts +0 -97
  41. package/src/adapters/elements/useElementsAdapter.ts +0 -261
  42. package/src/agent.ts +0 -1258
  43. package/src/backends/composite.ts +0 -273
  44. package/src/backends/filesystem.ts +0 -692
  45. package/src/backends/index.ts +0 -22
  46. package/src/backends/local-sandbox.ts +0 -175
  47. package/src/backends/persistent.ts +0 -593
  48. package/src/backends/sandbox.ts +0 -510
  49. package/src/backends/state.ts +0 -244
  50. package/src/backends/utils.ts +0 -287
  51. package/src/checkpointer/file-saver.ts +0 -98
  52. package/src/checkpointer/index.ts +0 -5
  53. package/src/checkpointer/kv-saver.ts +0 -82
  54. package/src/checkpointer/memory-saver.ts +0 -82
  55. package/src/checkpointer/types.ts +0 -125
  56. package/src/cli/components/ApiKeyInput.tsx +0 -300
  57. package/src/cli/components/FilePreview.tsx +0 -237
  58. package/src/cli/components/Input.tsx +0 -277
  59. package/src/cli/components/Message.tsx +0 -93
  60. package/src/cli/components/ModelSelection.tsx +0 -338
  61. package/src/cli/components/SlashMenu.tsx +0 -101
  62. package/src/cli/components/StatusBar.tsx +0 -89
  63. package/src/cli/components/Subagent.tsx +0 -91
  64. package/src/cli/components/TodoList.tsx +0 -133
  65. package/src/cli/components/ToolApproval.tsx +0 -70
  66. package/src/cli/components/ToolCall.tsx +0 -144
  67. package/src/cli/components/ToolCallSummary.tsx +0 -175
  68. package/src/cli/components/Welcome.tsx +0 -75
  69. package/src/cli/components/index.ts +0 -24
  70. package/src/cli/hooks/index.ts +0 -12
  71. package/src/cli/hooks/useAgent.ts +0 -933
  72. package/src/cli/index.tsx +0 -1066
  73. package/src/cli/theme.ts +0 -205
  74. package/src/cli/utils/model-list.ts +0 -365
  75. package/src/constants/errors.ts +0 -29
  76. package/src/constants/limits.ts +0 -195
  77. package/src/index.ts +0 -176
  78. package/src/middleware/agent-memory.ts +0 -330
  79. package/src/prompts.ts +0 -196
  80. package/src/skills/index.ts +0 -2
  81. package/src/skills/load.ts +0 -191
  82. package/src/skills/types.ts +0 -53
  83. package/src/tools/execute.ts +0 -167
  84. package/src/tools/filesystem.ts +0 -418
  85. package/src/tools/index.ts +0 -39
  86. package/src/tools/subagent.ts +0 -443
  87. package/src/tools/todos.ts +0 -101
  88. package/src/tools/web.ts +0 -567
  89. package/src/types/backend.ts +0 -177
  90. package/src/types/core.ts +0 -220
  91. package/src/types/events.ts +0 -430
  92. package/src/types/index.ts +0 -94
  93. package/src/types/structured-output.ts +0 -43
  94. package/src/types/subagent.ts +0 -96
  95. package/src/types.ts +0 -22
  96. package/src/utils/approval.ts +0 -213
  97. package/src/utils/events.ts +0 -416
  98. package/src/utils/eviction.ts +0 -181
  99. package/src/utils/index.ts +0 -34
  100. package/src/utils/model-parser.ts +0 -38
  101. package/src/utils/patch-tool-calls.ts +0 -233
  102. package/src/utils/project-detection.ts +0 -32
  103. package/src/utils/summarization.ts +0 -254
@@ -0,0 +1,2974 @@
1
+ import { Output, ToolLoopAgent, generateText, stepCountIs, streamText, tool, wrapLanguageModel } from "ai";
2
+ import { z } from "zod";
3
+ import micromatch from "micromatch";
4
+ import { basename } from "path";
5
+ import { tavily } from "@tavily/core";
6
+ import TurndownService from "turndown";
7
+ import { Readability } from "@mozilla/readability";
8
+ import { JSDOM } from "jsdom";
9
+
10
+ //#region src/constants/limits.ts
11
+ /**
12
+ * Centralized token, size, and timeout limits.
13
+ *
14
+ * These constants prevent magic number scattering across the codebase and provide
15
+ * a single source of truth for configuration values. When updating these values,
16
+ * consider the impact on performance, user experience, and API limits.
17
+ *
18
+ * @module constants/limits
19
+ */
20
+ /**
21
+ * Default token limit for tool result eviction.
22
+ *
23
+ * When a tool result exceeds this limit, it is automatically evicted to a file
24
+ * to prevent context overflow. The evicted content is stored in the backend and
25
+ * a summary is kept in the conversation history.
26
+ *
27
+ * @default 20000
28
+ * @see {@link ../utils/eviction | evictToolResult}
29
+ */
30
+ const DEFAULT_EVICTION_TOKEN_LIMIT$1 = 2e4;
31
+ /**
32
+ * Default threshold for message summarization.
33
+ *
34
+ * When the estimated token count of messages exceeds this threshold, the system
35
+ * automatically summarizes older messages to stay within context limits. This
36
+ * helps maintain conversation continuity while reducing token usage.
37
+ *
38
+ * @default 170000
39
+ * @see {@link ../utils/summarization | summarizeIfNeeded}
40
+ */
41
+ const DEFAULT_SUMMARIZATION_THRESHOLD$1 = 17e4;
42
+ /**
43
+ * Maximum context window size for Claude models.
44
+ *
45
+ * This represents the maximum number of tokens that can be processed in a single
46
+ * conversation. Used for calculating token usage percentages and determining when
47
+ * summarization is needed.
48
+ *
49
+ * @default 200000
50
+ * @see {@link ../utils/summarization | estimateMessagesTokens}
51
+ */
52
+ const CONTEXT_WINDOW = 2e5;
53
+ /**
54
+ * Default number of recent messages to keep during summarization.
55
+ *
56
+ * When summarization is triggered, this many of the most recent messages are
57
+ * preserved verbatim while older messages are summarized. This ensures recent
58
+ * context is immediately available to the agent.
59
+ *
60
+ * @default 6
61
+ */
62
+ const DEFAULT_KEEP_MESSAGES$1 = 6;
63
+ /**
64
+ * Default maximum number of reasoning steps for the main agent.
65
+ *
66
+ * The agent will stop after reaching this many steps to prevent infinite loops
67
+ * or excessive token usage. Each step represents one tool invocation cycle.
68
+ *
69
+ * @default 100
70
+ */
71
+ const DEFAULT_MAX_STEPS = 100;
72
+ /**
73
+ * Default maximum number of reasoning steps for subagents.
74
+ *
75
+ * Subagents are given a lower step limit than the main agent to prevent them
76
+ * from consuming too many resources. This ensures the parent agent maintains
77
+ * control over the overall task.
78
+ *
79
+ * @default 50
80
+ * @see {@link ../tools/subagent | createTaskTool}
81
+ */
82
+ const DEFAULT_SUBAGENT_MAX_STEPS = 50;
83
+ /**
84
+ * Default maximum number of lines to read from a file.
85
+ *
86
+ * The read_file tool defaults to reading this many lines to prevent loading
87
+ * extremely large files into context. Can be overridden per-read operation.
88
+ *
89
+ * @default 2000
90
+ * @see {@link ../tools/filesystem | createReadFileTool}
91
+ */
92
+ const DEFAULT_READ_LIMIT = 2e3;
93
+ /**
94
+ * Maximum line length before content is considered invalid.
95
+ *
96
+ * Lines exceeding this length may indicate minified code, binary content, or
97
+ * other data that should not be processed as text. Used for validation.
98
+ *
99
+ * @default 10000
100
+ */
101
+ const MAX_LINE_LENGTH = 1e4;
102
+ /**
103
+ * Maximum file size in megabytes for file operations.
104
+ *
105
+ * Files larger than this size will be rejected to prevent memory issues and
106
+ * excessive token usage. This is a soft limit that can be adjusted for specific
107
+ * use cases.
108
+ *
109
+ * @default 10
110
+ */
111
+ const MAX_FILE_SIZE_MB = 10;
112
+ /**
113
+ * Default timeout for network requests in seconds.
114
+ *
115
+ * Used by web tools (http_request, fetch_url) to prevent hanging indefinitely
116
+ * on slow or unresponsive servers. Can be overridden per-request.
117
+ *
118
+ * @default 30
119
+ * @see {@link ../tools/web | createHttpRequestTool}
120
+ */
121
+ const DEFAULT_TIMEOUT_SECONDS = 30;
122
+ /**
123
+ * Default timeout in milliseconds (derived from DEFAULT_TIMEOUT_SECONDS).
124
+ *
125
+ * Provided for convenience when working with APIs that expect milliseconds
126
+ * instead of seconds.
127
+ *
128
+ * @default 30000 (30 seconds)
129
+ */
130
+ const DEFAULT_TIMEOUT_MS = DEFAULT_TIMEOUT_SECONDS * 1e3;
131
+ /**
132
+ * Width for line number formatting in file read operations.
133
+ *
134
+ * When displaying file content with line numbers, this specifies the minimum
135
+ * width for the line number column. Ensures consistent alignment across
136
+ * different file sizes.
137
+ *
138
+ * @default 6
139
+ * @see {@link ../backends/utils | formatFileContent}
140
+ */
141
+ const LINE_NUMBER_WIDTH = 6;
142
+
143
+ //#endregion
144
+ //#region src/utils/events.ts
145
+ /**
146
+ * Create a todos-changed event.
147
+ */
148
+ function createTodosChangedEvent(todos) {
149
+ return {
150
+ type: "todos-changed",
151
+ todos
152
+ };
153
+ }
154
+ /**
155
+ * Create a file-write-start event (preview before write).
156
+ */
157
+ function createFileWriteStartEvent(path, content) {
158
+ return {
159
+ type: "file-write-start",
160
+ path,
161
+ content
162
+ };
163
+ }
164
+ /**
165
+ * Create a file-written event (after successful write).
166
+ */
167
+ function createFileWrittenEvent(path, content) {
168
+ return {
169
+ type: "file-written",
170
+ path,
171
+ content
172
+ };
173
+ }
174
+ /**
175
+ * Create a file-edited event.
176
+ */
177
+ function createFileEditedEvent(path, occurrences) {
178
+ return {
179
+ type: "file-edited",
180
+ path,
181
+ occurrences
182
+ };
183
+ }
184
+ /**
185
+ * Create a file-read event.
186
+ */
187
+ function createFileReadEvent(path, lines) {
188
+ return {
189
+ type: "file-read",
190
+ path,
191
+ lines
192
+ };
193
+ }
194
+ /**
195
+ * Create a web-search-start event.
196
+ */
197
+ function createWebSearchStartEvent(query) {
198
+ return {
199
+ type: "web-search-start",
200
+ query
201
+ };
202
+ }
203
+ /**
204
+ * Create a web-search-finish event.
205
+ */
206
+ function createWebSearchFinishEvent(query, resultCount) {
207
+ return {
208
+ type: "web-search-finish",
209
+ query,
210
+ resultCount
211
+ };
212
+ }
213
+ /**
214
+ * Create an http-request-start event.
215
+ */
216
+ function createHttpRequestStartEvent(url, method) {
217
+ return {
218
+ type: "http-request-start",
219
+ url,
220
+ method
221
+ };
222
+ }
223
+ /**
224
+ * Create an http-request-finish event.
225
+ */
226
+ function createHttpRequestFinishEvent(url, statusCode) {
227
+ return {
228
+ type: "http-request-finish",
229
+ url,
230
+ statusCode
231
+ };
232
+ }
233
+ /**
234
+ * Create a fetch-url-start event.
235
+ */
236
+ function createFetchUrlStartEvent(url) {
237
+ return {
238
+ type: "fetch-url-start",
239
+ url
240
+ };
241
+ }
242
+ /**
243
+ * Create a fetch-url-finish event.
244
+ */
245
+ function createFetchUrlFinishEvent(url, success) {
246
+ return {
247
+ type: "fetch-url-finish",
248
+ url,
249
+ success
250
+ };
251
+ }
252
+ /**
253
+ * Create a subagent-start event.
254
+ */
255
+ function createSubagentStartEvent(name, task) {
256
+ return {
257
+ type: "subagent-start",
258
+ name,
259
+ task
260
+ };
261
+ }
262
+ /**
263
+ * Create a subagent-finish event.
264
+ */
265
+ function createSubagentFinishEvent(name, result) {
266
+ return {
267
+ type: "subagent-finish",
268
+ name,
269
+ result
270
+ };
271
+ }
272
+ /**
273
+ * Create a subagent-step event.
274
+ */
275
+ function createSubagentStepEvent(stepIndex, toolCalls) {
276
+ return {
277
+ type: "subagent-step",
278
+ stepIndex,
279
+ toolCalls
280
+ };
281
+ }
282
+ /**
283
+ * Create a checkpoint-saved event.
284
+ */
285
+ function createCheckpointSavedEvent(threadId, step) {
286
+ return {
287
+ type: "checkpoint-saved",
288
+ threadId,
289
+ step
290
+ };
291
+ }
292
+ /**
293
+ * Create a checkpoint-loaded event.
294
+ */
295
+ function createCheckpointLoadedEvent(threadId, step, messagesCount) {
296
+ return {
297
+ type: "checkpoint-loaded",
298
+ threadId,
299
+ step,
300
+ messagesCount
301
+ };
302
+ }
303
+
304
+ //#endregion
305
+ //#region src/types/backend.ts
306
+ /**
307
+ * Type guard to check if a backend is a SandboxBackendProtocol.
308
+ */
309
+ function isSandboxBackend(backend) {
310
+ return typeof backend.execute === "function" && typeof backend.id === "string";
311
+ }
312
+
313
+ //#endregion
314
+ //#region src/prompts.ts
315
+ /**
316
+ * System prompts for Deep Agent.
317
+ */
318
+ const BASE_PROMPT = `In order to complete the objective that the user asks of you, you have access to a number of standard tools.`;
319
+ const TODO_SYSTEM_PROMPT = `## \`write_todos\` (task planning)
320
+
321
+ You have access to a \`write_todos\` tool to help you manage and plan tasks. Use this tool whenever you are working on a complex task.
322
+
323
+ ### When to Use This Tool
324
+
325
+ Use proactively for:
326
+ 1. Complex multi-step tasks (3+ distinct steps)
327
+ 2. Non-trivial tasks requiring careful planning
328
+ 3. After receiving new instructions - capture requirements as todos
329
+ 4. After completing tasks - mark complete and add follow-ups
330
+ 5. When starting new tasks - mark as in_progress (ideally only one at a time)
331
+
332
+ ### When NOT to Use
333
+
334
+ Skip for:
335
+ 1. Single, straightforward tasks
336
+ 2. Trivial tasks with no organizational benefit
337
+ 3. Tasks completable in < 3 trivial steps
338
+ 4. Purely conversational/informational requests
339
+
340
+ ### Task States and Management
341
+
342
+ 1. **Task States:**
343
+ - pending: Not yet started
344
+ - in_progress: Currently working on
345
+ - completed: Finished successfully
346
+ - cancelled: No longer needed
347
+
348
+ 2. **Task Management:**
349
+ - Update status in real-time
350
+ - Mark complete IMMEDIATELY after finishing
351
+ - Only ONE task in_progress at a time
352
+ - Complete current tasks before starting new ones`;
353
+ const FILESYSTEM_SYSTEM_PROMPT = `## Virtual Filesystem
354
+
355
+ You have access to a virtual filesystem. All file paths must start with a /.
356
+
357
+ - ls: list files in a directory (requires absolute path)
358
+ - read_file: read a file from the filesystem
359
+ - write_file: write to a file in the filesystem
360
+ - edit_file: edit a file in the filesystem
361
+ - glob: find files matching a pattern (e.g., "**/*.py")
362
+ - grep: search for text within files`;
363
+ const TASK_SYSTEM_PROMPT = `## \`task\` (subagent spawner)
364
+
365
+ You have access to a \`task\` tool to launch short-lived subagents that handle isolated tasks. These agents are ephemeral — they live only for the duration of the task and return a single result.
366
+
367
+ When to use the task tool:
368
+ - When a task is complex and multi-step, and can be fully delegated in isolation
369
+ - When a task is independent of other tasks and can run in parallel
370
+ - When a task requires focused reasoning or heavy token/context usage that would bloat the orchestrator thread
371
+ - When sandboxing improves reliability (e.g. code execution, structured searches, data formatting)
372
+ - When you only care about the output of the subagent, and not the intermediate steps
373
+
374
+ Subagent lifecycle:
375
+ 1. **Spawn** → Provide clear role, instructions, and expected output
376
+ 2. **Run** → The subagent completes the task autonomously
377
+ 3. **Return** → The subagent provides a single structured result
378
+ 4. **Reconcile** → Incorporate or synthesize the result into the main thread
379
+
380
+ When NOT to use the task tool:
381
+ - If you need to see the intermediate reasoning or steps after the subagent has completed (the task tool hides them)
382
+ - If the task is trivial (a few tool calls or simple lookup)
383
+ - If delegating does not reduce token usage, complexity, or context switching
384
+ - If splitting would add latency without benefit
385
+
386
+ ## Important Task Tool Usage Notes
387
+ - Whenever possible, parallelize the work that you do. Whenever you have independent steps to complete - kick off tasks (subagents) in parallel to accomplish them faster.
388
+ - Remember to use the \`task\` tool to silo independent tasks within a multi-part objective.
389
+ - You should use the \`task\` tool whenever you have a complex task that will take multiple steps, and is independent from other tasks that the agent needs to complete.`;
390
+ /**
391
+ * Get the task tool description with available subagent types.
392
+ */
393
+ function getTaskToolDescription(subagentDescriptions) {
394
+ return `
395
+ Launch an ephemeral subagent to handle complex, multi-step independent tasks with isolated context windows.
396
+
397
+ Available agent types and the tools they have access to:
398
+ ${subagentDescriptions.join("\n")}
399
+
400
+ When using the Task tool, you must specify a subagent_type parameter to select which agent type to use.
401
+
402
+ ## Usage notes:
403
+ 1. Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
404
+ 2. When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.
405
+ 3. Each agent invocation is stateless. You will not be able to send additional messages to the agent, nor will the agent be able to communicate with you outside of its final report. Therefore, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you.
406
+ 4. The agent's outputs should generally be trusted
407
+ 5. Clearly tell the agent whether you expect it to create content, perform analysis, or just do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent
408
+ 6. If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.
409
+ 7. When only the general-purpose agent is provided, you should use it for all tasks. It is great for isolating context and token usage, and completing specific, complex tasks, as it has all the same capabilities as the main agent.
410
+
411
+ ### Example usage of the general-purpose agent:
412
+
413
+ <example_agent_descriptions>
414
+ "general-purpose": use this agent for general purpose tasks, it has access to all tools as the main agent.
415
+ </example_agent_descriptions>
416
+
417
+ <example>
418
+ User: "I want to conduct research on the accomplishments of Lebron James, Michael Jordan, and Kobe Bryant, and then compare them."
419
+ Assistant: *Uses the task tool in parallel to conduct isolated research on each of the three players*
420
+ Assistant: *Synthesizes the results of the three isolated research tasks and responds to the User*
421
+ <commentary>
422
+ Research is a complex, multi-step task in it of itself.
423
+ The research of each individual player is not dependent on the research of the other players.
424
+ The assistant uses the task tool to break down the complex objective into three isolated tasks.
425
+ Each research task only needs to worry about context and tokens about one player, then returns synthesized information about each player as the Tool Result.
426
+ This means each research task can dive deep and spend tokens and context deeply researching each player, but the final result is synthesized information, and saves us tokens in the long run when comparing the players to each other.
427
+ </commentary>
428
+ </example>
429
+
430
+ <example>
431
+ User: "Analyze a single large code repository for security vulnerabilities and generate a report."
432
+ Assistant: *Launches a single \`task\` subagent for the repository analysis*
433
+ Assistant: *Receives report and integrates results into final summary*
434
+ <commentary>
435
+ Subagent is used to isolate a large, context-heavy task, even though there is only one. This prevents the main thread from being overloaded with details.
436
+ If the user then asks followup questions, we have a concise report to reference instead of the entire history of analysis and tool calls, which is good and saves us time and money.
437
+ </commentary>
438
+ </example>
439
+ `.trim();
440
+ }
441
+ const DEFAULT_GENERAL_PURPOSE_DESCRIPTION = "General-purpose agent for researching complex questions, searching for files and content, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you. This agent has access to all tools as the main agent.";
442
+ const DEFAULT_SUBAGENT_PROMPT = "In order to complete the objective that the user asks of you, you have access to a number of standard tools.";
443
+ const EXECUTE_SYSTEM_PROMPT = `## \`execute\` (shell command execution)
444
+
445
+ You have access to an \`execute\` tool to run shell commands in the sandbox environment.
446
+
447
+ ### When to Use This Tool
448
+
449
+ Use for:
450
+ - Running build commands (npm install, npm run build, bun install)
451
+ - Running tests (npm test, bun test, pytest)
452
+ - Executing scripts (node script.js, python script.py)
453
+ - Installing dependencies
454
+ - Checking system state (ls, cat, pwd, which)
455
+ - Any shell command that helps accomplish the task
456
+
457
+ ### Important Notes
458
+
459
+ 1. **Exit Codes**: Always check the exit code to determine success
460
+ - 0 = success
461
+ - non-zero = failure
462
+ - null = possibly timed out
463
+
464
+ 2. **Command Chaining**:
465
+ - Use \`&&\` to chain commands that depend on each other
466
+ - Use \`;\` to run commands sequentially regardless of success
467
+
468
+ 3. **Timeouts**: Long-running commands may timeout
469
+
470
+ 4. **Working Directory**: Commands run in the sandbox's working directory`;
471
+ /**
472
+ * Build skills section for system prompt with progressive disclosure.
473
+ */
474
+ function buildSkillsPrompt(skills) {
475
+ if (skills.length === 0) return "";
476
+ return `## Skills System
477
+
478
+ You have access to a skills library providing specialized domain knowledge and workflows.
479
+
480
+ **Available Skills:**
481
+
482
+ ${skills.map((skill) => `- **${skill.name}**: ${skill.description}\n → Read \`${skill.path}\` for full instructions`).join("\n")}
483
+
484
+ **How to Use Skills (Progressive Disclosure):**
485
+
486
+ 1. **Recognize when a skill applies**: Check if the user's task matches any skill's domain
487
+ 2. **Read the skill's full instructions**: Use read_file to load the SKILL.md content
488
+ 3. **Follow the skill's workflow**: Skills contain step-by-step instructions and examples
489
+ 4. **Access supporting files**: Skills may include helper scripts or configuration files in their directory
490
+
491
+ Skills provide expert knowledge for specialized tasks. Always read the full skill before using it.`;
492
+ }
493
+
494
+ //#endregion
495
+ //#region src/tools/todos.ts
496
+ /**
497
+ * Todo list tool for task planning and tracking.
498
+ */
499
+ const TodoItemSchema = z.object({
500
+ id: z.string().describe("Unique identifier for the todo item"),
501
+ content: z.string().max(100).describe("The description/content of the todo item (max 100 chars)"),
502
+ status: z.enum([
503
+ "pending",
504
+ "in_progress",
505
+ "completed",
506
+ "cancelled"
507
+ ]).describe("The current status of the todo item")
508
+ });
509
+ /**
510
+ * Create the write_todos tool for task planning.
511
+ * @param state - The shared agent state
512
+ * @param onEvent - Optional callback for emitting events
513
+ */
514
+ function createTodosTool(state, onEvent) {
515
+ return tool({
516
+ description: `Manage and plan tasks using a structured todo list. Use this tool for:
517
+ - Complex multi-step tasks (3+ steps)
518
+ - After receiving new instructions - capture requirements
519
+ - When starting tasks - mark as in_progress (only one at a time)
520
+ - After completing tasks - mark complete immediately
521
+
522
+ Task states: pending, in_progress, completed, cancelled
523
+
524
+ When merge=true, updates are merged with existing todos by id.
525
+ When merge=false, the new todos replace all existing todos.`,
526
+ inputSchema: z.object({
527
+ todos: z.array(TodoItemSchema).min(1).describe("Array of todo items to write"),
528
+ merge: z.boolean().default(true).describe("Whether to merge with existing todos (true) or replace all (false)")
529
+ }),
530
+ execute: async ({ todos, merge }) => {
531
+ if (merge) {
532
+ const existingMap = /* @__PURE__ */ new Map();
533
+ for (const todo of state.todos) existingMap.set(todo.id, todo);
534
+ for (const newTodo of todos) {
535
+ const existing = existingMap.get(newTodo.id);
536
+ if (existing) existingMap.set(newTodo.id, {
537
+ ...existing,
538
+ ...newTodo
539
+ });
540
+ else existingMap.set(newTodo.id, newTodo);
541
+ }
542
+ state.todos = Array.from(existingMap.values());
543
+ } else state.todos = todos;
544
+ if (onEvent) onEvent(createTodosChangedEvent([...state.todos]));
545
+ return `Todo list updated successfully.\n\nCurrent todos:\n${state.todos.map((t) => `- [${t.status}] ${t.id}: ${t.content}`).join("\n")}`;
546
+ }
547
+ });
548
+ }
549
+ /**
550
+ * Individual builtin tool reference for selective subagent configuration.
551
+ * This is a reference to the creator function, not an instance.
552
+ */
553
+ const write_todos = createTodosTool;
554
+
555
+ //#endregion
556
+ //#region src/constants/errors.ts
557
+ /**
558
+ * Centralized error message constants for backend operations
559
+ * Reduces duplication across 6 backend implementations
560
+ */
561
+ const FILE_NOT_FOUND = (path) => `Error: File '${path}' not found`;
562
+ const FILE_ALREADY_EXISTS = (path) => `Cannot write to ${path} because it already exists. Read and then make an edit, or write to a new path.`;
563
+ const STRING_NOT_FOUND = (path, string) => `Error: String not found in file: '${path}'\n\n${string}`;
564
+ const INVALID_REGEX = (message) => `Invalid regex pattern: ${message}`;
565
+ const WEB_SEARCH_ERROR = (message) => `Web search error: ${message}`;
566
+ const REQUEST_TIMEOUT = (timeout) => `Request timed out after ${timeout} seconds`;
567
+ const SYSTEM_REMINDER_FILE_EMPTY = "System reminder: File exists but has empty contents";
568
+
569
+ //#endregion
570
+ //#region src/backends/utils.ts
571
+ /**
572
+ * Shared utility functions for memory backend implementations.
573
+ */
574
+ const EMPTY_CONTENT_WARNING = SYSTEM_REMINDER_FILE_EMPTY;
575
+ /**
576
+ * Format file content with line numbers (cat -n style).
577
+ */
578
+ function formatContentWithLineNumbers(content, startLine = 1) {
579
+ let lines;
580
+ if (typeof content === "string") {
581
+ lines = content.split("\n");
582
+ if (lines.length > 0 && lines[lines.length - 1] === "") lines = lines.slice(0, -1);
583
+ } else lines = content;
584
+ const resultLines = [];
585
+ for (let i = 0; i < lines.length; i++) {
586
+ const line = lines[i];
587
+ const lineNum = i + startLine;
588
+ if (line && line.length <= MAX_LINE_LENGTH) resultLines.push(`${lineNum.toString().padStart(LINE_NUMBER_WIDTH)}\t${line}`);
589
+ else if (line) {
590
+ const numChunks = Math.ceil(line.length / MAX_LINE_LENGTH);
591
+ for (let chunkIdx = 0; chunkIdx < numChunks; chunkIdx++) {
592
+ const start = chunkIdx * MAX_LINE_LENGTH;
593
+ const end = Math.min(start + MAX_LINE_LENGTH, line.length);
594
+ const chunk = line.substring(start, end);
595
+ if (chunkIdx === 0) resultLines.push(`${lineNum.toString().padStart(LINE_NUMBER_WIDTH)}\t${chunk}`);
596
+ else {
597
+ const continuationMarker = `${lineNum}.${chunkIdx}`;
598
+ resultLines.push(`${continuationMarker.padStart(LINE_NUMBER_WIDTH)}\t${chunk}`);
599
+ }
600
+ }
601
+ } else resultLines.push(`${lineNum.toString().padStart(LINE_NUMBER_WIDTH)}\t`);
602
+ }
603
+ return resultLines.join("\n");
604
+ }
605
+ /**
606
+ * Check if content is empty and return warning message.
607
+ */
608
+ function checkEmptyContent(content) {
609
+ if (!content || content.trim() === "") return EMPTY_CONTENT_WARNING;
610
+ return null;
611
+ }
612
+ /**
613
+ * Convert FileData to plain string content.
614
+ */
615
+ function fileDataToString(fileData) {
616
+ return fileData.content.join("\n");
617
+ }
618
+ /**
619
+ * Create a FileData object with timestamps.
620
+ */
621
+ function createFileData(content, createdAt) {
622
+ const lines = typeof content === "string" ? content.split("\n") : content;
623
+ const now = (/* @__PURE__ */ new Date()).toISOString();
624
+ return {
625
+ content: lines,
626
+ created_at: createdAt || now,
627
+ modified_at: now
628
+ };
629
+ }
630
+ /**
631
+ * Update FileData with new content, preserving creation timestamp.
632
+ */
633
+ function updateFileData(fileData, content) {
634
+ const lines = typeof content === "string" ? content.split("\n") : content;
635
+ const now = (/* @__PURE__ */ new Date()).toISOString();
636
+ return {
637
+ content: lines,
638
+ created_at: fileData.created_at,
639
+ modified_at: now
640
+ };
641
+ }
642
+ /**
643
+ * Format file data for read response with line numbers.
644
+ */
645
+ function formatReadResponse(fileData, offset, limit) {
646
+ const content = fileDataToString(fileData);
647
+ const emptyMsg = checkEmptyContent(content);
648
+ if (emptyMsg) return emptyMsg;
649
+ const lines = content.split("\n");
650
+ const startIdx = offset;
651
+ const endIdx = Math.min(startIdx + limit, lines.length);
652
+ if (startIdx >= lines.length) return `Error: Line offset ${offset} exceeds file length (${lines.length} lines)`;
653
+ return formatContentWithLineNumbers(lines.slice(startIdx, endIdx), startIdx + 1);
654
+ }
655
+ /**
656
+ * Perform string replacement with occurrence validation.
657
+ */
658
+ function performStringReplacement(content, oldString, newString, replaceAll) {
659
+ const occurrences = content.split(oldString).length - 1;
660
+ if (occurrences === 0) return `Error: String not found in file: '${oldString}'`;
661
+ if (occurrences > 1 && !replaceAll) return `Error: String '${oldString}' appears ${occurrences} times in file. Use replace_all=true to replace all instances, or provide a more specific string with surrounding context.`;
662
+ return [content.split(oldString).join(newString), occurrences];
663
+ }
664
+ /**
665
+ * Validate and normalize a path.
666
+ */
667
+ function validatePath(path) {
668
+ const pathStr = path || "/";
669
+ if (!pathStr || pathStr.trim() === "") throw new Error("Path cannot be empty");
670
+ let normalized = pathStr.startsWith("/") ? pathStr : "/" + pathStr;
671
+ if (!normalized.endsWith("/")) normalized += "/";
672
+ return normalized;
673
+ }
674
+ /**
675
+ * Search files dict for paths matching glob pattern.
676
+ */
677
+ function globSearchFiles(files, pattern, path = "/") {
678
+ let normalizedPath;
679
+ try {
680
+ normalizedPath = validatePath(path);
681
+ } catch {
682
+ return "No files found";
683
+ }
684
+ const filtered = Object.fromEntries(Object.entries(files).filter(([fp]) => fp.startsWith(normalizedPath)));
685
+ const matches = [];
686
+ for (const [filePath, fileData] of Object.entries(filtered)) {
687
+ let relative = filePath.substring(normalizedPath.length);
688
+ if (relative.startsWith("/")) relative = relative.substring(1);
689
+ if (!relative) {
690
+ const parts = filePath.split("/");
691
+ relative = parts[parts.length - 1] || "";
692
+ }
693
+ if (micromatch.isMatch(relative, pattern, {
694
+ dot: true,
695
+ nobrace: false
696
+ })) matches.push([filePath, fileData.modified_at]);
697
+ }
698
+ matches.sort((a, b) => b[1].localeCompare(a[1]));
699
+ if (matches.length === 0) return "No files found";
700
+ return matches.map(([fp]) => fp).join("\n");
701
+ }
702
+ /**
703
+ * Return structured grep matches from an in-memory files mapping.
704
+ */
705
+ function grepMatchesFromFiles(files, pattern, path = null, glob$1 = null) {
706
+ let regex;
707
+ try {
708
+ regex = new RegExp(pattern);
709
+ } catch (e) {
710
+ return INVALID_REGEX(e.message);
711
+ }
712
+ let normalizedPath;
713
+ try {
714
+ normalizedPath = validatePath(path);
715
+ } catch {
716
+ return [];
717
+ }
718
+ let filtered = Object.fromEntries(Object.entries(files).filter(([fp]) => fp.startsWith(normalizedPath)));
719
+ if (glob$1) filtered = Object.fromEntries(Object.entries(filtered).filter(([fp]) => micromatch.isMatch(basename(fp), glob$1, {
720
+ dot: true,
721
+ nobrace: false
722
+ })));
723
+ const matches = [];
724
+ for (const [filePath, fileData] of Object.entries(filtered)) for (let i = 0; i < fileData.content.length; i++) {
725
+ const line = fileData.content[i];
726
+ const lineNum = i + 1;
727
+ if (line && regex.test(line)) matches.push({
728
+ path: filePath,
729
+ line: lineNum,
730
+ text: line
731
+ });
732
+ }
733
+ return matches;
734
+ }
735
+
736
+ //#endregion
737
+ //#region src/backends/state.ts
738
+ /**
739
+ * Backend that stores files in shared state (ephemeral).
740
+ *
741
+ * Files persist within a single agent invocation but not across invocations.
742
+ * This is the default backend for Deep Agent when no backend is specified.
743
+ *
744
+ * Files are stored in memory as part of the `DeepAgentState`, making this backend
745
+ * fast but non-persistent. Use `FilesystemBackend` or `PersistentBackend` for
746
+ * cross-session persistence.
747
+ *
748
+ * @example Default usage (no backend specified)
749
+ * ```typescript
750
+ * const agent = createDeepAgent({
751
+ * model: anthropic('claude-sonnet-4-20250514'),
752
+ * // StateBackend is used by default
753
+ * });
754
+ * ```
755
+ *
756
+ * @example Explicit usage
757
+ * ```typescript
758
+ * const state: DeepAgentState = { todos: [], files: {} };
759
+ * const backend = new StateBackend(state);
760
+ * const agent = createDeepAgent({
761
+ * model: anthropic('claude-sonnet-4-20250514'),
762
+ * backend,
763
+ * });
764
+ * ```
765
+ */
766
+ var StateBackend = class {
767
+ state;
768
+ /**
769
+ * Create a new StateBackend instance.
770
+ *
771
+ * @param state - The DeepAgentState object that will store the files.
772
+ * Files are stored in `state.files` as a Record<string, FileData>.
773
+ */
774
+ constructor(state) {
775
+ this.state = state;
776
+ }
777
+ /**
778
+ * Get files from current state.
779
+ */
780
+ getFiles() {
781
+ return this.state.files || {};
782
+ }
783
+ /**
784
+ * List files and directories in the specified directory (non-recursive).
785
+ */
786
+ lsInfo(path) {
787
+ const files = this.getFiles();
788
+ const infos = [];
789
+ const subdirs = /* @__PURE__ */ new Set();
790
+ const normalizedPath = path.endsWith("/") ? path : path + "/";
791
+ for (const [k, fd] of Object.entries(files)) {
792
+ if (!k.startsWith(normalizedPath)) continue;
793
+ const relative = k.substring(normalizedPath.length);
794
+ if (relative.includes("/")) {
795
+ const subdirName = relative.split("/")[0];
796
+ subdirs.add(normalizedPath + subdirName + "/");
797
+ continue;
798
+ }
799
+ const size = fd.content.join("\n").length;
800
+ infos.push({
801
+ path: k,
802
+ is_dir: false,
803
+ size,
804
+ modified_at: fd.modified_at
805
+ });
806
+ }
807
+ for (const subdir of Array.from(subdirs).sort()) infos.push({
808
+ path: subdir,
809
+ is_dir: true,
810
+ size: 0,
811
+ modified_at: ""
812
+ });
813
+ infos.sort((a, b) => a.path.localeCompare(b.path));
814
+ return infos;
815
+ }
816
+ /**
817
+ * Read file content with line numbers.
818
+ */
819
+ read(filePath, offset = 0, limit = 2e3) {
820
+ const fileData = this.getFiles()[filePath];
821
+ if (!fileData) return FILE_NOT_FOUND(filePath);
822
+ return formatReadResponse(fileData, offset, limit);
823
+ }
824
+ /**
825
+ * Read file content as raw FileData.
826
+ */
827
+ readRaw(filePath) {
828
+ const fileData = this.getFiles()[filePath];
829
+ if (!fileData) throw new Error(`File '${filePath}' not found`);
830
+ return fileData;
831
+ }
832
+ /**
833
+ * Create a new file with content.
834
+ */
835
+ write(filePath, content) {
836
+ const files = this.getFiles();
837
+ if (!filePath || filePath.trim() === "") return {
838
+ success: false,
839
+ error: "File path cannot be empty"
840
+ };
841
+ if (filePath in files) return {
842
+ success: false,
843
+ error: FILE_ALREADY_EXISTS(filePath)
844
+ };
845
+ const newFileData = createFileData(content);
846
+ this.state.files[filePath] = newFileData;
847
+ return {
848
+ success: true,
849
+ path: filePath
850
+ };
851
+ }
852
+ /**
853
+ * Edit a file by replacing string occurrences.
854
+ */
855
+ edit(filePath, oldString, newString, replaceAll = false) {
856
+ const fileData = this.getFiles()[filePath];
857
+ if (!fileData) return {
858
+ success: false,
859
+ error: FILE_NOT_FOUND(filePath)
860
+ };
861
+ const result = performStringReplacement(fileDataToString(fileData), oldString, newString, replaceAll);
862
+ if (typeof result === "string") return {
863
+ success: false,
864
+ error: result
865
+ };
866
+ const [newContent, occurrences] = result;
867
+ const newFileData = updateFileData(fileData, newContent);
868
+ this.state.files[filePath] = newFileData;
869
+ return {
870
+ success: true,
871
+ path: filePath,
872
+ occurrences
873
+ };
874
+ }
875
+ /**
876
+ * Structured search results or error string for invalid input.
877
+ */
878
+ grepRaw(pattern, path = "/", glob$1 = null) {
879
+ return grepMatchesFromFiles(this.getFiles(), pattern, path, glob$1);
880
+ }
881
+ /**
882
+ * Structured glob matching returning FileInfo objects.
883
+ */
884
+ globInfo(pattern, path = "/") {
885
+ const files = this.getFiles();
886
+ const result = globSearchFiles(files, pattern, path);
887
+ if (result === "No files found") return [];
888
+ const paths = result.split("\n");
889
+ const infos = [];
890
+ for (const p of paths) {
891
+ const fd = files[p];
892
+ const size = fd ? fd.content.join("\n").length : 0;
893
+ infos.push({
894
+ path: p,
895
+ is_dir: false,
896
+ size,
897
+ modified_at: fd?.modified_at || ""
898
+ });
899
+ }
900
+ return infos;
901
+ }
902
+ };
903
+
904
+ //#endregion
905
+ //#region src/utils/eviction.ts
906
+ /**
907
+ * Default token limit before evicting a tool result.
908
+ * Approximately 20,000 tokens (~80KB of text).
909
+ */
910
+ const DEFAULT_EVICTION_TOKEN_LIMIT = DEFAULT_EVICTION_TOKEN_LIMIT$1;
911
+ /**
912
+ * Approximate characters per token (rough estimate).
913
+ */
914
+ const CHARS_PER_TOKEN = 4;
915
+ /**
916
+ * Sanitize a tool call ID for use as a filename.
917
+ * Removes or replaces characters that are invalid in file paths.
918
+ */
919
+ function sanitizeToolCallId(toolCallId) {
920
+ return toolCallId.replace(/[^a-zA-Z0-9_-]/g, "_").substring(0, 100);
921
+ }
922
+ /**
923
+ * Estimate the number of tokens in a string.
924
+ * Uses a simple character-based approximation.
925
+ */
926
+ function estimateTokens(text) {
927
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
928
+ }
929
+ /**
930
+ * Check if a tool result should be evicted based on size.
931
+ */
932
+ function shouldEvict(result, tokenLimit = DEFAULT_EVICTION_TOKEN_LIMIT) {
933
+ return estimateTokens(result) > tokenLimit;
934
+ }
935
+ /**
936
+ * Evict a large tool result to the filesystem.
937
+ *
938
+ * If the result exceeds the token limit, writes it to a file and
939
+ * returns a truncated message with the file path.
940
+ *
941
+ * @param options - Eviction options
942
+ * @returns Eviction result with content and metadata
943
+ *
944
+ * @example
945
+ * ```typescript
946
+ * const result = await evictToolResult({
947
+ * result: veryLongString,
948
+ * toolCallId: "call_123",
949
+ * toolName: "grep",
950
+ * backend: filesystemBackend,
951
+ * });
952
+ *
953
+ * if (result.evicted) {
954
+ * console.log(`Content saved to ${result.evictedPath}`);
955
+ * }
956
+ * ```
957
+ */
958
+ async function evictToolResult(options) {
959
+ const { result, toolCallId, toolName, backend, tokenLimit = DEFAULT_EVICTION_TOKEN_LIMIT } = options;
960
+ if (!shouldEvict(result, tokenLimit)) return {
961
+ evicted: false,
962
+ content: result
963
+ };
964
+ const evictPath = `/large_tool_results/${toolName}_${sanitizeToolCallId(toolCallId)}.txt`;
965
+ const writeResult = await backend.write(evictPath, result);
966
+ if (writeResult.error) {
967
+ console.warn(`Failed to evict tool result: ${writeResult.error}`);
968
+ return {
969
+ evicted: false,
970
+ content: result
971
+ };
972
+ }
973
+ return {
974
+ evicted: true,
975
+ content: `Tool result too large (~${estimateTokens(result)} tokens). Content saved to ${evictPath}. Use read_file to access the full content.`,
976
+ evictedPath: evictPath
977
+ };
978
+ }
979
+ /**
980
+ * Create a tool result wrapper that automatically evicts large results.
981
+ *
982
+ * @param backend - Backend or factory for filesystem operations
983
+ * @param state - Current agent state (for factory backends)
984
+ * @param tokenLimit - Token limit before eviction
985
+ * @returns Function that wraps tool results with eviction
986
+ */
987
+ function createToolResultWrapper(backend, state, tokenLimit = DEFAULT_EVICTION_TOKEN_LIMIT) {
988
+ const resolvedBackend = typeof backend === "function" ? backend(state) : backend;
989
+ return async (result, toolCallId, toolName) => {
990
+ return (await evictToolResult({
991
+ result,
992
+ toolCallId,
993
+ toolName,
994
+ backend: resolvedBackend,
995
+ tokenLimit
996
+ })).content;
997
+ };
998
+ }
999
+
1000
+ //#endregion
1001
+ //#region src/tools/filesystem.ts
1002
+ /**
1003
+ * Filesystem tools for virtual file operations.
1004
+ */
1005
+ const LS_TOOL_DESCRIPTION = "List files and directories in a directory. Paths are relative to the working directory.";
1006
+ const READ_FILE_TOOL_DESCRIPTION = "Read the contents of a file. Paths are relative to the working directory.";
1007
+ const WRITE_FILE_TOOL_DESCRIPTION = "Write content to a new file. Returns an error if the file already exists. Paths are relative to the working directory.";
1008
+ const EDIT_FILE_TOOL_DESCRIPTION = "Edit a file by replacing a specific string with a new string. Paths are relative to the working directory.";
1009
+ const GLOB_TOOL_DESCRIPTION = "Find files matching a glob pattern (e.g., '**/*.py' for all Python files). Paths are relative to the working directory.";
1010
+ const GREP_TOOL_DESCRIPTION = "Search for a regex pattern in files. Returns matching files and line numbers. Paths are relative to the working directory.";
1011
+ /**
1012
+ * Resolve backend from factory or instance.
1013
+ */
1014
+ function getBackend$1(backend, state) {
1015
+ if (typeof backend === "function") return backend(state);
1016
+ return backend;
1017
+ }
1018
+ /**
1019
+ * Create the ls tool.
1020
+ */
1021
+ function createLsTool(state, backend, onEvent) {
1022
+ return tool({
1023
+ description: LS_TOOL_DESCRIPTION,
1024
+ inputSchema: z.object({ path: z.string().default(".").describe("Directory path to list (default: current directory)") }),
1025
+ execute: async ({ path }) => {
1026
+ const infos = await getBackend$1(backend, state).lsInfo(path || ".");
1027
+ if (onEvent) onEvent({
1028
+ type: "ls",
1029
+ path: path || ".",
1030
+ count: infos.length
1031
+ });
1032
+ if (infos.length === 0) return `No files found in ${path}`;
1033
+ const lines = [];
1034
+ for (const info of infos) if (info.is_dir) lines.push(`${info.path} (directory)`);
1035
+ else {
1036
+ const size = info.size ? ` (${info.size} bytes)` : "";
1037
+ lines.push(`${info.path}${size}`);
1038
+ }
1039
+ return lines.join("\n");
1040
+ }
1041
+ });
1042
+ }
1043
+ /**
1044
+ * Create the read_file tool.
1045
+ */
1046
+ function createReadFileTool(state, backend, evictionLimit, onEvent) {
1047
+ return tool({
1048
+ description: READ_FILE_TOOL_DESCRIPTION,
1049
+ inputSchema: z.object({
1050
+ file_path: z.string().describe("Path to the file to read (e.g., 'src/main.ts' or './main.ts')"),
1051
+ offset: z.number().default(0).describe("Line offset to start reading from (0-indexed)"),
1052
+ limit: z.number().default(2e3).describe("Maximum number of lines to read")
1053
+ }),
1054
+ execute: async ({ file_path, offset, limit }, { toolCallId }) => {
1055
+ const resolvedBackend = getBackend$1(backend, state);
1056
+ const content = await resolvedBackend.read(file_path, offset ?? 0, limit ?? 2e3);
1057
+ if (onEvent) {
1058
+ const lineCount = content.split("\n").length;
1059
+ onEvent(createFileReadEvent(file_path, lineCount));
1060
+ }
1061
+ if (evictionLimit && evictionLimit > 0) return (await evictToolResult({
1062
+ result: content,
1063
+ toolCallId: toolCallId || `read_${Date.now()}`,
1064
+ toolName: "read_file",
1065
+ backend: resolvedBackend,
1066
+ tokenLimit: evictionLimit
1067
+ })).content;
1068
+ return content;
1069
+ }
1070
+ });
1071
+ }
1072
+ /**
1073
+ * Create the write_file tool.
1074
+ */
1075
+ function createWriteFileTool(state, backend, onEvent) {
1076
+ return tool({
1077
+ description: WRITE_FILE_TOOL_DESCRIPTION,
1078
+ inputSchema: z.object({
1079
+ file_path: z.string().describe("Path to the file to write (e.g., 'src/main.ts' or './main.ts')"),
1080
+ content: z.string().describe("Content to write to the file")
1081
+ }),
1082
+ execute: async ({ file_path, content }) => {
1083
+ if (onEvent) onEvent(createFileWriteStartEvent(file_path, content));
1084
+ const result = await getBackend$1(backend, state).write(file_path, content);
1085
+ if (result.error) return result.error;
1086
+ if (onEvent) onEvent(createFileWrittenEvent(file_path, content));
1087
+ return `Successfully wrote to '${file_path}'`;
1088
+ }
1089
+ });
1090
+ }
1091
+ /**
1092
+ * Create the edit_file tool.
1093
+ */
1094
+ function createEditFileTool(state, backend, onEvent) {
1095
+ return tool({
1096
+ description: EDIT_FILE_TOOL_DESCRIPTION,
1097
+ inputSchema: z.object({
1098
+ file_path: z.string().describe("Path to the file to edit (e.g., 'src/main.ts' or './main.ts')"),
1099
+ old_string: z.string().describe("String to be replaced (must match exactly)"),
1100
+ new_string: z.string().describe("String to replace with"),
1101
+ replace_all: z.boolean().default(false).describe("Whether to replace all occurrences")
1102
+ }),
1103
+ execute: async ({ file_path, old_string, new_string, replace_all }) => {
1104
+ const result = await getBackend$1(backend, state).edit(file_path, old_string, new_string, replace_all ?? false);
1105
+ if (result.error) return result.error;
1106
+ if (onEvent) onEvent(createFileEditedEvent(file_path, result.occurrences ?? 0));
1107
+ return `Successfully replaced ${result.occurrences} occurrence(s) in '${file_path}'`;
1108
+ }
1109
+ });
1110
+ }
1111
+ /**
1112
+ * Create the glob tool.
1113
+ */
1114
+ function createGlobTool(state, backend, onEvent) {
1115
+ return tool({
1116
+ description: GLOB_TOOL_DESCRIPTION,
1117
+ inputSchema: z.object({
1118
+ pattern: z.string().describe("Glob pattern (e.g., '*.py', '**/*.ts')"),
1119
+ path: z.string().default(".").describe("Base path to search from (default: current directory)")
1120
+ }),
1121
+ execute: async ({ pattern, path }) => {
1122
+ const infos = await getBackend$1(backend, state).globInfo(pattern, path || ".");
1123
+ if (onEvent) onEvent({
1124
+ type: "glob",
1125
+ pattern,
1126
+ count: infos.length
1127
+ });
1128
+ if (infos.length === 0) return `No files found matching pattern '${pattern}'`;
1129
+ return infos.map((info) => info.path).join("\n");
1130
+ }
1131
+ });
1132
+ }
1133
+ /**
1134
+ * Create the grep tool.
1135
+ */
1136
+ function createGrepTool(state, backend, evictionLimit, onEvent) {
1137
+ return tool({
1138
+ description: GREP_TOOL_DESCRIPTION,
1139
+ inputSchema: z.object({
1140
+ pattern: z.string().describe("Regex pattern to search for"),
1141
+ path: z.string().default(".").describe("Base path to search from (default: current directory)"),
1142
+ glob: z.string().optional().nullable().describe("Optional glob pattern to filter files (e.g., '*.py')")
1143
+ }),
1144
+ execute: async ({ pattern, path, glob: glob$1 }, { toolCallId }) => {
1145
+ const resolvedBackend = getBackend$1(backend, state);
1146
+ const result = await resolvedBackend.grepRaw(pattern, path || ".", glob$1 ?? null);
1147
+ if (typeof result === "string") {
1148
+ if (onEvent) onEvent({
1149
+ type: "grep",
1150
+ pattern,
1151
+ count: 0
1152
+ });
1153
+ return result;
1154
+ }
1155
+ if (onEvent) onEvent({
1156
+ type: "grep",
1157
+ pattern,
1158
+ count: result.length
1159
+ });
1160
+ if (result.length === 0) return `No matches found for pattern '${pattern}'`;
1161
+ const lines = [];
1162
+ let currentFile = null;
1163
+ for (const match of result) {
1164
+ if (match.path !== currentFile) {
1165
+ currentFile = match.path;
1166
+ lines.push(`\n${currentFile}:`);
1167
+ }
1168
+ lines.push(` ${match.line}: ${match.text}`);
1169
+ }
1170
+ const content = lines.join("\n");
1171
+ if (evictionLimit && evictionLimit > 0) return (await evictToolResult({
1172
+ result: content,
1173
+ toolCallId: toolCallId || `grep_${Date.now()}`,
1174
+ toolName: "grep",
1175
+ backend: resolvedBackend,
1176
+ tokenLimit: evictionLimit
1177
+ })).content;
1178
+ return content;
1179
+ }
1180
+ });
1181
+ }
1182
+ /**
1183
+ * Create all filesystem tools.
1184
+ * @param state - The shared agent state
1185
+ * @param backendOrOptions - Backend or options object
1186
+ * @param onEvent - Optional callback for emitting events (deprecated, use options)
1187
+ */
1188
+ function createFilesystemTools(state, backendOrOptions, onEvent) {
1189
+ let backend;
1190
+ let eventCallback = onEvent;
1191
+ let evictionLimit;
1192
+ if (backendOrOptions && typeof backendOrOptions === "object" && "backend" in backendOrOptions) {
1193
+ const options = backendOrOptions;
1194
+ backend = options.backend;
1195
+ eventCallback = options.onEvent;
1196
+ evictionLimit = options.toolResultEvictionLimit;
1197
+ } else backend = backendOrOptions;
1198
+ const resolvedBackend = backend || ((s) => new StateBackend(s));
1199
+ return {
1200
+ ls: createLsTool(state, resolvedBackend, eventCallback),
1201
+ read_file: createReadFileTool(state, resolvedBackend, evictionLimit, eventCallback),
1202
+ write_file: createWriteFileTool(state, resolvedBackend, eventCallback),
1203
+ edit_file: createEditFileTool(state, resolvedBackend, eventCallback),
1204
+ glob: createGlobTool(state, resolvedBackend, eventCallback),
1205
+ grep: createGrepTool(state, resolvedBackend, evictionLimit, eventCallback)
1206
+ };
1207
+ }
1208
+ /**
1209
+ * Individual builtin tool references for selective subagent configuration.
1210
+ * These are references to the creator functions, not instances.
1211
+ */
1212
+ const ls = createLsTool;
1213
+ const read_file = createReadFileTool;
1214
+ const write_file = createWriteFileTool;
1215
+ const edit_file = createEditFileTool;
1216
+ const glob = createGlobTool;
1217
+ const grep = createGrepTool;
1218
+
1219
+ //#endregion
1220
+ //#region src/utils/approval.ts
1221
+ /**
1222
+ * Utilities for applying tool approval configuration.
1223
+ */
1224
+ /**
1225
+ * Check if approval is needed based on config.
1226
+ */
1227
+ async function checkNeedsApproval(config, args) {
1228
+ if (typeof config === "boolean") return config;
1229
+ if (config.shouldApprove) return config.shouldApprove(args);
1230
+ return true;
1231
+ }
1232
+ /**
1233
+ * Convert interruptOn config to needsApproval function for a tool.
1234
+ */
1235
+ function configToNeedsApproval(config) {
1236
+ if (typeof config === "boolean") return config;
1237
+ if (config.shouldApprove) return config.shouldApprove;
1238
+ return true;
1239
+ }
1240
+ let approvalCounter = 0;
1241
+ function generateApprovalId() {
1242
+ return `approval-${Date.now()}-${++approvalCounter}`;
1243
+ }
1244
+ /**
1245
+ * Apply interruptOn configuration to a toolset.
1246
+ *
1247
+ * This adds the `needsApproval` property to tools based on the config.
1248
+ *
1249
+ * @param tools - The original toolset
1250
+ * @param interruptOn - Configuration mapping tool names to approval settings
1251
+ * @returns New toolset with needsApproval applied
1252
+ *
1253
+ * @example
1254
+ * ```typescript
1255
+ * const approvedTools = applyInterruptConfig(tools, {
1256
+ * write_file: true,
1257
+ * execute: { shouldApprove: (args) => args.command.includes('rm') },
1258
+ * });
1259
+ * ```
1260
+ */
1261
+ function applyInterruptConfig(tools, interruptOn) {
1262
+ if (!interruptOn) return tools;
1263
+ const result = {};
1264
+ for (const [name, tool$1] of Object.entries(tools)) {
1265
+ const config = interruptOn[name];
1266
+ if (config === void 0 || config === false) result[name] = tool$1;
1267
+ else result[name] = {
1268
+ ...tool$1,
1269
+ needsApproval: configToNeedsApproval(config)
1270
+ };
1271
+ }
1272
+ return result;
1273
+ }
1274
+ /**
1275
+ * Wrap tools with approval checking that intercepts execution.
1276
+ *
1277
+ * Unlike applyInterruptConfig which just sets needsApproval metadata,
1278
+ * this actually wraps the execute function to request approval before running.
1279
+ *
1280
+ * If no approval callback is provided, tools requiring approval will be auto-denied.
1281
+ *
1282
+ * @param tools - The original toolset
1283
+ * @param interruptOn - Configuration mapping tool names to approval settings
1284
+ * @param onApprovalRequest - Callback to request approval from user (optional)
1285
+ * @returns New toolset with wrapped execute functions
1286
+ */
1287
+ function wrapToolsWithApproval(tools, interruptOn, onApprovalRequest) {
1288
+ if (!interruptOn) return tools;
1289
+ const result = {};
1290
+ for (const [name, existingTool] of Object.entries(tools)) {
1291
+ const config = interruptOn[name];
1292
+ if (config === void 0 || config === false) result[name] = existingTool;
1293
+ else {
1294
+ const originalExecute = existingTool.execute;
1295
+ if (!originalExecute) {
1296
+ result[name] = existingTool;
1297
+ continue;
1298
+ }
1299
+ result[name] = tool({
1300
+ description: existingTool.description,
1301
+ inputSchema: existingTool.inputSchema,
1302
+ execute: async (args, options) => {
1303
+ if (await checkNeedsApproval(config, args)) {
1304
+ if (!onApprovalRequest) return `Tool execution denied. No approval callback provided. The ${name} tool was not executed.`;
1305
+ const approvalId = generateApprovalId();
1306
+ if (!await onApprovalRequest({
1307
+ approvalId,
1308
+ toolCallId: options?.toolCallId || approvalId,
1309
+ toolName: name,
1310
+ args
1311
+ })) return `Tool execution denied by user. The ${name} tool was not executed.`;
1312
+ }
1313
+ return originalExecute(args, options);
1314
+ }
1315
+ });
1316
+ }
1317
+ }
1318
+ return result;
1319
+ }
1320
+
1321
+ //#endregion
1322
+ //#region src/tools/web.ts
1323
+ /**
1324
+ * Web tools for search and HTTP requests.
1325
+ * Based on LangChain DeepAgents implementation.
1326
+ */
1327
+ /**
1328
+ * Helper to resolve backend from factory or instance.
1329
+ */
1330
+ function getBackend(backend, state) {
1331
+ if (!backend) return null;
1332
+ if (typeof backend === "function") return backend(state);
1333
+ return backend;
1334
+ }
1335
+ /**
1336
+ * Convert HTML to Markdown with article extraction.
1337
+ * Uses Mozilla Readability to extract main content, then converts to Markdown.
1338
+ */
1339
+ function htmlToMarkdown(html, url) {
1340
+ try {
1341
+ const dom = new JSDOM(html, { url });
1342
+ const article = new Readability(dom.window.document).parse();
1343
+ if (!article) return (dom.window.document.body?.textContent || "").trim();
1344
+ const markdown = new TurndownService({
1345
+ headingStyle: "atx",
1346
+ codeBlockStyle: "fenced"
1347
+ }).turndown(article.content || "");
1348
+ if (article.title) return `# ${article.title}\n\n${markdown}`;
1349
+ return markdown;
1350
+ } catch (error) {
1351
+ return `Error converting HTML to Markdown: ${error instanceof Error ? error.message : String(error)}`;
1352
+ }
1353
+ }
1354
+ /**
1355
+ * Tool description for web_search.
1356
+ */
1357
+ const WEB_SEARCH_TOOL_DESCRIPTION = `Search the web using Tavily API for current information, news, and documentation.
1358
+
1359
+ Returns an array of search results with titles, URLs, relevant excerpts, and relevance scores.
1360
+
1361
+ IMPORTANT AGENT INSTRUCTIONS:
1362
+ - You MUST synthesize information from search results into a coherent answer
1363
+ - NEVER show raw JSON or result objects to the user
1364
+ - Cite sources by including URLs in your response
1365
+ - If search fails or returns no results, explain this clearly to the user`;
1366
+ /**
1367
+ * Create the web_search tool.
1368
+ */
1369
+ function createWebSearchTool(state, options) {
1370
+ const { backend, onEvent, toolResultEvictionLimit, tavilyApiKey } = options;
1371
+ return tool({
1372
+ description: WEB_SEARCH_TOOL_DESCRIPTION,
1373
+ inputSchema: z.object({
1374
+ query: z.string().describe("The search query (be specific and detailed for best results)"),
1375
+ max_results: z.number().default(5).describe("Number of results to return (1-20)"),
1376
+ topic: z.enum([
1377
+ "general",
1378
+ "news",
1379
+ "finance"
1380
+ ]).default("general").describe("Search topic category"),
1381
+ include_raw_content: z.boolean().default(false).describe("Include full page content (warning: uses more tokens)")
1382
+ }),
1383
+ execute: async ({ query, max_results, topic, include_raw_content }, { toolCallId }) => {
1384
+ if (onEvent) onEvent(createWebSearchStartEvent(query));
1385
+ try {
1386
+ const results = (await tavily({ apiKey: tavilyApiKey }).search(query, {
1387
+ maxResults: max_results,
1388
+ topic,
1389
+ includeRawContent: include_raw_content ? "text" : false
1390
+ })).results || [];
1391
+ const formattedResults = results.map((r, i) => `## Result ${i + 1}: ${r.title}\nURL: ${r.url}\nScore: ${r.score?.toFixed(2) || "N/A"}\nContent: ${r.content}\n`).join("\n---\n\n");
1392
+ const output = `Found ${results.length} results for query: "${query}"\n\n${formattedResults}`;
1393
+ if (onEvent) onEvent(createWebSearchFinishEvent(query, results.length));
1394
+ if (toolResultEvictionLimit && toolResultEvictionLimit > 0 && backend) {
1395
+ const resolvedBackend = getBackend(backend, state);
1396
+ if (resolvedBackend) return (await evictToolResult({
1397
+ result: output,
1398
+ toolCallId: toolCallId || `web_search_${Date.now()}`,
1399
+ toolName: "web_search",
1400
+ backend: resolvedBackend,
1401
+ tokenLimit: toolResultEvictionLimit
1402
+ })).content;
1403
+ }
1404
+ return output;
1405
+ } catch (error) {
1406
+ const errorMessage = WEB_SEARCH_ERROR(error.message);
1407
+ if (onEvent) onEvent(createWebSearchFinishEvent(query, 0));
1408
+ return errorMessage;
1409
+ }
1410
+ }
1411
+ });
1412
+ }
1413
+ /**
1414
+ * Tool description for http_request.
1415
+ */
1416
+ const HTTP_REQUEST_TOOL_DESCRIPTION = `Make HTTP requests to APIs and web services.
1417
+
1418
+ Supports GET, POST, PUT, DELETE, PATCH methods with custom headers, query parameters, and request bodies.
1419
+
1420
+ Returns structured response with status code, headers, and parsed content (JSON or text).`;
1421
+ /**
1422
+ * Create the http_request tool.
1423
+ */
1424
+ function createHttpRequestTool(state, options) {
1425
+ const { backend, onEvent, toolResultEvictionLimit, defaultTimeout } = options;
1426
+ return tool({
1427
+ description: HTTP_REQUEST_TOOL_DESCRIPTION,
1428
+ inputSchema: z.object({
1429
+ url: z.string().url().describe("Target URL (must be valid HTTP/HTTPS URL)"),
1430
+ method: z.enum([
1431
+ "GET",
1432
+ "POST",
1433
+ "PUT",
1434
+ "DELETE",
1435
+ "PATCH"
1436
+ ]).default("GET").describe("HTTP method"),
1437
+ headers: z.record(z.string()).optional().describe("HTTP headers as key-value pairs"),
1438
+ body: z.union([z.string(), z.record(z.any())]).optional().describe("Request body (string or JSON object)"),
1439
+ params: z.record(z.string()).optional().describe("URL query parameters as key-value pairs"),
1440
+ timeout: z.number().default(defaultTimeout).describe("Request timeout in seconds")
1441
+ }),
1442
+ execute: async ({ url, method, headers, body, params, timeout }, { toolCallId }) => {
1443
+ if (onEvent) onEvent(createHttpRequestStartEvent(url, method));
1444
+ try {
1445
+ const urlObj = new URL(url);
1446
+ if (params) Object.entries(params).forEach(([key, value]) => {
1447
+ urlObj.searchParams.append(key, value);
1448
+ });
1449
+ const requestOptions = {
1450
+ method,
1451
+ headers: headers || {},
1452
+ signal: AbortSignal.timeout(timeout * 1e3)
1453
+ };
1454
+ if (body) if (typeof body === "string") requestOptions.body = body;
1455
+ else {
1456
+ requestOptions.body = JSON.stringify(body);
1457
+ requestOptions.headers["Content-Type"] = "application/json";
1458
+ }
1459
+ const response = await fetch(urlObj.toString(), requestOptions);
1460
+ const contentType = response.headers.get("content-type") || "";
1461
+ let content;
1462
+ if (contentType.includes("application/json")) try {
1463
+ content = await response.json();
1464
+ } catch {
1465
+ content = await response.text();
1466
+ }
1467
+ else content = await response.text();
1468
+ const formattedOutput = `HTTP ${method} ${url}\nStatus: ${response.status}\nSuccess: ${response.ok}\nContent:\n${typeof content === "string" ? content : JSON.stringify(content, null, 2)}`;
1469
+ if (onEvent) onEvent(createHttpRequestFinishEvent(response.url, response.status));
1470
+ if (toolResultEvictionLimit && toolResultEvictionLimit > 0 && backend) {
1471
+ const resolvedBackend = getBackend(backend, state);
1472
+ if (resolvedBackend) return (await evictToolResult({
1473
+ result: formattedOutput,
1474
+ toolCallId: toolCallId || `http_request_${Date.now()}`,
1475
+ toolName: "http_request",
1476
+ backend: resolvedBackend,
1477
+ tokenLimit: toolResultEvictionLimit
1478
+ })).content;
1479
+ }
1480
+ return formattedOutput;
1481
+ } catch (error) {
1482
+ const err = error;
1483
+ let errorMessage;
1484
+ if (err.name === "TimeoutError" || err.name === "AbortError") errorMessage = REQUEST_TIMEOUT(timeout);
1485
+ else errorMessage = `HTTP request error: ${err.message}`;
1486
+ if (onEvent) onEvent(createHttpRequestFinishEvent(url, 0));
1487
+ return errorMessage;
1488
+ }
1489
+ }
1490
+ });
1491
+ }
1492
+ /**
1493
+ * Tool description for fetch_url.
1494
+ */
1495
+ const FETCH_URL_TOOL_DESCRIPTION = `Fetch web page content and convert HTML to clean Markdown format.
1496
+
1497
+ Uses Mozilla Readability to extract main article content and Turndown to convert to Markdown.
1498
+
1499
+ Returns the page content as formatted Markdown, suitable for analysis and summarization.
1500
+
1501
+ IMPORTANT AGENT INSTRUCTIONS:
1502
+ - Use this tool to read documentation, articles, and web pages
1503
+ - The content is already cleaned and formatted as Markdown
1504
+ - Cite the URL when referencing fetched content`;
1505
+ /**
1506
+ * Create the fetch_url tool.
1507
+ */
1508
+ function createFetchUrlTool(state, options) {
1509
+ const { backend, onEvent, toolResultEvictionLimit, defaultTimeout } = options;
1510
+ return tool({
1511
+ description: FETCH_URL_TOOL_DESCRIPTION,
1512
+ inputSchema: z.object({
1513
+ url: z.string().url().describe("The URL to fetch (must be valid HTTP/HTTPS URL)"),
1514
+ timeout: z.number().default(defaultTimeout).describe("Request timeout in seconds"),
1515
+ extract_article: z.boolean().default(true).describe("Extract main article content using Readability (disable for non-article pages)")
1516
+ }),
1517
+ execute: async ({ url, timeout, extract_article }, { toolCallId }) => {
1518
+ if (onEvent) onEvent(createFetchUrlStartEvent(url));
1519
+ try {
1520
+ const response = await fetch(url, {
1521
+ signal: AbortSignal.timeout(timeout * 1e3),
1522
+ headers: { "User-Agent": "Mozilla/5.0 (compatible; DeepAgents/1.0)" }
1523
+ });
1524
+ if (!response.ok) {
1525
+ const errorMsg = `HTTP error: ${response.status} ${response.statusText}`;
1526
+ if (onEvent) onEvent(createFetchUrlFinishEvent(response.url, false));
1527
+ return errorMsg;
1528
+ }
1529
+ const html = await response.text();
1530
+ const dom = new JSDOM(html, { url });
1531
+ let contentToConvert = html;
1532
+ if (extract_article) try {
1533
+ const article = new Readability(dom.window.document).parse();
1534
+ if (article && article.content) contentToConvert = article.content;
1535
+ } catch (readabilityError) {
1536
+ console.warn("Readability extraction failed, using full HTML");
1537
+ }
1538
+ const markdown = new TurndownService({
1539
+ headingStyle: "atx",
1540
+ codeBlockStyle: "fenced"
1541
+ }).turndown(contentToConvert);
1542
+ if (onEvent) onEvent(createFetchUrlFinishEvent(response.url, true));
1543
+ if (toolResultEvictionLimit && toolResultEvictionLimit > 0 && backend) {
1544
+ const resolvedBackend = getBackend(backend, state);
1545
+ if (resolvedBackend) return (await evictToolResult({
1546
+ result: markdown,
1547
+ toolCallId: toolCallId || `fetch_url_${Date.now()}`,
1548
+ toolName: "fetch_url",
1549
+ backend: resolvedBackend,
1550
+ tokenLimit: toolResultEvictionLimit
1551
+ })).content;
1552
+ }
1553
+ return markdown;
1554
+ } catch (error) {
1555
+ const err = error;
1556
+ let errorMessage;
1557
+ if (err.name === "TimeoutError" || err.name === "AbortError") errorMessage = REQUEST_TIMEOUT(timeout);
1558
+ else errorMessage = `Error fetching URL: ${err.message}`;
1559
+ if (onEvent) onEvent(createFetchUrlFinishEvent(url, false));
1560
+ return errorMessage;
1561
+ }
1562
+ }
1563
+ });
1564
+ }
1565
+ /**
1566
+ * Create all web tools (web_search, http_request, fetch_url).
1567
+ * Tools are only created if TAVILY_API_KEY is available.
1568
+ */
1569
+ function createWebTools(state, options) {
1570
+ const { backend, onEvent, toolResultEvictionLimit, tavilyApiKey = process.env.TAVILY_API_KEY, defaultTimeout = DEFAULT_TIMEOUT_SECONDS } = options || {};
1571
+ if (!tavilyApiKey) {
1572
+ console.warn("Tavily API key not found. Web tools (web_search, fetch_url, http_request) will not be available. Set TAVILY_API_KEY environment variable to enable web tools.");
1573
+ return {};
1574
+ }
1575
+ return {
1576
+ web_search: createWebSearchTool(state, {
1577
+ backend,
1578
+ onEvent,
1579
+ toolResultEvictionLimit,
1580
+ tavilyApiKey
1581
+ }),
1582
+ http_request: createHttpRequestTool(state, {
1583
+ backend,
1584
+ onEvent,
1585
+ toolResultEvictionLimit,
1586
+ defaultTimeout
1587
+ }),
1588
+ fetch_url: createFetchUrlTool(state, {
1589
+ backend,
1590
+ onEvent,
1591
+ toolResultEvictionLimit,
1592
+ defaultTimeout
1593
+ })
1594
+ };
1595
+ }
1596
+ /**
1597
+ * Individual builtin tool references for selective subagent configuration.
1598
+ * These are references to the creator functions, not instances.
1599
+ */
1600
+ const web_search = createWebSearchTool;
1601
+ const http_request = createHttpRequestTool;
1602
+ const fetch_url = createFetchUrlTool;
1603
+
1604
+ //#endregion
1605
+ //#region src/tools/execute.ts
1606
+ /**
1607
+ * Execute tool for running shell commands in sandbox backends.
1608
+ *
1609
+ * This tool is only available when the backend implements SandboxBackendProtocol.
1610
+ */
1611
+ /**
1612
+ * Tool description for the execute tool.
1613
+ */
1614
+ const EXECUTE_TOOL_DESCRIPTION = `Execute a shell command in the sandbox environment.
1615
+
1616
+ Use this tool to:
1617
+ - Run build commands (npm install, npm run build, bun install)
1618
+ - Run tests (npm test, bun test, pytest)
1619
+ - Execute scripts (node script.js, python script.py)
1620
+ - Check system state (ls, cat, pwd, which)
1621
+ - Install dependencies
1622
+ - Run any shell command
1623
+
1624
+ The command runs in the sandbox's working directory. Commands have a timeout limit.
1625
+
1626
+ IMPORTANT:
1627
+ - Always check the exit code to determine success (0 = success)
1628
+ - Long-running commands may timeout
1629
+ - Use && to chain commands that depend on each other
1630
+ - Use ; to run commands sequentially regardless of success`;
1631
+ /**
1632
+ * Create an execute tool for running shell commands.
1633
+ *
1634
+ * @param options - Options including the sandbox backend and optional event callback
1635
+ * @returns An AI SDK tool that executes shell commands
1636
+ *
1637
+ * @example Basic usage
1638
+ * ```typescript
1639
+ * import { LocalSandbox, createExecuteTool } from 'deepagentsdk';
1640
+ *
1641
+ * const sandbox = new LocalSandbox({ cwd: './workspace' });
1642
+ * const executeTool = createExecuteTool({ backend: sandbox });
1643
+ *
1644
+ * // Use with agent
1645
+ * const agent = createDeepAgent({
1646
+ * model: anthropic('claude-sonnet-4-20250514'),
1647
+ * backend: sandbox,
1648
+ * tools: { execute: executeTool },
1649
+ * });
1650
+ * ```
1651
+ *
1652
+ * @example With event streaming
1653
+ * ```typescript
1654
+ * const executeTool = createExecuteTool({
1655
+ * backend: sandbox,
1656
+ * onEvent: (event) => {
1657
+ * if (event.type === 'execute-start') {
1658
+ * console.log(`Running: ${event.command}`);
1659
+ * } else if (event.type === 'execute-finish') {
1660
+ * console.log(`Exit code: ${event.exitCode}`);
1661
+ * }
1662
+ * },
1663
+ * });
1664
+ * ```
1665
+ */
1666
+ function createExecuteTool(options) {
1667
+ const { backend, onEvent, description } = options;
1668
+ return tool({
1669
+ description: description || EXECUTE_TOOL_DESCRIPTION,
1670
+ inputSchema: z.object({ command: z.string().describe("The shell command to execute (e.g., 'npm install', 'ls -la', 'cat file.txt')") }),
1671
+ execute: async ({ command }) => {
1672
+ if (onEvent) onEvent({
1673
+ type: "execute-start",
1674
+ command,
1675
+ sandboxId: backend.id
1676
+ });
1677
+ const result = await backend.execute(command);
1678
+ if (onEvent) onEvent({
1679
+ type: "execute-finish",
1680
+ command,
1681
+ exitCode: result.exitCode,
1682
+ truncated: result.truncated,
1683
+ sandboxId: backend.id
1684
+ });
1685
+ const parts = [];
1686
+ if (result.output) parts.push(result.output);
1687
+ if (result.exitCode === 0) parts.push(`\n[Exit code: 0 (success)]`);
1688
+ else if (result.exitCode !== null) parts.push(`\n[Exit code: ${result.exitCode} (failure)]`);
1689
+ else parts.push(`\n[Exit code: unknown (possibly timed out)]`);
1690
+ if (result.truncated) parts.push(`[Output truncated due to size limit]`);
1691
+ return parts.join("");
1692
+ }
1693
+ });
1694
+ }
1695
+ /**
1696
+ * Convenience function to create execute tool from just a backend.
1697
+ * Useful for simple cases without event handling.
1698
+ *
1699
+ * @param backend - The sandbox backend
1700
+ * @returns An AI SDK tool that executes shell commands
1701
+ *
1702
+ * @example
1703
+ * ```typescript
1704
+ * const sandbox = new LocalSandbox({ cwd: './workspace' });
1705
+ * const tools = {
1706
+ * execute: createExecuteToolFromBackend(sandbox),
1707
+ * };
1708
+ * ```
1709
+ */
1710
+ function createExecuteToolFromBackend(backend) {
1711
+ return createExecuteTool({ backend });
1712
+ }
1713
+ /**
1714
+ * Individual builtin tool reference for selective subagent configuration.
1715
+ * This is a reference to the creator function, not an instance.
1716
+ */
1717
+ const execute = createExecuteTool;
1718
+
1719
+ //#endregion
1720
+ //#region src/tools/subagent.ts
1721
+ /**
1722
+ * Subagent tool for task delegation using AI SDK v6 ToolLoopAgent.
1723
+ */
1724
+ /**
1725
+ * Check if a value is a builtin tool creator function.
1726
+ */
1727
+ function isBuiltinToolCreator(value) {
1728
+ return typeof value === "function" && (value === createWebSearchTool || value === createHttpRequestTool || value === createFetchUrlTool || value === createLsTool || value === createReadFileTool || value === createWriteFileTool || value === createEditFileTool || value === createGlobTool || value === createGrepTool || value === createTodosTool || value === createExecuteTool);
1729
+ }
1730
+ /**
1731
+ * Instantiate a builtin tool creator with the given context.
1732
+ */
1733
+ function instantiateBuiltinTool(creator, state, options) {
1734
+ const { backend, onEvent, toolResultEvictionLimit } = options;
1735
+ const tavilyApiKey = process.env.TAVILY_API_KEY || "";
1736
+ const defaultTimeout = DEFAULT_TIMEOUT_SECONDS;
1737
+ if (creator === createWebSearchTool) {
1738
+ if (!tavilyApiKey) {
1739
+ console.warn("web_search tool requested but TAVILY_API_KEY not set");
1740
+ return {};
1741
+ }
1742
+ return { web_search: createWebSearchTool(state, {
1743
+ backend,
1744
+ onEvent,
1745
+ toolResultEvictionLimit,
1746
+ tavilyApiKey
1747
+ }) };
1748
+ }
1749
+ if (creator === createHttpRequestTool) return { http_request: createHttpRequestTool(state, {
1750
+ backend,
1751
+ onEvent,
1752
+ toolResultEvictionLimit,
1753
+ defaultTimeout
1754
+ }) };
1755
+ if (creator === createFetchUrlTool) return { fetch_url: createFetchUrlTool(state, {
1756
+ backend,
1757
+ onEvent,
1758
+ toolResultEvictionLimit,
1759
+ defaultTimeout
1760
+ }) };
1761
+ if (creator === createLsTool) return { ls: createLsTool(state, backend, onEvent) };
1762
+ if (creator === createReadFileTool) return { read_file: createReadFileTool(state, backend, toolResultEvictionLimit, onEvent) };
1763
+ if (creator === createWriteFileTool) return { write_file: createWriteFileTool(state, backend, onEvent) };
1764
+ if (creator === createEditFileTool) return { edit_file: createEditFileTool(state, backend, onEvent) };
1765
+ if (creator === createGlobTool) return { glob: createGlobTool(state, backend, onEvent) };
1766
+ if (creator === createGrepTool) return { grep: createGrepTool(state, backend, toolResultEvictionLimit, onEvent) };
1767
+ if (creator === createTodosTool) return { write_todos: createTodosTool(state, onEvent) };
1768
+ if (creator === createExecuteTool) throw new Error("execute tool cannot be used via selective tools - it requires a SandboxBackendProtocol");
1769
+ throw new Error(`Unknown builtin tool creator: ${creator}`);
1770
+ }
1771
+ /**
1772
+ * Process subagent tool configuration (array or ToolSet) into a ToolSet.
1773
+ */
1774
+ function processSubagentTools(toolConfig, state, options) {
1775
+ if (!toolConfig) return {};
1776
+ if (!Array.isArray(toolConfig)) return toolConfig;
1777
+ let result = {};
1778
+ for (const item of toolConfig) if (isBuiltinToolCreator(item)) {
1779
+ const instantiated = instantiateBuiltinTool(item, state, options);
1780
+ result = {
1781
+ ...result,
1782
+ ...instantiated
1783
+ };
1784
+ } else if (typeof item === "object" && item !== null) result = {
1785
+ ...result,
1786
+ ...item
1787
+ };
1788
+ return result;
1789
+ }
1790
+ /**
1791
+ * Build the system prompt for a subagent.
1792
+ */
1793
+ function buildSubagentSystemPrompt(customPrompt) {
1794
+ return `${customPrompt}
1795
+
1796
+ ${BASE_PROMPT}
1797
+
1798
+ ${TODO_SYSTEM_PROMPT}
1799
+
1800
+ ${FILESYSTEM_SYSTEM_PROMPT}`;
1801
+ }
1802
+ /**
1803
+ * Create the task tool for spawning subagents using ToolLoopAgent.
1804
+ */
1805
+ function createSubagentTool(state, options) {
1806
+ const { defaultModel, defaultTools = {}, subagents = [], includeGeneralPurposeAgent = true, backend, taskDescription = null, onEvent, interruptOn, parentGenerationOptions, parentAdvancedOptions } = options;
1807
+ const subagentRegistry = {};
1808
+ const subagentDescriptions = [];
1809
+ if (includeGeneralPurposeAgent) {
1810
+ subagentRegistry["general-purpose"] = {
1811
+ systemPrompt: buildSubagentSystemPrompt(DEFAULT_SUBAGENT_PROMPT),
1812
+ toolConfig: defaultTools,
1813
+ model: defaultModel
1814
+ };
1815
+ subagentDescriptions.push(`- general-purpose: ${DEFAULT_GENERAL_PURPOSE_DESCRIPTION}`);
1816
+ }
1817
+ for (const subagent of subagents) {
1818
+ subagentRegistry[subagent.name] = {
1819
+ systemPrompt: buildSubagentSystemPrompt(subagent.systemPrompt),
1820
+ toolConfig: subagent.tools || defaultTools,
1821
+ model: subagent.model || defaultModel,
1822
+ output: subagent.output
1823
+ };
1824
+ subagentDescriptions.push(`- ${subagent.name}: ${subagent.description}`);
1825
+ }
1826
+ return tool({
1827
+ description: taskDescription || getTaskToolDescription(subagentDescriptions),
1828
+ inputSchema: z.object({
1829
+ description: z.string().describe("The task to execute with the selected agent"),
1830
+ subagent_type: z.string().describe(`Name of the agent to use. Available: ${Object.keys(subagentRegistry).join(", ")}`)
1831
+ }),
1832
+ execute: async ({ description, subagent_type }) => {
1833
+ if (!(subagent_type in subagentRegistry)) return `Error: invoked agent of type ${subagent_type}, the only allowed types are ${Object.keys(subagentRegistry).map((k) => `\`${k}\``).join(", ")}`;
1834
+ const subagentConfig = subagentRegistry[subagent_type];
1835
+ const subagentSpec = subagents.find((sa) => sa.name === subagent_type);
1836
+ const subagentInterruptOn = subagentSpec?.interruptOn ?? interruptOn;
1837
+ const mergedGenerationOptions = {
1838
+ ...parentGenerationOptions,
1839
+ ...subagentSpec?.generationOptions
1840
+ };
1841
+ const mergedAdvancedOptions = {
1842
+ ...parentAdvancedOptions,
1843
+ ...subagentSpec?.advancedOptions
1844
+ };
1845
+ if (onEvent) onEvent(createSubagentStartEvent(subagent_type, description));
1846
+ const subagentState = {
1847
+ todos: [],
1848
+ files: state.files
1849
+ };
1850
+ const customTools = processSubagentTools(subagentConfig.toolConfig, subagentState, {
1851
+ backend,
1852
+ onEvent
1853
+ });
1854
+ let allTools = {
1855
+ write_todos: createTodosTool(subagentState, onEvent),
1856
+ ...createFilesystemTools(subagentState, backend, onEvent),
1857
+ ...customTools
1858
+ };
1859
+ allTools = applyInterruptConfig(allTools, subagentInterruptOn);
1860
+ try {
1861
+ const subagentSettings = {
1862
+ model: subagentConfig.model,
1863
+ instructions: subagentConfig.systemPrompt,
1864
+ tools: allTools,
1865
+ stopWhen: stepCountIs(DEFAULT_SUBAGENT_MAX_STEPS),
1866
+ ...subagentConfig.output ? { output: Output.object(subagentConfig.output) } : {}
1867
+ };
1868
+ if (Object.keys(mergedGenerationOptions).length > 0) Object.assign(subagentSettings, mergedGenerationOptions);
1869
+ if (mergedAdvancedOptions) {
1870
+ const { toolChoice, activeTools, ...safeAdvancedOptions } = mergedAdvancedOptions;
1871
+ Object.assign(subagentSettings, safeAdvancedOptions);
1872
+ }
1873
+ let subagentStepCount = 0;
1874
+ subagentSettings.onStepFinish = async ({ toolCalls, toolResults }) => {
1875
+ if (onEvent && toolCalls && toolCalls.length > 0) {
1876
+ const toolCallsWithResults = toolCalls.map((tc, index) => ({
1877
+ toolName: tc.toolName,
1878
+ args: tc.args,
1879
+ result: toolResults[index]
1880
+ }));
1881
+ onEvent(createSubagentStepEvent(subagentStepCount++, toolCallsWithResults));
1882
+ }
1883
+ };
1884
+ const result = await new ToolLoopAgent(subagentSettings).generate({ prompt: description });
1885
+ state.files = {
1886
+ ...state.files,
1887
+ ...subagentState.files
1888
+ };
1889
+ const resultText = result.text || "Task completed successfully.";
1890
+ let formattedResult = resultText;
1891
+ if (subagentConfig.output && "output" in result && result.output) formattedResult = `${resultText}\n\n[Structured Output]\n${JSON.stringify(result.output, null, 2)}`;
1892
+ if (onEvent) onEvent(createSubagentFinishEvent(subagent_type, formattedResult));
1893
+ return formattedResult;
1894
+ } catch (error) {
1895
+ const errorMessage = `Error executing subagent: ${error.message}`;
1896
+ if (onEvent) onEvent(createSubagentFinishEvent(subagent_type, errorMessage));
1897
+ return errorMessage;
1898
+ }
1899
+ }
1900
+ });
1901
+ }
1902
+
1903
+ //#endregion
1904
+ //#region src/utils/patch-tool-calls.ts
1905
+ /**
1906
+ * Check if a message is an assistant message with tool calls.
1907
+ */
1908
+ function hasToolCalls(message) {
1909
+ if (message.role !== "assistant") return false;
1910
+ const content = message.content;
1911
+ if (Array.isArray(content)) return content.some((part) => typeof part === "object" && part !== null && "type" in part && part.type === "tool-call");
1912
+ return false;
1913
+ }
1914
+ /**
1915
+ * Extract tool call IDs from an assistant message.
1916
+ */
1917
+ function getToolCallIds(message) {
1918
+ if (message.role !== "assistant") return [];
1919
+ const content = message.content;
1920
+ if (!Array.isArray(content)) return [];
1921
+ const ids = [];
1922
+ for (const part of content) if (typeof part === "object" && part !== null && "type" in part && part.type === "tool-call" && "toolCallId" in part) ids.push(part.toolCallId);
1923
+ return ids;
1924
+ }
1925
+ /**
1926
+ * Check if a message is a tool result for a specific tool call ID.
1927
+ */
1928
+ function isToolResultFor(message, toolCallId) {
1929
+ if (message.role !== "tool") return false;
1930
+ if ("toolCallId" in message && message.toolCallId === toolCallId) return true;
1931
+ const content = message.content;
1932
+ if (Array.isArray(content)) return content.some((part) => typeof part === "object" && part !== null && "type" in part && part.type === "tool-result" && "toolCallId" in part && part.toolCallId === toolCallId);
1933
+ return false;
1934
+ }
1935
+ /**
1936
+ * Create a synthetic tool result message for a cancelled tool call.
1937
+ */
1938
+ function createCancelledToolResult(toolCallId, toolName) {
1939
+ return {
1940
+ role: "tool",
1941
+ content: [{
1942
+ type: "tool-result",
1943
+ toolCallId,
1944
+ toolName,
1945
+ output: {
1946
+ type: "text",
1947
+ value: `Tool call ${toolName} with id ${toolCallId} was cancelled - another message came in before it could be completed.`
1948
+ }
1949
+ }]
1950
+ };
1951
+ }
1952
+ /**
1953
+ * Get tool name from a tool call part.
1954
+ */
1955
+ function getToolName(message, toolCallId) {
1956
+ if (message.role !== "assistant") return "unknown";
1957
+ const content = message.content;
1958
+ if (!Array.isArray(content)) return "unknown";
1959
+ for (const part of content) if (typeof part === "object" && part !== null && "type" in part && part.type === "tool-call" && "toolCallId" in part && part.toolCallId === toolCallId && "toolName" in part) return part.toolName;
1960
+ return "unknown";
1961
+ }
1962
+ /**
1963
+ * Patch dangling tool calls in a message array.
1964
+ *
1965
+ * Scans for assistant messages with tool_calls that don't have corresponding
1966
+ * tool result messages, and adds synthetic "cancelled" responses.
1967
+ *
1968
+ * @param messages - Array of messages to patch
1969
+ * @returns New array with patched messages (original array is not modified)
1970
+ *
1971
+ * @example
1972
+ * ```typescript
1973
+ * const messages = [
1974
+ * { role: "user", content: "Hello" },
1975
+ * { role: "assistant", content: [{ type: "tool-call", toolCallId: "1", toolName: "search" }] },
1976
+ * // Missing tool result for tool call "1"
1977
+ * { role: "user", content: "Never mind" },
1978
+ * ];
1979
+ *
1980
+ * const patched = patchToolCalls(messages);
1981
+ * // patched now includes a synthetic tool result for the dangling call
1982
+ * ```
1983
+ */
1984
+ function patchToolCalls(messages) {
1985
+ if (!messages || messages.length === 0) return messages;
1986
+ const result = [];
1987
+ for (let i = 0; i < messages.length; i++) {
1988
+ const message = messages[i];
1989
+ if (!message) continue;
1990
+ result.push(message);
1991
+ if (hasToolCalls(message)) {
1992
+ const toolCallIds = getToolCallIds(message);
1993
+ for (const toolCallId of toolCallIds) {
1994
+ let hasResult = false;
1995
+ for (let j = i + 1; j < messages.length; j++) {
1996
+ const subsequentMsg = messages[j];
1997
+ if (subsequentMsg && isToolResultFor(subsequentMsg, toolCallId)) {
1998
+ hasResult = true;
1999
+ break;
2000
+ }
2001
+ }
2002
+ if (!hasResult) {
2003
+ const toolName = getToolName(message, toolCallId);
2004
+ result.push(createCancelledToolResult(toolCallId, toolName));
2005
+ }
2006
+ }
2007
+ }
2008
+ }
2009
+ return result;
2010
+ }
2011
+ /**
2012
+ * Check if messages have any dangling tool calls.
2013
+ *
2014
+ * @param messages - Array of messages to check
2015
+ * @returns True if there are dangling tool calls
2016
+ */
2017
+ function hasDanglingToolCalls(messages) {
2018
+ if (!messages || messages.length === 0) return false;
2019
+ for (let i = 0; i < messages.length; i++) {
2020
+ const message = messages[i];
2021
+ if (!message) continue;
2022
+ if (hasToolCalls(message)) {
2023
+ const toolCallIds = getToolCallIds(message);
2024
+ for (const toolCallId of toolCallIds) {
2025
+ let hasResult = false;
2026
+ for (let j = i + 1; j < messages.length; j++) {
2027
+ const subsequentMsg = messages[j];
2028
+ if (subsequentMsg && isToolResultFor(subsequentMsg, toolCallId)) {
2029
+ hasResult = true;
2030
+ break;
2031
+ }
2032
+ }
2033
+ if (!hasResult) return true;
2034
+ }
2035
+ }
2036
+ }
2037
+ return false;
2038
+ }
2039
+
2040
+ //#endregion
2041
+ //#region src/utils/summarization.ts
2042
+ /**
2043
+ * Conversation summarization utility.
2044
+ *
2045
+ * Automatically summarizes older messages when approaching token limits
2046
+ * to prevent context overflow while preserving important context.
2047
+ */
2048
+ /**
2049
+ * Default token threshold before triggering summarization.
2050
+ * 170k tokens is a safe threshold for most models.
2051
+ */
2052
+ const DEFAULT_SUMMARIZATION_THRESHOLD = DEFAULT_SUMMARIZATION_THRESHOLD$1;
2053
+ /**
2054
+ * Default number of recent messages to keep intact.
2055
+ */
2056
+ const DEFAULT_KEEP_MESSAGES = DEFAULT_KEEP_MESSAGES$1;
2057
+ /**
2058
+ * Estimate total tokens in a messages array.
2059
+ */
2060
+ function estimateMessagesTokens(messages) {
2061
+ let total = 0;
2062
+ for (const message of messages) if (typeof message.content === "string") total += estimateTokens(message.content);
2063
+ else if (Array.isArray(message.content)) {
2064
+ for (const part of message.content) if (typeof part === "object" && part !== null && "text" in part) total += estimateTokens(String(part.text));
2065
+ }
2066
+ return total;
2067
+ }
2068
+ /**
2069
+ * Extract text content from a message.
2070
+ */
2071
+ function getMessageText(message) {
2072
+ if (typeof message.content === "string") return message.content;
2073
+ if (Array.isArray(message.content)) return message.content.map((part) => {
2074
+ if (typeof part === "object" && part !== null && "text" in part) return String(part.text);
2075
+ if (typeof part === "object" && part !== null && "type" in part) {
2076
+ if (part.type === "tool-call") return `[Tool call: ${part.toolName || "unknown"}]`;
2077
+ if (part.type === "tool-result") return `[Tool result]`;
2078
+ }
2079
+ return "";
2080
+ }).filter(Boolean).join("\n");
2081
+ return "";
2082
+ }
2083
+ /**
2084
+ * Format messages for summarization prompt.
2085
+ */
2086
+ function formatMessagesForSummary(messages) {
2087
+ return messages.map((msg) => {
2088
+ return `${msg.role === "user" ? "User" : msg.role === "assistant" ? "Assistant" : "System"}: ${getMessageText(msg)}`;
2089
+ }).join("\n\n");
2090
+ }
2091
+ /**
2092
+ * Generate a summary of conversation messages.
2093
+ */
2094
+ async function generateSummary(messages, model, generationOptions, advancedOptions) {
2095
+ const generateTextOptions = {
2096
+ model,
2097
+ system: `You are a conversation summarizer. Your task is to create a concise but comprehensive summary of the conversation that preserves:
2098
+ 1. Key decisions and conclusions
2099
+ 2. Important context and background information
2100
+ 3. Any tasks or todos mentioned
2101
+ 4. Technical details that may be referenced later
2102
+ 5. The overall flow and progression of the conversation
2103
+
2104
+ Keep the summary focused and avoid redundancy. The summary should allow someone to understand the conversation context without reading the full history.`,
2105
+ prompt: `Please summarize the following conversation:\n\n${formatMessagesForSummary(messages)}`
2106
+ };
2107
+ if (generationOptions) Object.assign(generateTextOptions, generationOptions);
2108
+ if (advancedOptions) Object.assign(generateTextOptions, advancedOptions);
2109
+ return (await generateText(generateTextOptions)).text;
2110
+ }
2111
+ /**
2112
+ * Summarize older messages when approaching token limits.
2113
+ *
2114
+ * This function checks if the total tokens in the messages exceed the threshold.
2115
+ * If so, it summarizes older messages while keeping recent ones intact.
2116
+ *
2117
+ * @param messages - Array of conversation messages
2118
+ * @param options - Summarization options
2119
+ * @returns Processed messages with optional summary
2120
+ *
2121
+ * @example
2122
+ * ```typescript
2123
+ * import { anthropic } from '@ai-sdk/anthropic';
2124
+ *
2125
+ * const result = await summarizeIfNeeded(messages, {
2126
+ * model: anthropic('claude-haiku-4-5-20251001'),
2127
+ * tokenThreshold: 170000,
2128
+ * keepMessages: 6,
2129
+ * });
2130
+ *
2131
+ * if (result.summarized) {
2132
+ * console.log(`Reduced from ${result.tokensBefore} to ${result.tokensAfter} tokens`);
2133
+ * }
2134
+ * ```
2135
+ */
2136
+ async function summarizeIfNeeded(messages, options) {
2137
+ const { model, tokenThreshold = DEFAULT_SUMMARIZATION_THRESHOLD, keepMessages = DEFAULT_KEEP_MESSAGES } = options;
2138
+ const tokensBefore = estimateMessagesTokens(messages);
2139
+ if (tokensBefore < tokenThreshold) return {
2140
+ summarized: false,
2141
+ messages,
2142
+ tokensBefore
2143
+ };
2144
+ if (messages.length <= keepMessages) return {
2145
+ summarized: false,
2146
+ messages,
2147
+ tokensBefore
2148
+ };
2149
+ const messagesToSummarize = messages.slice(0, -keepMessages);
2150
+ const messagesToKeep = messages.slice(-keepMessages);
2151
+ const newMessages = [{
2152
+ role: "system",
2153
+ content: `[Previous conversation summary]\n${await generateSummary(messagesToSummarize, model, options.generationOptions, options.advancedOptions)}\n\n[End of summary - recent messages follow]`
2154
+ }, ...messagesToKeep];
2155
+ return {
2156
+ summarized: true,
2157
+ messages: newMessages,
2158
+ tokensBefore,
2159
+ tokensAfter: estimateMessagesTokens(newMessages)
2160
+ };
2161
+ }
2162
+ /**
2163
+ * Check if messages need summarization without performing it.
2164
+ */
2165
+ function needsSummarization(messages, tokenThreshold = DEFAULT_SUMMARIZATION_THRESHOLD) {
2166
+ return estimateMessagesTokens(messages) >= tokenThreshold;
2167
+ }
2168
+
2169
+ //#endregion
2170
+ //#region src/agent.ts
2171
+ /**
2172
+ * Deep Agent implementation using Vercel AI SDK v6 ToolLoopAgent.
2173
+ */
2174
+ /**
2175
+ * Build the full system prompt from components.
2176
+ */
2177
+ function buildSystemPrompt(customPrompt, hasSubagents, hasSandbox, skills) {
2178
+ const parts = [
2179
+ customPrompt || "",
2180
+ BASE_PROMPT,
2181
+ TODO_SYSTEM_PROMPT,
2182
+ FILESYSTEM_SYSTEM_PROMPT
2183
+ ];
2184
+ if (hasSandbox) parts.push(EXECUTE_SYSTEM_PROMPT);
2185
+ if (hasSubagents) parts.push(TASK_SYSTEM_PROMPT);
2186
+ if (skills && skills.length > 0) parts.push(buildSkillsPrompt(skills));
2187
+ return parts.filter(Boolean).join("\n\n");
2188
+ }
2189
+ /**
2190
+ * Deep Agent wrapper class that provides generate() and stream() methods.
2191
+ * Uses ToolLoopAgent from AI SDK v6 for the agent loop.
2192
+ */
2193
+ var DeepAgent = class {
2194
+ model;
2195
+ systemPrompt;
2196
+ userTools;
2197
+ maxSteps;
2198
+ backend;
2199
+ subagentOptions;
2200
+ toolResultEvictionLimit;
2201
+ enablePromptCaching;
2202
+ summarizationConfig;
2203
+ hasSandboxBackend;
2204
+ interruptOn;
2205
+ checkpointer;
2206
+ skillsMetadata = [];
2207
+ outputConfig;
2208
+ loopControl;
2209
+ generationOptions;
2210
+ advancedOptions;
2211
+ constructor(params) {
2212
+ const { model, middleware, tools = {}, systemPrompt, subagents = [], backend, maxSteps = DEFAULT_MAX_STEPS, includeGeneralPurposeAgent = true, toolResultEvictionLimit, enablePromptCaching = false, summarization, interruptOn, checkpointer, skillsDir, agentId, output, loopControl, generationOptions, advancedOptions } = params;
2213
+ if (middleware) this.model = wrapLanguageModel({
2214
+ model,
2215
+ middleware: Array.isArray(middleware) ? middleware : [middleware]
2216
+ });
2217
+ else this.model = model;
2218
+ this.maxSteps = maxSteps;
2219
+ this.backend = backend || ((state) => new StateBackend(state));
2220
+ this.toolResultEvictionLimit = toolResultEvictionLimit;
2221
+ this.enablePromptCaching = enablePromptCaching;
2222
+ this.summarizationConfig = summarization;
2223
+ this.interruptOn = interruptOn;
2224
+ this.checkpointer = checkpointer;
2225
+ this.outputConfig = output;
2226
+ this.loopControl = loopControl;
2227
+ this.generationOptions = generationOptions;
2228
+ this.advancedOptions = advancedOptions;
2229
+ if (agentId) {
2230
+ if (skillsDir) console.warn("[DeepAgent] agentId parameter takes precedence over skillsDir. skillsDir is deprecated and will be ignored.");
2231
+ this.loadSkills({ agentId }).catch((error) => {
2232
+ console.warn("[DeepAgent] Failed to load skills:", error);
2233
+ });
2234
+ } else if (skillsDir) this.loadSkills({ skillsDir }).catch((error) => {
2235
+ console.warn("[DeepAgent] Failed to load skills:", error);
2236
+ });
2237
+ this.hasSandboxBackend = typeof backend !== "function" && backend !== void 0 && isSandboxBackend(backend);
2238
+ this.systemPrompt = buildSystemPrompt(systemPrompt, includeGeneralPurposeAgent || subagents && subagents.length > 0, this.hasSandboxBackend, this.skillsMetadata);
2239
+ this.userTools = tools;
2240
+ this.subagentOptions = {
2241
+ defaultModel: model,
2242
+ defaultTools: tools,
2243
+ subagents,
2244
+ includeGeneralPurposeAgent
2245
+ };
2246
+ }
2247
+ /**
2248
+ * Create core tools (todos and filesystem).
2249
+ * @private
2250
+ */
2251
+ createCoreTools(state, onEvent) {
2252
+ return {
2253
+ write_todos: createTodosTool(state, onEvent),
2254
+ ...createFilesystemTools(state, {
2255
+ backend: this.backend,
2256
+ onEvent,
2257
+ toolResultEvictionLimit: this.toolResultEvictionLimit
2258
+ }),
2259
+ ...this.userTools
2260
+ };
2261
+ }
2262
+ /**
2263
+ * Create web tools if TAVILY_API_KEY is available.
2264
+ * @private
2265
+ */
2266
+ createWebToolSet(state, onEvent) {
2267
+ return createWebTools(state, {
2268
+ backend: this.backend,
2269
+ onEvent,
2270
+ toolResultEvictionLimit: this.toolResultEvictionLimit
2271
+ });
2272
+ }
2273
+ /**
2274
+ * Create execute tool if backend is a sandbox.
2275
+ * @private
2276
+ */
2277
+ createExecuteToolSet(onEvent) {
2278
+ if (!this.hasSandboxBackend) return {};
2279
+ const sandboxBackend = this.backend;
2280
+ return { execute: createExecuteTool({
2281
+ backend: sandboxBackend,
2282
+ onEvent
2283
+ }) };
2284
+ }
2285
+ /**
2286
+ * Create subagent tool if configured.
2287
+ * @private
2288
+ */
2289
+ createSubagentToolSet(state, onEvent) {
2290
+ if (!this.subagentOptions.includeGeneralPurposeAgent && (!this.subagentOptions.subagents || this.subagentOptions.subagents.length === 0)) return {};
2291
+ return { task: createSubagentTool(state, {
2292
+ defaultModel: this.subagentOptions.defaultModel,
2293
+ defaultTools: this.userTools,
2294
+ subagents: this.subagentOptions.subagents,
2295
+ includeGeneralPurposeAgent: this.subagentOptions.includeGeneralPurposeAgent,
2296
+ backend: this.backend,
2297
+ onEvent,
2298
+ interruptOn: this.interruptOn,
2299
+ parentGenerationOptions: this.generationOptions,
2300
+ parentAdvancedOptions: this.advancedOptions
2301
+ }) };
2302
+ }
2303
+ /**
2304
+ * Create all tools for the agent, combining core, web, execute, and subagent tools.
2305
+ * @private
2306
+ */
2307
+ createTools(state, onEvent) {
2308
+ let allTools = this.createCoreTools(state, onEvent);
2309
+ const webTools = this.createWebToolSet(state, onEvent);
2310
+ if (Object.keys(webTools).length > 0) allTools = {
2311
+ ...allTools,
2312
+ ...webTools
2313
+ };
2314
+ const executeTools = this.createExecuteToolSet(onEvent);
2315
+ if (Object.keys(executeTools).length > 0) allTools = {
2316
+ ...allTools,
2317
+ ...executeTools
2318
+ };
2319
+ const subagentTools = this.createSubagentToolSet(state, onEvent);
2320
+ if (Object.keys(subagentTools).length > 0) allTools = {
2321
+ ...allTools,
2322
+ ...subagentTools
2323
+ };
2324
+ allTools = applyInterruptConfig(allTools, this.interruptOn);
2325
+ return allTools;
2326
+ }
2327
+ /**
2328
+ * Build stop conditions with maxSteps safety limit.
2329
+ * Combines user-provided stop conditions with the maxSteps limit.
2330
+ */
2331
+ buildStopConditions(maxSteps) {
2332
+ const conditions = [];
2333
+ conditions.push(stepCountIs(maxSteps ?? this.maxSteps));
2334
+ if (this.loopControl?.stopWhen) if (Array.isArray(this.loopControl.stopWhen)) conditions.push(...this.loopControl.stopWhen);
2335
+ else conditions.push(this.loopControl.stopWhen);
2336
+ return conditions;
2337
+ }
2338
+ /**
2339
+ * Build agent settings by combining passthrough options with defaults.
2340
+ */
2341
+ buildAgentSettings(onEvent) {
2342
+ const settings = {
2343
+ model: this.model,
2344
+ instructions: this.systemPrompt,
2345
+ tools: void 0
2346
+ };
2347
+ if (this.generationOptions) Object.assign(settings, this.generationOptions);
2348
+ if (this.advancedOptions) Object.assign(settings, this.advancedOptions);
2349
+ if (this.loopControl) {
2350
+ if (this.loopControl.prepareStep) settings.prepareStep = this.composePrepareStep(this.loopControl.prepareStep);
2351
+ if (this.loopControl.onStepFinish) settings.onStepFinish = this.composeOnStepFinish(this.loopControl.onStepFinish);
2352
+ if (this.loopControl.onFinish) settings.onFinish = this.composeOnFinish(this.loopControl.onFinish);
2353
+ }
2354
+ if (this.outputConfig) settings.output = Output.object(this.outputConfig);
2355
+ return settings;
2356
+ }
2357
+ /**
2358
+ * Create a ToolLoopAgent for a given state.
2359
+ * @param state - The shared agent state
2360
+ * @param maxSteps - Optional max steps override
2361
+ * @param onEvent - Optional callback for emitting events
2362
+ */
2363
+ createAgent(state, maxSteps, onEvent) {
2364
+ const tools = this.createTools(state, onEvent);
2365
+ const settings = this.buildAgentSettings(onEvent);
2366
+ const stopConditions = this.buildStopConditions(maxSteps);
2367
+ return new ToolLoopAgent({
2368
+ ...settings,
2369
+ tools,
2370
+ stopWhen: stopConditions
2371
+ });
2372
+ }
2373
+ /**
2374
+ * Load skills from directory asynchronously.
2375
+ * Supports both legacy skillsDir and new agentId modes.
2376
+ */
2377
+ async loadSkills(options) {
2378
+ const { listSkills } = await import("./load-94gjHorc.mjs");
2379
+ this.skillsMetadata = (await listSkills(options.agentId ? { agentId: options.agentId } : { projectSkillsDir: options.skillsDir })).map((s) => ({
2380
+ name: s.name,
2381
+ description: s.description,
2382
+ path: s.path
2383
+ }));
2384
+ }
2385
+ /**
2386
+ * Generate a response (non-streaming).
2387
+ */
2388
+ async generate(options) {
2389
+ const state = {
2390
+ todos: [],
2391
+ files: {}
2392
+ };
2393
+ const result = await this.createAgent(state, options.maxSteps).generate({ prompt: options.prompt });
2394
+ Object.defineProperty(result, "state", {
2395
+ value: state,
2396
+ enumerable: true,
2397
+ writable: false
2398
+ });
2399
+ return result;
2400
+ }
2401
+ /**
2402
+ * Stream a response.
2403
+ */
2404
+ async stream(options) {
2405
+ const state = {
2406
+ todos: [],
2407
+ files: {}
2408
+ };
2409
+ const result = await this.createAgent(state, options.maxSteps).stream({ prompt: options.prompt });
2410
+ Object.defineProperty(result, "state", {
2411
+ value: state,
2412
+ enumerable: true,
2413
+ writable: false
2414
+ });
2415
+ return result;
2416
+ }
2417
+ /**
2418
+ * Generate with an existing state (for continuing conversations).
2419
+ */
2420
+ async generateWithState(options) {
2421
+ const result = await this.createAgent(options.state, options.maxSteps).generate({ prompt: options.prompt });
2422
+ Object.defineProperty(result, "state", {
2423
+ value: options.state,
2424
+ enumerable: true,
2425
+ writable: false
2426
+ });
2427
+ return result;
2428
+ }
2429
+ /**
2430
+ * Get the underlying ToolLoopAgent for advanced usage.
2431
+ * This allows using AI SDK's createAgentUIStream and other utilities.
2432
+ */
2433
+ getAgent(state) {
2434
+ const agentState = state || {
2435
+ todos: [],
2436
+ files: {}
2437
+ };
2438
+ return this.createAgent(agentState);
2439
+ }
2440
+ /**
2441
+ * Stream a response with real-time events.
2442
+ * This is an async generator that yields DeepAgentEvent objects.
2443
+ *
2444
+ * Supports conversation history via the `messages` option for multi-turn conversations.
2445
+ *
2446
+ * @example
2447
+ * ```typescript
2448
+ * // Single turn
2449
+ * for await (const event of agent.streamWithEvents({ prompt: "..." })) {
2450
+ * switch (event.type) {
2451
+ * case 'text':
2452
+ * process.stdout.write(event.text);
2453
+ * break;
2454
+ * case 'done':
2455
+ * // event.messages contains the updated conversation history
2456
+ * console.log('Messages:', event.messages);
2457
+ * break;
2458
+ * }
2459
+ * }
2460
+ *
2461
+ * // Multi-turn conversation
2462
+ * let messages = [];
2463
+ * for await (const event of agent.streamWithEvents({ prompt: "Hello", messages })) {
2464
+ * if (event.type === 'done') {
2465
+ * messages = event.messages; // Save for next turn
2466
+ * }
2467
+ * }
2468
+ * for await (const event of agent.streamWithEvents({ prompt: "Follow up", messages })) {
2469
+ * // Agent now has context from previous turn
2470
+ * }
2471
+ * ```
2472
+ */
2473
+ /**
2474
+ * Compose user's onStepFinish callback with DeepAgent's internal checkpointing logic.
2475
+ * User callback executes first, errors are caught to prevent breaking checkpointing.
2476
+ */
2477
+ composeOnStepFinish(userOnStepFinish) {
2478
+ return async (params) => {
2479
+ if (userOnStepFinish) try {
2480
+ await userOnStepFinish(params);
2481
+ } catch (error) {
2482
+ console.error("[DeepAgent] User onStepFinish callback failed:", error);
2483
+ }
2484
+ };
2485
+ }
2486
+ /**
2487
+ * Compose user's onFinish callback with DeepAgent's internal cleanup logic.
2488
+ */
2489
+ composeOnFinish(userOnFinish) {
2490
+ return async (params) => {
2491
+ if (userOnFinish) try {
2492
+ await userOnFinish(params);
2493
+ } catch (error) {
2494
+ console.error("[DeepAgent] User onFinish callback failed:", error);
2495
+ }
2496
+ };
2497
+ }
2498
+ /**
2499
+ * Compose user's prepareStep callback with DeepAgent's internal step preparation.
2500
+ * Returns a function typed as `any` to avoid AI SDK's strict toolName inference.
2501
+ */
2502
+ composePrepareStep(userPrepareStep) {
2503
+ return async (params) => {
2504
+ if (userPrepareStep) try {
2505
+ return { ...await userPrepareStep(params) };
2506
+ } catch (error) {
2507
+ console.error("[DeepAgent] User prepareStep callback failed:", error);
2508
+ return params;
2509
+ }
2510
+ return params;
2511
+ };
2512
+ }
2513
+ /**
2514
+ * Build streamText options with callbacks for step tracking and checkpointing.
2515
+ *
2516
+ * @private
2517
+ */
2518
+ buildStreamTextOptions(inputMessages, tools, options, state, baseStep, pendingInterrupt, eventQueue, stepNumberRef) {
2519
+ const { threadId } = options;
2520
+ const streamOptions = {
2521
+ model: this.model,
2522
+ messages: inputMessages,
2523
+ tools,
2524
+ stopWhen: this.buildStopConditions(options.maxSteps),
2525
+ abortSignal: options.abortSignal,
2526
+ onStepFinish: async ({ toolCalls, toolResults }) => {
2527
+ if (this.loopControl?.onStepFinish) await this.composeOnStepFinish(this.loopControl.onStepFinish)({
2528
+ toolCalls,
2529
+ toolResults
2530
+ });
2531
+ stepNumberRef.value++;
2532
+ const cumulativeStep = baseStep + stepNumberRef.value;
2533
+ const stepEvent = {
2534
+ type: "step-finish",
2535
+ stepNumber: stepNumberRef.value,
2536
+ toolCalls: toolCalls.map((tc, i) => ({
2537
+ toolName: tc.toolName,
2538
+ args: "input" in tc ? tc.input : void 0,
2539
+ result: toolResults[i] ? "output" in toolResults[i] ? toolResults[i].output : void 0 : void 0
2540
+ }))
2541
+ };
2542
+ eventQueue.push(stepEvent);
2543
+ if (threadId && this.checkpointer) {
2544
+ const checkpoint = {
2545
+ threadId,
2546
+ step: cumulativeStep,
2547
+ messages: inputMessages,
2548
+ state: { ...state },
2549
+ interrupt: pendingInterrupt,
2550
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2551
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2552
+ };
2553
+ await this.checkpointer.save(checkpoint);
2554
+ eventQueue.push(createCheckpointSavedEvent(threadId, cumulativeStep));
2555
+ }
2556
+ }
2557
+ };
2558
+ if (this.generationOptions) Object.assign(streamOptions, this.generationOptions);
2559
+ if (this.advancedOptions) Object.assign(streamOptions, this.advancedOptions);
2560
+ if (this.outputConfig) streamOptions.output = Output.object(this.outputConfig);
2561
+ if (this.loopControl) {
2562
+ if (this.loopControl.prepareStep) streamOptions.prepareStep = this.composePrepareStep(this.loopControl.prepareStep);
2563
+ if (this.loopControl.onFinish) streamOptions.onFinish = this.composeOnFinish(this.loopControl.onFinish);
2564
+ }
2565
+ if (this.enablePromptCaching) streamOptions.messages = [{
2566
+ role: "system",
2567
+ content: this.systemPrompt,
2568
+ providerOptions: { anthropic: { cacheControl: { type: "ephemeral" } } }
2569
+ }, ...inputMessages];
2570
+ else streamOptions.system = this.systemPrompt;
2571
+ return streamOptions;
2572
+ }
2573
+ /**
2574
+ * Build message array from options, handling validation and priority logic.
2575
+ * Priority: explicit messages > prompt > checkpoint history.
2576
+ *
2577
+ * @private
2578
+ */
2579
+ async buildMessageArray(options, patchedHistory) {
2580
+ const { resume } = options;
2581
+ if (!options.prompt && !options.messages && !resume && !options.threadId) return {
2582
+ messages: [],
2583
+ patchedHistory,
2584
+ error: {
2585
+ type: "error",
2586
+ error: /* @__PURE__ */ new Error("Either 'prompt', 'messages', 'resume', or 'threadId' is required")
2587
+ }
2588
+ };
2589
+ let userMessages = [];
2590
+ let shouldUseCheckpointHistory = true;
2591
+ if (options.messages && options.messages.length > 0) {
2592
+ userMessages = options.messages;
2593
+ shouldUseCheckpointHistory = false;
2594
+ if (options.prompt && process.env.NODE_ENV !== "production") console.warn("prompt parameter is deprecated when messages are provided, using messages instead");
2595
+ } else if (options.messages) {
2596
+ shouldUseCheckpointHistory = false;
2597
+ patchedHistory = [];
2598
+ if (options.prompt && process.env.NODE_ENV !== "production") console.warn("prompt parameter is deprecated when empty messages are provided, prompt ignored");
2599
+ } else if (options.prompt) {
2600
+ userMessages = [{
2601
+ role: "user",
2602
+ content: options.prompt
2603
+ }];
2604
+ if (process.env.NODE_ENV !== "production") console.warn("prompt parameter is deprecated, use messages instead");
2605
+ }
2606
+ if (shouldUseCheckpointHistory && patchedHistory.length > 0) {
2607
+ patchedHistory = patchToolCalls(patchedHistory);
2608
+ if (this.summarizationConfig?.enabled && patchedHistory.length > 0) patchedHistory = (await summarizeIfNeeded(patchedHistory, {
2609
+ model: this.summarizationConfig.model || this.model,
2610
+ tokenThreshold: this.summarizationConfig.tokenThreshold,
2611
+ keepMessages: this.summarizationConfig.keepMessages,
2612
+ generationOptions: this.generationOptions,
2613
+ advancedOptions: this.advancedOptions
2614
+ })).messages;
2615
+ } else if (!shouldUseCheckpointHistory) patchedHistory = [];
2616
+ const hasEmptyMessages = options.messages && options.messages.length === 0;
2617
+ const hasValidInput = userMessages.length > 0 || patchedHistory.length > 0;
2618
+ if (hasEmptyMessages && !hasValidInput && !resume) return {
2619
+ messages: [],
2620
+ patchedHistory,
2621
+ shouldReturnEmpty: true
2622
+ };
2623
+ if (!hasValidInput && !resume) return {
2624
+ messages: [],
2625
+ patchedHistory,
2626
+ error: {
2627
+ type: "error",
2628
+ error: /* @__PURE__ */ new Error("No valid input: provide either non-empty messages, prompt, or threadId with existing checkpoint")
2629
+ }
2630
+ };
2631
+ return {
2632
+ messages: [...patchedHistory, ...userMessages],
2633
+ patchedHistory
2634
+ };
2635
+ }
2636
+ /**
2637
+ * Load checkpoint context if threadId is provided.
2638
+ * Handles checkpoint restoration and resume from interrupt.
2639
+ *
2640
+ * @private
2641
+ */
2642
+ async loadCheckpointContext(options) {
2643
+ const { threadId, resume } = options;
2644
+ let state = options.state || {
2645
+ todos: [],
2646
+ files: {}
2647
+ };
2648
+ let patchedHistory = [];
2649
+ let currentStep = 0;
2650
+ let pendingInterrupt;
2651
+ let checkpointEvent;
2652
+ if (threadId && this.checkpointer) {
2653
+ const checkpoint = await this.checkpointer.load(threadId);
2654
+ if (checkpoint) {
2655
+ state = checkpoint.state;
2656
+ patchedHistory = checkpoint.messages;
2657
+ currentStep = checkpoint.step;
2658
+ pendingInterrupt = checkpoint.interrupt;
2659
+ checkpointEvent = createCheckpointLoadedEvent(threadId, checkpoint.step, checkpoint.messages.length);
2660
+ }
2661
+ }
2662
+ if (resume && pendingInterrupt) if (resume.decisions[0]?.type === "approve") pendingInterrupt = void 0;
2663
+ else pendingInterrupt = void 0;
2664
+ return {
2665
+ state,
2666
+ patchedHistory,
2667
+ currentStep,
2668
+ pendingInterrupt,
2669
+ checkpointEvent
2670
+ };
2671
+ }
2672
+ async *streamWithEvents(options) {
2673
+ const { threadId, resume } = options;
2674
+ const context = await this.loadCheckpointContext(options);
2675
+ const { state, currentStep, pendingInterrupt, checkpointEvent } = context;
2676
+ let patchedHistory = context.patchedHistory;
2677
+ if (checkpointEvent) yield checkpointEvent;
2678
+ const messageResult = await this.buildMessageArray(options, patchedHistory);
2679
+ if (messageResult.error) {
2680
+ yield messageResult.error;
2681
+ return;
2682
+ }
2683
+ if (messageResult.shouldReturnEmpty) {
2684
+ yield {
2685
+ type: "done",
2686
+ text: "",
2687
+ messages: [],
2688
+ state
2689
+ };
2690
+ return;
2691
+ }
2692
+ const inputMessages = messageResult.messages;
2693
+ patchedHistory = messageResult.patchedHistory;
2694
+ const eventQueue = [];
2695
+ const stepNumberRef = { value: 0 };
2696
+ const baseStep = currentStep;
2697
+ const onEvent = (event) => {
2698
+ eventQueue.push(event);
2699
+ };
2700
+ let tools = this.createTools(state, onEvent);
2701
+ const hasInterruptOn = !!this.interruptOn;
2702
+ const hasApprovalCallback = !!options.onApprovalRequest;
2703
+ if (hasInterruptOn && hasApprovalCallback) tools = wrapToolsWithApproval(tools, this.interruptOn, options.onApprovalRequest);
2704
+ try {
2705
+ const result = streamText(this.buildStreamTextOptions(inputMessages, tools, options, state, baseStep, pendingInterrupt, eventQueue, stepNumberRef));
2706
+ yield {
2707
+ type: "step-start",
2708
+ stepNumber: 1
2709
+ };
2710
+ for await (const chunk of result.fullStream) {
2711
+ while (eventQueue.length > 0) {
2712
+ const event = eventQueue.shift();
2713
+ yield event;
2714
+ if (event.type === "step-finish") yield {
2715
+ type: "step-start",
2716
+ stepNumber: event.stepNumber + 1
2717
+ };
2718
+ }
2719
+ if (chunk.type === "text-delta") yield {
2720
+ type: "text",
2721
+ text: chunk.text
2722
+ };
2723
+ else if (chunk.type === "tool-call") yield {
2724
+ type: "tool-call",
2725
+ toolName: chunk.toolName,
2726
+ toolCallId: chunk.toolCallId,
2727
+ args: chunk.input
2728
+ };
2729
+ else if (chunk.type === "tool-result") yield {
2730
+ type: "tool-result",
2731
+ toolName: chunk.toolName,
2732
+ toolCallId: chunk.toolCallId,
2733
+ result: chunk.output,
2734
+ isError: false
2735
+ };
2736
+ else if (chunk.type === "tool-error") yield {
2737
+ type: "tool-result",
2738
+ toolName: chunk.toolName,
2739
+ toolCallId: chunk.toolCallId,
2740
+ result: chunk.error,
2741
+ isError: true
2742
+ };
2743
+ }
2744
+ while (eventQueue.length > 0) yield eventQueue.shift();
2745
+ const finalText = await result.text;
2746
+ const updatedMessages = [...inputMessages, ...finalText ? [{
2747
+ role: "assistant",
2748
+ content: finalText
2749
+ }] : []];
2750
+ const output = "output" in result ? result.output : void 0;
2751
+ yield {
2752
+ type: "done",
2753
+ state,
2754
+ text: finalText,
2755
+ messages: updatedMessages,
2756
+ ...output !== void 0 ? { output } : {}
2757
+ };
2758
+ if (threadId && this.checkpointer) {
2759
+ const finalCheckpoint = {
2760
+ threadId,
2761
+ step: baseStep + stepNumberRef.value,
2762
+ messages: updatedMessages,
2763
+ state,
2764
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2765
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2766
+ };
2767
+ await this.checkpointer.save(finalCheckpoint);
2768
+ yield createCheckpointSavedEvent(threadId, baseStep + stepNumberRef.value);
2769
+ }
2770
+ } catch (error) {
2771
+ yield {
2772
+ type: "error",
2773
+ error: error instanceof Error ? error : new Error(String(error))
2774
+ };
2775
+ }
2776
+ }
2777
+ /**
2778
+ * Stream with a simple callback interface.
2779
+ * This is a convenience wrapper around streamWithEvents.
2780
+ */
2781
+ async streamWithCallback(options, onEvent) {
2782
+ let finalState = options.state || {
2783
+ todos: [],
2784
+ files: {}
2785
+ };
2786
+ let finalText;
2787
+ let finalMessages;
2788
+ for await (const event of this.streamWithEvents(options)) {
2789
+ onEvent(event);
2790
+ if (event.type === "done") {
2791
+ finalState = event.state;
2792
+ finalText = event.text;
2793
+ finalMessages = event.messages;
2794
+ }
2795
+ }
2796
+ return {
2797
+ state: finalState,
2798
+ text: finalText,
2799
+ messages: finalMessages
2800
+ };
2801
+ }
2802
+ };
2803
+ /**
2804
+ * Create a Deep Agent with planning, filesystem, and subagent capabilities.
2805
+ *
2806
+ * @param params - Configuration object for the Deep Agent
2807
+ * @param params.model - **Required.** AI SDK LanguageModel instance (e.g., `anthropic('claude-sonnet-4-20250514')`, `openai('gpt-4o')`)
2808
+ * @param params.systemPrompt - Optional custom system prompt for the agent
2809
+ * @param params.tools - Optional custom tools to add to the agent (AI SDK ToolSet)
2810
+ * @param params.subagents - Optional array of specialized subagent configurations for task delegation
2811
+ * @param params.backend - Optional backend for filesystem operations (default: StateBackend for in-memory storage)
2812
+ * @param params.maxSteps - Optional maximum number of steps for the agent loop (default: 100)
2813
+ * @param params.includeGeneralPurposeAgent - Optional flag to include general-purpose subagent (default: true)
2814
+ * @param params.toolResultEvictionLimit - Optional token limit before evicting large tool results to filesystem (default: disabled)
2815
+ * @param params.enablePromptCaching - Optional flag to enable prompt caching for improved performance (Anthropic only, default: false)
2816
+ * @param params.summarization - Optional summarization configuration for automatic conversation summarization
2817
+ * @returns A configured DeepAgent instance
2818
+ *
2819
+ * @see {@link CreateDeepAgentParams} for detailed parameter types
2820
+ *
2821
+ * @example Basic usage
2822
+ * ```typescript
2823
+ * import { createDeepAgent } from 'deepagentsdk';
2824
+ * import { anthropic } from '@ai-sdk/anthropic';
2825
+ *
2826
+ * const agent = createDeepAgent({
2827
+ * model: anthropic('claude-sonnet-4-20250514'),
2828
+ * systemPrompt: 'You are a research assistant...',
2829
+ * });
2830
+ *
2831
+ * const result = await agent.generate({
2832
+ * prompt: 'Research the topic and write a report',
2833
+ * });
2834
+ * ```
2835
+ *
2836
+ * @example With custom tools
2837
+ * ```typescript
2838
+ * import { tool } from 'ai';
2839
+ * import { z } from 'zod';
2840
+ *
2841
+ * const customTool = tool({
2842
+ * description: 'Get current time',
2843
+ * inputSchema: z.object({}),
2844
+ * execute: async () => new Date().toISOString(),
2845
+ * });
2846
+ *
2847
+ * const agent = createDeepAgent({
2848
+ * model: anthropic('claude-sonnet-4-20250514'),
2849
+ * tools: { get_time: customTool },
2850
+ * });
2851
+ * ```
2852
+ *
2853
+ * @example With subagents
2854
+ * ```typescript
2855
+ * const agent = createDeepAgent({
2856
+ * model: anthropic('claude-sonnet-4-20250514'),
2857
+ * subagents: [{
2858
+ * name: 'research-agent',
2859
+ * description: 'Specialized for research tasks',
2860
+ * systemPrompt: 'You are a research specialist...',
2861
+ * }],
2862
+ * });
2863
+ * ```
2864
+ *
2865
+ * @example With StateBackend (default, explicit)
2866
+ * ```typescript
2867
+ * import { StateBackend } from 'deepagentsdk';
2868
+ *
2869
+ * const state = { todos: [], files: {} };
2870
+ * const agent = createDeepAgent({
2871
+ * model: anthropic('claude-sonnet-4-20250514'),
2872
+ * backend: new StateBackend(state), // Ephemeral in-memory storage
2873
+ * });
2874
+ * ```
2875
+ *
2876
+ * @example With FilesystemBackend
2877
+ * ```typescript
2878
+ * import { FilesystemBackend } from 'deepagentsdk';
2879
+ *
2880
+ * const agent = createDeepAgent({
2881
+ * model: anthropic('claude-sonnet-4-20250514'),
2882
+ * backend: new FilesystemBackend({ rootDir: './workspace' }), // Persist to disk
2883
+ * });
2884
+ * ```
2885
+ *
2886
+ * @example With PersistentBackend
2887
+ * ```typescript
2888
+ * import { PersistentBackend, InMemoryStore } from 'deepagentsdk';
2889
+ *
2890
+ * const store = new InMemoryStore();
2891
+ * const agent = createDeepAgent({
2892
+ * model: anthropic('claude-sonnet-4-20250514'),
2893
+ * backend: new PersistentBackend({ store, namespace: 'project-1' }), // Cross-session persistence
2894
+ * });
2895
+ * ```
2896
+ *
2897
+ * @example With CompositeBackend
2898
+ * ```typescript
2899
+ * import { CompositeBackend, FilesystemBackend, StateBackend } from 'deepagentsdk';
2900
+ *
2901
+ * const state = { todos: [], files: {} };
2902
+ * const agent = createDeepAgent({
2903
+ * model: anthropic('claude-sonnet-4-20250514'),
2904
+ * backend: new CompositeBackend(
2905
+ * new StateBackend(state),
2906
+ * { '/persistent/': new FilesystemBackend({ rootDir: './persistent' }) }
2907
+ * ), // Route files by path prefix
2908
+ * });
2909
+ * ```
2910
+ *
2911
+ * @example With middleware for logging and caching
2912
+ * ```typescript
2913
+ * import { createDeepAgent } from 'deepagentsdk';
2914
+ * import { anthropic } from '@ai-sdk/anthropic';
2915
+ *
2916
+ * const loggingMiddleware = {
2917
+ * wrapGenerate: async ({ doGenerate, params }) => {
2918
+ * console.log('Model called with:', params.prompt);
2919
+ * const result = await doGenerate();
2920
+ * console.log('Model returned:', result.text);
2921
+ * return result;
2922
+ * },
2923
+ * };
2924
+ *
2925
+ * const agent = createDeepAgent({
2926
+ * model: anthropic('claude-sonnet-4-20250514'),
2927
+ * middleware: [loggingMiddleware],
2928
+ * });
2929
+ * ```
2930
+ *
2931
+ * @example With middleware factory for context access
2932
+ * ```typescript
2933
+ * import { FilesystemBackend } from 'deepagentsdk';
2934
+ *
2935
+ * function createContextMiddleware(backend: BackendProtocol) {
2936
+ * return {
2937
+ * wrapGenerate: async ({ doGenerate }) => {
2938
+ * const state = await backend.read('state');
2939
+ * const result = await doGenerate();
2940
+ * await backend.write('state', { ...state, lastCall: result });
2941
+ * return result;
2942
+ * },
2943
+ * };
2944
+ * }
2945
+ *
2946
+ * const backend = new FilesystemBackend({ rootDir: './workspace' });
2947
+ * const agent = createDeepAgent({
2948
+ * model: anthropic('claude-sonnet-4-20250514'),
2949
+ * backend,
2950
+ * middleware: createContextMiddleware(backend),
2951
+ * });
2952
+ * ```
2953
+ *
2954
+ * @example With performance optimizations
2955
+ * ```typescript
2956
+ * const agent = createDeepAgent({
2957
+ * model: anthropic('claude-sonnet-4-20250514'),
2958
+ * enablePromptCaching: true,
2959
+ * toolResultEvictionLimit: 20000,
2960
+ * summarization: {
2961
+ * enabled: true,
2962
+ * tokenThreshold: 170000,
2963
+ * keepMessages: 6,
2964
+ * },
2965
+ * });
2966
+ * ```
2967
+ */
2968
+ function createDeepAgent(params) {
2969
+ return new DeepAgent(params);
2970
+ }
2971
+
2972
+ //#endregion
2973
+ export { SYSTEM_REMINDER_FILE_EMPTY as $, glob as A, StateBackend as B, createFilesystemTools as C, createReadFileTool as D, createLsTool as E, DEFAULT_EVICTION_TOKEN_LIMIT as F, formatReadResponse as G, createFileData as H, createToolResultWrapper as I, performStringReplacement as J, globSearchFiles as K, estimateTokens as L, ls as M, read_file as N, createWriteFileTool as O, write_file as P, STRING_NOT_FOUND as Q, evictToolResult as R, createEditFileTool as S, createGrepTool as T, fileDataToString as U, checkEmptyContent as V, formatContentWithLineNumbers as W, FILE_ALREADY_EXISTS as X, updateFileData as Y, FILE_NOT_FOUND as Z, createWebTools as _, estimateMessagesTokens as a, EXECUTE_SYSTEM_PROMPT as at, http_request as b, hasDanglingToolCalls as c, TODO_SYSTEM_PROMPT as ct, createExecuteTool as d, CONTEXT_WINDOW as dt, createTodosTool as et, createExecuteToolFromBackend as f, DEFAULT_EVICTION_TOKEN_LIMIT$1 as ft, createWebSearchTool as g, MAX_FILE_SIZE_MB as gt, createHttpRequestTool as h, DEFAULT_SUMMARIZATION_THRESHOLD$1 as ht, DEFAULT_SUMMARIZATION_THRESHOLD as i, DEFAULT_SUBAGENT_PROMPT as it, grep as j, edit_file as k, patchToolCalls as l, getTaskToolDescription as lt, createFetchUrlTool as m, DEFAULT_READ_LIMIT as mt, createDeepAgent as n, BASE_PROMPT as nt, needsSummarization as o, FILESYSTEM_SYSTEM_PROMPT as ot, execute as p, DEFAULT_KEEP_MESSAGES$1 as pt, grepMatchesFromFiles as q, DEFAULT_KEEP_MESSAGES as r, DEFAULT_GENERAL_PURPOSE_DESCRIPTION as rt, summarizeIfNeeded as s, TASK_SYSTEM_PROMPT as st, DeepAgent as t, write_todos as tt, createSubagentTool as u, isSandboxBackend as ut, fetch_url as v, createGlobTool as w, web_search as x, htmlToMarkdown as y, shouldEvict as z };
2974
+ //# sourceMappingURL=agent-CrH-He58.mjs.map