deepagents 0.3.7a1__tar.gz → 0.3.8__tar.gz
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-0.3.7a1 → deepagents-0.3.8}/PKG-INFO +1 -1
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents/backends/filesystem.py +55 -7
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents/graph.py +29 -10
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents/middleware/__init__.py +3 -1
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents/middleware/filesystem.py +111 -81
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents/middleware/memory.py +11 -7
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents/middleware/skills.py +4 -2
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents/middleware/subagents.py +35 -19
- deepagents-0.3.8/deepagents/middleware/summarization.py +758 -0
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents.egg-info/PKG-INFO +1 -1
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents.egg-info/SOURCES.txt +2 -1
- {deepagents-0.3.7a1 → deepagents-0.3.8}/pyproject.toml +1 -1
- {deepagents-0.3.7a1 → deepagents-0.3.8}/README.md +0 -0
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents/__init__.py +0 -0
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents/backends/__init__.py +0 -0
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents/backends/composite.py +0 -0
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents/backends/protocol.py +0 -0
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents/backends/sandbox.py +0 -0
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents/backends/state.py +0 -0
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents/backends/store.py +0 -0
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents/backends/utils.py +0 -0
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents/middleware/_utils.py +0 -0
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents/middleware/patch_tool_calls.py +0 -0
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents.egg-info/dependency_links.txt +0 -0
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents.egg-info/requires.txt +0 -0
- {deepagents-0.3.7a1 → deepagents-0.3.8}/deepagents.egg-info/top_level.txt +0 -0
- {deepagents-0.3.7a1 → deepagents-0.3.8}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepagents
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.8
|
|
4
4
|
Summary: General purpose 'deep agent' with sub-agent spawning, todo list capabilities, and mock file system. Built on LangGraph.
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://docs.langchain.com/oss/python/deepagents/overview
|
|
@@ -31,6 +31,39 @@ class FilesystemBackend(BackendProtocol):
|
|
|
31
31
|
Files are accessed using their actual filesystem paths. Relative paths are
|
|
32
32
|
resolved relative to the current working directory. Content is read/written
|
|
33
33
|
as plain text, and metadata (timestamps) are derived from filesystem stats.
|
|
34
|
+
|
|
35
|
+
!!! warning "Security Warning"
|
|
36
|
+
|
|
37
|
+
This backend grants agents direct filesystem read/write access. Use with
|
|
38
|
+
caution and only in appropriate environments.
|
|
39
|
+
|
|
40
|
+
**Appropriate use cases:**
|
|
41
|
+
|
|
42
|
+
- Local development CLIs (coding assistants, development tools)
|
|
43
|
+
- CI/CD pipelines (see security considerations below)
|
|
44
|
+
|
|
45
|
+
**Inappropriate use cases:**
|
|
46
|
+
|
|
47
|
+
- Web servers or HTTP APIs - use `StateBackend`, `StoreBackend`, or
|
|
48
|
+
`SandboxBackend` instead
|
|
49
|
+
|
|
50
|
+
**Security risks:**
|
|
51
|
+
|
|
52
|
+
- Agents can read any accessible file, including secrets (API keys,
|
|
53
|
+
credentials, `.env` files)
|
|
54
|
+
- Combined with network tools, secrets may be exfiltrated via SSRF attacks
|
|
55
|
+
- File modifications are permanent and irreversible
|
|
56
|
+
|
|
57
|
+
**Recommended safeguards:**
|
|
58
|
+
|
|
59
|
+
1. Enable Human-in-the-Loop (HITL) middleware to review sensitive operations
|
|
60
|
+
2. Exclude secrets from accessible filesystem paths (especially in CI/CD)
|
|
61
|
+
3. Use `SandboxBackend` for production environments requiring filesystem
|
|
62
|
+
interaction
|
|
63
|
+
4. **Always** use `virtual_mode=True` with `root_dir` to enable path-based
|
|
64
|
+
access restrictions (blocks `..`, `~`, and absolute paths outside root).
|
|
65
|
+
Note that the default (`virtual_mode=False`) provides no security even with
|
|
66
|
+
`root_dir` set.
|
|
34
67
|
"""
|
|
35
68
|
|
|
36
69
|
def __init__(
|
|
@@ -44,14 +77,29 @@ class FilesystemBackend(BackendProtocol):
|
|
|
44
77
|
Args:
|
|
45
78
|
root_dir: Optional root directory for file operations.
|
|
46
79
|
|
|
47
|
-
If provided,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
80
|
+
- If not provided, defaults to the current working directory.
|
|
81
|
+
- When `virtual_mode=False` (default): Only affects relative path
|
|
82
|
+
resolution. Provides **no security** - agents can access any file
|
|
83
|
+
using absolute paths or `..` sequences.
|
|
84
|
+
- When `virtual_mode=True`: All paths are restricted to this
|
|
85
|
+
directory with traversal protection enabled.
|
|
86
|
+
|
|
87
|
+
virtual_mode: Enable path-based access restrictions.
|
|
88
|
+
|
|
89
|
+
When `True`, all paths are treated as virtual paths anchored to
|
|
90
|
+
`root_dir`. Path traversal (`..`, `~`) is blocked and all resolved paths
|
|
91
|
+
are verified to remain within `root_dir`.
|
|
92
|
+
|
|
93
|
+
When `False` (default), **no security is provided**:
|
|
94
|
+
|
|
95
|
+
- Absolute paths (e.g., `/etc/passwd`) bypass `root_dir` entirely
|
|
96
|
+
- Relative paths with `..` can escape `root_dir`
|
|
97
|
+
- Agents have unrestricted filesystem access
|
|
98
|
+
|
|
99
|
+
**Security note:** `virtual_mode=True` provides path-based access
|
|
100
|
+
control, not process isolation. It restricts which files can be
|
|
101
|
+
accessed via paths, but does not sandbox the Python process itself.
|
|
51
102
|
|
|
52
|
-
Path traversal (using `..` or `~`) is disallowed and all resolved paths
|
|
53
|
-
must remain within the root directory. When `False` (default), absolute
|
|
54
|
-
paths are allowed as-is and relative paths resolve under cwd.
|
|
55
103
|
max_file_size_mb: Maximum file size in megabytes for operations like
|
|
56
104
|
grep's Python fallback search.
|
|
57
105
|
|
|
@@ -5,7 +5,6 @@ from typing import Any
|
|
|
5
5
|
|
|
6
6
|
from langchain.agents import create_agent
|
|
7
7
|
from langchain.agents.middleware import HumanInTheLoopMiddleware, InterruptOnConfig, TodoListMiddleware
|
|
8
|
-
from langchain.agents.middleware.summarization import SummarizationMiddleware
|
|
9
8
|
from langchain.agents.middleware.types import AgentMiddleware
|
|
10
9
|
from langchain.agents.structured_output import ResponseFormat
|
|
11
10
|
from langchain.chat_models import init_chat_model
|
|
@@ -26,6 +25,7 @@ from deepagents.middleware.memory import MemoryMiddleware
|
|
|
26
25
|
from deepagents.middleware.patch_tool_calls import PatchToolCallsMiddleware
|
|
27
26
|
from deepagents.middleware.skills import SkillsMiddleware
|
|
28
27
|
from deepagents.middleware.subagents import CompiledSubAgent, SubAgent, SubAgentMiddleware
|
|
28
|
+
from deepagents.middleware.summarization import SummarizationMiddleware
|
|
29
29
|
|
|
30
30
|
BASE_AGENT_PROMPT = "In order to complete the objective that the user asks of you, you have access to a number of standard tools."
|
|
31
31
|
|
|
@@ -38,7 +38,7 @@ def get_default_model() -> ChatAnthropic:
|
|
|
38
38
|
"""
|
|
39
39
|
return ChatAnthropic(
|
|
40
40
|
model_name="claude-sonnet-4-5-20250929",
|
|
41
|
-
max_tokens=20000,
|
|
41
|
+
max_tokens=20000, # type: ignore[call-arg]
|
|
42
42
|
)
|
|
43
43
|
|
|
44
44
|
|
|
@@ -63,11 +63,14 @@ def create_deep_agent(
|
|
|
63
63
|
) -> CompiledStateGraph:
|
|
64
64
|
"""Create a deep agent.
|
|
65
65
|
|
|
66
|
-
Deep agents require a LLM that supports tool calling
|
|
66
|
+
!!! warning "Deep agents require a LLM that supports tool calling!"
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
By default, this agent has access to the following tools:
|
|
69
|
+
|
|
70
|
+
- `write_todos`: manage a todo list
|
|
71
|
+
- `ls`, `read_file`, `write_file`, `edit_file`, `glob`, `grep`: file operations
|
|
72
|
+
- `execute`: run shell commands
|
|
73
|
+
- `task`: call subagents
|
|
71
74
|
|
|
72
75
|
The `execute` tool allows running shell commands if the backend implements `SandboxBackendProtocol`.
|
|
73
76
|
For non-sandbox backends, the `execute` tool will return an error message.
|
|
@@ -82,10 +85,14 @@ def create_deep_agent(
|
|
|
82
85
|
|
|
83
86
|
In addition to custom tools you provide, deep agents include built-in tools for planning,
|
|
84
87
|
file management, and subagent spawning.
|
|
85
|
-
system_prompt:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
system_prompt: Custom system instructions to prepend before the base deep agent
|
|
89
|
+
prompt.
|
|
90
|
+
|
|
91
|
+
If a string, it's concatenated with the base prompt.
|
|
92
|
+
middleware: Additional middleware to apply after the standard middleware stack
|
|
93
|
+
(`TodoListMiddleware`, `FilesystemMiddleware`, `SubAgentMiddleware`,
|
|
94
|
+
`SummarizationMiddleware`, `AnthropicPromptCachingMiddleware`,
|
|
95
|
+
`PatchToolCallsMiddleware`).
|
|
89
96
|
subagents: The subagents to use.
|
|
90
97
|
|
|
91
98
|
Each subagent should be a `dict` with the following keys:
|
|
@@ -142,9 +149,17 @@ def create_deep_agent(
|
|
|
142
149
|
):
|
|
143
150
|
trigger = ("fraction", 0.85)
|
|
144
151
|
keep = ("fraction", 0.10)
|
|
152
|
+
truncate_args_settings = {
|
|
153
|
+
"trigger": ("fraction", 0.85),
|
|
154
|
+
"keep": ("fraction", 0.10),
|
|
155
|
+
}
|
|
145
156
|
else:
|
|
146
157
|
trigger = ("tokens", 170000)
|
|
147
158
|
keep = ("messages", 6)
|
|
159
|
+
truncate_args_settings = {
|
|
160
|
+
"trigger": ("messages", 20),
|
|
161
|
+
"keep": ("messages", 20),
|
|
162
|
+
}
|
|
148
163
|
|
|
149
164
|
# Build middleware stack for subagents (includes skills if provided)
|
|
150
165
|
subagent_middleware: list[AgentMiddleware] = [
|
|
@@ -160,9 +175,11 @@ def create_deep_agent(
|
|
|
160
175
|
FilesystemMiddleware(backend=backend),
|
|
161
176
|
SummarizationMiddleware(
|
|
162
177
|
model=model,
|
|
178
|
+
backend=backend,
|
|
163
179
|
trigger=trigger,
|
|
164
180
|
keep=keep,
|
|
165
181
|
trim_tokens_to_summarize=None,
|
|
182
|
+
truncate_args_settings=truncate_args_settings,
|
|
166
183
|
),
|
|
167
184
|
AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
|
|
168
185
|
PatchToolCallsMiddleware(),
|
|
@@ -190,9 +207,11 @@ def create_deep_agent(
|
|
|
190
207
|
),
|
|
191
208
|
SummarizationMiddleware(
|
|
192
209
|
model=model,
|
|
210
|
+
backend=backend,
|
|
193
211
|
trigger=trigger,
|
|
194
212
|
keep=keep,
|
|
195
213
|
trim_tokens_to_summarize=None,
|
|
214
|
+
truncate_args_settings=truncate_args_settings,
|
|
196
215
|
),
|
|
197
216
|
AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
|
|
198
217
|
PatchToolCallsMiddleware(),
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
"""Middleware for the
|
|
1
|
+
"""Middleware for the agent."""
|
|
2
2
|
|
|
3
3
|
from deepagents.middleware.filesystem import FilesystemMiddleware
|
|
4
4
|
from deepagents.middleware.memory import MemoryMiddleware
|
|
5
5
|
from deepagents.middleware.skills import SkillsMiddleware
|
|
6
6
|
from deepagents.middleware.subagents import CompiledSubAgent, SubAgent, SubAgentMiddleware
|
|
7
|
+
from deepagents.middleware.summarization import SummarizationMiddleware
|
|
7
8
|
|
|
8
9
|
__all__ = [
|
|
9
10
|
"CompiledSubAgent",
|
|
@@ -12,4 +13,5 @@ __all__ = [
|
|
|
12
13
|
"SkillsMiddleware",
|
|
13
14
|
"SubAgent",
|
|
14
15
|
"SubAgentMiddleware",
|
|
16
|
+
"SummarizationMiddleware",
|
|
15
17
|
]
|
|
@@ -20,8 +20,9 @@ from langgraph.types import Command
|
|
|
20
20
|
from typing_extensions import TypedDict
|
|
21
21
|
|
|
22
22
|
from deepagents.backends import StateBackend
|
|
23
|
+
from deepagents.backends.composite import CompositeBackend
|
|
23
24
|
from deepagents.backends.protocol import (
|
|
24
|
-
BACKEND_TYPES as BACKEND_TYPES, # Re-export for backwards compatibility
|
|
25
|
+
BACKEND_TYPES as BACKEND_TYPES, # Re-export type here for backwards compatibility
|
|
25
26
|
BackendProtocol,
|
|
26
27
|
EditResult,
|
|
27
28
|
SandboxBackendProtocol,
|
|
@@ -154,19 +155,16 @@ class FilesystemState(AgentState):
|
|
|
154
155
|
"""Files in the filesystem."""
|
|
155
156
|
|
|
156
157
|
|
|
157
|
-
LIST_FILES_TOOL_DESCRIPTION = """Lists all files in
|
|
158
|
+
LIST_FILES_TOOL_DESCRIPTION = """Lists all files in a directory.
|
|
158
159
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
- You should almost ALWAYS use this tool before using the Read or Edit tools."""
|
|
160
|
+
This is useful for exploring the filesystem and finding the right file to read or edit.
|
|
161
|
+
You should almost ALWAYS use this tool before using the read_file or edit_file tools."""
|
|
162
|
+
|
|
163
|
+
READ_FILE_TOOL_DESCRIPTION = """Reads a file from the filesystem.
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
Assume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.
|
|
165
|
+
Assume this tool is able to read all files. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.
|
|
167
166
|
|
|
168
167
|
Usage:
|
|
169
|
-
- The file_path parameter must be an absolute path, not a relative path
|
|
170
168
|
- By default, it reads up to 100 lines starting from the beginning of the file
|
|
171
169
|
- **IMPORTANT for large files and codebase exploration**: Use pagination with offset and limit parameters to avoid context overflow
|
|
172
170
|
- First scan: read_file(path, limit=100) to see file structure
|
|
@@ -182,61 +180,46 @@ Usage:
|
|
|
182
180
|
EDIT_FILE_TOOL_DESCRIPTION = """Performs exact string replacements in files.
|
|
183
181
|
|
|
184
182
|
Usage:
|
|
185
|
-
- You must
|
|
186
|
-
- When editing
|
|
187
|
-
- ALWAYS prefer editing existing files
|
|
188
|
-
- Only use emojis if the user explicitly requests it.
|
|
189
|
-
- The edit will FAIL if `old_string` is not unique in the file. Either provide a larger string with more surrounding context to make it unique or use `replace_all` to change every instance of `old_string`.
|
|
190
|
-
- Use `replace_all` for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance."""
|
|
183
|
+
- You must read the file before editing. This tool will error if you attempt an edit without reading the file first.
|
|
184
|
+
- When editing, preserve the exact indentation (tabs/spaces) from the read output. Never include line number prefixes in old_string or new_string.
|
|
185
|
+
- ALWAYS prefer editing existing files over creating new ones.
|
|
186
|
+
- Only use emojis if the user explicitly requests it."""
|
|
191
187
|
|
|
192
188
|
|
|
193
189
|
WRITE_FILE_TOOL_DESCRIPTION = """Writes to a new file in the filesystem.
|
|
194
190
|
|
|
195
191
|
Usage:
|
|
196
|
-
- The file_path parameter must be an absolute path, not a relative path
|
|
197
|
-
- The content parameter must be a string
|
|
198
192
|
- The write_file tool will create the a new file.
|
|
199
|
-
- Prefer to edit existing files over creating new ones when possible.
|
|
200
|
-
|
|
193
|
+
- Prefer to edit existing files (with the edit_file tool) over creating new ones when possible.
|
|
194
|
+
"""
|
|
201
195
|
|
|
202
196
|
GLOB_TOOL_DESCRIPTION = """Find files matching a glob pattern.
|
|
203
197
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
- Supports standard glob patterns: `*` (any characters), `**` (any directories), `?` (single character)
|
|
207
|
-
- Patterns can be absolute (starting with `/`) or relative
|
|
208
|
-
- Returns a list of absolute file paths that match the pattern
|
|
198
|
+
Supports standard glob patterns: `*` (any characters), `**` (any directories), `?` (single character).
|
|
199
|
+
Returns a list of absolute file paths that match the pattern.
|
|
209
200
|
|
|
210
201
|
Examples:
|
|
211
202
|
- `**/*.py` - Find all Python files
|
|
212
203
|
- `*.txt` - Find all text files in root
|
|
213
204
|
- `/subdir/**/*.md` - Find all markdown files under /subdir"""
|
|
214
205
|
|
|
215
|
-
GREP_TOOL_DESCRIPTION = """Search for a pattern
|
|
206
|
+
GREP_TOOL_DESCRIPTION = """Search for a text pattern across files.
|
|
216
207
|
|
|
217
|
-
|
|
218
|
-
- The grep tool searches for text patterns across files
|
|
219
|
-
- The pattern parameter is the text to search for (literal string, not regex)
|
|
220
|
-
- The path parameter filters which directory to search in (default is the current working directory)
|
|
221
|
-
- The glob parameter accepts a glob pattern to filter which files to search (e.g., `*.py`)
|
|
222
|
-
- The output_mode parameter controls the output format:
|
|
223
|
-
- `files_with_matches`: List only file paths containing matches (default)
|
|
224
|
-
- `content`: Show matching lines with file path and line numbers
|
|
225
|
-
- `count`: Show count of matches per file
|
|
208
|
+
Searches for literal text (not regex) and returns matching files or content based on output_mode.
|
|
226
209
|
|
|
227
210
|
Examples:
|
|
228
211
|
- Search all files: `grep(pattern="TODO")`
|
|
229
212
|
- Search Python files only: `grep(pattern="import", glob="*.py")`
|
|
230
213
|
- Show matching lines: `grep(pattern="error", output_mode="content")`"""
|
|
231
214
|
|
|
232
|
-
EXECUTE_TOOL_DESCRIPTION = """Executes a
|
|
215
|
+
EXECUTE_TOOL_DESCRIPTION = """Executes a shell command in an isolated sandbox environment.
|
|
233
216
|
|
|
217
|
+
Usage:
|
|
218
|
+
Executes a given command in the sandbox environment with proper handling and security measures.
|
|
234
219
|
Before executing the command, please follow these steps:
|
|
235
|
-
|
|
236
220
|
1. Directory Verification:
|
|
237
221
|
- If the command will create new directories or files, first use the ls tool to verify the parent directory exists and is the correct location
|
|
238
222
|
- For example, before running "mkdir foo/bar", first use ls to check that "foo" exists and is the intended parent directory
|
|
239
|
-
|
|
240
223
|
2. Command Execution:
|
|
241
224
|
- Always quote file paths that contain spaces with double quotes (e.g., cd "path with spaces/file.txt")
|
|
242
225
|
- Examples of proper quoting:
|
|
@@ -246,9 +229,7 @@ Before executing the command, please follow these steps:
|
|
|
246
229
|
- python /path/with spaces/script.py (incorrect - will fail)
|
|
247
230
|
- After ensuring proper quoting, execute the command
|
|
248
231
|
- Capture the output of the command
|
|
249
|
-
|
|
250
232
|
Usage notes:
|
|
251
|
-
- The command parameter is required
|
|
252
233
|
- Commands run in an isolated sandbox environment
|
|
253
234
|
- Returns combined stdout/stderr output with exit code
|
|
254
235
|
- If the output is very large, it may be truncated
|
|
@@ -323,7 +304,10 @@ def _ls_tool_generator(
|
|
|
323
304
|
"""
|
|
324
305
|
tool_description = custom_description or LIST_FILES_TOOL_DESCRIPTION
|
|
325
306
|
|
|
326
|
-
def sync_ls(
|
|
307
|
+
def sync_ls(
|
|
308
|
+
runtime: ToolRuntime[None, FilesystemState],
|
|
309
|
+
path: Annotated[str, "Absolute path to the directory to list. Must be absolute, not relative."],
|
|
310
|
+
) -> str:
|
|
327
311
|
"""Synchronous wrapper for ls tool."""
|
|
328
312
|
resolved_backend = _get_backend(backend, runtime)
|
|
329
313
|
validated_path = _validate_path(path)
|
|
@@ -332,7 +316,10 @@ def _ls_tool_generator(
|
|
|
332
316
|
result = truncate_if_too_long(paths)
|
|
333
317
|
return str(result)
|
|
334
318
|
|
|
335
|
-
async def async_ls(
|
|
319
|
+
async def async_ls(
|
|
320
|
+
runtime: ToolRuntime[None, FilesystemState],
|
|
321
|
+
path: Annotated[str, "Absolute path to the directory to list. Must be absolute, not relative."],
|
|
322
|
+
) -> str:
|
|
336
323
|
"""Asynchronous wrapper for ls tool."""
|
|
337
324
|
resolved_backend = _get_backend(backend, runtime)
|
|
338
325
|
validated_path = _validate_path(path)
|
|
@@ -365,10 +352,10 @@ def _read_file_tool_generator(
|
|
|
365
352
|
tool_description = custom_description or READ_FILE_TOOL_DESCRIPTION
|
|
366
353
|
|
|
367
354
|
def sync_read_file(
|
|
368
|
-
file_path: str,
|
|
355
|
+
file_path: Annotated[str, "Absolute path to the file to read. Must be absolute, not relative."],
|
|
369
356
|
runtime: ToolRuntime[None, FilesystemState],
|
|
370
|
-
offset: int = DEFAULT_READ_OFFSET,
|
|
371
|
-
limit: int = DEFAULT_READ_LIMIT,
|
|
357
|
+
offset: Annotated[int, "Line number to start reading from (0-indexed). Use for pagination of large files."] = DEFAULT_READ_OFFSET,
|
|
358
|
+
limit: Annotated[int, "Maximum number of lines to read. Use for pagination of large files."] = DEFAULT_READ_LIMIT,
|
|
372
359
|
) -> str:
|
|
373
360
|
"""Synchronous wrapper for read_file tool."""
|
|
374
361
|
resolved_backend = _get_backend(backend, runtime)
|
|
@@ -383,10 +370,10 @@ def _read_file_tool_generator(
|
|
|
383
370
|
return result
|
|
384
371
|
|
|
385
372
|
async def async_read_file(
|
|
386
|
-
file_path: str,
|
|
373
|
+
file_path: Annotated[str, "Absolute path to the file to read. Must be absolute, not relative."],
|
|
387
374
|
runtime: ToolRuntime[None, FilesystemState],
|
|
388
|
-
offset: int = DEFAULT_READ_OFFSET,
|
|
389
|
-
limit: int = DEFAULT_READ_LIMIT,
|
|
375
|
+
offset: Annotated[int, "Line number to start reading from (0-indexed). Use for pagination of large files."] = DEFAULT_READ_OFFSET,
|
|
376
|
+
limit: Annotated[int, "Maximum number of lines to read. Use for pagination of large files."] = DEFAULT_READ_LIMIT,
|
|
390
377
|
) -> str:
|
|
391
378
|
"""Asynchronous wrapper for read_file tool."""
|
|
392
379
|
resolved_backend = _get_backend(backend, runtime)
|
|
@@ -424,8 +411,8 @@ def _write_file_tool_generator(
|
|
|
424
411
|
tool_description = custom_description or WRITE_FILE_TOOL_DESCRIPTION
|
|
425
412
|
|
|
426
413
|
def sync_write_file(
|
|
427
|
-
file_path: str,
|
|
428
|
-
content: str,
|
|
414
|
+
file_path: Annotated[str, "Absolute path where the file should be created. Must be absolute, not relative."],
|
|
415
|
+
content: Annotated[str, "The text content to write to the file. This parameter is required."],
|
|
429
416
|
runtime: ToolRuntime[None, FilesystemState],
|
|
430
417
|
) -> Command | str:
|
|
431
418
|
"""Synchronous wrapper for write_file tool."""
|
|
@@ -450,8 +437,8 @@ def _write_file_tool_generator(
|
|
|
450
437
|
return f"Updated file {res.path}"
|
|
451
438
|
|
|
452
439
|
async def async_write_file(
|
|
453
|
-
file_path: str,
|
|
454
|
-
content: str,
|
|
440
|
+
file_path: Annotated[str, "Absolute path where the file should be created. Must be absolute, not relative."],
|
|
441
|
+
content: Annotated[str, "The text content to write to the file. This parameter is required."],
|
|
455
442
|
runtime: ToolRuntime[None, FilesystemState],
|
|
456
443
|
) -> Command | str:
|
|
457
444
|
"""Asynchronous wrapper for write_file tool."""
|
|
@@ -499,12 +486,12 @@ def _edit_file_tool_generator(
|
|
|
499
486
|
tool_description = custom_description or EDIT_FILE_TOOL_DESCRIPTION
|
|
500
487
|
|
|
501
488
|
def sync_edit_file(
|
|
502
|
-
file_path: str,
|
|
503
|
-
old_string: str,
|
|
504
|
-
new_string: str,
|
|
489
|
+
file_path: Annotated[str, "Absolute path to the file to edit. Must be absolute, not relative."],
|
|
490
|
+
old_string: Annotated[str, "The exact text to find and replace. Must be unique in the file unless replace_all is True."],
|
|
491
|
+
new_string: Annotated[str, "The text to replace old_string with. Must be different from old_string."],
|
|
505
492
|
runtime: ToolRuntime[None, FilesystemState],
|
|
506
493
|
*,
|
|
507
|
-
replace_all: bool = False,
|
|
494
|
+
replace_all: Annotated[bool, "If True, replace all occurrences of old_string. If False (default), old_string must be unique."] = False,
|
|
508
495
|
) -> Command | str:
|
|
509
496
|
"""Synchronous wrapper for edit_file tool."""
|
|
510
497
|
resolved_backend = _get_backend(backend, runtime)
|
|
@@ -527,12 +514,12 @@ def _edit_file_tool_generator(
|
|
|
527
514
|
return f"Successfully replaced {res.occurrences} instance(s) of the string in '{res.path}'"
|
|
528
515
|
|
|
529
516
|
async def async_edit_file(
|
|
530
|
-
file_path: str,
|
|
531
|
-
old_string: str,
|
|
532
|
-
new_string: str,
|
|
517
|
+
file_path: Annotated[str, "Absolute path to the file to edit. Must be absolute, not relative."],
|
|
518
|
+
old_string: Annotated[str, "The exact text to find and replace. Must be unique in the file unless replace_all is True."],
|
|
519
|
+
new_string: Annotated[str, "The text to replace old_string with. Must be different from old_string."],
|
|
533
520
|
runtime: ToolRuntime[None, FilesystemState],
|
|
534
521
|
*,
|
|
535
|
-
replace_all: bool = False,
|
|
522
|
+
replace_all: Annotated[bool, "If True, replace all occurrences of old_string. If False (default), old_string must be unique."] = False,
|
|
536
523
|
) -> Command | str:
|
|
537
524
|
"""Asynchronous wrapper for edit_file tool."""
|
|
538
525
|
resolved_backend = _get_backend(backend, runtime)
|
|
@@ -577,7 +564,11 @@ def _glob_tool_generator(
|
|
|
577
564
|
"""
|
|
578
565
|
tool_description = custom_description or GLOB_TOOL_DESCRIPTION
|
|
579
566
|
|
|
580
|
-
def sync_glob(
|
|
567
|
+
def sync_glob(
|
|
568
|
+
pattern: Annotated[str, "Glob pattern to match files (e.g., '**/*.py', '*.txt', '/subdir/**/*.md')."],
|
|
569
|
+
runtime: ToolRuntime[None, FilesystemState],
|
|
570
|
+
path: Annotated[str, "Base directory to search from. Defaults to root '/'."] = "/",
|
|
571
|
+
) -> str:
|
|
581
572
|
"""Synchronous wrapper for glob tool."""
|
|
582
573
|
resolved_backend = _get_backend(backend, runtime)
|
|
583
574
|
infos = resolved_backend.glob_info(pattern, path=path)
|
|
@@ -585,7 +576,11 @@ def _glob_tool_generator(
|
|
|
585
576
|
result = truncate_if_too_long(paths)
|
|
586
577
|
return str(result)
|
|
587
578
|
|
|
588
|
-
async def async_glob(
|
|
579
|
+
async def async_glob(
|
|
580
|
+
pattern: Annotated[str, "Glob pattern to match files (e.g., '**/*.py', '*.txt', '/subdir/**/*.md')."],
|
|
581
|
+
runtime: ToolRuntime[None, FilesystemState],
|
|
582
|
+
path: Annotated[str, "Base directory to search from. Defaults to root '/'."] = "/",
|
|
583
|
+
) -> str:
|
|
589
584
|
"""Asynchronous wrapper for glob tool."""
|
|
590
585
|
resolved_backend = _get_backend(backend, runtime)
|
|
591
586
|
infos = await resolved_backend.aglob_info(pattern, path=path)
|
|
@@ -617,11 +612,14 @@ def _grep_tool_generator(
|
|
|
617
612
|
tool_description = custom_description or GREP_TOOL_DESCRIPTION
|
|
618
613
|
|
|
619
614
|
def sync_grep(
|
|
620
|
-
pattern: str,
|
|
615
|
+
pattern: Annotated[str, "Text pattern to search for (literal string, not regex)."],
|
|
621
616
|
runtime: ToolRuntime[None, FilesystemState],
|
|
622
|
-
path: str | None = None,
|
|
623
|
-
glob: str | None = None,
|
|
624
|
-
output_mode:
|
|
617
|
+
path: Annotated[str | None, "Directory to search in. Defaults to current working directory."] = None,
|
|
618
|
+
glob: Annotated[str | None, "Glob pattern to filter which files to search (e.g., '*.py')."] = None,
|
|
619
|
+
output_mode: Annotated[
|
|
620
|
+
Literal["files_with_matches", "content", "count"],
|
|
621
|
+
"Output format: 'files_with_matches' (file paths only, default), 'content' (matching lines with context), 'count' (match counts per file).",
|
|
622
|
+
] = "files_with_matches",
|
|
625
623
|
) -> str:
|
|
626
624
|
"""Synchronous wrapper for grep tool."""
|
|
627
625
|
resolved_backend = _get_backend(backend, runtime)
|
|
@@ -632,11 +630,14 @@ def _grep_tool_generator(
|
|
|
632
630
|
return truncate_if_too_long(formatted) # type: ignore[arg-type]
|
|
633
631
|
|
|
634
632
|
async def async_grep(
|
|
635
|
-
pattern: str,
|
|
633
|
+
pattern: Annotated[str, "Text pattern to search for (literal string, not regex)."],
|
|
636
634
|
runtime: ToolRuntime[None, FilesystemState],
|
|
637
|
-
path: str | None = None,
|
|
638
|
-
glob: str | None = None,
|
|
639
|
-
output_mode:
|
|
635
|
+
path: Annotated[str | None, "Directory to search in. Defaults to current working directory."] = None,
|
|
636
|
+
glob: Annotated[str | None, "Glob pattern to filter which files to search (e.g., '*.py')."] = None,
|
|
637
|
+
output_mode: Annotated[
|
|
638
|
+
Literal["files_with_matches", "content", "count"],
|
|
639
|
+
"Output format: 'files_with_matches' (file paths only, default), 'content' (matching lines with context), 'count' (match counts per file).",
|
|
640
|
+
] = "files_with_matches",
|
|
640
641
|
) -> str:
|
|
641
642
|
"""Asynchronous wrapper for grep tool."""
|
|
642
643
|
resolved_backend = _get_backend(backend, runtime)
|
|
@@ -666,9 +667,6 @@ def _supports_execution(backend: BackendProtocol) -> bool:
|
|
|
666
667
|
Returns:
|
|
667
668
|
True if the backend supports execution, False otherwise.
|
|
668
669
|
"""
|
|
669
|
-
# Import here to avoid circular dependency
|
|
670
|
-
from deepagents.backends.composite import CompositeBackend
|
|
671
|
-
|
|
672
670
|
# For CompositeBackend, check the default backend
|
|
673
671
|
if isinstance(backend, CompositeBackend):
|
|
674
672
|
return isinstance(backend.default, SandboxBackendProtocol)
|
|
@@ -693,7 +691,7 @@ def _execute_tool_generator(
|
|
|
693
691
|
tool_description = custom_description or EXECUTE_TOOL_DESCRIPTION
|
|
694
692
|
|
|
695
693
|
def sync_execute(
|
|
696
|
-
command: str,
|
|
694
|
+
command: Annotated[str, "Shell command to execute in the sandbox environment."],
|
|
697
695
|
runtime: ToolRuntime[None, FilesystemState],
|
|
698
696
|
) -> str:
|
|
699
697
|
"""Synchronous wrapper for execute tool."""
|
|
@@ -726,7 +724,7 @@ def _execute_tool_generator(
|
|
|
726
724
|
return "".join(parts)
|
|
727
725
|
|
|
728
726
|
async def async_execute(
|
|
729
|
-
command: str,
|
|
727
|
+
command: Annotated[str, "Shell command to execute in the sandbox environment."],
|
|
730
728
|
runtime: ToolRuntime[None, FilesystemState],
|
|
731
729
|
) -> str:
|
|
732
730
|
"""Asynchronous wrapper for execute tool."""
|
|
@@ -766,6 +764,37 @@ def _execute_tool_generator(
|
|
|
766
764
|
)
|
|
767
765
|
|
|
768
766
|
|
|
767
|
+
# Tools that should be excluded from the large result eviction logic.
|
|
768
|
+
#
|
|
769
|
+
# This tuple contains tools that should NOT have their results evicted to the filesystem
|
|
770
|
+
# when they exceed token limits. Tools are excluded for different reasons:
|
|
771
|
+
#
|
|
772
|
+
# 1. Tools with built-in truncation (ls, glob, grep):
|
|
773
|
+
# These tools truncate their own output when it becomes too large. When these tools
|
|
774
|
+
# produce truncated output due to many matches, it typically indicates the query
|
|
775
|
+
# needs refinement rather than full result preservation. In such cases, the truncated
|
|
776
|
+
# matches are potentially more like noise and the LLM should be prompted to narrow
|
|
777
|
+
# its search criteria instead.
|
|
778
|
+
#
|
|
779
|
+
# 2. Tools with problematic truncation behavior (read_file):
|
|
780
|
+
# read_file is tricky to handle as the failure mode here is single long lines
|
|
781
|
+
# (e.g., imagine a jsonl file with very long payloads on each line). If we try to
|
|
782
|
+
# truncate the result of read_file, the agent may then attempt to re-read the
|
|
783
|
+
# truncated file using read_file again, which won't help.
|
|
784
|
+
#
|
|
785
|
+
# 3. Tools that never exceed limits (edit_file, write_file):
|
|
786
|
+
# These tools return minimal confirmation messages and are never expected to produce
|
|
787
|
+
# output large enough to exceed token limits, so checking them would be unnecessary.
|
|
788
|
+
TOOLS_EXCLUDED_FROM_EVICTION = (
|
|
789
|
+
"ls",
|
|
790
|
+
"glob",
|
|
791
|
+
"grep",
|
|
792
|
+
"read_file",
|
|
793
|
+
"edit_file",
|
|
794
|
+
"write_file",
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
|
|
769
798
|
TOOL_GENERATORS = {
|
|
770
799
|
"ls": _ls_tool_generator,
|
|
771
800
|
"read_file": _read_file_tool_generator,
|
|
@@ -814,8 +843,9 @@ class FilesystemMiddleware(AgentMiddleware):
|
|
|
814
843
|
"""Middleware for providing filesystem and optional execution tools to an agent.
|
|
815
844
|
|
|
816
845
|
This middleware adds filesystem tools to the agent: `ls`, `read_file`, `write_file`,
|
|
817
|
-
`edit_file`, `glob`, and `grep`.
|
|
818
|
-
|
|
846
|
+
`edit_file`, `glob`, and `grep`.
|
|
847
|
+
|
|
848
|
+
Files can be stored using any backend that implements the `BackendProtocol`.
|
|
819
849
|
|
|
820
850
|
If the backend implements `SandboxBackendProtocol`, an `execute` tool is also added
|
|
821
851
|
for running shell commands.
|
|
@@ -836,8 +866,6 @@ class FilesystemMiddleware(AgentMiddleware):
|
|
|
836
866
|
tool_token_limit_before_evict: Token limit before evicting a tool result to the
|
|
837
867
|
filesystem.
|
|
838
868
|
|
|
839
|
-
Defaults to 20,000 tokens.
|
|
840
|
-
|
|
841
869
|
When exceeded, writes the result using the configured backend and replaces it
|
|
842
870
|
with a truncated preview and file reference.
|
|
843
871
|
|
|
@@ -1070,6 +1098,7 @@ class FilesystemMiddleware(AgentMiddleware):
|
|
|
1070
1098
|
processed_message = ToolMessage(
|
|
1071
1099
|
content=replacement_text,
|
|
1072
1100
|
tool_call_id=message.tool_call_id,
|
|
1101
|
+
name=message.name,
|
|
1073
1102
|
)
|
|
1074
1103
|
return processed_message, result.files_update
|
|
1075
1104
|
|
|
@@ -1128,6 +1157,7 @@ class FilesystemMiddleware(AgentMiddleware):
|
|
|
1128
1157
|
processed_message = ToolMessage(
|
|
1129
1158
|
content=replacement_text,
|
|
1130
1159
|
tool_call_id=message.tool_call_id,
|
|
1160
|
+
name=message.name,
|
|
1131
1161
|
)
|
|
1132
1162
|
return processed_message, result.files_update
|
|
1133
1163
|
|
|
@@ -1247,7 +1277,7 @@ class FilesystemMiddleware(AgentMiddleware):
|
|
|
1247
1277
|
Returns:
|
|
1248
1278
|
The raw ToolMessage, or a pseudo tool message with the ToolResult in state.
|
|
1249
1279
|
"""
|
|
1250
|
-
if self.tool_token_limit_before_evict is None or request.tool_call["name"] in
|
|
1280
|
+
if self.tool_token_limit_before_evict is None or request.tool_call["name"] in TOOLS_EXCLUDED_FROM_EVICTION:
|
|
1251
1281
|
return handler(request)
|
|
1252
1282
|
|
|
1253
1283
|
tool_result = handler(request)
|
|
@@ -1267,7 +1297,7 @@ class FilesystemMiddleware(AgentMiddleware):
|
|
|
1267
1297
|
Returns:
|
|
1268
1298
|
The raw ToolMessage, or a pseudo tool message with the ToolResult in state.
|
|
1269
1299
|
"""
|
|
1270
|
-
if self.tool_token_limit_before_evict is None or request.tool_call["name"] in
|
|
1300
|
+
if self.tool_token_limit_before_evict is None or request.tool_call["name"] in TOOLS_EXCLUDED_FROM_EVICTION:
|
|
1271
1301
|
return await handler(request)
|
|
1272
1302
|
|
|
1273
1303
|
tool_result = await handler(request)
|