deepagentsdk 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/package.json +95 -0
  4. package/src/agent.ts +1230 -0
  5. package/src/backends/composite.ts +273 -0
  6. package/src/backends/filesystem.ts +692 -0
  7. package/src/backends/index.ts +22 -0
  8. package/src/backends/local-sandbox.ts +175 -0
  9. package/src/backends/persistent.ts +593 -0
  10. package/src/backends/sandbox.ts +510 -0
  11. package/src/backends/state.ts +244 -0
  12. package/src/backends/utils.ts +287 -0
  13. package/src/checkpointer/file-saver.ts +98 -0
  14. package/src/checkpointer/index.ts +5 -0
  15. package/src/checkpointer/kv-saver.ts +82 -0
  16. package/src/checkpointer/memory-saver.ts +82 -0
  17. package/src/checkpointer/types.ts +125 -0
  18. package/src/cli/components/ApiKeyInput.tsx +300 -0
  19. package/src/cli/components/FilePreview.tsx +237 -0
  20. package/src/cli/components/Input.tsx +277 -0
  21. package/src/cli/components/Message.tsx +93 -0
  22. package/src/cli/components/ModelSelection.tsx +338 -0
  23. package/src/cli/components/SlashMenu.tsx +101 -0
  24. package/src/cli/components/StatusBar.tsx +89 -0
  25. package/src/cli/components/Subagent.tsx +91 -0
  26. package/src/cli/components/TodoList.tsx +133 -0
  27. package/src/cli/components/ToolApproval.tsx +70 -0
  28. package/src/cli/components/ToolCall.tsx +144 -0
  29. package/src/cli/components/ToolCallSummary.tsx +175 -0
  30. package/src/cli/components/Welcome.tsx +75 -0
  31. package/src/cli/components/index.ts +24 -0
  32. package/src/cli/hooks/index.ts +12 -0
  33. package/src/cli/hooks/useAgent.ts +933 -0
  34. package/src/cli/index.tsx +1066 -0
  35. package/src/cli/theme.ts +205 -0
  36. package/src/cli/utils/model-list.ts +365 -0
  37. package/src/constants/errors.ts +29 -0
  38. package/src/constants/limits.ts +195 -0
  39. package/src/index.ts +176 -0
  40. package/src/middleware/agent-memory.ts +330 -0
  41. package/src/prompts.ts +196 -0
  42. package/src/skills/index.ts +2 -0
  43. package/src/skills/load.ts +191 -0
  44. package/src/skills/types.ts +53 -0
  45. package/src/tools/execute.ts +167 -0
  46. package/src/tools/filesystem.ts +418 -0
  47. package/src/tools/index.ts +39 -0
  48. package/src/tools/subagent.ts +443 -0
  49. package/src/tools/todos.ts +101 -0
  50. package/src/tools/web.ts +567 -0
  51. package/src/types/backend.ts +177 -0
  52. package/src/types/core.ts +220 -0
  53. package/src/types/events.ts +429 -0
  54. package/src/types/index.ts +94 -0
  55. package/src/types/structured-output.ts +43 -0
  56. package/src/types/subagent.ts +96 -0
  57. package/src/types.ts +22 -0
  58. package/src/utils/approval.ts +213 -0
  59. package/src/utils/events.ts +416 -0
  60. package/src/utils/eviction.ts +181 -0
  61. package/src/utils/index.ts +34 -0
  62. package/src/utils/model-parser.ts +38 -0
  63. package/src/utils/patch-tool-calls.ts +233 -0
  64. package/src/utils/project-detection.ts +32 -0
  65. package/src/utils/summarization.ts +254 -0
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Centralized token, size, and timeout limits.
3
+ *
4
+ * These constants prevent magic number scattering across the codebase and provide
5
+ * a single source of truth for configuration values. When updating these values,
6
+ * consider the impact on performance, user experience, and API limits.
7
+ *
8
+ * @module constants/limits
9
+ */
10
+
11
+ // ============================================================================
12
+ // Token Limits
13
+ // ============================================================================
14
+
15
+ /**
16
+ * Default token limit for tool result eviction.
17
+ *
18
+ * When a tool result exceeds this limit, it is automatically evicted to a file
19
+ * to prevent context overflow. The evicted content is stored in the backend and
20
+ * a summary is kept in the conversation history.
21
+ *
22
+ * @default 20000
23
+ * @see {@link ../utils/eviction | evictToolResult}
24
+ */
25
+ export const DEFAULT_EVICTION_TOKEN_LIMIT = 20000;
26
+
27
+ /**
28
+ * Default threshold for message summarization.
29
+ *
30
+ * When the estimated token count of messages exceeds this threshold, the system
31
+ * automatically summarizes older messages to stay within context limits. This
32
+ * helps maintain conversation continuity while reducing token usage.
33
+ *
34
+ * @default 170000
35
+ * @see {@link ../utils/summarization | summarizeIfNeeded}
36
+ */
37
+ export const DEFAULT_SUMMARIZATION_THRESHOLD = 170000;
38
+
39
+ /**
40
+ * Maximum context window size for Claude models.
41
+ *
42
+ * This represents the maximum number of tokens that can be processed in a single
43
+ * conversation. Used for calculating token usage percentages and determining when
44
+ * summarization is needed.
45
+ *
46
+ * @default 200000
47
+ * @see {@link ../utils/summarization | estimateMessagesTokens}
48
+ */
49
+ export const CONTEXT_WINDOW = 200000;
50
+
51
+ // ============================================================================
52
+ // Message Limits
53
+ // ============================================================================
54
+
55
+ /**
56
+ * Default number of recent messages to keep during summarization.
57
+ *
58
+ * When summarization is triggered, this many of the most recent messages are
59
+ * preserved verbatim while older messages are summarized. This ensures recent
60
+ * context is immediately available to the agent.
61
+ *
62
+ * @default 6
63
+ */
64
+ export const DEFAULT_KEEP_MESSAGES = 6;
65
+
66
+ /**
67
+ * Default maximum number of reasoning steps for the main agent.
68
+ *
69
+ * The agent will stop after reaching this many steps to prevent infinite loops
70
+ * or excessive token usage. Each step represents one tool invocation cycle.
71
+ *
72
+ * @default 100
73
+ */
74
+ export const DEFAULT_MAX_STEPS = 100;
75
+
76
+ /**
77
+ * Default maximum number of reasoning steps for subagents.
78
+ *
79
+ * Subagents are given a lower step limit than the main agent to prevent them
80
+ * from consuming too many resources. This ensures the parent agent maintains
81
+ * control over the overall task.
82
+ *
83
+ * @default 50
84
+ * @see {@link ../tools/subagent | createTaskTool}
85
+ */
86
+ export const DEFAULT_SUBAGENT_MAX_STEPS = 50;
87
+
88
+ /**
89
+ * Default maximum number of messages to keep in CLI history.
90
+ *
91
+ * The CLI maintains a history of conversation messages for display purposes.
92
+ * This limit prevents memory issues in long-running sessions.
93
+ *
94
+ * @default 100
95
+ */
96
+ export const DEFAULT_MAX_HISTORY = 100;
97
+
98
+ // ============================================================================
99
+ // File Size Limits
100
+ // ============================================================================
101
+
102
+ /**
103
+ * Default maximum number of lines to read from a file.
104
+ *
105
+ * The read_file tool defaults to reading this many lines to prevent loading
106
+ * extremely large files into context. Can be overridden per-read operation.
107
+ *
108
+ * @default 2000
109
+ * @see {@link ../tools/filesystem | createReadFileTool}
110
+ */
111
+ export const DEFAULT_READ_LIMIT = 2000;
112
+
113
+ /**
114
+ * Maximum line length before content is considered invalid.
115
+ *
116
+ * Lines exceeding this length may indicate minified code, binary content, or
117
+ * other data that should not be processed as text. Used for validation.
118
+ *
119
+ * @default 10000
120
+ */
121
+ export const MAX_LINE_LENGTH = 10000;
122
+
123
+ /**
124
+ * Maximum file size in megabytes for file operations.
125
+ *
126
+ * Files larger than this size will be rejected to prevent memory issues and
127
+ * excessive token usage. This is a soft limit that can be adjusted for specific
128
+ * use cases.
129
+ *
130
+ * @default 10
131
+ */
132
+ export const MAX_FILE_SIZE_MB = 10;
133
+
134
+ /**
135
+ * Maximum output size in bytes before truncation.
136
+ *
137
+ * Tool results larger than this size will be truncated or evicted to prevent
138
+ * context overflow. This helps maintain stable performance even with large
139
+ * outputs.
140
+ *
141
+ * @default 1048576 (1 MB)
142
+ */
143
+ export const MAX_OUTPUT_SIZE_BYTES = 1048576; // 1MB
144
+
145
+ // ============================================================================
146
+ // Timeouts
147
+ // ============================================================================
148
+
149
+ /**
150
+ * Default timeout for network requests in seconds.
151
+ *
152
+ * Used by web tools (http_request, fetch_url) to prevent hanging indefinitely
153
+ * on slow or unresponsive servers. Can be overridden per-request.
154
+ *
155
+ * @default 30
156
+ * @see {@link ../tools/web | createHttpRequestTool}
157
+ */
158
+ export const DEFAULT_TIMEOUT_SECONDS = 30;
159
+
160
+ /**
161
+ * Default timeout in milliseconds (derived from DEFAULT_TIMEOUT_SECONDS).
162
+ *
163
+ * Provided for convenience when working with APIs that expect milliseconds
164
+ * instead of seconds.
165
+ *
166
+ * @default 30000 (30 seconds)
167
+ */
168
+ export const DEFAULT_TIMEOUT_MS = DEFAULT_TIMEOUT_SECONDS * 1000;
169
+
170
+ /**
171
+ * Timeout for filesystem operations in milliseconds.
172
+ *
173
+ * Used by sandboxed filesystem operations to prevent blocking indefinitely on
174
+ * slow I/O operations.
175
+ *
176
+ * @default 30000 (30 seconds)
177
+ * @see {@link ../backends/sandbox | SandboxBackend}
178
+ */
179
+ export const FILESYSTEM_TIMEOUT_MS = 30000;
180
+
181
+ // ============================================================================
182
+ // Formatting
183
+ // ============================================================================
184
+
185
+ /**
186
+ * Width for line number formatting in file read operations.
187
+ *
188
+ * When displaying file content with line numbers, this specifies the minimum
189
+ * width for the line number column. Ensures consistent alignment across
190
+ * different file sizes.
191
+ *
192
+ * @default 6
193
+ * @see {@link ../backends/utils | formatFileContent}
194
+ */
195
+ export const LINE_NUMBER_WIDTH = 6;
package/src/index.ts ADDED
@@ -0,0 +1,176 @@
1
+ /**
2
+ * AI SDK Deep Agent
3
+ *
4
+ * A TypeScript library for building controllable AI agents using Vercel AI SDK v6.
5
+ * Implements the four pillars of Deep Agent:
6
+ * - Planning tools (write_todos)
7
+ * - Filesystem access (ls, read_file, write_file, edit_file, glob, grep)
8
+ * - Subagent spawning (task)
9
+ * - Detailed prompting
10
+ */
11
+
12
+ // Main agent
13
+ export { createDeepAgent, DeepAgent } from "./agent";
14
+
15
+ // Re-export AI SDK v6 primitives for convenience
16
+ export { ToolLoopAgent, stepCountIs, hasToolCall } from "ai";
17
+
18
+ // Types
19
+ export type {
20
+ CreateDeepAgentParams,
21
+ DeepAgentState,
22
+ SubAgent,
23
+ TodoItem,
24
+ FileData,
25
+ FileInfo,
26
+ GrepMatch,
27
+ WriteResult,
28
+ EditResult,
29
+ BackendProtocol,
30
+ BackendFactory,
31
+ SummarizationConfig,
32
+ // Sandbox types
33
+ ExecuteResponse,
34
+ SandboxBackendProtocol,
35
+ // Event types for streaming
36
+ DeepAgentEvent,
37
+ EventCallback,
38
+ TextEvent,
39
+ StepStartEvent,
40
+ StepFinishEvent,
41
+ ToolCallEvent,
42
+ ToolResultEvent,
43
+ TodosChangedEvent,
44
+ FileWriteStartEvent,
45
+ FileWrittenEvent,
46
+ FileEditedEvent,
47
+ ExecuteStartEvent,
48
+ ExecuteFinishEvent,
49
+ WebSearchStartEvent,
50
+ WebSearchFinishEvent,
51
+ HttpRequestStartEvent,
52
+ HttpRequestFinishEvent,
53
+ FetchUrlStartEvent,
54
+ FetchUrlFinishEvent,
55
+ SubagentStartEvent,
56
+ SubagentFinishEvent,
57
+ ApprovalRequestedEvent,
58
+ ApprovalResponseEvent,
59
+ CheckpointSavedEvent,
60
+ CheckpointLoadedEvent,
61
+ DoneEvent,
62
+ ErrorEvent,
63
+ // Approval configuration types
64
+ InterruptOnConfig,
65
+ DynamicApprovalConfig,
66
+ } from "./types";
67
+
68
+ // Type guard for sandbox backends
69
+ export { isSandboxBackend } from "./types";
70
+
71
+ // Backends
72
+ export {
73
+ StateBackend,
74
+ FilesystemBackend,
75
+ CompositeBackend,
76
+ PersistentBackend,
77
+ InMemoryStore,
78
+ type KeyValueStore,
79
+ type PersistentBackendOptions,
80
+ // Sandbox backends
81
+ BaseSandbox,
82
+ LocalSandbox,
83
+ type LocalSandboxOptions,
84
+ } from "./backends/index";
85
+
86
+ // Tools (for advanced usage)
87
+ export {
88
+ createTodosTool,
89
+ createFilesystemTools,
90
+ createSubagentTool,
91
+ type CreateSubagentToolOptions,
92
+ // Execute tool for sandbox backends
93
+ createExecuteTool,
94
+ createExecuteToolFromBackend,
95
+ type CreateExecuteToolOptions,
96
+ // Web tools
97
+ createWebTools,
98
+ htmlToMarkdown,
99
+ type CreateWebToolsOptions,
100
+ // Individual tool creator functions
101
+ createLsTool,
102
+ createReadFileTool,
103
+ createWriteFileTool,
104
+ createEditFileTool,
105
+ createGlobTool,
106
+ createGrepTool,
107
+ createWebSearchTool,
108
+ createHttpRequestTool,
109
+ createFetchUrlTool,
110
+ // Individual builtin tool references (for selective subagent configuration)
111
+ web_search,
112
+ http_request,
113
+ fetch_url,
114
+ ls,
115
+ read_file,
116
+ write_file,
117
+ edit_file,
118
+ glob,
119
+ grep,
120
+ write_todos,
121
+ execute,
122
+ } from "./tools/index";
123
+
124
+ // Prompts (for customization)
125
+ export {
126
+ BASE_PROMPT,
127
+ TODO_SYSTEM_PROMPT,
128
+ FILESYSTEM_SYSTEM_PROMPT,
129
+ TASK_SYSTEM_PROMPT,
130
+ EXECUTE_SYSTEM_PROMPT,
131
+ getTaskToolDescription,
132
+ DEFAULT_GENERAL_PURPOSE_DESCRIPTION,
133
+ DEFAULT_SUBAGENT_PROMPT,
134
+ } from "./prompts";
135
+
136
+ // Utilities
137
+ export {
138
+ patchToolCalls,
139
+ hasDanglingToolCalls,
140
+ evictToolResult,
141
+ createToolResultWrapper,
142
+ shouldEvict,
143
+ estimateTokens,
144
+ DEFAULT_EVICTION_TOKEN_LIMIT,
145
+ type EvictOptions,
146
+ type EvictResult,
147
+ summarizeIfNeeded,
148
+ needsSummarization,
149
+ estimateMessagesTokens,
150
+ DEFAULT_SUMMARIZATION_THRESHOLD,
151
+ DEFAULT_KEEP_MESSAGES,
152
+ type SummarizationOptions,
153
+ type SummarizationResult,
154
+ } from "./utils/index";
155
+
156
+ // Checkpointer
157
+ export * from "./checkpointer/index";
158
+
159
+ // Re-export AI SDK middleware types for user convenience
160
+ export type { LanguageModelMiddleware } from 'ai';
161
+ export { wrapLanguageModel } from 'ai';
162
+
163
+ // Skills System
164
+ export { listSkills, parseSkillMetadata } from "./skills/index";
165
+ export type { SkillMetadata, SkillLoadOptions } from "./skills/index";
166
+
167
+ // Agent Memory Middleware
168
+ export { createAgentMemoryMiddleware } from "./middleware/agent-memory";
169
+ export type { AgentMemoryOptions } from "./types";
170
+
171
+ // Structured Output Utilities
172
+ export { getStructuredOutput, getEventOutput, hasStructuredOutput, eventHasStructuredOutput } from "./types/structured-output";
173
+ export type { StructuredAgentResult } from "./types/structured-output";
174
+
175
+ // Project Detection Utilities
176
+ export { findGitRoot } from "./utils/project-detection";
@@ -0,0 +1,330 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ import os from 'node:os';
4
+ import type { LanguageModelMiddleware } from 'ai';
5
+ import { findGitRoot } from '../utils/project-detection.js';
6
+
7
+ /**
8
+ * Configuration options for agent memory middleware.
9
+ */
10
+ export interface AgentMemoryOptions {
11
+ /**
12
+ * Unique identifier for the agent (e.g., "code-architect", "research-agent").
13
+ * Used to locate agent-specific memory at ~/.deepagents/{agentId}/agent.md
14
+ */
15
+ agentId: string;
16
+
17
+ /**
18
+ * Optional working directory for project-level memory detection.
19
+ * Defaults to process.cwd().
20
+ */
21
+ workingDirectory?: string;
22
+
23
+ /**
24
+ * Optional custom path for user-level .deepagents directory.
25
+ * Defaults to os.homedir() + '/.deepagents'.
26
+ *
27
+ * Useful for testing or custom deployment environments.
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * userDeepagentsDir: '/custom/path/.deepagents'
32
+ * // Will look for memory at: /custom/path/.deepagents/{agentId}/agent.md
33
+ * ```
34
+ */
35
+ userDeepagentsDir?: string;
36
+
37
+ /**
38
+ * Optional callback to request user approval for creating project-level .deepagents/ directory.
39
+ * If not provided, project memory will be silently skipped if directory doesn't exist.
40
+ *
41
+ * @param projectPath - Absolute path to the detected git root
42
+ * @returns Promise<boolean> - true if user approves, false otherwise
43
+ */
44
+ requestProjectApproval?: (projectPath: string) => Promise<boolean>;
45
+ }
46
+
47
+ /**
48
+ * Load agent memory from a file path.
49
+ * Returns empty string if file doesn't exist or can't be read.
50
+ */
51
+ async function loadAgentMemory(filePath: string): Promise<string> {
52
+ try {
53
+ const content = await fs.readFile(filePath, 'utf-8');
54
+ return content.trim();
55
+ } catch {
56
+ return '';
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Load all additional .md files from the agent's directory (excluding agent.md).
62
+ * Returns an array of { filename, content } objects.
63
+ */
64
+ async function loadAdditionalMemoryFiles(
65
+ dirPath: string
66
+ ): Promise<Array<{ filename: string; content: string }>> {
67
+ try {
68
+ const files = await fs.readdir(dirPath);
69
+ const mdFiles = files.filter(
70
+ (f) => f.endsWith('.md') && f !== 'agent.md'
71
+ );
72
+
73
+ const results = await Promise.all(
74
+ mdFiles.map(async (filename) => {
75
+ const content = await loadAgentMemory(path.join(dirPath, filename));
76
+ return { filename, content };
77
+ })
78
+ );
79
+
80
+ return results.filter((r) => r.content.length > 0);
81
+ } catch {
82
+ return [];
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Build the memory section for the system prompt.
88
+ * This comprehensive prompt teaches the agent how to use memory effectively.
89
+ */
90
+ function buildMemorySection(
91
+ userMemory: string,
92
+ projectMemory: string,
93
+ additionalFiles: Array<{ filename: string; content: string }>,
94
+ agentId: string,
95
+ userMemoryPath: string,
96
+ projectMemoryPath: string | null
97
+ ): string {
98
+ let sections: string[] = [];
99
+
100
+ // Build memory content sections
101
+ if (userMemory) {
102
+ sections.push(`# Agent Memory (User-Level)
103
+
104
+ The following is your persistent memory stored at ${userMemoryPath}:
105
+
106
+ ${userMemory}`);
107
+ }
108
+
109
+ if (projectMemory && projectMemoryPath) {
110
+ sections.push(`# Agent Memory (Project-Level)
111
+
112
+ The following is project-specific context stored at ${projectMemoryPath}:
113
+
114
+ ${projectMemory}`);
115
+ }
116
+
117
+ if (additionalFiles.length > 0) {
118
+ const additionalSections = additionalFiles.map(
119
+ ({ filename, content }) => `## ${filename}
120
+
121
+ ${content}`
122
+ );
123
+ sections.push(`# Additional Context Files
124
+
125
+ ${additionalSections.join('\n\n')}`);
126
+ }
127
+
128
+ if (sections.length === 0) {
129
+ return ''; // No memory to inject
130
+ }
131
+
132
+ // Build comprehensive instructions
133
+ const memoryContent = sections.join('\n\n---\n\n');
134
+ const instructions = `
135
+ <agent_memory>
136
+ ${memoryContent}
137
+
138
+ ---
139
+
140
+ ## How to Use This Memory
141
+
142
+ **What is this?**
143
+ - The content above is your persistent memory, stored in markdown files
144
+ - **User-level memory** (${userMemoryPath}) contains your core personality, preferences, and cross-project context
145
+ - **Project-level memory** ${projectMemoryPath ? `(${projectMemoryPath})` : '(not available)'} contains project-specific context and conventions
146
+
147
+ **When to read memory:**
148
+ - You already have the memory content above in your context - no need to read the files unless you need to verify exact content
149
+ - If you need to check current memory state or see if it's been updated, use \`read_file\` tool
150
+
151
+ **When to update memory:**
152
+ - **User memory**: When you learn something important about the user's preferences, working style, or recurring patterns
153
+ - **Project memory**: When you discover project-specific conventions, architecture decisions, or important context
154
+ - **Additional files**: For specialized context that doesn't fit in agent.md (e.g., decision logs, architecture notes)
155
+
156
+ **How to update memory:**
157
+ - Use the \`write_file\` or \`edit_file\` tools with the file paths shown above
158
+ - Keep entries concise and relevant
159
+ - Organize information clearly with markdown headings
160
+ - Remove outdated information when updating
161
+
162
+ **Important guidelines:**
163
+ - Memory is meant for long-term context, not temporary task tracking
164
+ - Don't store information that's already in the codebase or documentation
165
+ - Focus on insights, patterns, and preferences that aren't obvious from other sources
166
+ - When in doubt, ask the user if something should be remembered
167
+
168
+ **Example use cases:**
169
+ - User prefers TypeScript strict mode and comprehensive error handling
170
+ - Project uses custom testing framework located in \`test-utils/\`
171
+ - User wants all API responses to follow specific error format
172
+ - Project has specific commit message conventions
173
+ </agent_memory>
174
+ `;
175
+
176
+ return instructions.trim();
177
+ }
178
+
179
+ /**
180
+ * Create agent memory middleware for AI SDK v6.
181
+ *
182
+ * This middleware loads agent memory from:
183
+ * 1. User-level: ~/.deepagents/{agentId}/agent.md (personality, preferences)
184
+ * 2. Project-level: [git-root]/.deepagents/agent.md (project-specific context)
185
+ * 3. Additional files: Any other .md files in the user-level directory
186
+ *
187
+ * The memory is injected into the system prompt before each model call, teaching
188
+ * the agent when and how to read/update its own memory using filesystem tools.
189
+ *
190
+ * @param options - Configuration for agent memory
191
+ * @param options.agentId - Unique identifier for the agent (e.g., "code-architect")
192
+ * @param options.workingDirectory - Optional working directory for project detection (defaults to process.cwd())
193
+ * @param options.userDeepagentsDir - Optional custom path for user-level .deepagents directory (defaults to ~/.deepagents)
194
+ * @param options.requestProjectApproval - Optional callback to request approval before creating project .deepagents/ directory
195
+ * @returns AI SDK v6 middleware
196
+ *
197
+ * @example Basic usage
198
+ * ```typescript
199
+ * import { createDeepAgent } from 'deepagentsdk';
200
+ * import { createAgentMemoryMiddleware } from 'deepagentsdk/middleware';
201
+ * import { anthropic } from '@ai-sdk/anthropic';
202
+ *
203
+ * const memoryMiddleware = createAgentMemoryMiddleware({
204
+ * agentId: 'code-architect',
205
+ * });
206
+ *
207
+ * const agent = createDeepAgent({
208
+ * model: anthropic('claude-sonnet-4-5'),
209
+ * middleware: memoryMiddleware,
210
+ * });
211
+ * ```
212
+ *
213
+ * @example With project approval callback
214
+ * ```typescript
215
+ * const memoryMiddleware = createAgentMemoryMiddleware({
216
+ * agentId: 'code-architect',
217
+ * requestProjectApproval: async (projectPath) => {
218
+ * console.log(`Create .deepagents/ in ${projectPath}? (y/n)`);
219
+ * // ... get user input
220
+ * return userSaidYes;
221
+ * }
222
+ * });
223
+ * ```
224
+ *
225
+ * @example With custom user directory path
226
+ * ```typescript
227
+ * const memoryMiddleware = createAgentMemoryMiddleware({
228
+ * agentId: 'code-architect',
229
+ * userDeepagentsDir: '/custom/path/.deepagents',
230
+ * // Memory will be loaded from:
231
+ * // - /custom/path/.deepagents/code-architect/agent.md
232
+ * // - [git-root]/.deepagents/agent.md (project-level)
233
+ * });
234
+ * ```
235
+ */
236
+ export function createAgentMemoryMiddleware(
237
+ options: AgentMemoryOptions
238
+ ): LanguageModelMiddleware {
239
+ const { agentId, workingDirectory, userDeepagentsDir, requestProjectApproval } = options;
240
+
241
+ // Memory is loaded once and cached in closure variables
242
+ let memoryLoaded = false;
243
+ let cachedMemorySection = '';
244
+
245
+ return {
246
+ specificationVersion: 'v3',
247
+ transformParams: async ({ params }) => {
248
+ // Load memory on first call only (closure-based caching)
249
+ if (!memoryLoaded) {
250
+ const workDir = workingDirectory || process.cwd();
251
+
252
+ // 1. Load user-level memory
253
+ const baseUserDir = userDeepagentsDir || path.join(os.homedir(), '.deepagents');
254
+ const userAgentDir = path.join(baseUserDir, agentId);
255
+ const userMemoryPath = path.join(userAgentDir, 'agent.md');
256
+ const userMemory = await loadAgentMemory(userMemoryPath);
257
+
258
+ // Auto-create user directory if it doesn't exist (safe operation)
259
+ if (!userMemory) {
260
+ try {
261
+ await fs.mkdir(userAgentDir, { recursive: true });
262
+ } catch {
263
+ // Ignore errors - directory might already exist or permissions issue
264
+ }
265
+ }
266
+
267
+ // 2. Load additional .md files from user directory
268
+ const additionalFiles = await loadAdditionalMemoryFiles(userAgentDir);
269
+
270
+ // 3. Load project-level memory (if in git repository)
271
+ let projectMemory = '';
272
+ let projectMemoryPath: string | null = null;
273
+
274
+ const gitRoot = await findGitRoot(workDir);
275
+ if (gitRoot) {
276
+ const projectDeepagentsDir = path.join(gitRoot, '.deepagents');
277
+ projectMemoryPath = path.join(projectDeepagentsDir, 'agent.md');
278
+
279
+ // Check if project directory exists
280
+ try {
281
+ await fs.stat(projectDeepagentsDir);
282
+ projectMemory = await loadAgentMemory(projectMemoryPath);
283
+ } catch {
284
+ // Project directory doesn't exist - request approval if callback provided
285
+ if (requestProjectApproval) {
286
+ const approved = await requestProjectApproval(gitRoot);
287
+ if (approved) {
288
+ try {
289
+ await fs.mkdir(projectDeepagentsDir, { recursive: true });
290
+ // Don't create agent.md yet - let the agent do it when needed
291
+ } catch {
292
+ // Ignore errors - permissions issue or race condition
293
+ }
294
+ }
295
+ }
296
+ }
297
+ }
298
+
299
+ // Build and cache memory section
300
+ cachedMemorySection = buildMemorySection(
301
+ userMemory,
302
+ projectMemory,
303
+ additionalFiles,
304
+ agentId,
305
+ userMemoryPath,
306
+ projectMemoryPath
307
+ );
308
+
309
+ memoryLoaded = true;
310
+ }
311
+
312
+ // Inject memory into system prompt if available
313
+ if (cachedMemorySection) {
314
+ const updatedPrompt = params.prompt.map((msg) => {
315
+ if (msg.role === 'system') {
316
+ return {
317
+ ...msg,
318
+ content: `${msg.content}\n\n${cachedMemorySection}`,
319
+ };
320
+ }
321
+ return msg;
322
+ });
323
+
324
+ return { ...params, prompt: updatedPrompt };
325
+ }
326
+
327
+ return params;
328
+ },
329
+ };
330
+ }