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,213 @@
1
+ /**
2
+ * Utilities for applying tool approval configuration.
3
+ */
4
+
5
+ import { tool, type ToolSet } from "ai";
6
+ import type { InterruptOnConfig, DynamicApprovalConfig } from "../types";
7
+
8
+ /**
9
+ * Callback type for requesting approval from the user.
10
+ */
11
+ export type ApprovalCallback = (request: {
12
+ approvalId: string;
13
+ toolCallId: string;
14
+ toolName: string;
15
+ args: unknown;
16
+ }) => Promise<boolean>;
17
+
18
+ /**
19
+ * Check if approval is needed based on config.
20
+ */
21
+ async function checkNeedsApproval(
22
+ config: boolean | DynamicApprovalConfig,
23
+ args: unknown
24
+ ): Promise<boolean> {
25
+ if (typeof config === "boolean") {
26
+ return config;
27
+ }
28
+
29
+ if (config.shouldApprove) {
30
+ return config.shouldApprove(args);
31
+ }
32
+
33
+ return true;
34
+ }
35
+
36
+ /**
37
+ * Convert interruptOn config to needsApproval function for a tool.
38
+ */
39
+ function configToNeedsApproval(
40
+ config: boolean | DynamicApprovalConfig
41
+ ): boolean | ((args: unknown) => boolean | Promise<boolean>) {
42
+ if (typeof config === "boolean") {
43
+ return config;
44
+ }
45
+
46
+ if (config.shouldApprove) {
47
+ return config.shouldApprove;
48
+ }
49
+
50
+ return true;
51
+ }
52
+
53
+ let approvalCounter = 0;
54
+ function generateApprovalId(): string {
55
+ return `approval-${Date.now()}-${++approvalCounter}`;
56
+ }
57
+
58
+ /**
59
+ * Apply interruptOn configuration to a toolset.
60
+ *
61
+ * This adds the `needsApproval` property to tools based on the config.
62
+ *
63
+ * @param tools - The original toolset
64
+ * @param interruptOn - Configuration mapping tool names to approval settings
65
+ * @returns New toolset with needsApproval applied
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * const approvedTools = applyInterruptConfig(tools, {
70
+ * write_file: true,
71
+ * execute: { shouldApprove: (args) => args.command.includes('rm') },
72
+ * });
73
+ * ```
74
+ */
75
+ export function applyInterruptConfig(
76
+ tools: ToolSet,
77
+ interruptOn?: InterruptOnConfig
78
+ ): ToolSet {
79
+ if (!interruptOn) {
80
+ return tools;
81
+ }
82
+
83
+ const result: ToolSet = {};
84
+
85
+ for (const [name, tool] of Object.entries(tools)) {
86
+ const config = interruptOn[name];
87
+
88
+ if (config === undefined || config === false) {
89
+ // No approval needed - use tool as-is
90
+ result[name] = tool;
91
+ } else {
92
+ // Apply needsApproval
93
+ result[name] = {
94
+ ...tool,
95
+ needsApproval: configToNeedsApproval(config),
96
+ };
97
+ }
98
+ }
99
+
100
+ return result;
101
+ }
102
+
103
+ /**
104
+ * Wrap tools with approval checking that intercepts execution.
105
+ *
106
+ * Unlike applyInterruptConfig which just sets needsApproval metadata,
107
+ * this actually wraps the execute function to request approval before running.
108
+ *
109
+ * If no approval callback is provided, tools requiring approval will be auto-denied.
110
+ *
111
+ * @param tools - The original toolset
112
+ * @param interruptOn - Configuration mapping tool names to approval settings
113
+ * @param onApprovalRequest - Callback to request approval from user (optional)
114
+ * @returns New toolset with wrapped execute functions
115
+ */
116
+ export function wrapToolsWithApproval(
117
+ tools: ToolSet,
118
+ interruptOn: InterruptOnConfig | undefined,
119
+ onApprovalRequest: ApprovalCallback | undefined
120
+ ): ToolSet {
121
+ if (!interruptOn) {
122
+ return tools;
123
+ }
124
+
125
+ const result: ToolSet = {};
126
+
127
+ for (const [name, existingTool] of Object.entries(tools)) {
128
+ const config = interruptOn[name];
129
+
130
+ if (config === undefined || config === false) {
131
+ // No approval needed - use tool as-is
132
+ result[name] = existingTool;
133
+ } else {
134
+ // Wrap the execute function with approval check
135
+ const originalExecute = existingTool.execute;
136
+ if (!originalExecute) {
137
+ // Tool has no execute function - skip wrapping
138
+ result[name] = existingTool;
139
+ continue;
140
+ }
141
+
142
+ // Create a completely new tool using the AI SDK tool() function
143
+ // This ensures proper integration with AI SDK's execution mechanism
144
+ result[name] = tool({
145
+ description: existingTool.description,
146
+ inputSchema: existingTool.inputSchema,
147
+ execute: async (args, options) => {
148
+ // Check if this specific call needs approval
149
+ const needsApproval = await checkNeedsApproval(config, args);
150
+
151
+ if (needsApproval) {
152
+ // If no callback provided, auto-deny
153
+ if (!onApprovalRequest) {
154
+ return `Tool execution denied. No approval callback provided. The ${name} tool was not executed.`;
155
+ }
156
+
157
+ // Generate unique IDs for this approval request
158
+ const approvalId = generateApprovalId();
159
+ const toolCallId = options?.toolCallId || approvalId;
160
+
161
+ // Request approval from user
162
+ const approved = await onApprovalRequest({
163
+ approvalId,
164
+ toolCallId,
165
+ toolName: name,
166
+ args,
167
+ });
168
+
169
+ if (!approved) {
170
+ // User denied - return an error message instead of executing
171
+ return `Tool execution denied by user. The ${name} tool was not executed.`;
172
+ }
173
+ }
174
+
175
+ // Approved or no approval needed - execute the tool
176
+ return originalExecute(args, options);
177
+ },
178
+ });
179
+ }
180
+ }
181
+
182
+ return result;
183
+ }
184
+
185
+ /**
186
+ * Check if a toolset has any tools requiring approval.
187
+ */
188
+ export function hasApprovalTools(interruptOn?: InterruptOnConfig): boolean {
189
+ if (!interruptOn) return false;
190
+ return Object.values(interruptOn).some((v) => v !== false);
191
+ }
192
+
193
+ /**
194
+ * Create interrupt data for checkpoint when approval is requested.
195
+ *
196
+ * This is used to save checkpoint state when a tool requires approval,
197
+ * allowing the agent to resume from the interrupt point later.
198
+ */
199
+ export function createInterruptData(
200
+ toolCallId: string,
201
+ toolName: string,
202
+ args: unknown,
203
+ step: number
204
+ ): import("../checkpointer/types").InterruptData {
205
+ return {
206
+ toolCall: {
207
+ toolCallId,
208
+ toolName,
209
+ args,
210
+ },
211
+ step,
212
+ };
213
+ }
@@ -0,0 +1,416 @@
1
+ /**
2
+ * Type-safe event creation helpers for Deep Agent.
3
+ *
4
+ * These factory functions provide type-safe ways to create DeepAgentEvent objects,
5
+ * reducing duplication and ensuring event objects are correctly structured.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { createFileReadEvent, createToolCallEvent } from './utils/events';
10
+ *
11
+ * // Create events with type inference
12
+ * const fileEvent = createFileReadEvent('/path/to/file.ts', 100);
13
+ * const toolEvent = createToolCallEvent('read_file', { path: '/file.txt' }, 'call-123');
14
+ * ```
15
+ */
16
+
17
+ import type {
18
+ TextEvent,
19
+ StepStartEvent,
20
+ ToolCallEvent,
21
+ ToolResultEvent,
22
+ TodosChangedEvent,
23
+ FileWriteStartEvent,
24
+ FileWrittenEvent,
25
+ FileEditedEvent,
26
+ FileReadEvent,
27
+ LsEvent,
28
+ GlobEvent,
29
+ GrepEvent,
30
+ ExecuteStartEvent,
31
+ ExecuteFinishEvent,
32
+ WebSearchStartEvent,
33
+ WebSearchFinishEvent,
34
+ HttpRequestStartEvent,
35
+ HttpRequestFinishEvent,
36
+ FetchUrlStartEvent,
37
+ FetchUrlFinishEvent,
38
+ SubagentStartEvent,
39
+ SubagentFinishEvent,
40
+ SubagentStepEvent,
41
+ TextSegmentEvent,
42
+ UserMessageEvent,
43
+ DoneEvent,
44
+ ErrorEvent,
45
+ ApprovalRequestedEvent,
46
+ ApprovalResponseEvent,
47
+ CheckpointSavedEvent,
48
+ CheckpointLoadedEvent,
49
+ DeepAgentEvent,
50
+ DeepAgentState,
51
+ } from "../types";
52
+
53
+ // ============================================================================
54
+ // Basic Event Factories
55
+ // ============================================================================
56
+
57
+ /**
58
+ * Create a text streaming event.
59
+ */
60
+ export function createTextEvent(text: string): TextEvent {
61
+ return { type: "text", text };
62
+ }
63
+
64
+ /**
65
+ * Create a step-start event.
66
+ */
67
+ export function createStepStartEvent(stepNumber: number): StepStartEvent {
68
+ return { type: "step-start", stepNumber };
69
+ }
70
+
71
+ /**
72
+ * Create a tool-call event.
73
+ */
74
+ export function createToolCallEvent(
75
+ toolName: string,
76
+ args: unknown,
77
+ toolCallId: string
78
+ ): ToolCallEvent {
79
+ return { type: "tool-call", toolName, toolCallId, args };
80
+ }
81
+
82
+ /**
83
+ * Create a tool-result event.
84
+ */
85
+ export function createToolResultEvent(
86
+ toolName: string,
87
+ toolCallId: string,
88
+ result: unknown
89
+ ): ToolResultEvent {
90
+ return { type: "tool-result", toolName, toolCallId, result };
91
+ }
92
+
93
+ /**
94
+ * Create a text-segment event (for CLI display).
95
+ */
96
+ export function createTextSegmentEvent(text: string): TextSegmentEvent {
97
+ return { type: "text-segment", text };
98
+ }
99
+
100
+ /**
101
+ * Create a user-message event (for CLI history).
102
+ */
103
+ export function createUserMessageEvent(content: string): UserMessageEvent {
104
+ return { type: "user-message", content };
105
+ }
106
+
107
+ /**
108
+ * Create a done event.
109
+ */
110
+ export function createDoneEvent(
111
+ state: DeepAgentState,
112
+ options?: { text?: string; messages?: DoneEvent["messages"]; output?: DoneEvent["output"] }
113
+ ): DoneEvent {
114
+ const event: DoneEvent = { type: "done", state, ...options };
115
+ return event;
116
+ }
117
+
118
+ /**
119
+ * Create an error event.
120
+ */
121
+ export function createErrorEvent(error: Error): ErrorEvent {
122
+ return { type: "error", error };
123
+ }
124
+
125
+ // ============================================================================
126
+ // Todo Event Factories
127
+ // ============================================================================
128
+
129
+ /**
130
+ * Create a todos-changed event.
131
+ */
132
+ export function createTodosChangedEvent(todos: TodosChangedEvent["todos"]): TodosChangedEvent {
133
+ return { type: "todos-changed", todos };
134
+ }
135
+
136
+ // ============================================================================
137
+ // File Event Factories
138
+ // ============================================================================
139
+
140
+ /**
141
+ * Create a file-write-start event (preview before write).
142
+ */
143
+ export function createFileWriteStartEvent(
144
+ path: string,
145
+ content: string
146
+ ): FileWriteStartEvent {
147
+ return { type: "file-write-start", path, content };
148
+ }
149
+
150
+ /**
151
+ * Create a file-written event (after successful write).
152
+ */
153
+ export function createFileWrittenEvent(
154
+ path: string,
155
+ content: string
156
+ ): FileWrittenEvent {
157
+ return { type: "file-written", path, content };
158
+ }
159
+
160
+ /**
161
+ * Create a file-edited event.
162
+ */
163
+ export function createFileEditedEvent(
164
+ path: string,
165
+ occurrences: number
166
+ ): FileEditedEvent {
167
+ return { type: "file-edited", path, occurrences };
168
+ }
169
+
170
+ /**
171
+ * Create a file-read event.
172
+ */
173
+ export function createFileReadEvent(path: string, lines: number): FileReadEvent {
174
+ return { type: "file-read", path, lines };
175
+ }
176
+
177
+ // ============================================================================
178
+ // Filesystem Operation Event Factories
179
+ // ============================================================================
180
+
181
+ /**
182
+ * Create an ls (list) event.
183
+ */
184
+ export function createLsEvent(path: string, count: number): LsEvent {
185
+ return { type: "ls", path, count };
186
+ }
187
+
188
+ /**
189
+ * Create a glob (pattern search) event.
190
+ */
191
+ export function createGlobEvent(pattern: string, count: number): GlobEvent {
192
+ return { type: "glob", pattern, count };
193
+ }
194
+
195
+ /**
196
+ * Create a grep (content search) event.
197
+ */
198
+ export function createGrepEvent(pattern: string, count: number): GrepEvent {
199
+ return { type: "grep", pattern, count };
200
+ }
201
+
202
+ // ============================================================================
203
+ // Execute Event Factories
204
+ // ============================================================================
205
+
206
+ /**
207
+ * Create an execute-start event.
208
+ */
209
+ export function createExecuteStartEvent(
210
+ command: string,
211
+ sandboxId: string
212
+ ): ExecuteStartEvent {
213
+ return { type: "execute-start", command, sandboxId };
214
+ }
215
+
216
+ /**
217
+ * Create an execute-finish event.
218
+ */
219
+ export function createExecuteFinishEvent(
220
+ command: string,
221
+ sandboxId: string,
222
+ exitCode: number | null,
223
+ truncated: boolean
224
+ ): ExecuteFinishEvent {
225
+ return { type: "execute-finish", command, sandboxId, exitCode, truncated };
226
+ }
227
+
228
+ // ============================================================================
229
+ // Web Event Factories
230
+ // ============================================================================
231
+
232
+ /**
233
+ * Create a web-search-start event.
234
+ */
235
+ export function createWebSearchStartEvent(query: string): WebSearchStartEvent {
236
+ return { type: "web-search-start", query };
237
+ }
238
+
239
+ /**
240
+ * Create a web-search-finish event.
241
+ */
242
+ export function createWebSearchFinishEvent(
243
+ query: string,
244
+ resultCount: number
245
+ ): WebSearchFinishEvent {
246
+ return { type: "web-search-finish", query, resultCount };
247
+ }
248
+
249
+ /**
250
+ * Create an http-request-start event.
251
+ */
252
+ export function createHttpRequestStartEvent(
253
+ url: string,
254
+ method: string
255
+ ): HttpRequestStartEvent {
256
+ return { type: "http-request-start", url, method };
257
+ }
258
+
259
+ /**
260
+ * Create an http-request-finish event.
261
+ */
262
+ export function createHttpRequestFinishEvent(
263
+ url: string,
264
+ statusCode: number
265
+ ): HttpRequestFinishEvent {
266
+ return { type: "http-request-finish", url, statusCode };
267
+ }
268
+
269
+ /**
270
+ * Create a fetch-url-start event.
271
+ */
272
+ export function createFetchUrlStartEvent(url: string): FetchUrlStartEvent {
273
+ return { type: "fetch-url-start", url };
274
+ }
275
+
276
+ /**
277
+ * Create a fetch-url-finish event.
278
+ */
279
+ export function createFetchUrlFinishEvent(
280
+ url: string,
281
+ success: boolean
282
+ ): FetchUrlFinishEvent {
283
+ return { type: "fetch-url-finish", url, success };
284
+ }
285
+
286
+ // ============================================================================
287
+ // Subagent Event Factories
288
+ // ============================================================================
289
+
290
+ /**
291
+ * Create a subagent-start event.
292
+ */
293
+ export function createSubagentStartEvent(
294
+ name: string,
295
+ task: string
296
+ ): SubagentStartEvent {
297
+ return { type: "subagent-start", name, task };
298
+ }
299
+
300
+ /**
301
+ * Create a subagent-finish event.
302
+ */
303
+ export function createSubagentFinishEvent(
304
+ name: string,
305
+ result: string
306
+ ): SubagentFinishEvent {
307
+ return { type: "subagent-finish", name, result };
308
+ }
309
+
310
+ /**
311
+ * Create a subagent-step event.
312
+ */
313
+ export function createSubagentStepEvent(
314
+ stepIndex: number,
315
+ toolCalls: SubagentStepEvent["toolCalls"]
316
+ ): SubagentStepEvent {
317
+ return { type: "subagent-step", stepIndex, toolCalls };
318
+ }
319
+
320
+ // ============================================================================
321
+ // Approval Event Factories
322
+ // ============================================================================
323
+
324
+ /**
325
+ * Create an approval-requested event.
326
+ */
327
+ export function createApprovalRequestedEvent(
328
+ approvalId: string,
329
+ toolCallId: string,
330
+ toolName: string,
331
+ args: unknown
332
+ ): ApprovalRequestedEvent {
333
+ return { type: "approval-requested", approvalId, toolCallId, toolName, args };
334
+ }
335
+
336
+ /**
337
+ * Create an approval-response event.
338
+ */
339
+ export function createApprovalResponseEvent(
340
+ approvalId: string,
341
+ approved: boolean
342
+ ): ApprovalResponseEvent {
343
+ return { type: "approval-response", approvalId, approved };
344
+ }
345
+
346
+ // ============================================================================
347
+ // Checkpoint Event Factories
348
+ // ============================================================================
349
+
350
+ /**
351
+ * Create a checkpoint-saved event.
352
+ */
353
+ export function createCheckpointSavedEvent(
354
+ threadId: string,
355
+ step: number
356
+ ): CheckpointSavedEvent {
357
+ return { type: "checkpoint-saved", threadId, step };
358
+ }
359
+
360
+ /**
361
+ * Create a checkpoint-loaded event.
362
+ */
363
+ export function createCheckpointLoadedEvent(
364
+ threadId: string,
365
+ step: number,
366
+ messagesCount: number
367
+ ): CheckpointLoadedEvent {
368
+ return { type: "checkpoint-loaded", threadId, step, messagesCount };
369
+ }
370
+
371
+ // ============================================================================
372
+ // Utility Functions
373
+ // ============================================================================
374
+
375
+ /**
376
+ * Type guard to check if an event is a specific type.
377
+ * Useful for filtering or discriminating union types.
378
+ *
379
+ * @example
380
+ * ```typescript
381
+ * if (isEventType(event, "file-read")) {
382
+ * // TypeScript knows event is FileReadEvent here
383
+ * console.log(event.lines);
384
+ * }
385
+ * ```
386
+ */
387
+ export function isEventType<T extends DeepAgentEvent["type"]>(
388
+ event: DeepAgentEvent,
389
+ type: T
390
+ ): event is Extract<DeepAgentEvent, { type: T }> {
391
+ return event.type === type;
392
+ }
393
+
394
+ /**
395
+ * Get the event type as a string.
396
+ * Utility function for logging or debugging.
397
+ */
398
+ export function getEventType(event: DeepAgentEvent): string {
399
+ return event.type;
400
+ }
401
+
402
+ /**
403
+ * Create a generic event object from a type and data.
404
+ * This is a more flexible but less type-safe alternative to the specific factories.
405
+ *
406
+ * @example
407
+ * ```typescript
408
+ * const event = createEvent("file-read", { path: "/file.txt", lines: 100 });
409
+ * ```
410
+ */
411
+ export function createEvent<T extends DeepAgentEvent>(
412
+ type: T["type"],
413
+ data: Omit<T, "type">
414
+ ): T {
415
+ return { type, ...data } as T;
416
+ }