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,510 @@
1
+ /**
2
+ * BaseSandbox: Abstract base class for sandbox backends.
3
+ *
4
+ * Implements all BackendProtocol methods using shell commands executed via execute().
5
+ * Subclasses only need to implement execute() and id.
6
+ *
7
+ * This pattern allows creating sandbox backends for different environments
8
+ * (local, Modal, Runloop, Daytona, etc.) by only implementing the command
9
+ * execution layer.
10
+ */
11
+
12
+ import type {
13
+ EditResult,
14
+ ExecuteResponse,
15
+ FileData,
16
+ FileInfo,
17
+ GrepMatch,
18
+ SandboxBackendProtocol,
19
+ WriteResult,
20
+ } from "../types";
21
+ import {
22
+ FILE_NOT_FOUND,
23
+ SYSTEM_REMINDER_FILE_EMPTY,
24
+ STRING_NOT_FOUND,
25
+ } from "../constants/errors";
26
+ import { DEFAULT_READ_LIMIT } from "../constants/limits";
27
+
28
+ /**
29
+ * Encode string to base64 for safe shell transmission.
30
+ */
31
+ function toBase64(str: string): string {
32
+ return Buffer.from(str, "utf-8").toString("base64");
33
+ }
34
+
35
+ /**
36
+ * Build a Node.js script command with embedded base64 arguments.
37
+ * This avoids shell argument parsing issues by embedding values directly in the script.
38
+ */
39
+ function buildNodeScript(script: string, args: Record<string, string>): string {
40
+ // Replace placeholders with actual values
41
+ let result = script;
42
+ for (const [key, value] of Object.entries(args)) {
43
+ result = result.replace(new RegExp(`__${key}__`, "g"), value);
44
+ }
45
+ return `node -e '${result}'`;
46
+ }
47
+
48
+ /**
49
+ * Abstract base class for sandbox backends.
50
+ *
51
+ * Implements all file operations using shell commands via execute().
52
+ * Subclasses only need to implement execute() and id.
53
+ *
54
+ * @example Creating a custom sandbox backend
55
+ * ```typescript
56
+ * class MyCloudSandbox extends BaseSandbox {
57
+ * readonly id = 'my-cloud-123';
58
+ *
59
+ * async execute(command: string): Promise<ExecuteResponse> {
60
+ * // Call your cloud provider's API
61
+ * const result = await myCloudApi.runCommand(command);
62
+ * return {
63
+ * output: result.stdout + result.stderr,
64
+ * exitCode: result.exitCode,
65
+ * truncated: false,
66
+ * };
67
+ * }
68
+ * }
69
+ * ```
70
+ */
71
+ export abstract class BaseSandbox implements SandboxBackendProtocol {
72
+ /**
73
+ * Execute a shell command in the sandbox.
74
+ * Must be implemented by subclasses.
75
+ */
76
+ abstract execute(command: string): Promise<ExecuteResponse>;
77
+
78
+ /**
79
+ * Unique identifier for this sandbox instance.
80
+ * Must be implemented by subclasses.
81
+ */
82
+ abstract readonly id: string;
83
+
84
+ /**
85
+ * List files and directories in a path.
86
+ */
87
+ async lsInfo(path: string): Promise<FileInfo[]> {
88
+ const pathB64 = toBase64(path);
89
+ const script = `
90
+ const fs = require("fs");
91
+ const path = require("path");
92
+
93
+ const dirPath = Buffer.from("__PATH__", "base64").toString("utf-8");
94
+
95
+ try {
96
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
97
+ for (const entry of entries) {
98
+ const fullPath = path.join(dirPath, entry.name);
99
+ try {
100
+ const stat = fs.statSync(fullPath);
101
+ console.log(JSON.stringify({
102
+ path: entry.name,
103
+ is_dir: entry.isDirectory(),
104
+ size: stat.size,
105
+ modified_at: stat.mtime.toISOString()
106
+ }));
107
+ } catch (e) {}
108
+ }
109
+ } catch (e) {}
110
+ `;
111
+ const result = await this.execute(buildNodeScript(script, { PATH: pathB64 }));
112
+
113
+ const infos: FileInfo[] = [];
114
+ for (const line of result.output.trim().split("\n")) {
115
+ if (!line) continue;
116
+ try {
117
+ const data = JSON.parse(line);
118
+ infos.push({
119
+ path: data.path,
120
+ is_dir: data.is_dir,
121
+ size: data.size,
122
+ modified_at: data.modified_at,
123
+ });
124
+ } catch {
125
+ // Skip malformed lines
126
+ }
127
+ }
128
+ return infos;
129
+ }
130
+
131
+ /**
132
+ * Read file content with line numbers.
133
+ */
134
+ async read(
135
+ filePath: string,
136
+ offset: number = 0,
137
+ limit: number = DEFAULT_READ_LIMIT
138
+ ): Promise<string> {
139
+ const pathB64 = toBase64(filePath);
140
+ const emptyReminder = SYSTEM_REMINDER_FILE_EMPTY;
141
+ const script = `
142
+ const fs = require("fs");
143
+ const filePath = Buffer.from("__PATH__", "base64").toString("utf-8");
144
+ const offset = __OFFSET__;
145
+ const limit = __LIMIT__;
146
+
147
+ if (!fs.existsSync(filePath)) {
148
+ console.error("Error: File not found");
149
+ process.exit(1);
150
+ }
151
+
152
+ const stat = fs.statSync(filePath);
153
+ if (stat.size === 0) {
154
+ console.log("${emptyReminder}");
155
+ process.exit(0);
156
+ }
157
+
158
+ const content = fs.readFileSync(filePath, "utf-8");
159
+ const lines = content.split("\\n");
160
+ const selected = lines.slice(offset, offset + limit);
161
+
162
+ for (let i = 0; i < selected.length; i++) {
163
+ const lineNum = (offset + i + 1).toString().padStart(6, " ");
164
+ console.log(lineNum + "\\t" + selected[i]);
165
+ }
166
+ `;
167
+ const result = await this.execute(
168
+ buildNodeScript(script, {
169
+ PATH: pathB64,
170
+ OFFSET: String(offset),
171
+ LIMIT: String(limit),
172
+ })
173
+ );
174
+
175
+ if (result.exitCode !== 0) {
176
+ if (result.output.includes("Error: File not found")) {
177
+ return FILE_NOT_FOUND(filePath);
178
+ }
179
+ return result.output.trim();
180
+ }
181
+
182
+ return result.output.trimEnd();
183
+ }
184
+
185
+ /**
186
+ * Read raw file data.
187
+ */
188
+ async readRaw(filePath: string): Promise<FileData> {
189
+ const pathB64 = toBase64(filePath);
190
+ const script = `
191
+ const fs = require("fs");
192
+ const filePath = Buffer.from("__PATH__", "base64").toString("utf-8");
193
+
194
+ if (!fs.existsSync(filePath)) {
195
+ console.error("Error: File not found");
196
+ process.exit(1);
197
+ }
198
+
199
+ const stat = fs.statSync(filePath);
200
+ const content = fs.readFileSync(filePath, "utf-8");
201
+
202
+ console.log(JSON.stringify({
203
+ content: content.split("\\n"),
204
+ created_at: stat.birthtime.toISOString(),
205
+ modified_at: stat.mtime.toISOString()
206
+ }));
207
+ `;
208
+ const result = await this.execute(buildNodeScript(script, { PATH: pathB64 }));
209
+
210
+ if (result.exitCode !== 0) {
211
+ throw new Error(`File '${filePath}' not found`);
212
+ }
213
+
214
+ try {
215
+ const data = JSON.parse(result.output.trim());
216
+ return {
217
+ content: data.content,
218
+ created_at: data.created_at,
219
+ modified_at: data.modified_at,
220
+ };
221
+ } catch {
222
+ throw new Error(`Failed to parse file data for '${filePath}'`);
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Write content to a new file.
228
+ */
229
+ async write(filePath: string, content: string): Promise<WriteResult> {
230
+ const pathB64 = toBase64(filePath);
231
+ const contentB64 = toBase64(content);
232
+ const script = `
233
+ const fs = require("fs");
234
+ const path = require("path");
235
+
236
+ const filePath = Buffer.from("__PATH__", "base64").toString("utf-8");
237
+ const content = Buffer.from("__CONTENT__", "base64").toString("utf-8");
238
+
239
+ if (fs.existsSync(filePath)) {
240
+ console.error("Error: File already exists");
241
+ process.exit(1);
242
+ }
243
+
244
+ const dir = path.dirname(filePath);
245
+ if (dir && dir !== ".") {
246
+ fs.mkdirSync(dir, { recursive: true });
247
+ }
248
+
249
+ fs.writeFileSync(filePath, content, "utf-8");
250
+ `;
251
+ const result = await this.execute(
252
+ buildNodeScript(script, { PATH: pathB64, CONTENT: contentB64 })
253
+ );
254
+
255
+ if (result.exitCode !== 0) {
256
+ if (result.output.includes("already exists")) {
257
+ return {
258
+ success: false,
259
+ error: `Cannot write to ${filePath} because it already exists. Read and then make an edit, or write to a new path.`,
260
+ };
261
+ }
262
+ return { success: false, error: result.output.trim() || `Failed to write '${filePath}'` };
263
+ }
264
+
265
+ return { success: true, path: filePath };
266
+ }
267
+
268
+ /**
269
+ * Edit a file by replacing string occurrences.
270
+ */
271
+ async edit(
272
+ filePath: string,
273
+ oldString: string,
274
+ newString: string,
275
+ replaceAll: boolean = false
276
+ ): Promise<EditResult> {
277
+ const pathB64 = toBase64(filePath);
278
+ const oldB64 = toBase64(oldString);
279
+ const newB64 = toBase64(newString);
280
+ const script = `
281
+ const fs = require("fs");
282
+
283
+ const filePath = Buffer.from("__PATH__", "base64").toString("utf-8");
284
+ const oldStr = Buffer.from("__OLD__", "base64").toString("utf-8");
285
+ const newStr = Buffer.from("__NEW__", "base64").toString("utf-8");
286
+ const replaceAll = __REPLACE_ALL__;
287
+
288
+ if (!fs.existsSync(filePath)) {
289
+ console.error("Error: File not found");
290
+ process.exit(1);
291
+ }
292
+
293
+ let content = fs.readFileSync(filePath, "utf-8");
294
+ const count = content.split(oldStr).length - 1;
295
+
296
+ if (count === 0) {
297
+ process.exit(2);
298
+ }
299
+ if (count > 1 && !replaceAll) {
300
+ process.exit(3);
301
+ }
302
+
303
+ if (replaceAll) {
304
+ content = content.split(oldStr).join(newStr);
305
+ } else {
306
+ content = content.replace(oldStr, newStr);
307
+ }
308
+
309
+ fs.writeFileSync(filePath, content, "utf-8");
310
+ console.log(count);
311
+ `;
312
+ const result = await this.execute(
313
+ buildNodeScript(script, {
314
+ PATH: pathB64,
315
+ OLD: oldB64,
316
+ NEW: newB64,
317
+ REPLACE_ALL: String(replaceAll),
318
+ })
319
+ );
320
+
321
+ if (result.exitCode === 1) {
322
+ return { success: false, error: FILE_NOT_FOUND(filePath) };
323
+ }
324
+ if (result.exitCode === 2) {
325
+ return { success: false, error: STRING_NOT_FOUND(filePath, oldString) };
326
+ }
327
+ if (result.exitCode === 3) {
328
+ return {
329
+ success: false,
330
+ error: `Error: String '${oldString}' appears multiple times. Use replaceAll=true to replace all occurrences.`,
331
+ };
332
+ }
333
+
334
+ const count = parseInt(result.output.trim(), 10) || 1;
335
+ return { success: true, path: filePath, occurrences: count };
336
+ }
337
+
338
+ /**
339
+ * Search for pattern in files.
340
+ */
341
+ async grepRaw(
342
+ pattern: string,
343
+ path: string = "/",
344
+ glob: string | null = null
345
+ ): Promise<GrepMatch[] | string> {
346
+ const patternB64 = toBase64(pattern);
347
+ const pathB64 = toBase64(path);
348
+ const globB64 = glob ? toBase64(glob) : toBase64("**/*");
349
+ const script = `
350
+ const fs = require("fs");
351
+ const path = require("path");
352
+
353
+ const pattern = Buffer.from("__PATTERN__", "base64").toString("utf-8");
354
+ const basePath = Buffer.from("__PATH__", "base64").toString("utf-8");
355
+ const fileGlob = Buffer.from("__GLOB__", "base64").toString("utf-8");
356
+
357
+ function walkDir(dir, baseDir) {
358
+ const results = [];
359
+ try {
360
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
361
+ for (const entry of entries) {
362
+ const fullPath = path.join(dir, entry.name);
363
+ const relativePath = path.relative(baseDir, fullPath);
364
+
365
+ if (entry.isDirectory()) {
366
+ results.push(...walkDir(fullPath, baseDir));
367
+ } else {
368
+ results.push(relativePath);
369
+ }
370
+ }
371
+ } catch (e) {}
372
+ return results;
373
+ }
374
+
375
+ function matchGlob(filepath, pattern) {
376
+ if (!pattern || pattern === "**/*") return true;
377
+ const regex = pattern
378
+ .replace(/\\./g, "\\\\.")
379
+ .replace(/\\*\\*/g, "<<<GLOBSTAR>>>")
380
+ .replace(/\\*/g, "[^/]*")
381
+ .replace(/<<<GLOBSTAR>>>/g, ".*")
382
+ .replace(/\\?/g, ".");
383
+ return new RegExp("^" + regex + "$").test(filepath);
384
+ }
385
+
386
+ const allFiles = walkDir(basePath, basePath);
387
+ const files = allFiles.filter(f => matchGlob(f, fileGlob)).sort();
388
+
389
+ for (const file of files) {
390
+ try {
391
+ const fullPath = path.join(basePath, file);
392
+ const content = fs.readFileSync(fullPath, "utf-8");
393
+ const lines = content.split("\\n");
394
+
395
+ for (let i = 0; i < lines.length; i++) {
396
+ if (lines[i].includes(pattern)) {
397
+ console.log(JSON.stringify({
398
+ path: file,
399
+ line: i + 1,
400
+ text: lines[i]
401
+ }));
402
+ }
403
+ }
404
+ } catch (e) {}
405
+ }
406
+ `;
407
+ const result = await this.execute(
408
+ buildNodeScript(script, {
409
+ PATTERN: patternB64,
410
+ PATH: pathB64,
411
+ GLOB: globB64,
412
+ })
413
+ );
414
+
415
+ const matches: GrepMatch[] = [];
416
+ for (const line of result.output.trim().split("\n")) {
417
+ if (!line) continue;
418
+ try {
419
+ const data = JSON.parse(line);
420
+ matches.push({
421
+ path: data.path,
422
+ line: data.line,
423
+ text: data.text,
424
+ });
425
+ } catch {
426
+ // Skip malformed lines
427
+ }
428
+ }
429
+ return matches;
430
+ }
431
+
432
+ /**
433
+ * Find files matching glob pattern.
434
+ */
435
+ async globInfo(pattern: string, path: string = "/"): Promise<FileInfo[]> {
436
+ const pathB64 = toBase64(path);
437
+ const patternB64 = toBase64(pattern);
438
+ const script = `
439
+ const fs = require("fs");
440
+ const path = require("path");
441
+
442
+ const basePath = Buffer.from("__PATH__", "base64").toString("utf-8");
443
+ const pattern = Buffer.from("__PATTERN__", "base64").toString("utf-8");
444
+
445
+ function walkDir(dir, baseDir) {
446
+ const results = [];
447
+ try {
448
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
449
+ for (const entry of entries) {
450
+ const fullPath = path.join(dir, entry.name);
451
+ const relativePath = path.relative(baseDir, fullPath);
452
+
453
+ if (entry.isDirectory()) {
454
+ results.push(...walkDir(fullPath, baseDir));
455
+ } else {
456
+ results.push(relativePath);
457
+ }
458
+ }
459
+ } catch (e) {}
460
+ return results;
461
+ }
462
+
463
+ function matchGlob(filepath, pattern) {
464
+ const regex = pattern
465
+ .replace(/\\./g, "\\\\.")
466
+ .replace(/\\*\\*/g, "<<<GLOBSTAR>>>")
467
+ .replace(/\\*/g, "[^/]*")
468
+ .replace(/<<<GLOBSTAR>>>/g, ".*")
469
+ .replace(/\\?/g, ".");
470
+ return new RegExp("^" + regex + "$").test(filepath);
471
+ }
472
+
473
+ const allFiles = walkDir(basePath, basePath);
474
+ const matches = allFiles.filter(f => matchGlob(f, pattern)).sort();
475
+
476
+ for (const m of matches) {
477
+ try {
478
+ const fullPath = path.join(basePath, m);
479
+ const stat = fs.statSync(fullPath);
480
+ console.log(JSON.stringify({
481
+ path: m,
482
+ is_dir: stat.isDirectory(),
483
+ size: stat.size,
484
+ modified_at: stat.mtime.toISOString()
485
+ }));
486
+ } catch (e) {}
487
+ }
488
+ `;
489
+ const result = await this.execute(
490
+ buildNodeScript(script, { PATH: pathB64, PATTERN: patternB64 })
491
+ );
492
+
493
+ const infos: FileInfo[] = [];
494
+ for (const line of result.output.trim().split("\n")) {
495
+ if (!line) continue;
496
+ try {
497
+ const data = JSON.parse(line);
498
+ infos.push({
499
+ path: data.path,
500
+ is_dir: data.is_dir,
501
+ size: data.size,
502
+ modified_at: data.modified_at,
503
+ });
504
+ } catch {
505
+ // Skip malformed lines
506
+ }
507
+ }
508
+ return infos;
509
+ }
510
+ }