deepagentsdk 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/package.json +95 -0
  4. package/src/agent.ts +1230 -0
  5. package/src/backends/composite.ts +273 -0
  6. package/src/backends/filesystem.ts +692 -0
  7. package/src/backends/index.ts +22 -0
  8. package/src/backends/local-sandbox.ts +175 -0
  9. package/src/backends/persistent.ts +593 -0
  10. package/src/backends/sandbox.ts +510 -0
  11. package/src/backends/state.ts +244 -0
  12. package/src/backends/utils.ts +287 -0
  13. package/src/checkpointer/file-saver.ts +98 -0
  14. package/src/checkpointer/index.ts +5 -0
  15. package/src/checkpointer/kv-saver.ts +82 -0
  16. package/src/checkpointer/memory-saver.ts +82 -0
  17. package/src/checkpointer/types.ts +125 -0
  18. package/src/cli/components/ApiKeyInput.tsx +300 -0
  19. package/src/cli/components/FilePreview.tsx +237 -0
  20. package/src/cli/components/Input.tsx +277 -0
  21. package/src/cli/components/Message.tsx +93 -0
  22. package/src/cli/components/ModelSelection.tsx +338 -0
  23. package/src/cli/components/SlashMenu.tsx +101 -0
  24. package/src/cli/components/StatusBar.tsx +89 -0
  25. package/src/cli/components/Subagent.tsx +91 -0
  26. package/src/cli/components/TodoList.tsx +133 -0
  27. package/src/cli/components/ToolApproval.tsx +70 -0
  28. package/src/cli/components/ToolCall.tsx +144 -0
  29. package/src/cli/components/ToolCallSummary.tsx +175 -0
  30. package/src/cli/components/Welcome.tsx +75 -0
  31. package/src/cli/components/index.ts +24 -0
  32. package/src/cli/hooks/index.ts +12 -0
  33. package/src/cli/hooks/useAgent.ts +933 -0
  34. package/src/cli/index.tsx +1066 -0
  35. package/src/cli/theme.ts +205 -0
  36. package/src/cli/utils/model-list.ts +365 -0
  37. package/src/constants/errors.ts +29 -0
  38. package/src/constants/limits.ts +195 -0
  39. package/src/index.ts +176 -0
  40. package/src/middleware/agent-memory.ts +330 -0
  41. package/src/prompts.ts +196 -0
  42. package/src/skills/index.ts +2 -0
  43. package/src/skills/load.ts +191 -0
  44. package/src/skills/types.ts +53 -0
  45. package/src/tools/execute.ts +167 -0
  46. package/src/tools/filesystem.ts +418 -0
  47. package/src/tools/index.ts +39 -0
  48. package/src/tools/subagent.ts +443 -0
  49. package/src/tools/todos.ts +101 -0
  50. package/src/tools/web.ts +567 -0
  51. package/src/types/backend.ts +177 -0
  52. package/src/types/core.ts +220 -0
  53. package/src/types/events.ts +429 -0
  54. package/src/types/index.ts +94 -0
  55. package/src/types/structured-output.ts +43 -0
  56. package/src/types/subagent.ts +96 -0
  57. package/src/types.ts +22 -0
  58. package/src/utils/approval.ts +213 -0
  59. package/src/utils/events.ts +416 -0
  60. package/src/utils/eviction.ts +181 -0
  61. package/src/utils/index.ts +34 -0
  62. package/src/utils/model-parser.ts +38 -0
  63. package/src/utils/patch-tool-calls.ts +233 -0
  64. package/src/utils/project-detection.ts +32 -0
  65. package/src/utils/summarization.ts +254 -0
@@ -0,0 +1,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
+