deepagentsdk 0.9.2

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