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,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CompositeBackend: Route operations to different backends based on path prefix.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
BackendProtocol,
|
|
7
|
+
EditResult,
|
|
8
|
+
FileData,
|
|
9
|
+
FileInfo,
|
|
10
|
+
GrepMatch,
|
|
11
|
+
WriteResult,
|
|
12
|
+
} from "../types";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Backend that routes file operations to different backends based on path prefix.
|
|
16
|
+
*
|
|
17
|
+
* This enables hybrid storage strategies by routing files to different backends
|
|
18
|
+
* based on their path prefix. Useful for separating persistent and ephemeral storage,
|
|
19
|
+
* or using different storage backends for different types of files.
|
|
20
|
+
*
|
|
21
|
+
* @example Hybrid storage strategy
|
|
22
|
+
* ```typescript
|
|
23
|
+
* import { CompositeBackend, FilesystemBackend, StateBackend } from 'deepagentsdk';
|
|
24
|
+
*
|
|
25
|
+
* const state = { todos: [], files: {} };
|
|
26
|
+
* const backend = new CompositeBackend(
|
|
27
|
+
* new StateBackend(state), // Default: ephemeral storage
|
|
28
|
+
* {
|
|
29
|
+
* '/persistent/': new FilesystemBackend({ rootDir: './persistent' }), // Persistent files
|
|
30
|
+
* '/cache/': new StateBackend(state), // Cached files (ephemeral)
|
|
31
|
+
* }
|
|
32
|
+
* );
|
|
33
|
+
*
|
|
34
|
+
* const agent = createDeepAgent({
|
|
35
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
36
|
+
* backend,
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @example Multiple persistent backends
|
|
41
|
+
* ```typescript
|
|
42
|
+
* const backend = new CompositeBackend(
|
|
43
|
+
* new FilesystemBackend({ rootDir: './default' }),
|
|
44
|
+
* {
|
|
45
|
+
* '/user-data/': new FilesystemBackend({ rootDir: './user-data' }),
|
|
46
|
+
* '/system/': new FilesystemBackend({ rootDir: './system' }),
|
|
47
|
+
* }
|
|
48
|
+
* );
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export class CompositeBackend implements BackendProtocol {
|
|
52
|
+
private defaultBackend: BackendProtocol;
|
|
53
|
+
private routes: Record<string, BackendProtocol>;
|
|
54
|
+
private sortedRoutes: Array<[string, BackendProtocol]>;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create a new CompositeBackend instance.
|
|
58
|
+
*
|
|
59
|
+
* @param defaultBackend - Backend to use for paths that don't match any route prefix
|
|
60
|
+
* @param routes - Record mapping path prefixes to backends.
|
|
61
|
+
* Routes are matched by longest prefix first.
|
|
62
|
+
* Example: `{ '/persistent/': filesystemBackend, '/cache/': stateBackend }`
|
|
63
|
+
*/
|
|
64
|
+
constructor(
|
|
65
|
+
defaultBackend: BackendProtocol,
|
|
66
|
+
routes: Record<string, BackendProtocol>
|
|
67
|
+
) {
|
|
68
|
+
this.defaultBackend = defaultBackend;
|
|
69
|
+
this.routes = routes;
|
|
70
|
+
|
|
71
|
+
// Sort routes by length (longest first) for correct prefix matching
|
|
72
|
+
this.sortedRoutes = Object.entries(routes).sort(
|
|
73
|
+
(a, b) => b[0].length - a[0].length
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Determine which backend handles this key and strip prefix.
|
|
79
|
+
*/
|
|
80
|
+
private getBackendAndKey(key: string): [BackendProtocol, string] {
|
|
81
|
+
for (const [prefix, backend] of this.sortedRoutes) {
|
|
82
|
+
if (key.startsWith(prefix)) {
|
|
83
|
+
const suffix = key.substring(prefix.length);
|
|
84
|
+
const strippedKey = suffix ? "/" + suffix : "/";
|
|
85
|
+
return [backend, strippedKey];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return [this.defaultBackend, key];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* List files and directories in the specified directory (non-recursive).
|
|
94
|
+
*/
|
|
95
|
+
async lsInfo(path: string): Promise<FileInfo[]> {
|
|
96
|
+
// Check if path matches a specific route
|
|
97
|
+
for (const [routePrefix, backend] of this.sortedRoutes) {
|
|
98
|
+
if (path.startsWith(routePrefix.replace(/\/$/, ""))) {
|
|
99
|
+
const suffix = path.substring(routePrefix.length);
|
|
100
|
+
const searchPath = suffix ? "/" + suffix : "/";
|
|
101
|
+
const infos = await backend.lsInfo(searchPath);
|
|
102
|
+
|
|
103
|
+
// Add route prefix back to paths
|
|
104
|
+
const prefixed: FileInfo[] = [];
|
|
105
|
+
for (const fi of infos) {
|
|
106
|
+
prefixed.push({
|
|
107
|
+
...fi,
|
|
108
|
+
path: routePrefix.slice(0, -1) + fi.path,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return prefixed;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// At root, aggregate default and all routed backends
|
|
116
|
+
if (path === "/") {
|
|
117
|
+
const results: FileInfo[] = [];
|
|
118
|
+
const defaultInfos = await this.defaultBackend.lsInfo(path);
|
|
119
|
+
results.push(...defaultInfos);
|
|
120
|
+
|
|
121
|
+
// Add the route itself as a directory
|
|
122
|
+
for (const [routePrefix] of this.sortedRoutes) {
|
|
123
|
+
results.push({
|
|
124
|
+
path: routePrefix,
|
|
125
|
+
is_dir: true,
|
|
126
|
+
size: 0,
|
|
127
|
+
modified_at: "",
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
results.sort((a, b) => a.path.localeCompare(b.path));
|
|
132
|
+
return results;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Path doesn't match a route: query only default backend
|
|
136
|
+
return await this.defaultBackend.lsInfo(path);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Read file content, routing to appropriate backend.
|
|
141
|
+
*/
|
|
142
|
+
async read(
|
|
143
|
+
filePath: string,
|
|
144
|
+
offset: number = 0,
|
|
145
|
+
limit: number = 2000
|
|
146
|
+
): Promise<string> {
|
|
147
|
+
const [backend, strippedKey] = this.getBackendAndKey(filePath);
|
|
148
|
+
return await backend.read(strippedKey, offset, limit);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Read file content as raw FileData.
|
|
153
|
+
*/
|
|
154
|
+
async readRaw(filePath: string): Promise<FileData> {
|
|
155
|
+
const [backend, strippedKey] = this.getBackendAndKey(filePath);
|
|
156
|
+
return await backend.readRaw(strippedKey);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Structured search results or error string for invalid input.
|
|
161
|
+
*/
|
|
162
|
+
async grepRaw(
|
|
163
|
+
pattern: string,
|
|
164
|
+
path: string = "/",
|
|
165
|
+
glob: string | null = null
|
|
166
|
+
): Promise<GrepMatch[] | string> {
|
|
167
|
+
// If path targets a specific route, search only that backend
|
|
168
|
+
for (const [routePrefix, backend] of this.sortedRoutes) {
|
|
169
|
+
if (path.startsWith(routePrefix.replace(/\/$/, ""))) {
|
|
170
|
+
const suffix = path.substring(routePrefix.length);
|
|
171
|
+
const searchPath = suffix ? "/" + suffix : "/";
|
|
172
|
+
const raw = await backend.grepRaw(pattern, searchPath, glob);
|
|
173
|
+
|
|
174
|
+
if (typeof raw === "string") {
|
|
175
|
+
return raw;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return raw.map((m) => ({
|
|
179
|
+
...m,
|
|
180
|
+
path: routePrefix.slice(0, -1) + m.path,
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Otherwise, search default and all routed backends and merge
|
|
186
|
+
const allMatches: GrepMatch[] = [];
|
|
187
|
+
const rawDefault = await this.defaultBackend.grepRaw(pattern, path, glob);
|
|
188
|
+
|
|
189
|
+
if (typeof rawDefault === "string") {
|
|
190
|
+
return rawDefault;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
allMatches.push(...rawDefault);
|
|
194
|
+
|
|
195
|
+
// Search all routes
|
|
196
|
+
for (const [routePrefix, backend] of Object.entries(this.routes)) {
|
|
197
|
+
const raw = await backend.grepRaw(pattern, "/", glob);
|
|
198
|
+
|
|
199
|
+
if (typeof raw === "string") {
|
|
200
|
+
return raw;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
allMatches.push(
|
|
204
|
+
...raw.map((m) => ({
|
|
205
|
+
...m,
|
|
206
|
+
path: routePrefix.slice(0, -1) + m.path,
|
|
207
|
+
}))
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return allMatches;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Structured glob matching returning FileInfo objects.
|
|
216
|
+
*/
|
|
217
|
+
async globInfo(pattern: string, path: string = "/"): Promise<FileInfo[]> {
|
|
218
|
+
const results: FileInfo[] = [];
|
|
219
|
+
|
|
220
|
+
// Route based on path
|
|
221
|
+
for (const [routePrefix, backend] of this.sortedRoutes) {
|
|
222
|
+
if (path.startsWith(routePrefix.replace(/\/$/, ""))) {
|
|
223
|
+
const suffix = path.substring(routePrefix.length);
|
|
224
|
+
const searchPath = suffix ? "/" + suffix : "/";
|
|
225
|
+
const infos = await backend.globInfo(pattern, searchPath);
|
|
226
|
+
|
|
227
|
+
return infos.map((fi) => ({
|
|
228
|
+
...fi,
|
|
229
|
+
path: routePrefix.slice(0, -1) + fi.path,
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Path doesn't match any specific route - search all backends
|
|
235
|
+
const defaultInfos = await this.defaultBackend.globInfo(pattern, path);
|
|
236
|
+
results.push(...defaultInfos);
|
|
237
|
+
|
|
238
|
+
for (const [routePrefix, backend] of Object.entries(this.routes)) {
|
|
239
|
+
const infos = await backend.globInfo(pattern, "/");
|
|
240
|
+
results.push(
|
|
241
|
+
...infos.map((fi) => ({
|
|
242
|
+
...fi,
|
|
243
|
+
path: routePrefix.slice(0, -1) + fi.path,
|
|
244
|
+
}))
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
results.sort((a, b) => a.path.localeCompare(b.path));
|
|
249
|
+
return results;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Create a new file, routing to appropriate backend.
|
|
254
|
+
*/
|
|
255
|
+
async write(filePath: string, content: string): Promise<WriteResult> {
|
|
256
|
+
const [backend, strippedKey] = this.getBackendAndKey(filePath);
|
|
257
|
+
return await backend.write(strippedKey, content);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Edit a file, routing to appropriate backend.
|
|
262
|
+
*/
|
|
263
|
+
async edit(
|
|
264
|
+
filePath: string,
|
|
265
|
+
oldString: string,
|
|
266
|
+
newString: string,
|
|
267
|
+
replaceAll: boolean = false
|
|
268
|
+
): Promise<EditResult> {
|
|
269
|
+
const [backend, strippedKey] = this.getBackendAndKey(filePath);
|
|
270
|
+
return await backend.edit(strippedKey, oldString, newString, replaceAll);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|