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,82 @@
1
+ /**
2
+ * In-memory checkpoint saver for testing and single-session use.
3
+ */
4
+
5
+ import type { Checkpoint, BaseCheckpointSaver, CheckpointSaverOptions } from "./types";
6
+
7
+ /**
8
+ * In-memory checkpoint saver.
9
+ *
10
+ * Stores checkpoints in a Map. Data is lost when the process exits.
11
+ * Useful for testing or single-session applications.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const saver = new MemorySaver();
16
+ * const agent = createDeepAgent({
17
+ * model: anthropic('claude-sonnet-4-20250514'),
18
+ * checkpointer: saver,
19
+ * });
20
+ * ```
21
+ */
22
+ export class MemorySaver implements BaseCheckpointSaver {
23
+ private checkpoints = new Map<string, Checkpoint>();
24
+ private namespace: string;
25
+
26
+ constructor(options: CheckpointSaverOptions = {}) {
27
+ this.namespace = options.namespace || "default";
28
+ }
29
+
30
+ private getKey(threadId: string): string {
31
+ return `${this.namespace}:${threadId}`;
32
+ }
33
+
34
+ async save(checkpoint: Checkpoint): Promise<void> {
35
+ const key = this.getKey(checkpoint.threadId);
36
+ this.checkpoints.set(key, {
37
+ ...checkpoint,
38
+ updatedAt: new Date().toISOString(),
39
+ });
40
+ }
41
+
42
+ async load(threadId: string): Promise<Checkpoint | undefined> {
43
+ const key = this.getKey(threadId);
44
+ return this.checkpoints.get(key);
45
+ }
46
+
47
+ async list(): Promise<string[]> {
48
+ const prefix = `${this.namespace}:`;
49
+ const threadIds: string[] = [];
50
+ for (const key of this.checkpoints.keys()) {
51
+ if (key.startsWith(prefix)) {
52
+ threadIds.push(key.substring(prefix.length));
53
+ }
54
+ }
55
+ return threadIds;
56
+ }
57
+
58
+ async delete(threadId: string): Promise<void> {
59
+ const key = this.getKey(threadId);
60
+ this.checkpoints.delete(key);
61
+ }
62
+
63
+ async exists(threadId: string): Promise<boolean> {
64
+ const key = this.getKey(threadId);
65
+ return this.checkpoints.has(key);
66
+ }
67
+
68
+ /**
69
+ * Clear all checkpoints (useful for testing).
70
+ */
71
+ clear(): void {
72
+ this.checkpoints.clear();
73
+ }
74
+
75
+ /**
76
+ * Get the number of stored checkpoints.
77
+ */
78
+ size(): number {
79
+ return this.checkpoints.size;
80
+ }
81
+ }
82
+
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Type definitions for checkpointer support.
3
+ */
4
+
5
+ import type { DeepAgentState, ModelMessage } from "../types";
6
+
7
+ /**
8
+ * Data stored in a checkpoint.
9
+ * Contains everything needed to resume an agent session.
10
+ */
11
+ export interface Checkpoint {
12
+ /** Unique identifier for the conversation thread */
13
+ threadId: string;
14
+
15
+ /** Step number when this checkpoint was created */
16
+ step: number;
17
+
18
+ /** Conversation history (serialized messages) */
19
+ messages: ModelMessage[];
20
+
21
+ /** Agent state (todos and StateBackend files) */
22
+ state: DeepAgentState;
23
+
24
+ /**
25
+ * Interrupt data if the agent was paused mid-execution.
26
+ * Present when waiting for tool approval.
27
+ */
28
+ interrupt?: InterruptData;
29
+
30
+ /** ISO 8601 timestamp when checkpoint was created */
31
+ createdAt: string;
32
+
33
+ /** ISO 8601 timestamp when checkpoint was last updated */
34
+ updatedAt: string;
35
+ }
36
+
37
+ /**
38
+ * Data about an interrupted tool execution.
39
+ * Used to resume from approval requests.
40
+ */
41
+ export interface InterruptData {
42
+ /** The tool call that requires approval */
43
+ toolCall: {
44
+ toolCallId: string;
45
+ toolName: string;
46
+ args: unknown;
47
+ };
48
+
49
+ /** Step number where interrupt occurred */
50
+ step: number;
51
+ }
52
+
53
+ /**
54
+ * Decision to resume from an interrupt.
55
+ */
56
+ export interface ResumeDecision {
57
+ /** Type of decision */
58
+ type: 'approve' | 'deny';
59
+
60
+ /** Optional modified arguments (for future "edit" feature) */
61
+ modifiedArgs?: unknown;
62
+ }
63
+
64
+ /**
65
+ * Options for resuming from a checkpoint.
66
+ */
67
+ export interface ResumeOptions {
68
+ /** Decisions for pending tool approvals */
69
+ decisions: ResumeDecision[];
70
+ }
71
+
72
+ /**
73
+ * Interface for checkpoint storage implementations.
74
+ *
75
+ * Implement this interface to use any storage backend (memory, files,
76
+ * Redis, database, etc.) for persisting checkpoints.
77
+ */
78
+ export interface BaseCheckpointSaver {
79
+ /**
80
+ * Save a checkpoint.
81
+ * If a checkpoint for the threadId already exists, it is overwritten.
82
+ *
83
+ * @param checkpoint - The checkpoint data to save
84
+ */
85
+ save(checkpoint: Checkpoint): Promise<void>;
86
+
87
+ /**
88
+ * Load the latest checkpoint for a thread.
89
+ *
90
+ * @param threadId - The thread identifier
91
+ * @returns The checkpoint, or undefined if not found
92
+ */
93
+ load(threadId: string): Promise<Checkpoint | undefined>;
94
+
95
+ /**
96
+ * List all thread IDs with saved checkpoints.
97
+ *
98
+ * @returns Array of thread IDs
99
+ */
100
+ list(): Promise<string[]>;
101
+
102
+ /**
103
+ * Delete a checkpoint.
104
+ *
105
+ * @param threadId - The thread identifier to delete
106
+ */
107
+ delete(threadId: string): Promise<void>;
108
+
109
+ /**
110
+ * Check if a checkpoint exists for a thread.
111
+ *
112
+ * @param threadId - The thread identifier
113
+ * @returns True if checkpoint exists
114
+ */
115
+ exists(threadId: string): Promise<boolean>;
116
+ }
117
+
118
+ /**
119
+ * Options for creating a checkpoint saver.
120
+ */
121
+ export interface CheckpointSaverOptions {
122
+ /** Optional namespace prefix for isolation */
123
+ namespace?: string;
124
+ }
125
+
@@ -0,0 +1,300 @@
1
+ /**
2
+ * API Key Input Panel - Interactive provider selection and key input.
3
+ * Shows current status and allows adding/updating keys.
4
+ */
5
+
6
+ import React, { useState } from "react";
7
+ import { Box, Text, useInput } from "ink";
8
+ import { colors, emoji } from "../theme.js";
9
+
10
+ type Provider = "anthropic" | "openai";
11
+
12
+ interface ApiKeyInputPanelProps {
13
+ /** Callback when API key is saved */
14
+ onKeySaved?: (provider: Provider, key: string) => void;
15
+ /** Callback to close the panel */
16
+ onClose?: () => void;
17
+ }
18
+
19
+ type Step = "select-provider" | "enter-key" | "success";
20
+
21
+ export function ApiKeyInputPanel({
22
+ onKeySaved,
23
+ onClose,
24
+ }: ApiKeyInputPanelProps): React.ReactElement {
25
+ const [step, setStep] = useState<Step>("select-provider");
26
+ const [selectedProvider, setSelectedProvider] = useState<Provider | null>(null);
27
+ const [apiKey, setApiKey] = useState("");
28
+ const [error, setError] = useState<string | null>(null);
29
+
30
+ // Get current API keys
31
+ const anthropicKey = process.env.ANTHROPIC_API_KEY;
32
+ const openaiKey = process.env.OPENAI_API_KEY;
33
+
34
+ const maskKey = (key: string | undefined) => {
35
+ if (!key) return null;
36
+ if (key.length <= 8) return "•".repeat(key.length);
37
+ return key.substring(0, 10) + "..." + key.substring(key.length - 4);
38
+ };
39
+
40
+ // Handle keyboard input
41
+ useInput((input, key) => {
42
+ if (step === "select-provider") {
43
+ if (input === "1" || input.toLowerCase() === "a") {
44
+ setSelectedProvider("anthropic");
45
+ setStep("enter-key");
46
+ setError(null);
47
+ // Pre-fill existing key if available
48
+ if (anthropicKey) {
49
+ setApiKey(anthropicKey);
50
+ }
51
+ } else if (input === "2" || input.toLowerCase() === "o") {
52
+ setSelectedProvider("openai");
53
+ setStep("enter-key");
54
+ setError(null);
55
+ // Pre-fill existing key if available
56
+ if (openaiKey) {
57
+ setApiKey(openaiKey);
58
+ }
59
+ } else if (key.escape) {
60
+ onClose?.();
61
+ }
62
+ } else if (step === "enter-key") {
63
+ if (key.escape) {
64
+ // Go back to provider selection
65
+ setStep("select-provider");
66
+ setApiKey("");
67
+ setSelectedProvider(null);
68
+ setError(null);
69
+ } else if (key.return) {
70
+ // Validate and save
71
+ if (!apiKey.trim()) {
72
+ setError("API key cannot be empty");
73
+ return;
74
+ }
75
+
76
+ // Basic validation
77
+ if (selectedProvider === "anthropic" && !apiKey.startsWith("sk-ant-")) {
78
+ setError("Anthropic API keys typically start with 'sk-ant-'");
79
+ return;
80
+ }
81
+ if (selectedProvider === "openai" && !apiKey.startsWith("sk-")) {
82
+ setError("OpenAI API keys typically start with 'sk-'");
83
+ return;
84
+ }
85
+
86
+ // Save to environment
87
+ if (selectedProvider === "anthropic") {
88
+ process.env.ANTHROPIC_API_KEY = apiKey.trim();
89
+ } else if (selectedProvider === "openai") {
90
+ process.env.OPENAI_API_KEY = apiKey.trim();
91
+ }
92
+
93
+ setStep("success");
94
+ onKeySaved?.(selectedProvider!, apiKey.trim());
95
+
96
+ // Auto-return to provider selection after success
97
+ setTimeout(() => {
98
+ setStep("select-provider");
99
+ setApiKey("");
100
+ setSelectedProvider(null);
101
+ }, 1500);
102
+ } else if (key.backspace || key.delete) {
103
+ setApiKey((prev) => prev.slice(0, -1));
104
+ setError(null);
105
+ } else if (input && !key.ctrl && !key.meta) {
106
+ setApiKey((prev) => prev + input);
107
+ setError(null);
108
+ }
109
+ } else if (step === "success") {
110
+ if (key.return || key.escape) {
111
+ // Return to provider selection
112
+ setStep("select-provider");
113
+ setApiKey("");
114
+ setSelectedProvider(null);
115
+ }
116
+ }
117
+ });
118
+
119
+ const maskKeyForInput = (key: string): string => {
120
+ if (key.length <= 8) return "•".repeat(key.length);
121
+ return key.substring(0, 7) + "•".repeat(Math.min(key.length - 11, 20)) + key.substring(key.length - 4);
122
+ };
123
+
124
+ return (
125
+ <Box
126
+ flexDirection="column"
127
+ borderStyle="single"
128
+ borderColor={colors.primary}
129
+ paddingX={2}
130
+ paddingY={1}
131
+ marginY={1}
132
+ >
133
+ <Text bold color={colors.info}>
134
+ {emoji.key} API Key Management
135
+ </Text>
136
+ <Box height={1} />
137
+
138
+ {/* Always show current status */}
139
+ <Text bold>Current Status:</Text>
140
+ <Box height={1} />
141
+ <Box marginLeft={2}>
142
+ {anthropicKey ? (
143
+ <>
144
+ <Text color={colors.success}>✓ </Text>
145
+ <Text>Anthropic: </Text>
146
+ <Text dimColor>{maskKey(anthropicKey)}</Text>
147
+ </>
148
+ ) : (
149
+ <>
150
+ <Text color={colors.warning}>✗ </Text>
151
+ <Text>Anthropic: </Text>
152
+ <Text dimColor>not set</Text>
153
+ </>
154
+ )}
155
+ </Box>
156
+ <Box marginLeft={2}>
157
+ {openaiKey ? (
158
+ <>
159
+ <Text color={colors.success}>✓ </Text>
160
+ <Text>OpenAI: </Text>
161
+ <Text dimColor>{maskKey(openaiKey)}</Text>
162
+ </>
163
+ ) : (
164
+ <>
165
+ <Text color={colors.warning}>✗ </Text>
166
+ <Text>OpenAI: </Text>
167
+ <Text dimColor>not set</Text>
168
+ </>
169
+ )}
170
+ </Box>
171
+ <Box height={1} />
172
+
173
+ {step === "select-provider" && (
174
+ <>
175
+ <Text bold>Add or Update Key:</Text>
176
+ <Box height={1} />
177
+ <Box marginLeft={2}>
178
+ <Text color={colors.primary}>[1]</Text>
179
+ <Text> Anthropic (Claude)</Text>
180
+ {anthropicKey && <Text dimColor> (overwrite)</Text>}
181
+ </Box>
182
+ <Box marginLeft={2}>
183
+ <Text color={colors.primary}>[2]</Text>
184
+ <Text> OpenAI (GPT)</Text>
185
+ {openaiKey && <Text dimColor> (overwrite)</Text>}
186
+ </Box>
187
+ <Box height={1} />
188
+ <Text dimColor>Press 1 or 2 to select, Esc to close</Text>
189
+ </>
190
+ )}
191
+
192
+ {step === "enter-key" && selectedProvider && (
193
+ <>
194
+ <Text>
195
+ Enter your{" "}
196
+ <Text color={colors.primary}>
197
+ {selectedProvider === "anthropic" ? "Anthropic" : "OpenAI"}
198
+ </Text>{" "}
199
+ API key:
200
+ {selectedProvider === "anthropic" && anthropicKey && (
201
+ <Text dimColor> (current: {maskKey(anthropicKey)})</Text>
202
+ )}
203
+ {selectedProvider === "openai" && openaiKey && (
204
+ <Text dimColor> (current: {maskKey(openaiKey)})</Text>
205
+ )}
206
+ </Text>
207
+ <Box height={1} />
208
+ <Box>
209
+ <Text dimColor>{">"} </Text>
210
+ <Text>{apiKey ? maskKeyForInput(apiKey) : <Text dimColor>Paste your API key here...</Text>}</Text>
211
+ <Text color={colors.primary}>█</Text>
212
+ </Box>
213
+ {error && (
214
+ <>
215
+ <Box height={1} />
216
+ <Text color={colors.error}>{emoji.warning} {error}</Text>
217
+ </>
218
+ )}
219
+ <Box height={1} />
220
+ <Text dimColor>Press Enter to save, Esc to go back</Text>
221
+ </>
222
+ )}
223
+
224
+ {step === "success" && selectedProvider && (
225
+ <>
226
+ <Text color={colors.success}>
227
+ {emoji.completed} API key saved for{" "}
228
+ {selectedProvider === "anthropic" ? "Anthropic" : "OpenAI"}!
229
+ </Text>
230
+ <Box height={1} />
231
+ <Text dimColor>Press Enter or Esc to return to menu</Text>
232
+ </>
233
+ )}
234
+ </Box>
235
+ );
236
+ }
237
+
238
+ /**
239
+ * Simple API Key Status display (read-only).
240
+ */
241
+ export function ApiKeyStatus(): React.ReactElement {
242
+ const anthropicKey = process.env.ANTHROPIC_API_KEY;
243
+ const openaiKey = process.env.OPENAI_API_KEY;
244
+
245
+ const maskKey = (key: string | undefined) => {
246
+ if (!key) return null;
247
+ return key.substring(0, 10) + "..." + key.substring(key.length - 4);
248
+ };
249
+
250
+ return (
251
+ <Box
252
+ flexDirection="column"
253
+ borderStyle="single"
254
+ borderColor={colors.muted}
255
+ paddingX={2}
256
+ paddingY={1}
257
+ marginY={1}
258
+ >
259
+ <Text bold color={colors.info}>
260
+ {emoji.key} API Keys
261
+ </Text>
262
+ <Box height={1} />
263
+ <Box>
264
+ {anthropicKey ? (
265
+ <>
266
+ <Text color={colors.success}>✓ </Text>
267
+ <Text>Anthropic: </Text>
268
+ <Text dimColor>{maskKey(anthropicKey)}</Text>
269
+ </>
270
+ ) : (
271
+ <>
272
+ <Text color={colors.warning}>✗ </Text>
273
+ <Text>Anthropic: </Text>
274
+ <Text dimColor>not set</Text>
275
+ </>
276
+ )}
277
+ </Box>
278
+ <Box>
279
+ {openaiKey ? (
280
+ <>
281
+ <Text color={colors.success}>✓ </Text>
282
+ <Text>OpenAI: </Text>
283
+ <Text dimColor>{maskKey(openaiKey)}</Text>
284
+ </>
285
+ ) : (
286
+ <>
287
+ <Text color={colors.warning}>✗ </Text>
288
+ <Text>OpenAI: </Text>
289
+ <Text dimColor>not set</Text>
290
+ </>
291
+ )}
292
+ </Box>
293
+ <Box height={1} />
294
+ <Text dimColor>Use /apikey set to add or update keys</Text>
295
+ </Box>
296
+ );
297
+ }
298
+
299
+
300
+