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,237 @@
1
+ /**
2
+ * File preview component with line numbers.
3
+ */
4
+ import React from "react";
5
+ import { Box, Text } from "ink";
6
+ import { emoji, colors } from "../theme.js";
7
+ import type { FileInfo } from "../../types.js";
8
+
9
+ interface FilePreviewProps {
10
+ /** File path */
11
+ path: string;
12
+ /** File content */
13
+ content: string;
14
+ /** Maximum lines to show */
15
+ maxLines?: number;
16
+ /** Whether this is a write preview (vs read) */
17
+ isWrite?: boolean;
18
+ }
19
+
20
+ export function FilePreview({
21
+ path,
22
+ content,
23
+ maxLines = 20,
24
+ isWrite = false,
25
+ }: FilePreviewProps): React.ReactElement {
26
+ const lines = content.split("\n");
27
+ const totalLines = lines.length;
28
+ const displayLines = lines.slice(0, maxLines);
29
+ const truncated = totalLines > maxLines;
30
+
31
+ return (
32
+ <Box
33
+ flexDirection="column"
34
+ borderStyle="single"
35
+ borderColor={colors.muted}
36
+ marginY={1}
37
+ >
38
+ {/* Header */}
39
+ <Box paddingX={2} paddingY={1} borderBottom>
40
+ <Text color={colors.info}>
41
+ {emoji.file} {isWrite ? "Writing:" : "Reading:"}{" "}
42
+ </Text>
43
+ <Text color={colors.file}>{path}</Text>
44
+ <Text dimColor> ({totalLines} lines)</Text>
45
+ </Box>
46
+
47
+ {/* Content with line numbers */}
48
+ <Box flexDirection="column" paddingX={2} paddingY={1}>
49
+ {displayLines.map((line, index) => (
50
+ <Box key={index}>
51
+ <Text dimColor>{String(index + 1).padStart(4, " ")} </Text>
52
+ <Text>{truncateLine(line, 70)}</Text>
53
+ </Box>
54
+ ))}
55
+ {truncated && (
56
+ <Box marginTop={1}>
57
+ <Text dimColor>
58
+ ... {totalLines - maxLines} more lines ...
59
+ </Text>
60
+ </Box>
61
+ )}
62
+ </Box>
63
+ </Box>
64
+ );
65
+ }
66
+
67
+ /**
68
+ * Truncate a line if too long.
69
+ */
70
+ function truncateLine(line: string, maxLength: number): string {
71
+ if (line.length <= maxLength) {
72
+ return line;
73
+ }
74
+ return line.substring(0, maxLength - 3) + "...";
75
+ }
76
+
77
+ /**
78
+ * Compact file written notification.
79
+ */
80
+ interface FileWrittenProps {
81
+ path: string;
82
+ }
83
+
84
+ export function FileWritten({ path }: FileWrittenProps): React.ReactElement {
85
+ return (
86
+ <Box>
87
+ <Text color={colors.success}>✓ Wrote: </Text>
88
+ <Text color={colors.file}>{path}</Text>
89
+ </Box>
90
+ );
91
+ }
92
+
93
+ /**
94
+ * Compact file edited notification.
95
+ */
96
+ interface FileEditedProps {
97
+ path: string;
98
+ occurrences: number;
99
+ }
100
+
101
+ export function FileEdited({
102
+ path,
103
+ occurrences,
104
+ }: FileEditedProps): React.ReactElement {
105
+ return (
106
+ <Box>
107
+ <Text color={colors.success}>{emoji.edit} Edited: </Text>
108
+ <Text color={colors.file}>{path}</Text>
109
+ <Text dimColor>
110
+ {" "}
111
+ ({occurrences} change{occurrences === 1 ? "" : "s"})
112
+ </Text>
113
+ </Box>
114
+ );
115
+ }
116
+
117
+ /**
118
+ * Compact file read notification.
119
+ */
120
+ interface FileReadProps {
121
+ path: string;
122
+ lines: number;
123
+ }
124
+
125
+ export function FileRead({ path, lines }: FileReadProps): React.ReactElement {
126
+ return (
127
+ <Box>
128
+ <Text color={colors.info}>📖 Read: </Text>
129
+ <Text color={colors.file}>{path}</Text>
130
+ <Text dimColor> ({lines} lines)</Text>
131
+ </Box>
132
+ );
133
+ }
134
+
135
+ /**
136
+ * Compact ls result notification.
137
+ */
138
+ interface LsResultProps {
139
+ path: string;
140
+ count: number;
141
+ }
142
+
143
+ export function LsResult({ path, count }: LsResultProps): React.ReactElement {
144
+ return (
145
+ <Box>
146
+ <Text color={colors.info}>📂 Listed: </Text>
147
+ <Text color={colors.file}>{path}</Text>
148
+ <Text dimColor> ({count} item{count === 1 ? "" : "s"})</Text>
149
+ </Box>
150
+ );
151
+ }
152
+
153
+ /**
154
+ * Compact glob result notification.
155
+ */
156
+ interface GlobResultProps {
157
+ pattern: string;
158
+ count: number;
159
+ }
160
+
161
+ export function GlobResult({ pattern, count }: GlobResultProps): React.ReactElement {
162
+ return (
163
+ <Box>
164
+ <Text color={colors.info}>🔍 Glob: </Text>
165
+ <Text color={colors.tool}>{pattern}</Text>
166
+ <Text dimColor> ({count} match{count === 1 ? "" : "es"})</Text>
167
+ </Box>
168
+ );
169
+ }
170
+
171
+ /**
172
+ * Compact grep result notification.
173
+ */
174
+ interface GrepResultProps {
175
+ pattern: string;
176
+ count: number;
177
+ }
178
+
179
+ export function GrepResult({ pattern, count }: GrepResultProps): React.ReactElement {
180
+ return (
181
+ <Box>
182
+ <Text color={colors.info}>🔎 Grep: </Text>
183
+ <Text color={colors.tool}>{pattern}</Text>
184
+ <Text dimColor> ({count} match{count === 1 ? "" : "es"})</Text>
185
+ </Box>
186
+ );
187
+ }
188
+
189
+ /**
190
+ * File list panel for /files command.
191
+ */
192
+ interface FileListProps {
193
+ files: FileInfo[];
194
+ workDir?: string;
195
+ }
196
+
197
+ export function FileList({
198
+ files,
199
+ workDir,
200
+ }: FileListProps): React.ReactElement {
201
+ return (
202
+ <Box
203
+ flexDirection="column"
204
+ borderStyle="single"
205
+ borderColor={colors.muted}
206
+ paddingX={2}
207
+ paddingY={1}
208
+ marginY={1}
209
+ >
210
+ <Box marginBottom={1}>
211
+ <Text bold color={colors.info}>
212
+ {emoji.file} Files
213
+ </Text>
214
+ {workDir && <Text dimColor> in {workDir}</Text>}
215
+ </Box>
216
+
217
+ {files.length === 0 ? (
218
+ <Box paddingLeft={2}>
219
+ <Text dimColor>No files found.</Text>
220
+ </Box>
221
+ ) : (
222
+ <Box flexDirection="column">
223
+ {files.map((file) => (
224
+ <Box key={file.path} paddingLeft={2}>
225
+ <Text>{file.is_dir ? "📁" : "📄"} </Text>
226
+ <Text color={colors.file}>{file.path}</Text>
227
+ {file.size !== undefined && (
228
+ <Text dimColor> ({file.size} bytes)</Text>
229
+ )}
230
+ </Box>
231
+ ))}
232
+ </Box>
233
+ )}
234
+ </Box>
235
+ );
236
+ }
237
+
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Text input component with slash command suggestions.
3
+ * Clean, minimal design inspired by Claude Code and OpenAI Codex.
4
+ */
5
+ import React, { useState, useRef } from "react";
6
+ import { Box, Text, useInput } from "ink";
7
+ import { colors } from "../theme.js";
8
+ import { SlashMenu } from "./SlashMenu.js";
9
+
10
+ interface InputProps {
11
+ /** Called when user submits input */
12
+ onSubmit: (value: string) => void;
13
+ /** Whether input is disabled (e.g., during generation) */
14
+ disabled?: boolean;
15
+ /** Placeholder text */
16
+ placeholder?: string;
17
+ }
18
+
19
+ // Module-level history storage to persist across re-renders
20
+ const inputHistory: string[] = [];
21
+ const MAX_HISTORY = 100;
22
+
23
+ export function Input({
24
+ onSubmit,
25
+ disabled = false,
26
+ placeholder = "Plan, search, build anything",
27
+ }: InputProps): React.ReactElement {
28
+ const [value, setValue] = useState("");
29
+ const [cursorPos, setCursorPos] = useState(0);
30
+ const showMenu = value.startsWith("/") && !disabled;
31
+
32
+ // History navigation state
33
+ // -1 means we're at the current input (not browsing history)
34
+ const [historyIndex, setHistoryIndex] = useState(-1);
35
+ // Store the current input when user starts navigating history
36
+ const savedInputRef = useRef("");
37
+
38
+ // Helper function to delete the previous word from cursor position
39
+ const deleteWord = () => {
40
+ if (cursorPos === 0) return;
41
+
42
+ let end = cursorPos;
43
+ // Skip trailing spaces
44
+ while (end > 0 && value[end - 1] === " ") {
45
+ end--;
46
+ }
47
+ // Find start of current word
48
+ while (end > 0 && value[end - 1] !== " ") {
49
+ end--;
50
+ }
51
+
52
+ const newValue = value.slice(0, end) + value.slice(cursorPos);
53
+ setValue(newValue);
54
+ setCursorPos(end);
55
+ };
56
+
57
+ useInput(
58
+ (input, key) => {
59
+ if (disabled) return;
60
+
61
+ // Handle Enter - submit
62
+ if (key.return) {
63
+ if (value.trim()) {
64
+ // Add to history (avoid duplicates of the last entry)
65
+ if (inputHistory.length === 0 || inputHistory[0] !== value) {
66
+ inputHistory.unshift(value);
67
+ if (inputHistory.length > MAX_HISTORY) {
68
+ inputHistory.pop();
69
+ }
70
+ }
71
+ onSubmit(value);
72
+ setValue("");
73
+ setCursorPos(0);
74
+ setHistoryIndex(-1);
75
+ savedInputRef.current = "";
76
+ }
77
+ return;
78
+ }
79
+
80
+ // Handle up arrow - navigate to older history
81
+ if (key.upArrow) {
82
+ if (inputHistory.length === 0) return;
83
+
84
+ if (historyIndex === -1) {
85
+ // Save current input before navigating history
86
+ savedInputRef.current = value;
87
+ }
88
+
89
+ const newIndex = Math.min(historyIndex + 1, inputHistory.length - 1);
90
+ if (newIndex !== historyIndex) {
91
+ const historyValue = inputHistory[newIndex];
92
+ if (historyValue !== undefined) {
93
+ setHistoryIndex(newIndex);
94
+ setValue(historyValue);
95
+ setCursorPos(historyValue.length);
96
+ }
97
+ }
98
+ return;
99
+ }
100
+
101
+ // Handle down arrow - navigate to newer history
102
+ if (key.downArrow) {
103
+ if (historyIndex === -1) return;
104
+
105
+ const newIndex = historyIndex - 1;
106
+ if (newIndex === -1) {
107
+ // Return to saved current input
108
+ setHistoryIndex(-1);
109
+ setValue(savedInputRef.current);
110
+ setCursorPos(savedInputRef.current.length);
111
+ } else {
112
+ const historyValue = inputHistory[newIndex];
113
+ if (historyValue !== undefined) {
114
+ setHistoryIndex(newIndex);
115
+ setValue(historyValue);
116
+ setCursorPos(historyValue.length);
117
+ }
118
+ }
119
+ return;
120
+ }
121
+
122
+ // Handle left arrow - move cursor left
123
+ if (key.leftArrow) {
124
+ if (key.meta || key.ctrl) {
125
+ // Option/Ctrl+Left: jump to start of previous word
126
+ let pos = cursorPos;
127
+ // Skip spaces
128
+ while (pos > 0 && value[pos - 1] === " ") {
129
+ pos--;
130
+ }
131
+ // Skip word characters
132
+ while (pos > 0 && value[pos - 1] !== " ") {
133
+ pos--;
134
+ }
135
+ setCursorPos(pos);
136
+ } else {
137
+ setCursorPos((prev) => Math.max(0, prev - 1));
138
+ }
139
+ return;
140
+ }
141
+
142
+ // Handle right arrow - move cursor right
143
+ if (key.rightArrow) {
144
+ if (key.meta || key.ctrl) {
145
+ // Option/Ctrl+Right: jump to end of next word
146
+ let pos = cursorPos;
147
+ // Skip current word characters
148
+ while (pos < value.length && value[pos] !== " ") {
149
+ pos++;
150
+ }
151
+ // Skip spaces
152
+ while (pos < value.length && value[pos] === " ") {
153
+ pos++;
154
+ }
155
+ setCursorPos(pos);
156
+ } else {
157
+ setCursorPos((prev) => Math.min(value.length, prev + 1));
158
+ }
159
+ return;
160
+ }
161
+
162
+ // Handle Ctrl+A - move to start of line
163
+ if (key.ctrl && input === "a") {
164
+ setCursorPos(0);
165
+ return;
166
+ }
167
+
168
+ // Handle Ctrl+E - move to end of line
169
+ if (key.ctrl && input === "e") {
170
+ setCursorPos(value.length);
171
+ return;
172
+ }
173
+
174
+ // Handle Option+Backspace (Alt+Backspace) - delete previous word
175
+ if ((key.backspace || key.delete) && key.meta) {
176
+ deleteWord();
177
+ return;
178
+ }
179
+
180
+ // Handle Ctrl+W - delete previous word (Unix-style)
181
+ if (key.ctrl && input === "w") {
182
+ deleteWord();
183
+ return;
184
+ }
185
+
186
+ // Handle Ctrl+U - delete from start to cursor
187
+ if (key.ctrl && input === "u") {
188
+ setValue(value.slice(cursorPos));
189
+ setCursorPos(0);
190
+ return;
191
+ }
192
+
193
+ // Handle Ctrl+K - delete from cursor to end
194
+ if (key.ctrl && input === "k") {
195
+ setValue(value.slice(0, cursorPos));
196
+ return;
197
+ }
198
+
199
+ // Handle Backspace/Delete - single character
200
+ if (key.backspace || key.delete) {
201
+ if (cursorPos > 0) {
202
+ setValue((prev) => prev.slice(0, cursorPos - 1) + prev.slice(cursorPos));
203
+ setCursorPos((prev) => prev - 1);
204
+ }
205
+ return;
206
+ }
207
+
208
+ // Tab for autocomplete - complete first matching command
209
+ if (key.tab && value.startsWith("/")) {
210
+ return;
211
+ }
212
+
213
+ // Ignore other control keys
214
+ if (key.ctrl || key.meta || key.escape || key.tab) {
215
+ return;
216
+ }
217
+
218
+ // Handle pasted text or typed characters
219
+ // Filter to only printable characters
220
+ if (input) {
221
+ const printable = input
222
+ .split("")
223
+ .filter((char) => char >= " " || char === "\t")
224
+ .join("");
225
+
226
+ if (printable) {
227
+ // Reset history navigation when user types
228
+ if (historyIndex !== -1) {
229
+ setHistoryIndex(-1);
230
+ savedInputRef.current = "";
231
+ }
232
+ setValue((prev) => prev.slice(0, cursorPos) + printable + prev.slice(cursorPos));
233
+ setCursorPos((prev) => prev + printable.length);
234
+ }
235
+ }
236
+ },
237
+ { isActive: !disabled }
238
+ );
239
+
240
+ // Render text with cursor at the correct position
241
+ const renderTextWithCursor = () => {
242
+ if (!value) {
243
+ return (
244
+ <Text>
245
+ <Text color={colors.primary}>▌</Text>
246
+ <Text dimColor>{placeholder}</Text>
247
+ </Text>
248
+ );
249
+ }
250
+
251
+ const beforeCursor = value.slice(0, cursorPos);
252
+ const afterCursor = value.slice(cursorPos);
253
+
254
+ return (
255
+ <Text>
256
+ {beforeCursor}
257
+ <Text color={colors.primary}>▌</Text>
258
+ {afterCursor}
259
+ </Text>
260
+ );
261
+ };
262
+
263
+ return (
264
+ <Box flexDirection="column">
265
+ <Box>
266
+ <Text color={colors.muted}>{"→ "}</Text>
267
+ {disabled ? (
268
+ <Text dimColor>...</Text>
269
+ ) : (
270
+ renderTextWithCursor()
271
+ )}
272
+ </Box>
273
+ {showMenu && <SlashMenu filter={value} />}
274
+ </Box>
275
+ );
276
+ }
277
+
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Message display component for user and assistant messages.
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
+ import { ToolCallSummary } from "./ToolCallSummary.js";
9
+
10
+ export type MessageRole = "user" | "assistant";
11
+
12
+ /**
13
+ * Data for a single tool call.
14
+ */
15
+ export interface ToolCallData {
16
+ /** Tool name that was called */
17
+ toolName: string;
18
+ /** Arguments passed to the tool */
19
+ args?: unknown;
20
+ /** Result returned by the tool */
21
+ result?: unknown;
22
+ /** Whether the tool call succeeded or failed */
23
+ status: "success" | "error";
24
+ }
25
+
26
+ export interface MessageData {
27
+ id: string;
28
+ role: MessageRole;
29
+ content: string;
30
+ timestamp?: Date;
31
+ /** Tool calls made during this message (for assistant messages) */
32
+ toolCalls?: ToolCallData[];
33
+ }
34
+
35
+ interface MessageProps {
36
+ message: MessageData;
37
+ }
38
+
39
+ export function Message({ message }: MessageProps): React.ReactElement {
40
+ const isUser = message.role === "user";
41
+ const hasToolCalls = message.toolCalls && message.toolCalls.length > 0;
42
+
43
+ if (isUser) {
44
+ // User message: "> message" style (like Claude Code)
45
+ return (
46
+ <Box marginBottom={1}>
47
+ <Text color={colors.muted} bold>{"> "}</Text>
48
+ <Text bold>{message.content}</Text>
49
+ </Box>
50
+ );
51
+ }
52
+
53
+ // Assistant message: "● message" style (like Claude Code)
54
+ return (
55
+ <Box flexDirection="column" marginBottom={1}>
56
+ <Box>
57
+ <Text color={colors.success}>{"● "}</Text>
58
+ <Text>{message.content}</Text>
59
+ </Box>
60
+ {/* Show tool calls summary for assistant messages */}
61
+ {hasToolCalls && (
62
+ <Box marginLeft={2}>
63
+ <ToolCallSummary toolCalls={message.toolCalls!} />
64
+ </Box>
65
+ )}
66
+ </Box>
67
+ );
68
+ }
69
+
70
+ interface StreamingMessageProps {
71
+ /** Current streamed text content */
72
+ content: string;
73
+ /** Whether the message is still streaming */
74
+ isStreaming?: boolean;
75
+ }
76
+
77
+ export function StreamingMessage({
78
+ content,
79
+ isStreaming = true,
80
+ }: StreamingMessageProps): React.ReactElement {
81
+ return (
82
+ <Box flexDirection="column" marginBottom={1}>
83
+ <Box>
84
+ <Text color={colors.success}>{"● "}</Text>
85
+ <Text>
86
+ {content}
87
+ {isStreaming && <Text color={colors.muted}>▌</Text>}
88
+ </Text>
89
+ </Box>
90
+ </Box>
91
+ );
92
+ }
93
+