deepagents 0.2.5__py3-none-any.whl → 0.2.6__py3-none-any.whl
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.
- deepagents/backends/composite.py +37 -2
- deepagents/backends/protocol.py +48 -0
- deepagents/backends/sandbox.py +341 -0
- deepagents/backends/store.py +3 -11
- deepagents/graph.py +7 -3
- deepagents/middleware/filesystem.py +224 -21
- deepagents/middleware/subagents.py +7 -4
- {deepagents-0.2.5.dist-info → deepagents-0.2.6.dist-info}/METADATA +5 -4
- deepagents-0.2.6.dist-info/RECORD +19 -0
- deepagents-0.2.6.dist-info/top_level.txt +1 -0
- deepagents-0.2.5.dist-info/RECORD +0 -38
- deepagents-0.2.5.dist-info/licenses/LICENSE +0 -21
- deepagents-0.2.5.dist-info/top_level.txt +0 -2
- deepagents-cli/README.md +0 -3
- deepagents-cli/deepagents_cli/README.md +0 -196
- deepagents-cli/deepagents_cli/__init__.py +0 -5
- deepagents-cli/deepagents_cli/__main__.py +0 -6
- deepagents-cli/deepagents_cli/agent.py +0 -278
- deepagents-cli/deepagents_cli/agent_memory.py +0 -226
- deepagents-cli/deepagents_cli/commands.py +0 -89
- deepagents-cli/deepagents_cli/config.py +0 -118
- deepagents-cli/deepagents_cli/default_agent_prompt.md +0 -110
- deepagents-cli/deepagents_cli/execution.py +0 -636
- deepagents-cli/deepagents_cli/file_ops.py +0 -347
- deepagents-cli/deepagents_cli/input.py +0 -270
- deepagents-cli/deepagents_cli/main.py +0 -226
- deepagents-cli/deepagents_cli/py.typed +0 -0
- deepagents-cli/deepagents_cli/token_utils.py +0 -63
- deepagents-cli/deepagents_cli/tools.py +0 -140
- deepagents-cli/deepagents_cli/ui.py +0 -489
- deepagents-cli/tests/test_file_ops.py +0 -119
- deepagents-cli/tests/test_placeholder.py +0 -5
- {deepagents-0.2.5.dist-info → deepagents-0.2.6.dist-info}/WHEEL +0 -0
deepagents/backends/composite.py
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
"""CompositeBackend: Route operations to different backends based on path prefix."""
|
|
2
2
|
|
|
3
|
-
from deepagents.backends.protocol import
|
|
3
|
+
from deepagents.backends.protocol import (
|
|
4
|
+
BackendProtocol,
|
|
5
|
+
EditResult,
|
|
6
|
+
ExecuteResponse,
|
|
7
|
+
FileInfo,
|
|
8
|
+
GrepMatch,
|
|
9
|
+
SandboxBackendProtocol,
|
|
10
|
+
WriteResult,
|
|
11
|
+
)
|
|
4
12
|
from deepagents.backends.state import StateBackend
|
|
5
|
-
from deepagents.backends.utils import FileInfo, GrepMatch
|
|
6
13
|
|
|
7
14
|
|
|
8
15
|
class CompositeBackend:
|
|
@@ -212,3 +219,31 @@ class CompositeBackend:
|
|
|
212
219
|
except Exception:
|
|
213
220
|
pass
|
|
214
221
|
return res
|
|
222
|
+
|
|
223
|
+
def execute(
|
|
224
|
+
self,
|
|
225
|
+
command: str,
|
|
226
|
+
) -> ExecuteResponse:
|
|
227
|
+
"""Execute a command via the default backend.
|
|
228
|
+
|
|
229
|
+
Execution is not path-specific, so it always delegates to the default backend.
|
|
230
|
+
The default backend must implement SandboxBackendProtocol for this to work.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
command: Full shell command string to execute.
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
ExecuteResponse with combined output, exit code, and truncation flag.
|
|
237
|
+
|
|
238
|
+
Raises:
|
|
239
|
+
NotImplementedError: If default backend doesn't support execution.
|
|
240
|
+
"""
|
|
241
|
+
if isinstance(self.default, SandboxBackendProtocol):
|
|
242
|
+
return self.default.execute(command)
|
|
243
|
+
|
|
244
|
+
# This shouldn't be reached if the runtime check in the execute tool works correctly,
|
|
245
|
+
# but we include it as a safety fallback.
|
|
246
|
+
raise NotImplementedError(
|
|
247
|
+
"Default backend doesn't support command execution (SandboxBackendProtocol). "
|
|
248
|
+
"To enable execution, provide a default backend that implements SandboxBackendProtocol."
|
|
249
|
+
)
|
deepagents/backends/protocol.py
CHANGED
|
@@ -145,4 +145,52 @@ class BackendProtocol(Protocol):
|
|
|
145
145
|
...
|
|
146
146
|
|
|
147
147
|
|
|
148
|
+
@dataclass
|
|
149
|
+
class ExecuteResponse:
|
|
150
|
+
"""Result of code execution.
|
|
151
|
+
|
|
152
|
+
Simplified schema optimized for LLM consumption.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
output: str
|
|
156
|
+
"""Combined stdout and stderr output of the executed command."""
|
|
157
|
+
|
|
158
|
+
exit_code: int | None = None
|
|
159
|
+
"""The process exit code. 0 indicates success, non-zero indicates failure."""
|
|
160
|
+
|
|
161
|
+
truncated: bool = False
|
|
162
|
+
"""Whether the output was truncated due to backend limitations."""
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@runtime_checkable
|
|
166
|
+
class SandboxBackendProtocol(BackendProtocol, Protocol):
|
|
167
|
+
"""Protocol for sandboxed backends with isolated runtime.
|
|
168
|
+
|
|
169
|
+
Sandboxed backends run in isolated environments (e.g., separate processes,
|
|
170
|
+
containers) and communicate via defined interfaces.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
def execute(
|
|
174
|
+
self,
|
|
175
|
+
command: str,
|
|
176
|
+
) -> ExecuteResponse:
|
|
177
|
+
"""Execute a command in the process.
|
|
178
|
+
|
|
179
|
+
Simplified interface optimized for LLM consumption.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
command: Full shell command string to execute.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
ExecuteResponse with combined output, exit code, optional signal, and truncation flag.
|
|
186
|
+
"""
|
|
187
|
+
...
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def id(self) -> str:
|
|
191
|
+
"""Unique identifier for the sandbox backend."""
|
|
192
|
+
...
|
|
193
|
+
|
|
194
|
+
|
|
148
195
|
BackendFactory: TypeAlias = Callable[[ToolRuntime], BackendProtocol]
|
|
196
|
+
BACKEND_TYPES = BackendProtocol | BackendFactory
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
"""Base sandbox implementation with execute() as the only abstract method.
|
|
2
|
+
|
|
3
|
+
This module provides a base class that implements all SandboxBackendProtocol
|
|
4
|
+
methods using shell commands executed via execute(). Concrete implementations
|
|
5
|
+
only need to implement the execute() method.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import base64
|
|
11
|
+
import json
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
|
|
14
|
+
from deepagents.backends.protocol import (
|
|
15
|
+
EditResult,
|
|
16
|
+
ExecuteResponse,
|
|
17
|
+
FileInfo,
|
|
18
|
+
GrepMatch,
|
|
19
|
+
SandboxBackendProtocol,
|
|
20
|
+
WriteResult,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
_GLOB_COMMAND_TEMPLATE = """python3 -c "
|
|
24
|
+
import glob
|
|
25
|
+
import os
|
|
26
|
+
import json
|
|
27
|
+
import base64
|
|
28
|
+
|
|
29
|
+
# Decode base64-encoded parameters
|
|
30
|
+
path = base64.b64decode('{path_b64}').decode('utf-8')
|
|
31
|
+
pattern = base64.b64decode('{pattern_b64}').decode('utf-8')
|
|
32
|
+
|
|
33
|
+
os.chdir(path)
|
|
34
|
+
matches = sorted(glob.glob(pattern, recursive=True))
|
|
35
|
+
for m in matches:
|
|
36
|
+
stat = os.stat(m)
|
|
37
|
+
result = {{
|
|
38
|
+
'path': m,
|
|
39
|
+
'size': stat.st_size,
|
|
40
|
+
'mtime': stat.st_mtime,
|
|
41
|
+
'is_dir': os.path.isdir(m)
|
|
42
|
+
}}
|
|
43
|
+
print(json.dumps(result))
|
|
44
|
+
" 2>/dev/null"""
|
|
45
|
+
|
|
46
|
+
_WRITE_COMMAND_TEMPLATE = """python3 -c "
|
|
47
|
+
import os
|
|
48
|
+
import sys
|
|
49
|
+
import base64
|
|
50
|
+
|
|
51
|
+
file_path = '{file_path}'
|
|
52
|
+
|
|
53
|
+
# Check if file already exists (atomic with write)
|
|
54
|
+
if os.path.exists(file_path):
|
|
55
|
+
print(f'Error: File \\'{file_path}\\' already exists', file=sys.stderr)
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
|
|
58
|
+
# Create parent directory if needed
|
|
59
|
+
parent_dir = os.path.dirname(file_path) or '.'
|
|
60
|
+
os.makedirs(parent_dir, exist_ok=True)
|
|
61
|
+
|
|
62
|
+
# Decode and write content
|
|
63
|
+
content = base64.b64decode('{content_b64}').decode('utf-8')
|
|
64
|
+
with open(file_path, 'w') as f:
|
|
65
|
+
f.write(content)
|
|
66
|
+
" 2>&1"""
|
|
67
|
+
|
|
68
|
+
_EDIT_COMMAND_TEMPLATE = """python3 -c "
|
|
69
|
+
import sys
|
|
70
|
+
import base64
|
|
71
|
+
|
|
72
|
+
# Read file content
|
|
73
|
+
with open('{file_path}', 'r') as f:
|
|
74
|
+
text = f.read()
|
|
75
|
+
|
|
76
|
+
# Decode base64-encoded strings
|
|
77
|
+
old = base64.b64decode('{old_b64}').decode('utf-8')
|
|
78
|
+
new = base64.b64decode('{new_b64}').decode('utf-8')
|
|
79
|
+
|
|
80
|
+
# Count occurrences
|
|
81
|
+
count = text.count(old)
|
|
82
|
+
|
|
83
|
+
# Exit with error codes if issues found
|
|
84
|
+
if count == 0:
|
|
85
|
+
sys.exit(1) # String not found
|
|
86
|
+
elif count > 1 and not {replace_all}:
|
|
87
|
+
sys.exit(2) # Multiple occurrences without replace_all
|
|
88
|
+
|
|
89
|
+
# Perform replacement
|
|
90
|
+
if {replace_all}:
|
|
91
|
+
result = text.replace(old, new)
|
|
92
|
+
else:
|
|
93
|
+
result = text.replace(old, new, 1)
|
|
94
|
+
|
|
95
|
+
# Write back to file
|
|
96
|
+
with open('{file_path}', 'w') as f:
|
|
97
|
+
f.write(result)
|
|
98
|
+
|
|
99
|
+
print(count)
|
|
100
|
+
" 2>&1"""
|
|
101
|
+
|
|
102
|
+
_READ_COMMAND_TEMPLATE = """python3 -c "
|
|
103
|
+
import os
|
|
104
|
+
import sys
|
|
105
|
+
|
|
106
|
+
file_path = '{file_path}'
|
|
107
|
+
offset = {offset}
|
|
108
|
+
limit = {limit}
|
|
109
|
+
|
|
110
|
+
# Check if file exists
|
|
111
|
+
if not os.path.isfile(file_path):
|
|
112
|
+
print('Error: File not found')
|
|
113
|
+
sys.exit(1)
|
|
114
|
+
|
|
115
|
+
# Check if file is empty
|
|
116
|
+
if os.path.getsize(file_path) == 0:
|
|
117
|
+
print('System reminder: File exists but has empty contents')
|
|
118
|
+
sys.exit(0)
|
|
119
|
+
|
|
120
|
+
# Read file with offset and limit
|
|
121
|
+
with open(file_path, 'r') as f:
|
|
122
|
+
lines = f.readlines()
|
|
123
|
+
|
|
124
|
+
# Apply offset and limit
|
|
125
|
+
start_idx = offset
|
|
126
|
+
end_idx = offset + limit
|
|
127
|
+
selected_lines = lines[start_idx:end_idx]
|
|
128
|
+
|
|
129
|
+
# Format with line numbers (1-indexed, starting from offset + 1)
|
|
130
|
+
for i, line in enumerate(selected_lines):
|
|
131
|
+
line_num = offset + i + 1
|
|
132
|
+
# Remove trailing newline for formatting, then add it back
|
|
133
|
+
line_content = line.rstrip('\\n')
|
|
134
|
+
print(f'{{line_num:6d}}\\t{{line_content}}')
|
|
135
|
+
" 2>&1"""
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class BaseSandbox(SandboxBackendProtocol, ABC):
|
|
139
|
+
"""Base sandbox implementation with execute() as abstract method.
|
|
140
|
+
|
|
141
|
+
This class provides default implementations for all protocol methods
|
|
142
|
+
using shell commands. Subclasses only need to implement execute().
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
@abstractmethod
|
|
146
|
+
def execute(
|
|
147
|
+
self,
|
|
148
|
+
command: str,
|
|
149
|
+
) -> ExecuteResponse:
|
|
150
|
+
"""Execute a command in the sandbox and return ExecuteResponse.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
command: Full shell command string to execute.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
ExecuteResponse with combined output, exit code, optional signal, and truncation flag.
|
|
157
|
+
"""
|
|
158
|
+
...
|
|
159
|
+
|
|
160
|
+
def ls_info(self, path: str) -> list[FileInfo]:
|
|
161
|
+
"""Structured listing with file metadata using os.scandir."""
|
|
162
|
+
cmd = f"""python3 -c "
|
|
163
|
+
import os
|
|
164
|
+
import json
|
|
165
|
+
|
|
166
|
+
path = '{path}'
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
with os.scandir(path) as it:
|
|
170
|
+
for entry in it:
|
|
171
|
+
result = {{
|
|
172
|
+
'path': entry.name,
|
|
173
|
+
'is_dir': entry.is_dir(follow_symlinks=False)
|
|
174
|
+
}}
|
|
175
|
+
print(json.dumps(result))
|
|
176
|
+
except FileNotFoundError:
|
|
177
|
+
pass
|
|
178
|
+
except PermissionError:
|
|
179
|
+
pass
|
|
180
|
+
" 2>/dev/null"""
|
|
181
|
+
|
|
182
|
+
result = self.execute(cmd)
|
|
183
|
+
|
|
184
|
+
file_infos: list[FileInfo] = []
|
|
185
|
+
for line in result.output.strip().split("\n"):
|
|
186
|
+
if not line:
|
|
187
|
+
continue
|
|
188
|
+
try:
|
|
189
|
+
data = json.loads(line)
|
|
190
|
+
file_infos.append({"path": data["path"], "is_dir": data["is_dir"]})
|
|
191
|
+
except json.JSONDecodeError:
|
|
192
|
+
continue
|
|
193
|
+
|
|
194
|
+
return file_infos
|
|
195
|
+
|
|
196
|
+
def read(
|
|
197
|
+
self,
|
|
198
|
+
file_path: str,
|
|
199
|
+
offset: int = 0,
|
|
200
|
+
limit: int = 2000,
|
|
201
|
+
) -> str:
|
|
202
|
+
"""Read file content with line numbers using a single shell command."""
|
|
203
|
+
# Use template for reading file with offset and limit
|
|
204
|
+
cmd = _READ_COMMAND_TEMPLATE.format(file_path=file_path, offset=offset, limit=limit)
|
|
205
|
+
result = self.execute(cmd)
|
|
206
|
+
|
|
207
|
+
output = result.output.rstrip()
|
|
208
|
+
exit_code = result.exit_code
|
|
209
|
+
|
|
210
|
+
if exit_code != 0 or "Error: File not found" in output:
|
|
211
|
+
return f"Error: File '{file_path}' not found"
|
|
212
|
+
|
|
213
|
+
return output
|
|
214
|
+
|
|
215
|
+
def write(
|
|
216
|
+
self,
|
|
217
|
+
file_path: str,
|
|
218
|
+
content: str,
|
|
219
|
+
) -> WriteResult:
|
|
220
|
+
"""Create a new file. Returns WriteResult; error populated on failure."""
|
|
221
|
+
# Encode content as base64 to avoid any escaping issues
|
|
222
|
+
content_b64 = base64.b64encode(content.encode("utf-8")).decode("ascii")
|
|
223
|
+
|
|
224
|
+
# Single atomic check + write command
|
|
225
|
+
cmd = _WRITE_COMMAND_TEMPLATE.format(file_path=file_path, content_b64=content_b64)
|
|
226
|
+
result = self.execute(cmd)
|
|
227
|
+
|
|
228
|
+
# Check for errors (exit code or error message in output)
|
|
229
|
+
if result.exit_code != 0 or "Error:" in result.output:
|
|
230
|
+
error_msg = result.output.strip() or f"Failed to write file '{file_path}'"
|
|
231
|
+
return WriteResult(error=error_msg)
|
|
232
|
+
|
|
233
|
+
# External storage - no files_update needed
|
|
234
|
+
return WriteResult(path=file_path, files_update=None)
|
|
235
|
+
|
|
236
|
+
def edit(
|
|
237
|
+
self,
|
|
238
|
+
file_path: str,
|
|
239
|
+
old_string: str,
|
|
240
|
+
new_string: str,
|
|
241
|
+
replace_all: bool = False,
|
|
242
|
+
) -> EditResult:
|
|
243
|
+
"""Edit a file by replacing string occurrences. Returns EditResult."""
|
|
244
|
+
# Encode strings as base64 to avoid any escaping issues
|
|
245
|
+
old_b64 = base64.b64encode(old_string.encode("utf-8")).decode("ascii")
|
|
246
|
+
new_b64 = base64.b64encode(new_string.encode("utf-8")).decode("ascii")
|
|
247
|
+
|
|
248
|
+
# Use template for string replacement
|
|
249
|
+
cmd = _EDIT_COMMAND_TEMPLATE.format(file_path=file_path, old_b64=old_b64, new_b64=new_b64, replace_all=replace_all)
|
|
250
|
+
result = self.execute(cmd)
|
|
251
|
+
|
|
252
|
+
exit_code = result.exit_code
|
|
253
|
+
output = result.output.strip()
|
|
254
|
+
|
|
255
|
+
if exit_code == 1:
|
|
256
|
+
return EditResult(error=f"Error: String not found in file: '{old_string}'")
|
|
257
|
+
if exit_code == 2:
|
|
258
|
+
return EditResult(error=f"Error: String '{old_string}' appears multiple times. Use replace_all=True to replace all occurrences.")
|
|
259
|
+
if exit_code != 0:
|
|
260
|
+
return EditResult(error=f"Error: File '{file_path}' not found")
|
|
261
|
+
|
|
262
|
+
count = int(output)
|
|
263
|
+
# External storage - no files_update needed
|
|
264
|
+
return EditResult(path=file_path, files_update=None, occurrences=count)
|
|
265
|
+
|
|
266
|
+
def grep_raw(
|
|
267
|
+
self,
|
|
268
|
+
pattern: str,
|
|
269
|
+
path: str | None = None,
|
|
270
|
+
glob: str | None = None,
|
|
271
|
+
) -> list[GrepMatch] | str:
|
|
272
|
+
"""Structured search results or error string for invalid input."""
|
|
273
|
+
search_path = path or "."
|
|
274
|
+
|
|
275
|
+
# Build grep command to get structured output
|
|
276
|
+
grep_opts = "-rHn" # recursive, with filename, with line number
|
|
277
|
+
|
|
278
|
+
# Add glob pattern if specified
|
|
279
|
+
glob_pattern = ""
|
|
280
|
+
if glob:
|
|
281
|
+
glob_pattern = f"--include='{glob}'"
|
|
282
|
+
|
|
283
|
+
# Escape pattern for shell
|
|
284
|
+
pattern_escaped = pattern.replace("'", "'\\\\''")
|
|
285
|
+
|
|
286
|
+
cmd = f"grep {grep_opts} {glob_pattern} -e '{pattern_escaped}' '{search_path}' 2>/dev/null || true"
|
|
287
|
+
result = self.execute(cmd)
|
|
288
|
+
|
|
289
|
+
output = result.output.rstrip()
|
|
290
|
+
if not output:
|
|
291
|
+
return []
|
|
292
|
+
|
|
293
|
+
# Parse grep output into GrepMatch objects
|
|
294
|
+
matches: list[GrepMatch] = []
|
|
295
|
+
for line in output.split("\n"):
|
|
296
|
+
# Format is: path:line_number:text
|
|
297
|
+
parts = line.split(":", 2)
|
|
298
|
+
if len(parts) >= 3:
|
|
299
|
+
matches.append(
|
|
300
|
+
{
|
|
301
|
+
"path": parts[0],
|
|
302
|
+
"line": int(parts[1]),
|
|
303
|
+
"text": parts[2],
|
|
304
|
+
}
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
return matches
|
|
308
|
+
|
|
309
|
+
def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]:
|
|
310
|
+
"""Structured glob matching returning FileInfo dicts."""
|
|
311
|
+
# Encode pattern and path as base64 to avoid escaping issues
|
|
312
|
+
pattern_b64 = base64.b64encode(pattern.encode("utf-8")).decode("ascii")
|
|
313
|
+
path_b64 = base64.b64encode(path.encode("utf-8")).decode("ascii")
|
|
314
|
+
|
|
315
|
+
cmd = _GLOB_COMMAND_TEMPLATE.format(path_b64=path_b64, pattern_b64=pattern_b64)
|
|
316
|
+
result = self.execute(cmd)
|
|
317
|
+
|
|
318
|
+
output = result.output.strip()
|
|
319
|
+
if not output:
|
|
320
|
+
return []
|
|
321
|
+
|
|
322
|
+
# Parse JSON output into FileInfo dicts
|
|
323
|
+
file_infos: list[FileInfo] = []
|
|
324
|
+
for line in output.split("\n"):
|
|
325
|
+
try:
|
|
326
|
+
data = json.loads(line)
|
|
327
|
+
file_infos.append(
|
|
328
|
+
{
|
|
329
|
+
"path": data["path"],
|
|
330
|
+
"is_dir": data["is_dir"],
|
|
331
|
+
}
|
|
332
|
+
)
|
|
333
|
+
except json.JSONDecodeError:
|
|
334
|
+
continue
|
|
335
|
+
|
|
336
|
+
return file_infos
|
|
337
|
+
|
|
338
|
+
@property
|
|
339
|
+
@abstractmethod
|
|
340
|
+
def id(self) -> str:
|
|
341
|
+
"""Unique identifier for this backend instance."""
|
deepagents/backends/store.py
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
"""StoreBackend: Adapter for LangGraph's BaseStore (persistent, cross-thread)."""
|
|
2
2
|
|
|
3
|
-
from typing import
|
|
4
|
-
|
|
5
|
-
if TYPE_CHECKING:
|
|
6
|
-
from langchain.tools import ToolRuntime
|
|
3
|
+
from typing import Any
|
|
7
4
|
|
|
8
5
|
from langgraph.config import get_config
|
|
9
6
|
from langgraph.store.base import BaseStore, Item
|
|
10
7
|
|
|
11
|
-
from deepagents.backends.protocol import EditResult, WriteResult
|
|
8
|
+
from deepagents.backends.protocol import BackendProtocol, EditResult, FileInfo, GrepMatch, WriteResult
|
|
12
9
|
from deepagents.backends.utils import (
|
|
13
|
-
FileInfo,
|
|
14
|
-
GrepMatch,
|
|
15
10
|
_glob_search_files,
|
|
16
11
|
create_file_data,
|
|
17
12
|
file_data_to_string,
|
|
@@ -22,7 +17,7 @@ from deepagents.backends.utils import (
|
|
|
22
17
|
)
|
|
23
18
|
|
|
24
19
|
|
|
25
|
-
class StoreBackend:
|
|
20
|
+
class StoreBackend(BackendProtocol):
|
|
26
21
|
"""Backend that stores files in LangGraph's BaseStore (persistent).
|
|
27
22
|
|
|
28
23
|
Uses LangGraph's Store for persistent, cross-conversation storage.
|
|
@@ -381,6 +376,3 @@ class StoreBackend:
|
|
|
381
376
|
}
|
|
382
377
|
)
|
|
383
378
|
return infos
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
# Provider classes removed: prefer callables like `lambda rt: StoreBackend(rt)`
|
deepagents/graph.py
CHANGED
|
@@ -57,9 +57,12 @@ def create_deep_agent(
|
|
|
57
57
|
"""Create a deep agent.
|
|
58
58
|
|
|
59
59
|
This agent will by default have access to a tool to write todos (write_todos),
|
|
60
|
-
|
|
60
|
+
seven file and execution tools: ls, read_file, write_file, edit_file, glob, grep, execute,
|
|
61
61
|
and a tool to call subagents.
|
|
62
62
|
|
|
63
|
+
The execute tool allows running shell commands if the backend implements SandboxBackendProtocol.
|
|
64
|
+
For non-sandbox backends, the execute tool will return an error message.
|
|
65
|
+
|
|
63
66
|
Args:
|
|
64
67
|
model: The model to use. Defaults to Claude Sonnet 4.
|
|
65
68
|
tools: The tools the agent should have access to.
|
|
@@ -80,8 +83,9 @@ def create_deep_agent(
|
|
|
80
83
|
context_schema: The schema of the deep agent.
|
|
81
84
|
checkpointer: Optional checkpointer for persisting agent state between runs.
|
|
82
85
|
store: Optional store for persistent storage (required if backend uses StoreBackend).
|
|
83
|
-
backend: Optional backend for file storage. Pass either a Backend instance
|
|
84
|
-
callable factory like `lambda rt: StateBackend(rt)`.
|
|
86
|
+
backend: Optional backend for file storage and execution. Pass either a Backend instance
|
|
87
|
+
or a callable factory like `lambda rt: StateBackend(rt)`. For execution support,
|
|
88
|
+
use a backend that implements SandboxBackendProtocol.
|
|
85
89
|
interrupt_on: Optional Dict[str, bool | InterruptOnConfig] mapping tool names to
|
|
86
90
|
interrupt configs.
|
|
87
91
|
debug: Whether to enable debug mode. Passed through to create_agent.
|