deepagentsdk 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +159 -0
- package/package.json +95 -0
- package/src/agent.ts +1230 -0
- package/src/backends/composite.ts +273 -0
- package/src/backends/filesystem.ts +692 -0
- package/src/backends/index.ts +22 -0
- package/src/backends/local-sandbox.ts +175 -0
- package/src/backends/persistent.ts +593 -0
- package/src/backends/sandbox.ts +510 -0
- package/src/backends/state.ts +244 -0
- package/src/backends/utils.ts +287 -0
- package/src/checkpointer/file-saver.ts +98 -0
- package/src/checkpointer/index.ts +5 -0
- package/src/checkpointer/kv-saver.ts +82 -0
- package/src/checkpointer/memory-saver.ts +82 -0
- package/src/checkpointer/types.ts +125 -0
- package/src/cli/components/ApiKeyInput.tsx +300 -0
- package/src/cli/components/FilePreview.tsx +237 -0
- package/src/cli/components/Input.tsx +277 -0
- package/src/cli/components/Message.tsx +93 -0
- package/src/cli/components/ModelSelection.tsx +338 -0
- package/src/cli/components/SlashMenu.tsx +101 -0
- package/src/cli/components/StatusBar.tsx +89 -0
- package/src/cli/components/Subagent.tsx +91 -0
- package/src/cli/components/TodoList.tsx +133 -0
- package/src/cli/components/ToolApproval.tsx +70 -0
- package/src/cli/components/ToolCall.tsx +144 -0
- package/src/cli/components/ToolCallSummary.tsx +175 -0
- package/src/cli/components/Welcome.tsx +75 -0
- package/src/cli/components/index.ts +24 -0
- package/src/cli/hooks/index.ts +12 -0
- package/src/cli/hooks/useAgent.ts +933 -0
- package/src/cli/index.tsx +1066 -0
- package/src/cli/theme.ts +205 -0
- package/src/cli/utils/model-list.ts +365 -0
- package/src/constants/errors.ts +29 -0
- package/src/constants/limits.ts +195 -0
- package/src/index.ts +176 -0
- package/src/middleware/agent-memory.ts +330 -0
- package/src/prompts.ts +196 -0
- package/src/skills/index.ts +2 -0
- package/src/skills/load.ts +191 -0
- package/src/skills/types.ts +53 -0
- package/src/tools/execute.ts +167 -0
- package/src/tools/filesystem.ts +418 -0
- package/src/tools/index.ts +39 -0
- package/src/tools/subagent.ts +443 -0
- package/src/tools/todos.ts +101 -0
- package/src/tools/web.ts +567 -0
- package/src/types/backend.ts +177 -0
- package/src/types/core.ts +220 -0
- package/src/types/events.ts +429 -0
- package/src/types/index.ts +94 -0
- package/src/types/structured-output.ts +43 -0
- package/src/types/subagent.ts +96 -0
- package/src/types.ts +22 -0
- package/src/utils/approval.ts +213 -0
- package/src/utils/events.ts +416 -0
- package/src/utils/eviction.ts +181 -0
- package/src/utils/index.ts +34 -0
- package/src/utils/model-parser.ts +38 -0
- package/src/utils/patch-tool-calls.ts +233 -0
- package/src/utils/project-detection.ts +32 -0
- package/src/utils/summarization.ts +254 -0
|
@@ -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
|
+
}
|