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,338 @@
1
+ /**
2
+ * Model Selection Panel - Interactive model selection with arrow keys.
3
+ */
4
+
5
+ import React, { useState, useEffect, useMemo } from "react";
6
+ import { Box, Text, useInput } from "ink";
7
+ import { Spinner } from "@inkjs/ui";
8
+ import { colors, emoji } from "../theme.js";
9
+ import {
10
+ getModelsByProvider,
11
+ detectAvailableProviders,
12
+ type AvailableModel,
13
+ } from "../utils/model-list.js";
14
+
15
+ interface ModelSelectionPanelProps {
16
+ currentModel?: string;
17
+ /** Callback when a model is selected */
18
+ onModelSelect?: (modelId: string) => void;
19
+ /** Callback to close the panel */
20
+ onClose?: () => void;
21
+ }
22
+
23
+ interface LoadingState {
24
+ loading: boolean;
25
+ anthropicModels: AvailableModel[];
26
+ openaiModels: AvailableModel[];
27
+ errors: { provider: string; error: string }[];
28
+ }
29
+
30
+ export function ModelSelectionPanel({
31
+ currentModel,
32
+ onModelSelect,
33
+ onClose,
34
+ }: ModelSelectionPanelProps): React.ReactElement {
35
+ const providers = detectAvailableProviders();
36
+ const hasAnyKey = providers.anthropic || providers.openai;
37
+
38
+ const [state, setState] = useState<LoadingState>({
39
+ loading: true,
40
+ anthropicModels: [],
41
+ openaiModels: [],
42
+ errors: [],
43
+ });
44
+
45
+ const [selectedIndex, setSelectedIndex] = useState(0);
46
+
47
+ // Combine all models into a flat list for navigation
48
+ const allModels = useMemo(() => {
49
+ const models: AvailableModel[] = [];
50
+ if (state.anthropicModels.length > 0) {
51
+ models.push(...state.anthropicModels);
52
+ }
53
+ if (state.openaiModels.length > 0) {
54
+ models.push(...state.openaiModels);
55
+ }
56
+ return models;
57
+ }, [state.anthropicModels, state.openaiModels]);
58
+
59
+ // Find current model index to set initial selection
60
+ useEffect(() => {
61
+ if (allModels.length > 0 && currentModel) {
62
+ const currentIndex = allModels.findIndex((m) => isCurrentModel(currentModel, m));
63
+ if (currentIndex >= 0) {
64
+ setSelectedIndex(currentIndex);
65
+ }
66
+ }
67
+ }, [allModels, currentModel]);
68
+
69
+ // Handle keyboard input
70
+ useInput((input, key) => {
71
+ if (state.loading) return;
72
+
73
+ if (key.upArrow) {
74
+ setSelectedIndex((prev) => (prev > 0 ? prev - 1 : allModels.length - 1));
75
+ } else if (key.downArrow) {
76
+ setSelectedIndex((prev) => (prev < allModels.length - 1 ? prev + 1 : 0));
77
+ } else if (key.return) {
78
+ const selectedModel = allModels[selectedIndex];
79
+ if (selectedModel) {
80
+ onModelSelect?.(selectedModel.id);
81
+ onClose?.();
82
+ }
83
+ } else if (key.escape) {
84
+ onClose?.();
85
+ }
86
+ });
87
+
88
+ // Fetch models on mount
89
+ useEffect(() => {
90
+ if (!hasAnyKey) {
91
+ setState({ loading: false, anthropicModels: [], openaiModels: [], errors: [] });
92
+ return;
93
+ }
94
+
95
+ let cancelled = false;
96
+
97
+ async function loadModels() {
98
+ try {
99
+ const result = await getModelsByProvider();
100
+ if (!cancelled) {
101
+ setState({
102
+ loading: false,
103
+ anthropicModels: result.anthropic || [],
104
+ openaiModels: result.openai || [],
105
+ errors: result.errors,
106
+ });
107
+ }
108
+ } catch (error) {
109
+ if (!cancelled) {
110
+ setState({
111
+ loading: false,
112
+ anthropicModels: [],
113
+ openaiModels: [],
114
+ errors: [{ provider: "Unknown", error: String(error) }],
115
+ });
116
+ }
117
+ }
118
+ }
119
+
120
+ loadModels();
121
+
122
+ return () => {
123
+ cancelled = true;
124
+ };
125
+ }, [hasAnyKey]);
126
+
127
+ // No API keys configured
128
+ if (!hasAnyKey) {
129
+ return (
130
+ <Box
131
+ flexDirection="column"
132
+ borderStyle="single"
133
+ borderColor={colors.warning}
134
+ paddingX={2}
135
+ paddingY={1}
136
+ marginY={1}
137
+ >
138
+ <Text bold color={colors.warning}>
139
+ ⚠️ No API Keys Found
140
+ </Text>
141
+ <Box height={1} />
142
+ <Text>Add an API key first to see available models.</Text>
143
+ <Box height={1} />
144
+ <Text color={colors.primary}>Run /apikey to add your API key</Text>
145
+ <Box height={1} />
146
+ <Text dimColor>Supported providers:</Text>
147
+ <Text dimColor> • Anthropic (Claude)</Text>
148
+ <Text dimColor> • OpenAI (GPT)</Text>
149
+ </Box>
150
+ );
151
+ }
152
+
153
+ // Loading state
154
+ if (state.loading) {
155
+ return (
156
+ <Box
157
+ flexDirection="column"
158
+ borderStyle="single"
159
+ borderColor={colors.muted}
160
+ paddingX={2}
161
+ paddingY={1}
162
+ marginY={1}
163
+ >
164
+ <Text bold color={colors.info}>
165
+ {emoji.model} Select Model
166
+ </Text>
167
+ <Box height={1} />
168
+ <Box>
169
+ <Spinner label="Fetching models from API..." />
170
+ </Box>
171
+ </Box>
172
+ );
173
+ }
174
+
175
+ // Check if we have any models
176
+ const hasModels = allModels.length > 0;
177
+
178
+ // No models found (API errors)
179
+ if (!hasModels && state.errors.length > 0) {
180
+ return (
181
+ <Box
182
+ flexDirection="column"
183
+ borderStyle="single"
184
+ borderColor={colors.error}
185
+ paddingX={2}
186
+ paddingY={1}
187
+ marginY={1}
188
+ >
189
+ <Text bold color={colors.error}>
190
+ {emoji.error} Failed to Fetch Models
191
+ </Text>
192
+ <Box height={1} />
193
+ {state.errors.map((err, i) => (
194
+ <Text key={i} color={colors.error}>
195
+ {err.provider}: {err.error}
196
+ </Text>
197
+ ))}
198
+ <Box height={1} />
199
+ <Text dimColor>Check your API key and try again with /apikey</Text>
200
+ </Box>
201
+ );
202
+ }
203
+
204
+ // Calculate the offset for each provider section
205
+ let anthropicOffset = 0;
206
+ let openaiOffset = state.anthropicModels.length;
207
+
208
+ return (
209
+ <Box
210
+ flexDirection="column"
211
+ borderStyle="single"
212
+ borderColor={colors.primary}
213
+ paddingX={2}
214
+ paddingY={1}
215
+ marginY={1}
216
+ >
217
+ <Text bold color={colors.info}>
218
+ {emoji.model} Select Model
219
+ </Text>
220
+ <Box height={1} />
221
+
222
+ {/* Show any errors */}
223
+ {state.errors.length > 0 && (
224
+ <>
225
+ {state.errors.map((err, i) => (
226
+ <Text key={i} color={colors.warning}>
227
+ {emoji.warning} {err.provider}: {err.error}
228
+ </Text>
229
+ ))}
230
+ <Box height={1} />
231
+ </>
232
+ )}
233
+
234
+ {/* Anthropic Models */}
235
+ {state.anthropicModels.length > 0 && (
236
+ <>
237
+ <Text bold color={colors.primary}>
238
+ Anthropic Claude
239
+ </Text>
240
+ {state.anthropicModels.map((model, index) => {
241
+ const globalIndex = anthropicOffset + index;
242
+ const isSelected = globalIndex === selectedIndex;
243
+ const isCurrent = isCurrentModel(currentModel, model);
244
+ return (
245
+ <ModelItem
246
+ key={model.id}
247
+ model={model}
248
+ isSelected={isSelected}
249
+ isCurrent={isCurrent}
250
+ />
251
+ );
252
+ })}
253
+ <Box height={1} />
254
+ </>
255
+ )}
256
+
257
+ {/* OpenAI Models */}
258
+ {state.openaiModels.length > 0 && (
259
+ <>
260
+ <Text bold color={colors.primary}>
261
+ OpenAI GPT
262
+ </Text>
263
+ {state.openaiModels.map((model, index) => {
264
+ const globalIndex = openaiOffset + index;
265
+ const isSelected = globalIndex === selectedIndex;
266
+ const isCurrent = isCurrentModel(currentModel, model);
267
+ return (
268
+ <ModelItem
269
+ key={model.id}
270
+ model={model}
271
+ isSelected={isSelected}
272
+ isCurrent={isCurrent}
273
+ />
274
+ );
275
+ })}
276
+ <Box height={1} />
277
+ </>
278
+ )}
279
+
280
+ {/* Navigation hint */}
281
+ <Text dimColor>↑/↓ Navigate • Enter Select • Esc Cancel</Text>
282
+ </Box>
283
+ );
284
+ }
285
+
286
+ /**
287
+ * Check if a model matches the current model.
288
+ */
289
+ function isCurrentModel(currentModel: string | undefined, model: AvailableModel): boolean {
290
+ if (!currentModel) return false;
291
+ return (
292
+ currentModel === model.id ||
293
+ currentModel === model.name ||
294
+ currentModel === `${model.provider}/${model.name}` ||
295
+ (currentModel.startsWith(`${model.provider}/`) && currentModel === model.id)
296
+ );
297
+ }
298
+
299
+ interface ModelItemProps {
300
+ model: AvailableModel;
301
+ isSelected: boolean;
302
+ isCurrent: boolean;
303
+ }
304
+
305
+ function ModelItem({ model, isSelected, isCurrent }: ModelItemProps): React.ReactElement {
306
+ // Determine the indicator
307
+ let indicator = " ";
308
+ let textColor: string | undefined = undefined;
309
+ let isBold = false;
310
+
311
+ if (isSelected) {
312
+ indicator = "▸ ";
313
+ textColor = colors.primary;
314
+ isBold = true;
315
+ }
316
+
317
+ if (isCurrent) {
318
+ indicator = isSelected ? "▸✓" : " ✓";
319
+ textColor = isSelected ? colors.primary : colors.success;
320
+ }
321
+
322
+ return (
323
+ <Box marginLeft={1}>
324
+ <Text color={isSelected ? colors.primary : isCurrent ? colors.success : undefined}>
325
+ {indicator}
326
+ </Text>
327
+ <Text color={textColor} bold={isBold}>
328
+ {model.id}
329
+ </Text>
330
+ {model.description && (
331
+ <>
332
+ <Text dimColor> - </Text>
333
+ <Text dimColor>{model.description}</Text>
334
+ </>
335
+ )}
336
+ </Box>
337
+ );
338
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Slash command autocomplete menu component.
3
+ */
4
+ import React from "react";
5
+ import { Box, Text } from "ink";
6
+ import { colors, filterCommands, type SlashCommand } from "../theme.js";
7
+
8
+ interface SlashMenuProps {
9
+ /** Current input value to filter commands */
10
+ filter: string;
11
+ /** Maximum number of items to show */
12
+ maxItems?: number;
13
+ }
14
+
15
+ export function SlashMenu({
16
+ filter,
17
+ maxItems = 8,
18
+ }: SlashMenuProps): React.ReactElement | null {
19
+ // Only show menu when input starts with /
20
+ if (!filter.startsWith("/")) {
21
+ return null;
22
+ }
23
+
24
+ const filtered = filterCommands(filter);
25
+
26
+ if (filtered.length === 0) {
27
+ return (
28
+ <Box paddingLeft={2} marginTop={1}>
29
+ <Text dimColor>No matching commands</Text>
30
+ </Box>
31
+ );
32
+ }
33
+
34
+ const displayItems = filtered.slice(0, maxItems);
35
+ const hasMore = filtered.length > maxItems;
36
+
37
+ return (
38
+ <Box flexDirection="column" marginTop={1} paddingLeft={2}>
39
+ {displayItems.map((cmd) => (
40
+ <SlashMenuItem key={cmd.command} command={cmd} />
41
+ ))}
42
+ {hasMore && (
43
+ <Text dimColor>... and {filtered.length - maxItems} more</Text>
44
+ )}
45
+ </Box>
46
+ );
47
+ }
48
+
49
+ interface SlashMenuItemProps {
50
+ command: SlashCommand;
51
+ }
52
+
53
+ function SlashMenuItem({ command }: SlashMenuItemProps): React.ReactElement {
54
+ const aliases =
55
+ command.aliases.length > 0 ? ` (${command.aliases.join(", ")})` : "";
56
+
57
+ return (
58
+ <Box>
59
+ <Text color={colors.info}>{command.command}</Text>
60
+ <Text dimColor>{aliases}</Text>
61
+ <Text dimColor> - {command.description}</Text>
62
+ </Box>
63
+ );
64
+ }
65
+
66
+ /**
67
+ * Full slash menu panel for /help command.
68
+ */
69
+ export function SlashMenuPanel(): React.ReactElement {
70
+ const commands = filterCommands();
71
+
72
+ return (
73
+ <Box
74
+ flexDirection="column"
75
+ borderStyle="single"
76
+ borderColor={colors.muted}
77
+ paddingX={2}
78
+ paddingY={1}
79
+ marginY={1}
80
+ >
81
+ <Text bold color={colors.info}>
82
+ Available Commands
83
+ </Text>
84
+ <Box height={1} />
85
+ {commands.map((cmd) => (
86
+ <Box key={cmd.command} flexDirection="column" marginBottom={1}>
87
+ <Box>
88
+ <Text color={colors.info}>{cmd.command}</Text>
89
+ {cmd.aliases.length > 0 && (
90
+ <Text dimColor> ({cmd.aliases.join(", ")})</Text>
91
+ )}
92
+ </Box>
93
+ <Box paddingLeft={2}>
94
+ <Text dimColor>{cmd.description}</Text>
95
+ </Box>
96
+ </Box>
97
+ ))}
98
+ </Box>
99
+ );
100
+ }
101
+
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Compact status bar component.
3
+ * Clean, minimal design inspired by Claude Code and OpenAI Codex.
4
+ */
5
+ import React from "react";
6
+ import { Box, Text } from "ink";
7
+ import { colors } from "../theme.js";
8
+
9
+ interface FeatureFlags {
10
+ promptCaching: boolean;
11
+ eviction: boolean;
12
+ summarization: boolean;
13
+ }
14
+
15
+ interface StatusBarProps {
16
+ /** Current working directory */
17
+ workDir: string;
18
+ /** Current model name */
19
+ model: string;
20
+ /** Optional status indicator (idle, generating, etc.) */
21
+ status?: "idle" | "thinking" | "streaming" | "tool-call" | "subagent" | "done" | "error";
22
+ /** Feature flags to display */
23
+ features?: FeatureFlags;
24
+ /** Whether auto-approve mode is enabled */
25
+ autoApproveEnabled?: boolean;
26
+ /** Current session ID if checkpointing is enabled */
27
+ sessionId?: string;
28
+ }
29
+
30
+ export function StatusBar({
31
+ workDir,
32
+ model,
33
+ status = "idle",
34
+ features,
35
+ autoApproveEnabled = false,
36
+ sessionId,
37
+ }: StatusBarProps): React.ReactElement {
38
+ // Get short model name
39
+ const shortModel = model.split("/").pop() || model;
40
+
41
+ // Status indicator - minimal
42
+ const getStatusDisplay = () => {
43
+ switch (status) {
44
+ case "thinking":
45
+ return <Text color={colors.warning}>●</Text>;
46
+ case "streaming":
47
+ return <Text color={colors.success}>●</Text>;
48
+ case "tool-call":
49
+ return <Text color={colors.tool}>●</Text>;
50
+ case "subagent":
51
+ return <Text color={colors.secondary}>●</Text>;
52
+ case "error":
53
+ return <Text color={colors.error}>●</Text>;
54
+ case "done":
55
+ return <Text color={colors.success}>●</Text>;
56
+ default:
57
+ return <Text dimColor>○</Text>;
58
+ }
59
+ };
60
+
61
+ // Feature badges - compact
62
+ const featureBadges: string[] = [];
63
+ if (features?.promptCaching) featureBadges.push("⚡");
64
+ if (features?.eviction) featureBadges.push("📦");
65
+ if (features?.summarization) featureBadges.push("📝");
66
+
67
+ return (
68
+ <Box marginTop={1}>
69
+ <Text dimColor>
70
+ {getStatusDisplay()} {shortModel}
71
+ {featureBadges.length > 0 && ` ${featureBadges.join(" ")}`}
72
+ {" · "}
73
+ {autoApproveEnabled ? (
74
+ <Text color={colors.success}>🟢 Auto-approve</Text>
75
+ ) : (
76
+ <Text color={colors.warning}>🔴 Safe mode</Text>
77
+ )}
78
+ {sessionId && (
79
+ <>
80
+ {" · "}
81
+ <Text dimColor>Session: {sessionId}</Text>
82
+ </>
83
+ )}
84
+ {" · "}? for shortcuts
85
+ </Text>
86
+ </Box>
87
+ );
88
+ }
89
+
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Subagent status display components.
3
+ */
4
+ import React from "react";
5
+ import { Box, Text } from "ink";
6
+ import { Spinner, StatusMessage } from "@inkjs/ui";
7
+ import { emoji, colors } from "../theme.js";
8
+
9
+ interface SubagentStartProps {
10
+ /** Subagent name */
11
+ name: string;
12
+ /** Task description */
13
+ task: string;
14
+ /** Maximum task length to display */
15
+ maxTaskLength?: number;
16
+ }
17
+
18
+ export function SubagentStart({
19
+ name,
20
+ task,
21
+ maxTaskLength = 60,
22
+ }: SubagentStartProps): React.ReactElement {
23
+ const shortTask =
24
+ task.length > maxTaskLength
25
+ ? task.substring(0, maxTaskLength) + "..."
26
+ : task;
27
+
28
+ return (
29
+ <Box flexDirection="column" marginY={1}>
30
+ <Box>
31
+ <Spinner
32
+ label={`${emoji.subagent} Starting subagent: ${name}`}
33
+ />
34
+ </Box>
35
+ <Box paddingLeft={4}>
36
+ <Text dimColor>└─ {shortTask}</Text>
37
+ </Box>
38
+ </Box>
39
+ );
40
+ }
41
+
42
+ interface SubagentFinishProps {
43
+ /** Subagent name */
44
+ name: string;
45
+ }
46
+
47
+ export function SubagentFinish({
48
+ name,
49
+ }: SubagentFinishProps): React.ReactElement {
50
+ return (
51
+ <Box marginY={1}>
52
+ <StatusMessage variant="success">
53
+ {emoji.subagent} Subagent {name} completed
54
+ </StatusMessage>
55
+ </Box>
56
+ );
57
+ }
58
+
59
+ /**
60
+ * Subagent running indicator (for when a subagent is actively working).
61
+ */
62
+ interface SubagentRunningProps {
63
+ name: string;
64
+ task: string;
65
+ }
66
+
67
+ export function SubagentRunning({
68
+ name,
69
+ task,
70
+ }: SubagentRunningProps): React.ReactElement {
71
+ const shortTask = task.length > 50 ? task.substring(0, 50) + "..." : task;
72
+
73
+ return (
74
+ <Box
75
+ flexDirection="column"
76
+ borderStyle="single"
77
+ borderColor={colors.secondary}
78
+ paddingX={2}
79
+ paddingY={1}
80
+ marginY={1}
81
+ >
82
+ <Box>
83
+ <Spinner label={`${emoji.subagent} ${name}`} />
84
+ </Box>
85
+ <Box paddingLeft={2}>
86
+ <Text dimColor>{shortTask}</Text>
87
+ </Box>
88
+ </Box>
89
+ );
90
+ }
91
+