deepagentsdk 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/elements/index.cjs +324 -0
- package/dist/adapters/elements/index.cjs.map +1 -0
- package/dist/adapters/elements/index.d.cts +212 -0
- package/dist/adapters/elements/index.d.mts +212 -0
- package/dist/adapters/elements/index.mjs +320 -0
- package/dist/adapters/elements/index.mjs.map +1 -0
- package/dist/agent-CrH-He58.mjs +2974 -0
- package/dist/agent-CrH-He58.mjs.map +1 -0
- package/dist/agent-Cuks-Idh.cjs +3396 -0
- package/dist/agent-Cuks-Idh.cjs.map +1 -0
- package/dist/chunk-CbDLau6x.cjs +34 -0
- package/dist/cli/index.cjs +3162 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +1 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +3120 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/file-saver-BJCqMIb5.mjs +655 -0
- package/dist/file-saver-BJCqMIb5.mjs.map +1 -0
- package/dist/file-saver-C6O2LAvg.cjs +679 -0
- package/dist/file-saver-C6O2LAvg.cjs.map +1 -0
- package/dist/index.cjs +1471 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1581 -0
- package/dist/index.d.mts +1581 -0
- package/dist/index.mjs +1371 -0
- package/dist/index.mjs.map +1 -0
- package/dist/load-79a2H4m0.cjs +163 -0
- package/dist/load-79a2H4m0.cjs.map +1 -0
- package/dist/load-94gjHorc.mjs +3 -0
- package/dist/load-B6CA5js_.mjs +142 -0
- package/dist/load-B6CA5js_.mjs.map +1 -0
- package/dist/load-C2qVmZMp.cjs +3 -0
- package/dist/types-4g9UvXal.d.mts +1151 -0
- package/dist/types-IulnvhFg.d.cts +1151 -0
- package/package.json +26 -12
- package/src/adapters/elements/index.ts +0 -27
- package/src/adapters/elements/messageAdapter.ts +0 -165
- package/src/adapters/elements/statusAdapter.ts +0 -39
- package/src/adapters/elements/types.ts +0 -97
- package/src/adapters/elements/useElementsAdapter.ts +0 -261
- package/src/agent.ts +0 -1258
- package/src/backends/composite.ts +0 -273
- package/src/backends/filesystem.ts +0 -692
- package/src/backends/index.ts +0 -22
- package/src/backends/local-sandbox.ts +0 -175
- package/src/backends/persistent.ts +0 -593
- package/src/backends/sandbox.ts +0 -510
- package/src/backends/state.ts +0 -244
- package/src/backends/utils.ts +0 -287
- package/src/checkpointer/file-saver.ts +0 -98
- package/src/checkpointer/index.ts +0 -5
- package/src/checkpointer/kv-saver.ts +0 -82
- package/src/checkpointer/memory-saver.ts +0 -82
- package/src/checkpointer/types.ts +0 -125
- package/src/cli/components/ApiKeyInput.tsx +0 -300
- package/src/cli/components/FilePreview.tsx +0 -237
- package/src/cli/components/Input.tsx +0 -277
- package/src/cli/components/Message.tsx +0 -93
- package/src/cli/components/ModelSelection.tsx +0 -338
- package/src/cli/components/SlashMenu.tsx +0 -101
- package/src/cli/components/StatusBar.tsx +0 -89
- package/src/cli/components/Subagent.tsx +0 -91
- package/src/cli/components/TodoList.tsx +0 -133
- package/src/cli/components/ToolApproval.tsx +0 -70
- package/src/cli/components/ToolCall.tsx +0 -144
- package/src/cli/components/ToolCallSummary.tsx +0 -175
- package/src/cli/components/Welcome.tsx +0 -75
- package/src/cli/components/index.ts +0 -24
- package/src/cli/hooks/index.ts +0 -12
- package/src/cli/hooks/useAgent.ts +0 -933
- package/src/cli/index.tsx +0 -1066
- package/src/cli/theme.ts +0 -205
- package/src/cli/utils/model-list.ts +0 -365
- package/src/constants/errors.ts +0 -29
- package/src/constants/limits.ts +0 -195
- package/src/index.ts +0 -176
- package/src/middleware/agent-memory.ts +0 -330
- package/src/prompts.ts +0 -196
- package/src/skills/index.ts +0 -2
- package/src/skills/load.ts +0 -191
- package/src/skills/types.ts +0 -53
- package/src/tools/execute.ts +0 -167
- package/src/tools/filesystem.ts +0 -418
- package/src/tools/index.ts +0 -39
- package/src/tools/subagent.ts +0 -443
- package/src/tools/todos.ts +0 -101
- package/src/tools/web.ts +0 -567
- package/src/types/backend.ts +0 -177
- package/src/types/core.ts +0 -220
- package/src/types/events.ts +0 -430
- package/src/types/index.ts +0 -94
- package/src/types/structured-output.ts +0 -43
- package/src/types/subagent.ts +0 -96
- package/src/types.ts +0 -22
- package/src/utils/approval.ts +0 -213
- package/src/utils/events.ts +0 -416
- package/src/utils/eviction.ts +0 -181
- package/src/utils/index.ts +0 -34
- package/src/utils/model-parser.ts +0 -38
- package/src/utils/patch-tool-calls.ts +0 -233
- package/src/utils/project-detection.ts +0 -32
- package/src/utils/summarization.ts +0 -254
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1371 @@
|
|
|
1
|
+
import { A as glob, B as StateBackend, C as createFilesystemTools, D as createReadFileTool, E as createLsTool, F as DEFAULT_EVICTION_TOKEN_LIMIT, G as formatReadResponse, H as createFileData, I as createToolResultWrapper, J as performStringReplacement, K as globSearchFiles, L as estimateTokens, M as ls, N as read_file, O as createWriteFileTool, P as write_file, R as evictToolResult, S as createEditFileTool, T as createGrepTool, U as fileDataToString, V as checkEmptyContent, W as formatContentWithLineNumbers, X as FILE_ALREADY_EXISTS, Y as updateFileData, Z as FILE_NOT_FOUND, _ as createWebTools, a as estimateMessagesTokens, at as EXECUTE_SYSTEM_PROMPT, b as http_request, c as hasDanglingToolCalls, ct as TODO_SYSTEM_PROMPT, d as createExecuteTool, et as createTodosTool, f as createExecuteToolFromBackend, g as createWebSearchTool, gt as MAX_FILE_SIZE_MB, h as createHttpRequestTool, i as DEFAULT_SUMMARIZATION_THRESHOLD, it as DEFAULT_SUBAGENT_PROMPT, j as grep, k as edit_file, l as patchToolCalls, lt as getTaskToolDescription, m as createFetchUrlTool, mt as DEFAULT_READ_LIMIT, n as createDeepAgent, nt as BASE_PROMPT, o as needsSummarization, ot as FILESYSTEM_SYSTEM_PROMPT, p as execute, q as grepMatchesFromFiles, r as DEFAULT_KEEP_MESSAGES, rt as DEFAULT_GENERAL_PURPOSE_DESCRIPTION, s as summarizeIfNeeded, st as TASK_SYSTEM_PROMPT, t as DeepAgent, tt as write_todos, u as createSubagentTool, ut as isSandboxBackend, v as fetch_url, w as createGlobTool, x as web_search, y as htmlToMarkdown, z as shouldEvict } from "./agent-CrH-He58.mjs";
|
|
2
|
+
import { i as BaseSandbox, r as LocalSandbox, t as FileSaver } from "./file-saver-BJCqMIb5.mjs";
|
|
3
|
+
import { n as parseSkillMetadata, r as findGitRoot, t as listSkills } from "./load-B6CA5js_.mjs";
|
|
4
|
+
import { ToolLoopAgent, hasToolCall, stepCountIs, wrapLanguageModel } from "ai";
|
|
5
|
+
import micromatch from "micromatch";
|
|
6
|
+
import * as path$1 from "path";
|
|
7
|
+
import * as fs$1 from "fs/promises";
|
|
8
|
+
import * as fsSync from "fs";
|
|
9
|
+
import { spawn } from "child_process";
|
|
10
|
+
import fg from "fast-glob";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
import * as fs from "node:fs/promises";
|
|
13
|
+
import os from "node:os";
|
|
14
|
+
|
|
15
|
+
//#region src/types/structured-output.ts
|
|
16
|
+
/**
|
|
17
|
+
* Type guard for checking if a result has structured output
|
|
18
|
+
*/
|
|
19
|
+
function hasStructuredOutput(result) {
|
|
20
|
+
return result && typeof result === "object" && "output" in result;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Type guard for checking if an event has structured output
|
|
24
|
+
*/
|
|
25
|
+
function eventHasStructuredOutput(event) {
|
|
26
|
+
return event && event.type === "done" && "output" in event;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Extract structured output from agent result with type safety
|
|
30
|
+
*/
|
|
31
|
+
function getStructuredOutput(result) {
|
|
32
|
+
return hasStructuredOutput(result) ? result.output : void 0;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Extract structured output from event with type safety
|
|
36
|
+
*/
|
|
37
|
+
function getEventOutput(event) {
|
|
38
|
+
return eventHasStructuredOutput(event) ? event.output : void 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/backends/filesystem.ts
|
|
43
|
+
/**
|
|
44
|
+
* FilesystemBackend: Read and write files directly from the filesystem.
|
|
45
|
+
*/
|
|
46
|
+
const SUPPORTS_NOFOLLOW = fsSync.constants.O_NOFOLLOW !== void 0;
|
|
47
|
+
/**
|
|
48
|
+
* Backend that reads and writes files directly from the filesystem.
|
|
49
|
+
*
|
|
50
|
+
* Files are persisted to disk, making them available across agent invocations.
|
|
51
|
+
* This backend provides real file I/O operations with security checks to prevent
|
|
52
|
+
* directory traversal attacks.
|
|
53
|
+
*
|
|
54
|
+
* @example Basic usage
|
|
55
|
+
* ```typescript
|
|
56
|
+
* const backend = new FilesystemBackend({ rootDir: './workspace' });
|
|
57
|
+
* const agent = createDeepAgent({
|
|
58
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
59
|
+
* backend,
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* @example With custom options
|
|
64
|
+
* ```typescript
|
|
65
|
+
* const backend = new FilesystemBackend({
|
|
66
|
+
* rootDir: './my-project',
|
|
67
|
+
* virtualMode: false,
|
|
68
|
+
* maxFileSizeMb: 50, // Allow larger files
|
|
69
|
+
* });
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
var FilesystemBackend = class {
|
|
73
|
+
cwd;
|
|
74
|
+
virtualMode;
|
|
75
|
+
maxFileSizeBytes;
|
|
76
|
+
/**
|
|
77
|
+
* Create a new FilesystemBackend instance.
|
|
78
|
+
*
|
|
79
|
+
* @param options - Configuration options
|
|
80
|
+
* @param options.rootDir - Optional root directory for file operations (default: current working directory).
|
|
81
|
+
* All file paths are resolved relative to this directory.
|
|
82
|
+
* @param options.virtualMode - Optional flag for virtual mode (default: false).
|
|
83
|
+
* When true, files are stored in memory but paths are validated against filesystem.
|
|
84
|
+
* @param options.maxFileSizeMb - Optional maximum file size in MB (default: 10).
|
|
85
|
+
* Files larger than this will be rejected.
|
|
86
|
+
*/
|
|
87
|
+
constructor(options = {}) {
|
|
88
|
+
const { rootDir, virtualMode = false, maxFileSizeMb = MAX_FILE_SIZE_MB } = options;
|
|
89
|
+
this.cwd = rootDir ? path$1.resolve(rootDir) : process.cwd();
|
|
90
|
+
this.virtualMode = virtualMode;
|
|
91
|
+
this.maxFileSizeBytes = maxFileSizeMb * 1024 * 1024;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Resolve a file path with security checks.
|
|
95
|
+
*/
|
|
96
|
+
resolvePath(key) {
|
|
97
|
+
if (this.virtualMode) {
|
|
98
|
+
const vpath = key.startsWith("/") ? key : "/" + key;
|
|
99
|
+
if (vpath.includes("..") || vpath.startsWith("~")) throw new Error("Path traversal not allowed");
|
|
100
|
+
const full = path$1.resolve(this.cwd, vpath.substring(1));
|
|
101
|
+
const relative = path$1.relative(this.cwd, full);
|
|
102
|
+
if (relative.startsWith("..") || path$1.isAbsolute(relative)) throw new Error(`Path: ${full} outside root directory: ${this.cwd}`);
|
|
103
|
+
return full;
|
|
104
|
+
}
|
|
105
|
+
if (path$1.isAbsolute(key)) return key;
|
|
106
|
+
return path$1.resolve(this.cwd, key);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* List files and directories in the specified directory (non-recursive).
|
|
110
|
+
*/
|
|
111
|
+
async lsInfo(dirPath) {
|
|
112
|
+
try {
|
|
113
|
+
const resolvedPath = this.resolvePath(dirPath);
|
|
114
|
+
if (!(await fs$1.stat(resolvedPath)).isDirectory()) return [];
|
|
115
|
+
const entries = await fs$1.readdir(resolvedPath, { withFileTypes: true });
|
|
116
|
+
const results = [];
|
|
117
|
+
const cwdStr = this.cwd.endsWith(path$1.sep) ? this.cwd : this.cwd + path$1.sep;
|
|
118
|
+
for (const entry of entries) {
|
|
119
|
+
const fullPath = path$1.join(resolvedPath, entry.name);
|
|
120
|
+
try {
|
|
121
|
+
const entryStat = await fs$1.stat(fullPath);
|
|
122
|
+
const isFile = entryStat.isFile();
|
|
123
|
+
const isDir = entryStat.isDirectory();
|
|
124
|
+
if (!this.virtualMode) {
|
|
125
|
+
if (isFile) results.push({
|
|
126
|
+
path: fullPath,
|
|
127
|
+
is_dir: false,
|
|
128
|
+
size: entryStat.size,
|
|
129
|
+
modified_at: entryStat.mtime.toISOString()
|
|
130
|
+
});
|
|
131
|
+
else if (isDir) results.push({
|
|
132
|
+
path: fullPath + path$1.sep,
|
|
133
|
+
is_dir: true,
|
|
134
|
+
size: 0,
|
|
135
|
+
modified_at: entryStat.mtime.toISOString()
|
|
136
|
+
});
|
|
137
|
+
} else {
|
|
138
|
+
let relativePath;
|
|
139
|
+
if (fullPath.startsWith(cwdStr)) relativePath = fullPath.substring(cwdStr.length);
|
|
140
|
+
else if (fullPath.startsWith(this.cwd)) relativePath = fullPath.substring(this.cwd.length).replace(/^[/\\]/, "");
|
|
141
|
+
else relativePath = fullPath;
|
|
142
|
+
relativePath = relativePath.split(path$1.sep).join("/");
|
|
143
|
+
const virtPath = "/" + relativePath;
|
|
144
|
+
if (isFile) results.push({
|
|
145
|
+
path: virtPath,
|
|
146
|
+
is_dir: false,
|
|
147
|
+
size: entryStat.size,
|
|
148
|
+
modified_at: entryStat.mtime.toISOString()
|
|
149
|
+
});
|
|
150
|
+
else if (isDir) results.push({
|
|
151
|
+
path: virtPath + "/",
|
|
152
|
+
is_dir: true,
|
|
153
|
+
size: 0,
|
|
154
|
+
modified_at: entryStat.mtime.toISOString()
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
results.sort((a, b) => a.path.localeCompare(b.path));
|
|
162
|
+
return results;
|
|
163
|
+
} catch {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Read file content with line numbers.
|
|
169
|
+
*/
|
|
170
|
+
async read(filePath, offset = 0, limit = DEFAULT_READ_LIMIT) {
|
|
171
|
+
try {
|
|
172
|
+
const resolvedPath = this.resolvePath(filePath);
|
|
173
|
+
let content;
|
|
174
|
+
if (SUPPORTS_NOFOLLOW) {
|
|
175
|
+
if (!(await fs$1.stat(resolvedPath)).isFile()) return FILE_NOT_FOUND(filePath);
|
|
176
|
+
const fd = await fs$1.open(resolvedPath, fsSync.constants.O_RDONLY | fsSync.constants.O_NOFOLLOW);
|
|
177
|
+
try {
|
|
178
|
+
content = await fd.readFile({ encoding: "utf-8" });
|
|
179
|
+
} finally {
|
|
180
|
+
await fd.close();
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
const stat = await fs$1.lstat(resolvedPath);
|
|
184
|
+
if (stat.isSymbolicLink()) return `Error: Symlinks are not allowed: ${filePath}`;
|
|
185
|
+
if (!stat.isFile()) return FILE_NOT_FOUND(filePath);
|
|
186
|
+
content = await fs$1.readFile(resolvedPath, "utf-8");
|
|
187
|
+
}
|
|
188
|
+
const emptyMsg = checkEmptyContent(content);
|
|
189
|
+
if (emptyMsg) return emptyMsg;
|
|
190
|
+
const lines = content.split("\n");
|
|
191
|
+
const startIdx = offset;
|
|
192
|
+
const endIdx = Math.min(startIdx + limit, lines.length);
|
|
193
|
+
if (startIdx >= lines.length) return `Error: Line offset ${offset} exceeds file length (${lines.length} lines)`;
|
|
194
|
+
return formatContentWithLineNumbers(lines.slice(startIdx, endIdx), startIdx + 1);
|
|
195
|
+
} catch (e) {
|
|
196
|
+
return `Error reading file '${filePath}': ${e.message}`;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Read file content as raw FileData.
|
|
201
|
+
*/
|
|
202
|
+
async readRaw(filePath) {
|
|
203
|
+
const resolvedPath = this.resolvePath(filePath);
|
|
204
|
+
let content;
|
|
205
|
+
let stat;
|
|
206
|
+
if (SUPPORTS_NOFOLLOW) {
|
|
207
|
+
stat = await fs$1.stat(resolvedPath);
|
|
208
|
+
if (!stat.isFile()) throw new Error(`File '${filePath}' not found`);
|
|
209
|
+
const fd = await fs$1.open(resolvedPath, fsSync.constants.O_RDONLY | fsSync.constants.O_NOFOLLOW);
|
|
210
|
+
try {
|
|
211
|
+
content = await fd.readFile({ encoding: "utf-8" });
|
|
212
|
+
} finally {
|
|
213
|
+
await fd.close();
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
stat = await fs$1.lstat(resolvedPath);
|
|
217
|
+
if (stat.isSymbolicLink()) throw new Error(`Symlinks are not allowed: ${filePath}`);
|
|
218
|
+
if (!stat.isFile()) throw new Error(`File '${filePath}' not found`);
|
|
219
|
+
content = await fs$1.readFile(resolvedPath, "utf-8");
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
content: content.split("\n"),
|
|
223
|
+
created_at: stat.ctime.toISOString(),
|
|
224
|
+
modified_at: stat.mtime.toISOString()
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Create a new file with content.
|
|
229
|
+
*/
|
|
230
|
+
async write(filePath, content) {
|
|
231
|
+
try {
|
|
232
|
+
const resolvedPath = this.resolvePath(filePath);
|
|
233
|
+
try {
|
|
234
|
+
if ((await fs$1.lstat(resolvedPath)).isSymbolicLink()) return {
|
|
235
|
+
success: false,
|
|
236
|
+
error: `Cannot write to ${filePath} because it is a symlink. Symlinks are not allowed.`
|
|
237
|
+
};
|
|
238
|
+
return {
|
|
239
|
+
success: false,
|
|
240
|
+
error: FILE_ALREADY_EXISTS(filePath)
|
|
241
|
+
};
|
|
242
|
+
} catch {}
|
|
243
|
+
await fs$1.mkdir(path$1.dirname(resolvedPath), { recursive: true });
|
|
244
|
+
if (SUPPORTS_NOFOLLOW) {
|
|
245
|
+
const flags = fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_TRUNC | fsSync.constants.O_NOFOLLOW;
|
|
246
|
+
const fd = await fs$1.open(resolvedPath, flags, 420);
|
|
247
|
+
try {
|
|
248
|
+
await fd.writeFile(content, "utf-8");
|
|
249
|
+
} finally {
|
|
250
|
+
await fd.close();
|
|
251
|
+
}
|
|
252
|
+
} else await fs$1.writeFile(resolvedPath, content, "utf-8");
|
|
253
|
+
return {
|
|
254
|
+
success: true,
|
|
255
|
+
path: filePath
|
|
256
|
+
};
|
|
257
|
+
} catch (e) {
|
|
258
|
+
return {
|
|
259
|
+
success: false,
|
|
260
|
+
error: `Error writing file '${filePath}': ${e.message}`
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Edit a file by replacing string occurrences.
|
|
266
|
+
*/
|
|
267
|
+
async edit(filePath, oldString, newString, replaceAll = false) {
|
|
268
|
+
try {
|
|
269
|
+
const resolvedPath = this.resolvePath(filePath);
|
|
270
|
+
let content;
|
|
271
|
+
if (SUPPORTS_NOFOLLOW) {
|
|
272
|
+
if (!(await fs$1.stat(resolvedPath)).isFile()) return {
|
|
273
|
+
success: false,
|
|
274
|
+
error: FILE_NOT_FOUND(filePath)
|
|
275
|
+
};
|
|
276
|
+
const fd = await fs$1.open(resolvedPath, fsSync.constants.O_RDONLY | fsSync.constants.O_NOFOLLOW);
|
|
277
|
+
try {
|
|
278
|
+
content = await fd.readFile({ encoding: "utf-8" });
|
|
279
|
+
} finally {
|
|
280
|
+
await fd.close();
|
|
281
|
+
}
|
|
282
|
+
} else {
|
|
283
|
+
const stat = await fs$1.lstat(resolvedPath);
|
|
284
|
+
if (stat.isSymbolicLink()) return {
|
|
285
|
+
success: false,
|
|
286
|
+
error: `Error: Symlinks are not allowed: ${filePath}`
|
|
287
|
+
};
|
|
288
|
+
if (!stat.isFile()) return {
|
|
289
|
+
success: false,
|
|
290
|
+
error: FILE_NOT_FOUND(filePath)
|
|
291
|
+
};
|
|
292
|
+
content = await fs$1.readFile(resolvedPath, "utf-8");
|
|
293
|
+
}
|
|
294
|
+
const result = performStringReplacement(content, oldString, newString, replaceAll);
|
|
295
|
+
if (typeof result === "string") return {
|
|
296
|
+
success: false,
|
|
297
|
+
error: result
|
|
298
|
+
};
|
|
299
|
+
const [newContent, occurrences] = result;
|
|
300
|
+
if (SUPPORTS_NOFOLLOW) {
|
|
301
|
+
const flags = fsSync.constants.O_WRONLY | fsSync.constants.O_TRUNC | fsSync.constants.O_NOFOLLOW;
|
|
302
|
+
const fd = await fs$1.open(resolvedPath, flags);
|
|
303
|
+
try {
|
|
304
|
+
await fd.writeFile(newContent, "utf-8");
|
|
305
|
+
} finally {
|
|
306
|
+
await fd.close();
|
|
307
|
+
}
|
|
308
|
+
} else await fs$1.writeFile(resolvedPath, newContent, "utf-8");
|
|
309
|
+
return {
|
|
310
|
+
success: true,
|
|
311
|
+
path: filePath,
|
|
312
|
+
occurrences
|
|
313
|
+
};
|
|
314
|
+
} catch (e) {
|
|
315
|
+
return {
|
|
316
|
+
success: false,
|
|
317
|
+
error: `Error editing file '${filePath}': ${e.message}`
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Structured search results or error string for invalid input.
|
|
323
|
+
*/
|
|
324
|
+
async grepRaw(pattern, dirPath = "/", glob$1 = null) {
|
|
325
|
+
try {
|
|
326
|
+
new RegExp(pattern);
|
|
327
|
+
} catch (e) {
|
|
328
|
+
return `Invalid regex pattern: ${e.message}`;
|
|
329
|
+
}
|
|
330
|
+
let baseFull;
|
|
331
|
+
try {
|
|
332
|
+
baseFull = this.resolvePath(dirPath || ".");
|
|
333
|
+
} catch {
|
|
334
|
+
return [];
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
await fs$1.stat(baseFull);
|
|
338
|
+
} catch {
|
|
339
|
+
return [];
|
|
340
|
+
}
|
|
341
|
+
let results = await this.ripgrepSearch(pattern, baseFull, glob$1);
|
|
342
|
+
if (results === null) results = await this.regexSearch(pattern, baseFull, glob$1);
|
|
343
|
+
const matches = [];
|
|
344
|
+
for (const [fpath, items] of Object.entries(results)) for (const [lineNum, lineText] of items) matches.push({
|
|
345
|
+
path: fpath,
|
|
346
|
+
line: lineNum,
|
|
347
|
+
text: lineText
|
|
348
|
+
});
|
|
349
|
+
return matches;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Try to use ripgrep for fast searching.
|
|
353
|
+
*/
|
|
354
|
+
async ripgrepSearch(pattern, baseFull, includeGlob) {
|
|
355
|
+
return new Promise((resolve) => {
|
|
356
|
+
const args = ["--json"];
|
|
357
|
+
if (includeGlob) args.push("--glob", includeGlob);
|
|
358
|
+
args.push("--", pattern, baseFull);
|
|
359
|
+
const proc = spawn("rg", args, { timeout: 3e4 });
|
|
360
|
+
const results = {};
|
|
361
|
+
let output = "";
|
|
362
|
+
proc.stdout.on("data", (data) => {
|
|
363
|
+
output += data.toString();
|
|
364
|
+
});
|
|
365
|
+
proc.on("close", (code) => {
|
|
366
|
+
if (code !== 0 && code !== 1) {
|
|
367
|
+
resolve(null);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
for (const line of output.split("\n")) {
|
|
371
|
+
if (!line.trim()) continue;
|
|
372
|
+
try {
|
|
373
|
+
const data = JSON.parse(line);
|
|
374
|
+
if (data.type !== "match") continue;
|
|
375
|
+
const pdata = data.data || {};
|
|
376
|
+
const ftext = pdata.path?.text;
|
|
377
|
+
if (!ftext) continue;
|
|
378
|
+
let virtPath;
|
|
379
|
+
if (this.virtualMode) try {
|
|
380
|
+
const resolved = path$1.resolve(ftext);
|
|
381
|
+
const relative = path$1.relative(this.cwd, resolved);
|
|
382
|
+
if (relative.startsWith("..")) continue;
|
|
383
|
+
virtPath = "/" + relative.split(path$1.sep).join("/");
|
|
384
|
+
} catch {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
else virtPath = ftext;
|
|
388
|
+
const ln = pdata.line_number;
|
|
389
|
+
const lt = pdata.lines?.text?.replace(/\n$/, "") || "";
|
|
390
|
+
if (ln === void 0) continue;
|
|
391
|
+
if (!results[virtPath]) results[virtPath] = [];
|
|
392
|
+
results[virtPath].push([ln, lt]);
|
|
393
|
+
} catch {
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
resolve(results);
|
|
398
|
+
});
|
|
399
|
+
proc.on("error", () => {
|
|
400
|
+
resolve(null);
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Fallback regex search implementation.
|
|
406
|
+
*/
|
|
407
|
+
async regexSearch(pattern, baseFull, includeGlob) {
|
|
408
|
+
let regex;
|
|
409
|
+
try {
|
|
410
|
+
regex = new RegExp(pattern);
|
|
411
|
+
} catch {
|
|
412
|
+
return {};
|
|
413
|
+
}
|
|
414
|
+
const results = {};
|
|
415
|
+
const files = await fg("**/*", {
|
|
416
|
+
cwd: (await fs$1.stat(baseFull)).isDirectory() ? baseFull : path$1.dirname(baseFull),
|
|
417
|
+
absolute: true,
|
|
418
|
+
onlyFiles: true,
|
|
419
|
+
dot: true
|
|
420
|
+
});
|
|
421
|
+
for (const fp of files) try {
|
|
422
|
+
if (includeGlob && !micromatch.isMatch(path$1.basename(fp), includeGlob)) continue;
|
|
423
|
+
if ((await fs$1.stat(fp)).size > this.maxFileSizeBytes) continue;
|
|
424
|
+
const lines = (await fs$1.readFile(fp, "utf-8")).split("\n");
|
|
425
|
+
for (let i = 0; i < lines.length; i++) {
|
|
426
|
+
const line = lines[i];
|
|
427
|
+
if (line && regex.test(line)) {
|
|
428
|
+
let virtPath;
|
|
429
|
+
if (this.virtualMode) try {
|
|
430
|
+
const relative = path$1.relative(this.cwd, fp);
|
|
431
|
+
if (relative.startsWith("..")) continue;
|
|
432
|
+
virtPath = "/" + relative.split(path$1.sep).join("/");
|
|
433
|
+
} catch {
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
else virtPath = fp;
|
|
437
|
+
if (!results[virtPath]) results[virtPath] = [];
|
|
438
|
+
results[virtPath].push([i + 1, line]);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
} catch {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
return results;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Structured glob matching returning FileInfo objects.
|
|
448
|
+
*/
|
|
449
|
+
async globInfo(pattern, searchPath = "/") {
|
|
450
|
+
if (pattern.startsWith("/")) pattern = pattern.substring(1);
|
|
451
|
+
const resolvedSearchPath = searchPath === "/" ? this.cwd : this.resolvePath(searchPath);
|
|
452
|
+
try {
|
|
453
|
+
if (!(await fs$1.stat(resolvedSearchPath)).isDirectory()) return [];
|
|
454
|
+
} catch {
|
|
455
|
+
return [];
|
|
456
|
+
}
|
|
457
|
+
const results = [];
|
|
458
|
+
try {
|
|
459
|
+
const matches = await fg(pattern, {
|
|
460
|
+
cwd: resolvedSearchPath,
|
|
461
|
+
absolute: true,
|
|
462
|
+
onlyFiles: true,
|
|
463
|
+
dot: true
|
|
464
|
+
});
|
|
465
|
+
for (const matchedPath of matches) try {
|
|
466
|
+
const fileStat = await fs$1.stat(matchedPath);
|
|
467
|
+
if (!fileStat.isFile()) continue;
|
|
468
|
+
const normalizedPath = matchedPath.split("/").join(path$1.sep);
|
|
469
|
+
if (!this.virtualMode) results.push({
|
|
470
|
+
path: normalizedPath,
|
|
471
|
+
is_dir: false,
|
|
472
|
+
size: fileStat.size,
|
|
473
|
+
modified_at: fileStat.mtime.toISOString()
|
|
474
|
+
});
|
|
475
|
+
else {
|
|
476
|
+
const cwdStr = this.cwd.endsWith(path$1.sep) ? this.cwd : this.cwd + path$1.sep;
|
|
477
|
+
let relativePath;
|
|
478
|
+
if (normalizedPath.startsWith(cwdStr)) relativePath = normalizedPath.substring(cwdStr.length);
|
|
479
|
+
else if (normalizedPath.startsWith(this.cwd)) relativePath = normalizedPath.substring(this.cwd.length).replace(/^[/\\]/, "");
|
|
480
|
+
else relativePath = normalizedPath;
|
|
481
|
+
relativePath = relativePath.split(path$1.sep).join("/");
|
|
482
|
+
const virt = "/" + relativePath;
|
|
483
|
+
results.push({
|
|
484
|
+
path: virt,
|
|
485
|
+
is_dir: false,
|
|
486
|
+
size: fileStat.size,
|
|
487
|
+
modified_at: fileStat.mtime.toISOString()
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
} catch {
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
} catch {}
|
|
494
|
+
results.sort((a, b) => a.path.localeCompare(b.path));
|
|
495
|
+
return results;
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
//#endregion
|
|
500
|
+
//#region src/backends/composite.ts
|
|
501
|
+
/**
|
|
502
|
+
* Backend that routes file operations to different backends based on path prefix.
|
|
503
|
+
*
|
|
504
|
+
* This enables hybrid storage strategies by routing files to different backends
|
|
505
|
+
* based on their path prefix. Useful for separating persistent and ephemeral storage,
|
|
506
|
+
* or using different storage backends for different types of files.
|
|
507
|
+
*
|
|
508
|
+
* @example Hybrid storage strategy
|
|
509
|
+
* ```typescript
|
|
510
|
+
* import { CompositeBackend, FilesystemBackend, StateBackend } from 'deepagentsdk';
|
|
511
|
+
*
|
|
512
|
+
* const state = { todos: [], files: {} };
|
|
513
|
+
* const backend = new CompositeBackend(
|
|
514
|
+
* new StateBackend(state), // Default: ephemeral storage
|
|
515
|
+
* {
|
|
516
|
+
* '/persistent/': new FilesystemBackend({ rootDir: './persistent' }), // Persistent files
|
|
517
|
+
* '/cache/': new StateBackend(state), // Cached files (ephemeral)
|
|
518
|
+
* }
|
|
519
|
+
* );
|
|
520
|
+
*
|
|
521
|
+
* const agent = createDeepAgent({
|
|
522
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
523
|
+
* backend,
|
|
524
|
+
* });
|
|
525
|
+
* ```
|
|
526
|
+
*
|
|
527
|
+
* @example Multiple persistent backends
|
|
528
|
+
* ```typescript
|
|
529
|
+
* const backend = new CompositeBackend(
|
|
530
|
+
* new FilesystemBackend({ rootDir: './default' }),
|
|
531
|
+
* {
|
|
532
|
+
* '/user-data/': new FilesystemBackend({ rootDir: './user-data' }),
|
|
533
|
+
* '/system/': new FilesystemBackend({ rootDir: './system' }),
|
|
534
|
+
* }
|
|
535
|
+
* );
|
|
536
|
+
* ```
|
|
537
|
+
*/
|
|
538
|
+
var CompositeBackend = class {
|
|
539
|
+
defaultBackend;
|
|
540
|
+
routes;
|
|
541
|
+
sortedRoutes;
|
|
542
|
+
/**
|
|
543
|
+
* Create a new CompositeBackend instance.
|
|
544
|
+
*
|
|
545
|
+
* @param defaultBackend - Backend to use for paths that don't match any route prefix
|
|
546
|
+
* @param routes - Record mapping path prefixes to backends.
|
|
547
|
+
* Routes are matched by longest prefix first.
|
|
548
|
+
* Example: `{ '/persistent/': filesystemBackend, '/cache/': stateBackend }`
|
|
549
|
+
*/
|
|
550
|
+
constructor(defaultBackend, routes) {
|
|
551
|
+
this.defaultBackend = defaultBackend;
|
|
552
|
+
this.routes = routes;
|
|
553
|
+
this.sortedRoutes = Object.entries(routes).sort((a, b) => b[0].length - a[0].length);
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Determine which backend handles this key and strip prefix.
|
|
557
|
+
*/
|
|
558
|
+
getBackendAndKey(key) {
|
|
559
|
+
for (const [prefix, backend] of this.sortedRoutes) if (key.startsWith(prefix)) {
|
|
560
|
+
const suffix = key.substring(prefix.length);
|
|
561
|
+
return [backend, suffix ? "/" + suffix : "/"];
|
|
562
|
+
}
|
|
563
|
+
return [this.defaultBackend, key];
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* List files and directories in the specified directory (non-recursive).
|
|
567
|
+
*/
|
|
568
|
+
async lsInfo(path$2) {
|
|
569
|
+
for (const [routePrefix, backend] of this.sortedRoutes) if (path$2.startsWith(routePrefix.replace(/\/$/, ""))) {
|
|
570
|
+
const suffix = path$2.substring(routePrefix.length);
|
|
571
|
+
const searchPath = suffix ? "/" + suffix : "/";
|
|
572
|
+
const infos = await backend.lsInfo(searchPath);
|
|
573
|
+
const prefixed = [];
|
|
574
|
+
for (const fi of infos) prefixed.push({
|
|
575
|
+
...fi,
|
|
576
|
+
path: routePrefix.slice(0, -1) + fi.path
|
|
577
|
+
});
|
|
578
|
+
return prefixed;
|
|
579
|
+
}
|
|
580
|
+
if (path$2 === "/") {
|
|
581
|
+
const results = [];
|
|
582
|
+
const defaultInfos = await this.defaultBackend.lsInfo(path$2);
|
|
583
|
+
results.push(...defaultInfos);
|
|
584
|
+
for (const [routePrefix] of this.sortedRoutes) results.push({
|
|
585
|
+
path: routePrefix,
|
|
586
|
+
is_dir: true,
|
|
587
|
+
size: 0,
|
|
588
|
+
modified_at: ""
|
|
589
|
+
});
|
|
590
|
+
results.sort((a, b) => a.path.localeCompare(b.path));
|
|
591
|
+
return results;
|
|
592
|
+
}
|
|
593
|
+
return await this.defaultBackend.lsInfo(path$2);
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Read file content, routing to appropriate backend.
|
|
597
|
+
*/
|
|
598
|
+
async read(filePath, offset = 0, limit = 2e3) {
|
|
599
|
+
const [backend, strippedKey] = this.getBackendAndKey(filePath);
|
|
600
|
+
return await backend.read(strippedKey, offset, limit);
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Read file content as raw FileData.
|
|
604
|
+
*/
|
|
605
|
+
async readRaw(filePath) {
|
|
606
|
+
const [backend, strippedKey] = this.getBackendAndKey(filePath);
|
|
607
|
+
return await backend.readRaw(strippedKey);
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Structured search results or error string for invalid input.
|
|
611
|
+
*/
|
|
612
|
+
async grepRaw(pattern, path$2 = "/", glob$1 = null) {
|
|
613
|
+
for (const [routePrefix, backend] of this.sortedRoutes) if (path$2.startsWith(routePrefix.replace(/\/$/, ""))) {
|
|
614
|
+
const suffix = path$2.substring(routePrefix.length);
|
|
615
|
+
const searchPath = suffix ? "/" + suffix : "/";
|
|
616
|
+
const raw = await backend.grepRaw(pattern, searchPath, glob$1);
|
|
617
|
+
if (typeof raw === "string") return raw;
|
|
618
|
+
return raw.map((m) => ({
|
|
619
|
+
...m,
|
|
620
|
+
path: routePrefix.slice(0, -1) + m.path
|
|
621
|
+
}));
|
|
622
|
+
}
|
|
623
|
+
const allMatches = [];
|
|
624
|
+
const rawDefault = await this.defaultBackend.grepRaw(pattern, path$2, glob$1);
|
|
625
|
+
if (typeof rawDefault === "string") return rawDefault;
|
|
626
|
+
allMatches.push(...rawDefault);
|
|
627
|
+
for (const [routePrefix, backend] of Object.entries(this.routes)) {
|
|
628
|
+
const raw = await backend.grepRaw(pattern, "/", glob$1);
|
|
629
|
+
if (typeof raw === "string") return raw;
|
|
630
|
+
allMatches.push(...raw.map((m) => ({
|
|
631
|
+
...m,
|
|
632
|
+
path: routePrefix.slice(0, -1) + m.path
|
|
633
|
+
})));
|
|
634
|
+
}
|
|
635
|
+
return allMatches;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Structured glob matching returning FileInfo objects.
|
|
639
|
+
*/
|
|
640
|
+
async globInfo(pattern, path$2 = "/") {
|
|
641
|
+
const results = [];
|
|
642
|
+
for (const [routePrefix, backend] of this.sortedRoutes) if (path$2.startsWith(routePrefix.replace(/\/$/, ""))) {
|
|
643
|
+
const suffix = path$2.substring(routePrefix.length);
|
|
644
|
+
const searchPath = suffix ? "/" + suffix : "/";
|
|
645
|
+
return (await backend.globInfo(pattern, searchPath)).map((fi) => ({
|
|
646
|
+
...fi,
|
|
647
|
+
path: routePrefix.slice(0, -1) + fi.path
|
|
648
|
+
}));
|
|
649
|
+
}
|
|
650
|
+
const defaultInfos = await this.defaultBackend.globInfo(pattern, path$2);
|
|
651
|
+
results.push(...defaultInfos);
|
|
652
|
+
for (const [routePrefix, backend] of Object.entries(this.routes)) {
|
|
653
|
+
const infos = await backend.globInfo(pattern, "/");
|
|
654
|
+
results.push(...infos.map((fi) => ({
|
|
655
|
+
...fi,
|
|
656
|
+
path: routePrefix.slice(0, -1) + fi.path
|
|
657
|
+
})));
|
|
658
|
+
}
|
|
659
|
+
results.sort((a, b) => a.path.localeCompare(b.path));
|
|
660
|
+
return results;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Create a new file, routing to appropriate backend.
|
|
664
|
+
*/
|
|
665
|
+
async write(filePath, content) {
|
|
666
|
+
const [backend, strippedKey] = this.getBackendAndKey(filePath);
|
|
667
|
+
return await backend.write(strippedKey, content);
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Edit a file, routing to appropriate backend.
|
|
671
|
+
*/
|
|
672
|
+
async edit(filePath, oldString, newString, replaceAll = false) {
|
|
673
|
+
const [backend, strippedKey] = this.getBackendAndKey(filePath);
|
|
674
|
+
return await backend.edit(strippedKey, oldString, newString, replaceAll);
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
//#endregion
|
|
679
|
+
//#region src/backends/persistent.ts
|
|
680
|
+
/**
|
|
681
|
+
* Simple in-memory implementation of KeyValueStore.
|
|
682
|
+
*
|
|
683
|
+
* Useful for testing or single-session persistence. Data is stored in a Map
|
|
684
|
+
* and does not persist across application restarts.
|
|
685
|
+
*
|
|
686
|
+
* @example Basic usage
|
|
687
|
+
* ```typescript
|
|
688
|
+
* const store = new InMemoryStore();
|
|
689
|
+
* const backend = new PersistentBackend({ store });
|
|
690
|
+
* ```
|
|
691
|
+
*
|
|
692
|
+
* @example For testing
|
|
693
|
+
* ```typescript
|
|
694
|
+
* const store = new InMemoryStore();
|
|
695
|
+
* // ... run tests ...
|
|
696
|
+
* store.clear(); // Clean up after tests
|
|
697
|
+
* ```
|
|
698
|
+
*/
|
|
699
|
+
var InMemoryStore = class {
|
|
700
|
+
data = /* @__PURE__ */ new Map();
|
|
701
|
+
makeKey(namespace, key) {
|
|
702
|
+
return [...namespace, key].join(":");
|
|
703
|
+
}
|
|
704
|
+
parseKey(fullKey, namespace) {
|
|
705
|
+
const prefix = namespace.join(":") + ":";
|
|
706
|
+
if (fullKey.startsWith(prefix)) return fullKey.substring(prefix.length);
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
async get(namespace, key) {
|
|
710
|
+
return this.data.get(this.makeKey(namespace, key));
|
|
711
|
+
}
|
|
712
|
+
async put(namespace, key, value) {
|
|
713
|
+
this.data.set(this.makeKey(namespace, key), value);
|
|
714
|
+
}
|
|
715
|
+
async delete(namespace, key) {
|
|
716
|
+
this.data.delete(this.makeKey(namespace, key));
|
|
717
|
+
}
|
|
718
|
+
async list(namespace) {
|
|
719
|
+
const results = [];
|
|
720
|
+
const prefix = namespace.join(":") + ":";
|
|
721
|
+
for (const [fullKey, value] of this.data.entries()) if (fullKey.startsWith(prefix)) {
|
|
722
|
+
const key = fullKey.substring(prefix.length);
|
|
723
|
+
if (!key.includes(":")) results.push({
|
|
724
|
+
key,
|
|
725
|
+
value
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
return results;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Clear all data (useful for testing).
|
|
732
|
+
*/
|
|
733
|
+
clear() {
|
|
734
|
+
this.data.clear();
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Get the number of stored items.
|
|
738
|
+
*/
|
|
739
|
+
size() {
|
|
740
|
+
return this.data.size;
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
/**
|
|
744
|
+
* Backend that stores files in a persistent key-value store.
|
|
745
|
+
*
|
|
746
|
+
* This provides cross-conversation file persistence that survives between agent sessions.
|
|
747
|
+
* Files are stored in the provided key-value store, allowing you to use any storage backend
|
|
748
|
+
* (Redis, SQLite, cloud storage, etc.) by implementing the `KeyValueStore` interface.
|
|
749
|
+
*
|
|
750
|
+
* @example Using InMemoryStore (for testing or single-session persistence)
|
|
751
|
+
* ```typescript
|
|
752
|
+
* import { createDeepAgent } from 'deepagentsdk';
|
|
753
|
+
* import { PersistentBackend, InMemoryStore } from 'deepagentsdk';
|
|
754
|
+
* import { anthropic } from '@ai-sdk/anthropic';
|
|
755
|
+
*
|
|
756
|
+
* const store = new InMemoryStore();
|
|
757
|
+
* const backend = new PersistentBackend({ store });
|
|
758
|
+
* const agent = createDeepAgent({
|
|
759
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
760
|
+
* backend,
|
|
761
|
+
* });
|
|
762
|
+
* ```
|
|
763
|
+
*
|
|
764
|
+
* @example With custom namespace for project isolation
|
|
765
|
+
* ```typescript
|
|
766
|
+
* import { createDeepAgent } from 'deepagentsdk';
|
|
767
|
+
* import { PersistentBackend, InMemoryStore } from 'deepagentsdk';
|
|
768
|
+
* import { anthropic } from '@ai-sdk/anthropic';
|
|
769
|
+
*
|
|
770
|
+
* const store = new InMemoryStore();
|
|
771
|
+
* const backend = new PersistentBackend({
|
|
772
|
+
* store,
|
|
773
|
+
* namespace: 'project-123', // Isolate files for this project
|
|
774
|
+
* });
|
|
775
|
+
* const agent = createDeepAgent({
|
|
776
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
777
|
+
* backend,
|
|
778
|
+
* });
|
|
779
|
+
* ```
|
|
780
|
+
*
|
|
781
|
+
* @example Custom KeyValueStore implementation (Redis)
|
|
782
|
+
* ```typescript
|
|
783
|
+
* import { createDeepAgent } from 'deepagentsdk';
|
|
784
|
+
* import { PersistentBackend, type KeyValueStore } from 'deepagentsdk';
|
|
785
|
+
* import { anthropic } from '@ai-sdk/anthropic';
|
|
786
|
+
* import { createClient } from 'redis';
|
|
787
|
+
*
|
|
788
|
+
* class RedisStore implements KeyValueStore {
|
|
789
|
+
* constructor(private redis: ReturnType<typeof createClient>) {}
|
|
790
|
+
*
|
|
791
|
+
* async get(namespace: string[], key: string) {
|
|
792
|
+
* const redisKey = [...namespace, key].join(':');
|
|
793
|
+
* const data = await this.redis.get(redisKey);
|
|
794
|
+
* return data ? JSON.parse(data) : undefined;
|
|
795
|
+
* }
|
|
796
|
+
*
|
|
797
|
+
* async put(namespace: string[], key: string, value: Record<string, unknown>) {
|
|
798
|
+
* const redisKey = [...namespace, key].join(':');
|
|
799
|
+
* await this.redis.set(redisKey, JSON.stringify(value));
|
|
800
|
+
* }
|
|
801
|
+
*
|
|
802
|
+
* async delete(namespace: string[], key: string) {
|
|
803
|
+
* const redisKey = [...namespace, key].join(':');
|
|
804
|
+
* await this.redis.del(redisKey);
|
|
805
|
+
* }
|
|
806
|
+
*
|
|
807
|
+
* async list(namespace: string[]) {
|
|
808
|
+
* const prefix = [...namespace].join(':') + ':';
|
|
809
|
+
* const keys = await this.redis.keys(prefix + '*');
|
|
810
|
+
* const results = [];
|
|
811
|
+
* for (const key of keys) {
|
|
812
|
+
* const data = await this.redis.get(key);
|
|
813
|
+
* if (data) {
|
|
814
|
+
* const relativeKey = key.substring(prefix.length);
|
|
815
|
+
* results.push({ key: relativeKey, value: JSON.parse(data) });
|
|
816
|
+
* }
|
|
817
|
+
* }
|
|
818
|
+
* return results;
|
|
819
|
+
* }
|
|
820
|
+
* }
|
|
821
|
+
*
|
|
822
|
+
* const redis = createClient();
|
|
823
|
+
* await redis.connect();
|
|
824
|
+
*
|
|
825
|
+
* const backend = new PersistentBackend({
|
|
826
|
+
* store: new RedisStore(redis),
|
|
827
|
+
* namespace: 'production'
|
|
828
|
+
* });
|
|
829
|
+
*
|
|
830
|
+
* const agent = createDeepAgent({
|
|
831
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
832
|
+
* backend,
|
|
833
|
+
* });
|
|
834
|
+
* ```
|
|
835
|
+
*/
|
|
836
|
+
var PersistentBackend = class {
|
|
837
|
+
store;
|
|
838
|
+
namespacePrefix;
|
|
839
|
+
/**
|
|
840
|
+
* Create a new PersistentBackend instance.
|
|
841
|
+
*
|
|
842
|
+
* @param options - Configuration options
|
|
843
|
+
* @param options.store - The key-value store implementation to use
|
|
844
|
+
* @param options.namespace - Optional namespace prefix for file isolation
|
|
845
|
+
*/
|
|
846
|
+
constructor(options) {
|
|
847
|
+
this.store = options.store;
|
|
848
|
+
this.namespacePrefix = options.namespace || "default";
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Get the namespace for store operations.
|
|
852
|
+
*/
|
|
853
|
+
getNamespace() {
|
|
854
|
+
return [this.namespacePrefix, "filesystem"];
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Convert a store value to FileData format.
|
|
858
|
+
*/
|
|
859
|
+
convertToFileData(value) {
|
|
860
|
+
if (!value.content || !Array.isArray(value.content) || typeof value.created_at !== "string" || typeof value.modified_at !== "string") throw new Error(`Store item does not contain valid FileData fields. Got keys: ${Object.keys(value).join(", ")}`);
|
|
861
|
+
return {
|
|
862
|
+
content: value.content,
|
|
863
|
+
created_at: value.created_at,
|
|
864
|
+
modified_at: value.modified_at
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Convert FileData to a value suitable for store.put().
|
|
869
|
+
*/
|
|
870
|
+
convertFromFileData(fileData) {
|
|
871
|
+
return {
|
|
872
|
+
content: fileData.content,
|
|
873
|
+
created_at: fileData.created_at,
|
|
874
|
+
modified_at: fileData.modified_at
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* List files and directories in the specified directory (non-recursive).
|
|
879
|
+
*/
|
|
880
|
+
async lsInfo(path$2) {
|
|
881
|
+
const namespace = this.getNamespace();
|
|
882
|
+
const items = await this.store.list(namespace);
|
|
883
|
+
const infos = [];
|
|
884
|
+
const subdirs = /* @__PURE__ */ new Set();
|
|
885
|
+
const normalizedPath = path$2.endsWith("/") ? path$2 : path$2 + "/";
|
|
886
|
+
for (const item of items) {
|
|
887
|
+
const itemKey = item.key;
|
|
888
|
+
if (!itemKey.startsWith(normalizedPath)) continue;
|
|
889
|
+
const relative = itemKey.substring(normalizedPath.length);
|
|
890
|
+
if (relative.includes("/")) {
|
|
891
|
+
const subdirName = relative.split("/")[0];
|
|
892
|
+
subdirs.add(normalizedPath + subdirName + "/");
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
try {
|
|
896
|
+
const fd = this.convertToFileData(item.value);
|
|
897
|
+
const size = fd.content.join("\n").length;
|
|
898
|
+
infos.push({
|
|
899
|
+
path: itemKey,
|
|
900
|
+
is_dir: false,
|
|
901
|
+
size,
|
|
902
|
+
modified_at: fd.modified_at
|
|
903
|
+
});
|
|
904
|
+
} catch {
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
for (const subdir of Array.from(subdirs).sort()) infos.push({
|
|
909
|
+
path: subdir,
|
|
910
|
+
is_dir: true,
|
|
911
|
+
size: 0,
|
|
912
|
+
modified_at: ""
|
|
913
|
+
});
|
|
914
|
+
infos.sort((a, b) => a.path.localeCompare(b.path));
|
|
915
|
+
return infos;
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Read file content with line numbers.
|
|
919
|
+
*/
|
|
920
|
+
async read(filePath, offset = 0, limit = 2e3) {
|
|
921
|
+
try {
|
|
922
|
+
return formatReadResponse(await this.readRaw(filePath), offset, limit);
|
|
923
|
+
} catch (e) {
|
|
924
|
+
return `Error: ${e.message}`;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Read file content as raw FileData.
|
|
929
|
+
*/
|
|
930
|
+
async readRaw(filePath) {
|
|
931
|
+
const namespace = this.getNamespace();
|
|
932
|
+
const value = await this.store.get(namespace, filePath);
|
|
933
|
+
if (!value) throw new Error(`File '${filePath}' not found`);
|
|
934
|
+
return this.convertToFileData(value);
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Create a new file with content.
|
|
938
|
+
*/
|
|
939
|
+
async write(filePath, content) {
|
|
940
|
+
const namespace = this.getNamespace();
|
|
941
|
+
if (await this.store.get(namespace, filePath)) return {
|
|
942
|
+
success: false,
|
|
943
|
+
error: FILE_ALREADY_EXISTS(filePath)
|
|
944
|
+
};
|
|
945
|
+
const fileData = createFileData(content);
|
|
946
|
+
const storeValue = this.convertFromFileData(fileData);
|
|
947
|
+
await this.store.put(namespace, filePath, storeValue);
|
|
948
|
+
return {
|
|
949
|
+
success: true,
|
|
950
|
+
path: filePath
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Edit a file by replacing string occurrences.
|
|
955
|
+
*/
|
|
956
|
+
async edit(filePath, oldString, newString, replaceAll = false) {
|
|
957
|
+
const namespace = this.getNamespace();
|
|
958
|
+
const value = await this.store.get(namespace, filePath);
|
|
959
|
+
if (!value) return {
|
|
960
|
+
success: false,
|
|
961
|
+
error: FILE_NOT_FOUND(filePath)
|
|
962
|
+
};
|
|
963
|
+
try {
|
|
964
|
+
const fileData = this.convertToFileData(value);
|
|
965
|
+
const result = performStringReplacement(fileDataToString(fileData), oldString, newString, replaceAll);
|
|
966
|
+
if (typeof result === "string") return {
|
|
967
|
+
success: false,
|
|
968
|
+
error: result
|
|
969
|
+
};
|
|
970
|
+
const [newContent, occurrences] = result;
|
|
971
|
+
const newFileData = updateFileData(fileData, newContent);
|
|
972
|
+
const storeValue = this.convertFromFileData(newFileData);
|
|
973
|
+
await this.store.put(namespace, filePath, storeValue);
|
|
974
|
+
return {
|
|
975
|
+
success: true,
|
|
976
|
+
path: filePath,
|
|
977
|
+
occurrences
|
|
978
|
+
};
|
|
979
|
+
} catch (e) {
|
|
980
|
+
return {
|
|
981
|
+
success: false,
|
|
982
|
+
error: `Error: ${e.message}`
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Structured search results or error string for invalid input.
|
|
988
|
+
*/
|
|
989
|
+
async grepRaw(pattern, path$2 = "/", glob$1 = null) {
|
|
990
|
+
const namespace = this.getNamespace();
|
|
991
|
+
const items = await this.store.list(namespace);
|
|
992
|
+
const files = {};
|
|
993
|
+
for (const item of items) try {
|
|
994
|
+
files[item.key] = this.convertToFileData(item.value);
|
|
995
|
+
} catch {
|
|
996
|
+
continue;
|
|
997
|
+
}
|
|
998
|
+
return grepMatchesFromFiles(files, pattern, path$2, glob$1);
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Structured glob matching returning FileInfo objects.
|
|
1002
|
+
*/
|
|
1003
|
+
async globInfo(pattern, path$2 = "/") {
|
|
1004
|
+
const namespace = this.getNamespace();
|
|
1005
|
+
const items = await this.store.list(namespace);
|
|
1006
|
+
const files = {};
|
|
1007
|
+
for (const item of items) try {
|
|
1008
|
+
files[item.key] = this.convertToFileData(item.value);
|
|
1009
|
+
} catch {
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
const result = globSearchFiles(files, pattern, path$2);
|
|
1013
|
+
if (result === "No files found") return [];
|
|
1014
|
+
const paths = result.split("\n");
|
|
1015
|
+
const infos = [];
|
|
1016
|
+
for (const p of paths) {
|
|
1017
|
+
const fd = files[p];
|
|
1018
|
+
const size = fd ? fd.content.join("\n").length : 0;
|
|
1019
|
+
infos.push({
|
|
1020
|
+
path: p,
|
|
1021
|
+
is_dir: false,
|
|
1022
|
+
size,
|
|
1023
|
+
modified_at: fd?.modified_at || ""
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
return infos;
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Delete a file.
|
|
1030
|
+
*/
|
|
1031
|
+
async deleteFile(filePath) {
|
|
1032
|
+
const namespace = this.getNamespace();
|
|
1033
|
+
if (!await this.store.get(namespace, filePath)) return { error: `File '${filePath}' not found` };
|
|
1034
|
+
await this.store.delete(namespace, filePath);
|
|
1035
|
+
return {};
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
|
|
1039
|
+
//#endregion
|
|
1040
|
+
//#region src/checkpointer/memory-saver.ts
|
|
1041
|
+
/**
|
|
1042
|
+
* In-memory checkpoint saver.
|
|
1043
|
+
*
|
|
1044
|
+
* Stores checkpoints in a Map. Data is lost when the process exits.
|
|
1045
|
+
* Useful for testing or single-session applications.
|
|
1046
|
+
*
|
|
1047
|
+
* @example
|
|
1048
|
+
* ```typescript
|
|
1049
|
+
* const saver = new MemorySaver();
|
|
1050
|
+
* const agent = createDeepAgent({
|
|
1051
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
1052
|
+
* checkpointer: saver,
|
|
1053
|
+
* });
|
|
1054
|
+
* ```
|
|
1055
|
+
*/
|
|
1056
|
+
var MemorySaver = class {
|
|
1057
|
+
checkpoints = /* @__PURE__ */ new Map();
|
|
1058
|
+
namespace;
|
|
1059
|
+
constructor(options = {}) {
|
|
1060
|
+
this.namespace = options.namespace || "default";
|
|
1061
|
+
}
|
|
1062
|
+
getKey(threadId) {
|
|
1063
|
+
return `${this.namespace}:${threadId}`;
|
|
1064
|
+
}
|
|
1065
|
+
async save(checkpoint) {
|
|
1066
|
+
const key = this.getKey(checkpoint.threadId);
|
|
1067
|
+
this.checkpoints.set(key, {
|
|
1068
|
+
...checkpoint,
|
|
1069
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
async load(threadId) {
|
|
1073
|
+
const key = this.getKey(threadId);
|
|
1074
|
+
return this.checkpoints.get(key);
|
|
1075
|
+
}
|
|
1076
|
+
async list() {
|
|
1077
|
+
const prefix = `${this.namespace}:`;
|
|
1078
|
+
const threadIds = [];
|
|
1079
|
+
for (const key of this.checkpoints.keys()) if (key.startsWith(prefix)) threadIds.push(key.substring(prefix.length));
|
|
1080
|
+
return threadIds;
|
|
1081
|
+
}
|
|
1082
|
+
async delete(threadId) {
|
|
1083
|
+
const key = this.getKey(threadId);
|
|
1084
|
+
this.checkpoints.delete(key);
|
|
1085
|
+
}
|
|
1086
|
+
async exists(threadId) {
|
|
1087
|
+
const key = this.getKey(threadId);
|
|
1088
|
+
return this.checkpoints.has(key);
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Clear all checkpoints (useful for testing).
|
|
1092
|
+
*/
|
|
1093
|
+
clear() {
|
|
1094
|
+
this.checkpoints.clear();
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Get the number of stored checkpoints.
|
|
1098
|
+
*/
|
|
1099
|
+
size() {
|
|
1100
|
+
return this.checkpoints.size;
|
|
1101
|
+
}
|
|
1102
|
+
};
|
|
1103
|
+
|
|
1104
|
+
//#endregion
|
|
1105
|
+
//#region src/checkpointer/kv-saver.ts
|
|
1106
|
+
/**
|
|
1107
|
+
* Checkpoint saver using KeyValueStore interface.
|
|
1108
|
+
*
|
|
1109
|
+
* This adapter allows using any KeyValueStore implementation (Redis,
|
|
1110
|
+
* database, cloud storage, etc.) for checkpoint storage.
|
|
1111
|
+
*
|
|
1112
|
+
* @example
|
|
1113
|
+
* ```typescript
|
|
1114
|
+
* import { InMemoryStore } from 'deepagentsdk';
|
|
1115
|
+
*
|
|
1116
|
+
* const store = new InMemoryStore();
|
|
1117
|
+
* const saver = new KeyValueStoreSaver({ store });
|
|
1118
|
+
* const agent = createDeepAgent({
|
|
1119
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
1120
|
+
* checkpointer: saver,
|
|
1121
|
+
* });
|
|
1122
|
+
* ```
|
|
1123
|
+
*
|
|
1124
|
+
* @example With Redis
|
|
1125
|
+
* ```typescript
|
|
1126
|
+
* const redisStore = new RedisStore(redisClient); // Your implementation
|
|
1127
|
+
* const saver = new KeyValueStoreSaver({ store: redisStore });
|
|
1128
|
+
* ```
|
|
1129
|
+
*/
|
|
1130
|
+
var KeyValueStoreSaver = class {
|
|
1131
|
+
store;
|
|
1132
|
+
namespace;
|
|
1133
|
+
constructor(options) {
|
|
1134
|
+
this.store = options.store;
|
|
1135
|
+
this.namespace = [options.namespace || "default", "checkpoints"];
|
|
1136
|
+
}
|
|
1137
|
+
async save(checkpoint) {
|
|
1138
|
+
const data = {
|
|
1139
|
+
...checkpoint,
|
|
1140
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1141
|
+
};
|
|
1142
|
+
await this.store.put(this.namespace, checkpoint.threadId, data);
|
|
1143
|
+
}
|
|
1144
|
+
async load(threadId) {
|
|
1145
|
+
const data = await this.store.get(this.namespace, threadId);
|
|
1146
|
+
if (!data) return;
|
|
1147
|
+
return data;
|
|
1148
|
+
}
|
|
1149
|
+
async list() {
|
|
1150
|
+
return (await this.store.list(this.namespace)).map((item) => item.key);
|
|
1151
|
+
}
|
|
1152
|
+
async delete(threadId) {
|
|
1153
|
+
await this.store.delete(this.namespace, threadId);
|
|
1154
|
+
}
|
|
1155
|
+
async exists(threadId) {
|
|
1156
|
+
return await this.store.get(this.namespace, threadId) !== void 0;
|
|
1157
|
+
}
|
|
1158
|
+
};
|
|
1159
|
+
|
|
1160
|
+
//#endregion
|
|
1161
|
+
//#region src/middleware/agent-memory.ts
|
|
1162
|
+
/**
|
|
1163
|
+
* Load agent memory from a file path.
|
|
1164
|
+
* Returns empty string if file doesn't exist or can't be read.
|
|
1165
|
+
*/
|
|
1166
|
+
async function loadAgentMemory(filePath) {
|
|
1167
|
+
try {
|
|
1168
|
+
return (await fs.readFile(filePath, "utf-8")).trim();
|
|
1169
|
+
} catch {
|
|
1170
|
+
return "";
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Load all additional .md files from the agent's directory (excluding agent.md).
|
|
1175
|
+
* Returns an array of { filename, content } objects.
|
|
1176
|
+
*/
|
|
1177
|
+
async function loadAdditionalMemoryFiles(dirPath) {
|
|
1178
|
+
try {
|
|
1179
|
+
const mdFiles = (await fs.readdir(dirPath)).filter((f) => f.endsWith(".md") && f !== "agent.md");
|
|
1180
|
+
return (await Promise.all(mdFiles.map(async (filename) => {
|
|
1181
|
+
return {
|
|
1182
|
+
filename,
|
|
1183
|
+
content: await loadAgentMemory(path.join(dirPath, filename))
|
|
1184
|
+
};
|
|
1185
|
+
}))).filter((r) => r.content.length > 0);
|
|
1186
|
+
} catch {
|
|
1187
|
+
return [];
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Build the memory section for the system prompt.
|
|
1192
|
+
* This comprehensive prompt teaches the agent how to use memory effectively.
|
|
1193
|
+
*/
|
|
1194
|
+
function buildMemorySection(userMemory, projectMemory, additionalFiles, agentId, userMemoryPath, projectMemoryPath) {
|
|
1195
|
+
let sections = [];
|
|
1196
|
+
if (userMemory) sections.push(`# Agent Memory (User-Level)
|
|
1197
|
+
|
|
1198
|
+
The following is your persistent memory stored at ${userMemoryPath}:
|
|
1199
|
+
|
|
1200
|
+
${userMemory}`);
|
|
1201
|
+
if (projectMemory && projectMemoryPath) sections.push(`# Agent Memory (Project-Level)
|
|
1202
|
+
|
|
1203
|
+
The following is project-specific context stored at ${projectMemoryPath}:
|
|
1204
|
+
|
|
1205
|
+
${projectMemory}`);
|
|
1206
|
+
if (additionalFiles.length > 0) {
|
|
1207
|
+
const additionalSections = additionalFiles.map(({ filename, content }) => `## ${filename}
|
|
1208
|
+
|
|
1209
|
+
${content}`);
|
|
1210
|
+
sections.push(`# Additional Context Files
|
|
1211
|
+
|
|
1212
|
+
${additionalSections.join("\n\n")}`);
|
|
1213
|
+
}
|
|
1214
|
+
if (sections.length === 0) return "";
|
|
1215
|
+
return `
|
|
1216
|
+
<agent_memory>
|
|
1217
|
+
${sections.join("\n\n---\n\n")}
|
|
1218
|
+
|
|
1219
|
+
---
|
|
1220
|
+
|
|
1221
|
+
## How to Use This Memory
|
|
1222
|
+
|
|
1223
|
+
**What is this?**
|
|
1224
|
+
- The content above is your persistent memory, stored in markdown files
|
|
1225
|
+
- **User-level memory** (${userMemoryPath}) contains your core personality, preferences, and cross-project context
|
|
1226
|
+
- **Project-level memory** ${projectMemoryPath ? `(${projectMemoryPath})` : "(not available)"} contains project-specific context and conventions
|
|
1227
|
+
|
|
1228
|
+
**When to read memory:**
|
|
1229
|
+
- You already have the memory content above in your context - no need to read the files unless you need to verify exact content
|
|
1230
|
+
- If you need to check current memory state or see if it's been updated, use \`read_file\` tool
|
|
1231
|
+
|
|
1232
|
+
**When to update memory:**
|
|
1233
|
+
- **User memory**: When you learn something important about the user's preferences, working style, or recurring patterns
|
|
1234
|
+
- **Project memory**: When you discover project-specific conventions, architecture decisions, or important context
|
|
1235
|
+
- **Additional files**: For specialized context that doesn't fit in agent.md (e.g., decision logs, architecture notes)
|
|
1236
|
+
|
|
1237
|
+
**How to update memory:**
|
|
1238
|
+
- Use the \`write_file\` or \`edit_file\` tools with the file paths shown above
|
|
1239
|
+
- Keep entries concise and relevant
|
|
1240
|
+
- Organize information clearly with markdown headings
|
|
1241
|
+
- Remove outdated information when updating
|
|
1242
|
+
|
|
1243
|
+
**Important guidelines:**
|
|
1244
|
+
- Memory is meant for long-term context, not temporary task tracking
|
|
1245
|
+
- Don't store information that's already in the codebase or documentation
|
|
1246
|
+
- Focus on insights, patterns, and preferences that aren't obvious from other sources
|
|
1247
|
+
- When in doubt, ask the user if something should be remembered
|
|
1248
|
+
|
|
1249
|
+
**Example use cases:**
|
|
1250
|
+
- User prefers TypeScript strict mode and comprehensive error handling
|
|
1251
|
+
- Project uses custom testing framework located in \`test-utils/\`
|
|
1252
|
+
- User wants all API responses to follow specific error format
|
|
1253
|
+
- Project has specific commit message conventions
|
|
1254
|
+
</agent_memory>
|
|
1255
|
+
`.trim();
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Create agent memory middleware for AI SDK v6.
|
|
1259
|
+
*
|
|
1260
|
+
* This middleware loads agent memory from:
|
|
1261
|
+
* 1. User-level: ~/.deepagents/{agentId}/agent.md (personality, preferences)
|
|
1262
|
+
* 2. Project-level: [git-root]/.deepagents/agent.md (project-specific context)
|
|
1263
|
+
* 3. Additional files: Any other .md files in the user-level directory
|
|
1264
|
+
*
|
|
1265
|
+
* The memory is injected into the system prompt before each model call, teaching
|
|
1266
|
+
* the agent when and how to read/update its own memory using filesystem tools.
|
|
1267
|
+
*
|
|
1268
|
+
* @param options - Configuration for agent memory
|
|
1269
|
+
* @param options.agentId - Unique identifier for the agent (e.g., "code-architect")
|
|
1270
|
+
* @param options.workingDirectory - Optional working directory for project detection (defaults to process.cwd())
|
|
1271
|
+
* @param options.userDeepagentsDir - Optional custom path for user-level .deepagents directory (defaults to ~/.deepagents)
|
|
1272
|
+
* @param options.requestProjectApproval - Optional callback to request approval before creating project .deepagents/ directory
|
|
1273
|
+
* @returns AI SDK v6 middleware
|
|
1274
|
+
*
|
|
1275
|
+
* @example Basic usage
|
|
1276
|
+
* ```typescript
|
|
1277
|
+
* import { createDeepAgent } from 'deepagentsdk';
|
|
1278
|
+
* import { createAgentMemoryMiddleware } from 'deepagentsdk/middleware';
|
|
1279
|
+
* import { anthropic } from '@ai-sdk/anthropic';
|
|
1280
|
+
*
|
|
1281
|
+
* const memoryMiddleware = createAgentMemoryMiddleware({
|
|
1282
|
+
* agentId: 'code-architect',
|
|
1283
|
+
* });
|
|
1284
|
+
*
|
|
1285
|
+
* const agent = createDeepAgent({
|
|
1286
|
+
* model: anthropic('claude-sonnet-4-5'),
|
|
1287
|
+
* middleware: memoryMiddleware,
|
|
1288
|
+
* });
|
|
1289
|
+
* ```
|
|
1290
|
+
*
|
|
1291
|
+
* @example With project approval callback
|
|
1292
|
+
* ```typescript
|
|
1293
|
+
* const memoryMiddleware = createAgentMemoryMiddleware({
|
|
1294
|
+
* agentId: 'code-architect',
|
|
1295
|
+
* requestProjectApproval: async (projectPath) => {
|
|
1296
|
+
* console.log(`Create .deepagents/ in ${projectPath}? (y/n)`);
|
|
1297
|
+
* // ... get user input
|
|
1298
|
+
* return userSaidYes;
|
|
1299
|
+
* }
|
|
1300
|
+
* });
|
|
1301
|
+
* ```
|
|
1302
|
+
*
|
|
1303
|
+
* @example With custom user directory path
|
|
1304
|
+
* ```typescript
|
|
1305
|
+
* const memoryMiddleware = createAgentMemoryMiddleware({
|
|
1306
|
+
* agentId: 'code-architect',
|
|
1307
|
+
* userDeepagentsDir: '/custom/path/.deepagents',
|
|
1308
|
+
* // Memory will be loaded from:
|
|
1309
|
+
* // - /custom/path/.deepagents/code-architect/agent.md
|
|
1310
|
+
* // - [git-root]/.deepagents/agent.md (project-level)
|
|
1311
|
+
* });
|
|
1312
|
+
* ```
|
|
1313
|
+
*/
|
|
1314
|
+
function createAgentMemoryMiddleware(options) {
|
|
1315
|
+
const { agentId, workingDirectory, userDeepagentsDir, requestProjectApproval } = options;
|
|
1316
|
+
let memoryLoaded = false;
|
|
1317
|
+
let cachedMemorySection = "";
|
|
1318
|
+
return {
|
|
1319
|
+
specificationVersion: "v3",
|
|
1320
|
+
transformParams: async ({ params }) => {
|
|
1321
|
+
if (!memoryLoaded) {
|
|
1322
|
+
const workDir = workingDirectory || process.cwd();
|
|
1323
|
+
const baseUserDir = userDeepagentsDir || path.join(os.homedir(), ".deepagents");
|
|
1324
|
+
const userAgentDir = path.join(baseUserDir, agentId);
|
|
1325
|
+
const userMemoryPath = path.join(userAgentDir, "agent.md");
|
|
1326
|
+
const userMemory = await loadAgentMemory(userMemoryPath);
|
|
1327
|
+
if (!userMemory) try {
|
|
1328
|
+
await fs.mkdir(userAgentDir, { recursive: true });
|
|
1329
|
+
} catch {}
|
|
1330
|
+
const additionalFiles = await loadAdditionalMemoryFiles(userAgentDir);
|
|
1331
|
+
let projectMemory = "";
|
|
1332
|
+
let projectMemoryPath = null;
|
|
1333
|
+
const gitRoot = await findGitRoot(workDir);
|
|
1334
|
+
if (gitRoot) {
|
|
1335
|
+
const projectDeepagentsDir = path.join(gitRoot, ".deepagents");
|
|
1336
|
+
projectMemoryPath = path.join(projectDeepagentsDir, "agent.md");
|
|
1337
|
+
try {
|
|
1338
|
+
await fs.stat(projectDeepagentsDir);
|
|
1339
|
+
projectMemory = await loadAgentMemory(projectMemoryPath);
|
|
1340
|
+
} catch {
|
|
1341
|
+
if (requestProjectApproval) {
|
|
1342
|
+
if (await requestProjectApproval(gitRoot)) try {
|
|
1343
|
+
await fs.mkdir(projectDeepagentsDir, { recursive: true });
|
|
1344
|
+
} catch {}
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
cachedMemorySection = buildMemorySection(userMemory, projectMemory, additionalFiles, agentId, userMemoryPath, projectMemoryPath);
|
|
1349
|
+
memoryLoaded = true;
|
|
1350
|
+
}
|
|
1351
|
+
if (cachedMemorySection) {
|
|
1352
|
+
const updatedPrompt = params.prompt.map((msg) => {
|
|
1353
|
+
if (msg.role === "system") return {
|
|
1354
|
+
...msg,
|
|
1355
|
+
content: `${msg.content}\n\n${cachedMemorySection}`
|
|
1356
|
+
};
|
|
1357
|
+
return msg;
|
|
1358
|
+
});
|
|
1359
|
+
return {
|
|
1360
|
+
...params,
|
|
1361
|
+
prompt: updatedPrompt
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
return params;
|
|
1365
|
+
}
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
//#endregion
|
|
1370
|
+
export { BASE_PROMPT, BaseSandbox, CompositeBackend, DEFAULT_EVICTION_TOKEN_LIMIT, DEFAULT_GENERAL_PURPOSE_DESCRIPTION, DEFAULT_KEEP_MESSAGES, DEFAULT_SUBAGENT_PROMPT, DEFAULT_SUMMARIZATION_THRESHOLD, DeepAgent, EXECUTE_SYSTEM_PROMPT, FILESYSTEM_SYSTEM_PROMPT, FileSaver, FilesystemBackend, InMemoryStore, KeyValueStoreSaver, LocalSandbox, MemorySaver, PersistentBackend, StateBackend, TASK_SYSTEM_PROMPT, TODO_SYSTEM_PROMPT, ToolLoopAgent, createAgentMemoryMiddleware, createDeepAgent, createEditFileTool, createExecuteTool, createExecuteToolFromBackend, createFetchUrlTool, createFilesystemTools, createGlobTool, createGrepTool, createHttpRequestTool, createLsTool, createReadFileTool, createSubagentTool, createTodosTool, createToolResultWrapper, createWebSearchTool, createWebTools, createWriteFileTool, edit_file, estimateMessagesTokens, estimateTokens, eventHasStructuredOutput, evictToolResult, execute, fetch_url, findGitRoot, getEventOutput, getStructuredOutput, getTaskToolDescription, glob, grep, hasDanglingToolCalls, hasStructuredOutput, hasToolCall, htmlToMarkdown, http_request, isSandboxBackend, listSkills, ls, needsSummarization, parseSkillMetadata, patchToolCalls, read_file, shouldEvict, stepCountIs, summarizeIfNeeded, web_search, wrapLanguageModel, write_file, write_todos };
|
|
1371
|
+
//# sourceMappingURL=index.mjs.map
|