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.
Files changed (33) hide show
  1. deepagents/backends/composite.py +37 -2
  2. deepagents/backends/protocol.py +48 -0
  3. deepagents/backends/sandbox.py +341 -0
  4. deepagents/backends/store.py +3 -11
  5. deepagents/graph.py +7 -3
  6. deepagents/middleware/filesystem.py +224 -21
  7. deepagents/middleware/subagents.py +7 -4
  8. {deepagents-0.2.5.dist-info → deepagents-0.2.6.dist-info}/METADATA +5 -4
  9. deepagents-0.2.6.dist-info/RECORD +19 -0
  10. deepagents-0.2.6.dist-info/top_level.txt +1 -0
  11. deepagents-0.2.5.dist-info/RECORD +0 -38
  12. deepagents-0.2.5.dist-info/licenses/LICENSE +0 -21
  13. deepagents-0.2.5.dist-info/top_level.txt +0 -2
  14. deepagents-cli/README.md +0 -3
  15. deepagents-cli/deepagents_cli/README.md +0 -196
  16. deepagents-cli/deepagents_cli/__init__.py +0 -5
  17. deepagents-cli/deepagents_cli/__main__.py +0 -6
  18. deepagents-cli/deepagents_cli/agent.py +0 -278
  19. deepagents-cli/deepagents_cli/agent_memory.py +0 -226
  20. deepagents-cli/deepagents_cli/commands.py +0 -89
  21. deepagents-cli/deepagents_cli/config.py +0 -118
  22. deepagents-cli/deepagents_cli/default_agent_prompt.md +0 -110
  23. deepagents-cli/deepagents_cli/execution.py +0 -636
  24. deepagents-cli/deepagents_cli/file_ops.py +0 -347
  25. deepagents-cli/deepagents_cli/input.py +0 -270
  26. deepagents-cli/deepagents_cli/main.py +0 -226
  27. deepagents-cli/deepagents_cli/py.typed +0 -0
  28. deepagents-cli/deepagents_cli/token_utils.py +0 -63
  29. deepagents-cli/deepagents_cli/tools.py +0 -140
  30. deepagents-cli/deepagents_cli/ui.py +0 -489
  31. deepagents-cli/tests/test_file_ops.py +0 -119
  32. deepagents-cli/tests/test_placeholder.py +0 -5
  33. {deepagents-0.2.5.dist-info → deepagents-0.2.6.dist-info}/WHEEL +0 -0
@@ -1,196 +0,0 @@
1
- # DeepAgents CLI
2
-
3
- Interactive command-line interface for DeepAgents - an AI coding assistant with file operations, web search, and shell command execution.
4
-
5
- ## Architecture
6
-
7
- The CLI is organized into focused modules:
8
-
9
- ```
10
- cli/
11
- ├── __init__.py # Package exports
12
- ├── __main__.py # Entry point for `python -m deepagents.cli`
13
- ├── main.py # CLI loop, argument parsing, main orchestration
14
- ├── config.py # Configuration, constants, colors, model creation
15
- ├── tools.py # Custom tools (http_request, web_search)
16
- ├── ui.py # Display logic, TokenTracker, help screens
17
- ├── input.py # Input handling, completers, prompt session
18
- ├── commands.py # Slash command and bash command handlers
19
- ├── execution.py # Task execution, streaming, HITL approval
20
- └── agent.py # Agent creation, management, listing, reset
21
- ```
22
-
23
- ## Module Responsibilities
24
-
25
- ### `main.py` - Entry Point & Main Loop
26
- - **Purpose**: CLI entry point, argument parsing, main interactive loop
27
- - **Key Functions**:
28
- - `cli_main()` - Console script entry point (called when you run `deepagents`)
29
- - `main()` - Async main function that orchestrates agent creation and CLI
30
- - `simple_cli()` - Main interactive loop handling user input
31
- - `parse_args()` - Command-line argument parsing
32
- - `check_cli_dependencies()` - Validates required packages are installed
33
-
34
- ### `config.py` - Configuration & Constants
35
- - **Purpose**: Centralized configuration, constants, and model creation
36
- - **Key Exports**:
37
- - `COLORS` - Color scheme for terminal output
38
- - `DEEP_AGENTS_ASCII` - ASCII art banner
39
- - `COMMANDS` - Available slash commands
40
- - `console` - Rich Console instance
41
- - `create_model()` - Creates OpenAI or Anthropic model based on API keys
42
- - `get_default_coding_instructions()` - Loads default agent prompt
43
-
44
- ### `tools.py` - Custom Agent Tools
45
- - **Purpose**: Additional tools for the agent beyond built-in filesystem operations
46
- - **Tools**:
47
- - `http_request()` - Make HTTP requests to APIs
48
- - `web_search()` - Search the web using Tavily API
49
- - `tavily_client` - Initialized Tavily client (if API key available)
50
-
51
- ### `ui.py` - Display & Rendering
52
- - **Purpose**: All UI rendering and display logic
53
- - **Key Components**:
54
- - `TokenTracker` - Track and display token usage across the session
55
- - `render_todo_list()` - Render todo list with checkboxes
56
- - `show_interactive_help()` - Display available commands during session
57
- - `show_help()` - Full help screen
58
- - `format_tool_message_content()` - Format tool messages for display
59
- - `truncate_value()` - Truncate long values for readable display
60
-
61
- ### `input.py` - Input Handling
62
- - **Purpose**: User input, completers, and prompt session configuration
63
- - **Key Components**:
64
- - `FilePathCompleter` - Autocomplete for `@file` mentions
65
- - `CommandCompleter` - Autocomplete for `/commands`
66
- - `BashCompleter` - Autocomplete for `!bash` commands
67
- - `parse_file_mentions()` - Extract `@file` mentions and inject content
68
- - `create_prompt_session()` - Configure prompt_toolkit session with:
69
- - Multi-line input (Alt+Enter for newlines, Enter to submit)
70
- - Command history
71
- - File/command autocomplete
72
- - External editor support (Ctrl+E)
73
-
74
- ### `commands.py` - Command Handlers
75
- - **Purpose**: Handle slash commands (`/help`, `/clear`, etc.) and bash execution
76
- - **Key Functions**:
77
- - `handle_command()` - Route and execute slash commands
78
- - `execute_bash_command()` - Execute bash commands prefixed with `!`
79
-
80
- ### `execution.py` - Task Execution & Streaming
81
- - **Purpose**: Core execution logic, streaming responses, HITL (Human-in-the-Loop)
82
- - **Key Functions**:
83
- - `execute_task()` - Main execution function that:
84
- - Parses file mentions
85
- - Streams agent responses
86
- - Displays tool calls with icons
87
- - Renders todo list updates
88
- - Tracks token usage
89
- - Handles Ctrl+C interruptions
90
- - `prompt_for_shell_approval()` - Interactive shell command approval with arrow keys
91
- - **Features**:
92
- - Dual-stream mode (messages + updates) for HITL support
93
- - Real-time todo list rendering
94
- - Spinner with status updates
95
- - Token tracking integration
96
-
97
- ### `agent.py` - Agent Management
98
- - **Purpose**: Agent creation, configuration, and management commands
99
- - **Key Functions**:
100
- - `create_agent_with_config()` - Create agent with:
101
- - Filesystem backends (working directory + agent directory)
102
- - Long-term memory middleware
103
- - Shell execution with HITL approval
104
- - Custom system prompt
105
- - `list_agents()` - List all agents in `~/.deepagents/`
106
- - `reset_agent()` - Reset agent to default or copy from another agent
107
-
108
- ## Data Flow
109
-
110
- ```
111
- User Input
112
-
113
- main.py (simple_cli)
114
-
115
- input.py (parse_file_mentions, completers)
116
-
117
- execution.py (execute_task)
118
-
119
- agent.py (agent created with tools from tools.py)
120
-
121
- Stream responses → ui.py (render todos, display tool calls)
122
- → execution.py (HITL approval if needed)
123
-
124
- Display output via ui.py (TokenTracker, console)
125
- ```
126
-
127
- ## Key Features
128
-
129
- ### File Context Injection
130
- Type `@filename` and press Tab to autocomplete and inject file content into your prompt.
131
-
132
- ### Interactive Commands
133
- - `/help` - Show help
134
- - `/clear` - Clear screen and reset conversation
135
- - `/tokens` - Show token usage
136
- - `/quit` or `/exit` - Exit the CLI
137
-
138
- ### Bash Commands
139
- Type `!command` to execute bash commands directly (e.g., `!ls`, `!git status`)
140
-
141
- ### Todo List Tracking
142
- The agent can create and update a visual todo list for multi-step tasks.
143
-
144
- ### File Operation Summaries & Diff Viewer
145
- - File reads now show a concise summary with the number of lines streamed (e.g., `⏺ Read(example.py)` followed by `⎿ Read 44 lines (lines 1-44)`).
146
- - Writes and edits capture before/after snapshots, reporting lines added or removed plus bytes written.
147
- - A Rich-powered unified diff renders in-line with syntax highlighting so you can review every proposed change before confirming.
148
- - Diff output truncates gracefully for very large edits while still surfacing a summary.
149
- - When Human-in-the-Loop approval is required, the proposed diff is shown *before* you choose Approve/Reject.
150
-
151
- ### Human-in-the-Loop Shell Approval
152
- Shell commands require user approval with an interactive arrow-key menu.
153
-
154
- ### Multi-line Input
155
- - **Enter** - Submit (or accept completion if menu is open)
156
- - **Alt+Enter** - Insert newline (Option+Enter on Mac, or ESC then Enter)
157
- - **Ctrl+E** - Open in external editor (nano by default)
158
-
159
- ## Agent Storage
160
-
161
- Each agent stores its state in `~/.deepagents/AGENT_NAME/`:
162
- - `agent.md` - Agent's custom instructions (long-term memory)
163
- - `memories/` - Additional context files
164
- - `history` - Command history
165
-
166
- ## Development
167
-
168
- To modify the CLI:
169
-
170
- 1. **UI changes** → Edit `ui.py` or `input.py`
171
- 2. **Add new tools** → Edit `tools.py`
172
- 3. **Change execution flow** → Edit `execution.py`
173
- 4. **Add commands** → Edit `commands.py`
174
- 5. **Agent configuration** → Edit `agent.py`
175
- 6. **Constants/colors** → Edit `config.py`
176
-
177
- ## Running During Development
178
-
179
- ```bash
180
- # From project root
181
- uv run python -m deepagents.cli
182
-
183
- # Or install in editable mode
184
- uv pip install -e .
185
- deepagents
186
- ```
187
-
188
- ## Entry Point
189
-
190
- The CLI is registered in `pyproject.toml` as:
191
- ```toml
192
- [project.scripts]
193
- deepagents = "deepagents.cli:cli_main"
194
- ```
195
-
196
- This means when users install the package, they can run `deepagents` directly.
@@ -1,5 +0,0 @@
1
- """DeepAgents CLI - Interactive AI coding assistant."""
2
-
3
- from .main import cli_main
4
-
5
- __all__ = ["cli_main"]
@@ -1,6 +0,0 @@
1
- """Allow running the CLI as: python -m deepagents.cli"""
2
-
3
- from .main import cli_main
4
-
5
- if __name__ == "__main__":
6
- cli_main()
@@ -1,278 +0,0 @@
1
- """Agent management and creation for the CLI."""
2
-
3
- import os
4
- import shutil
5
- from pathlib import Path
6
-
7
- from deepagents import create_deep_agent
8
- from deepagents.backends import CompositeBackend
9
- from deepagents.backends.filesystem import FilesystemBackend
10
- from deepagents.middleware.resumable_shell import ResumableShellToolMiddleware
11
- from langchain.agents.middleware import HostExecutionPolicy
12
- from langgraph.checkpoint.memory import InMemorySaver
13
-
14
- from .agent_memory import AgentMemoryMiddleware
15
- from .config import COLORS, config, console, get_default_coding_instructions
16
-
17
-
18
- def list_agents():
19
- """List all available agents."""
20
- agents_dir = Path.home() / ".deepagents"
21
-
22
- if not agents_dir.exists() or not any(agents_dir.iterdir()):
23
- console.print("[yellow]No agents found.[/yellow]")
24
- console.print(
25
- "[dim]Agents will be created in ~/.deepagents/ when you first use them.[/dim]",
26
- style=COLORS["dim"],
27
- )
28
- return
29
-
30
- console.print("\n[bold]Available Agents:[/bold]\n", style=COLORS["primary"])
31
-
32
- for agent_path in sorted(agents_dir.iterdir()):
33
- if agent_path.is_dir():
34
- agent_name = agent_path.name
35
- agent_md = agent_path / "agent.md"
36
-
37
- if agent_md.exists():
38
- console.print(f" • [bold]{agent_name}[/bold]", style=COLORS["primary"])
39
- console.print(f" {agent_path}", style=COLORS["dim"])
40
- else:
41
- console.print(
42
- f" • [bold]{agent_name}[/bold] [dim](incomplete)[/dim]", style=COLORS["tool"]
43
- )
44
- console.print(f" {agent_path}", style=COLORS["dim"])
45
-
46
- console.print()
47
-
48
-
49
- def reset_agent(agent_name: str, source_agent: str = None):
50
- """Reset an agent to default or copy from another agent."""
51
- agents_dir = Path.home() / ".deepagents"
52
- agent_dir = agents_dir / agent_name
53
-
54
- if source_agent:
55
- source_dir = agents_dir / source_agent
56
- source_md = source_dir / "agent.md"
57
-
58
- if not source_md.exists():
59
- console.print(
60
- f"[bold red]Error:[/bold red] Source agent '{source_agent}' not found or has no agent.md"
61
- )
62
- return
63
-
64
- source_content = source_md.read_text()
65
- action_desc = f"contents of agent '{source_agent}'"
66
- else:
67
- source_content = get_default_coding_instructions()
68
- action_desc = "default"
69
-
70
- if agent_dir.exists():
71
- shutil.rmtree(agent_dir)
72
- console.print(f"Removed existing agent directory: {agent_dir}", style=COLORS["tool"])
73
-
74
- agent_dir.mkdir(parents=True, exist_ok=True)
75
- agent_md = agent_dir / "agent.md"
76
- agent_md.write_text(source_content)
77
-
78
- console.print(f"✓ Agent '{agent_name}' reset to {action_desc}", style=COLORS["primary"])
79
- console.print(f"Location: {agent_dir}\n", style=COLORS["dim"])
80
-
81
-
82
- def get_system_prompt() -> str:
83
- """Get the base system prompt for the agent.
84
-
85
- Returns:
86
- The system prompt string (without agent.md content)
87
- """
88
- return f"""### Current Working Directory
89
-
90
- The filesystem backend is currently operating in: `{Path.cwd()}`
91
-
92
- ### Memory System Reminder
93
-
94
- Your long-term memory is stored in /memories/ and persists across sessions.
95
-
96
- **IMPORTANT - Check memories before answering:**
97
- - When asked "what do you know about X?" → Run `ls /memories/` FIRST, then read relevant files
98
- - When starting a task → Check if you have guides or examples in /memories/
99
- - At the beginning of new sessions → Consider checking `ls /memories/` to see what context you have
100
-
101
- Base your answers on saved knowledge (from /memories/) when available, supplemented by general knowledge.
102
-
103
- ### Human-in-the-Loop Tool Approval
104
-
105
- Some tool calls require user approval before execution. When a tool call is rejected by the user:
106
- 1. Accept their decision immediately - do NOT retry the same command
107
- 2. Explain that you understand they rejected the action
108
- 3. Suggest an alternative approach or ask for clarification
109
- 4. Never attempt the exact same rejected command again
110
-
111
- Respect the user's decisions and work with them collaboratively.
112
-
113
- ### Web Search Tool Usage
114
-
115
- When you use the web_search tool:
116
- 1. The tool will return search results with titles, URLs, and content excerpts
117
- 2. You MUST read and process these results, then respond naturally to the user
118
- 3. NEVER show raw JSON or tool results directly to the user
119
- 4. Synthesize the information from multiple sources into a coherent answer
120
- 5. Cite your sources by mentioning page titles or URLs when relevant
121
- 6. If the search doesn't find what you need, explain what you found and ask clarifying questions
122
-
123
- The user only sees your text responses - not tool results. Always provide a complete, natural language answer after using web_search.
124
-
125
- ### Todo List Management
126
-
127
- When using the write_todos tool:
128
- 1. Keep the todo list MINIMAL - aim for 3-6 items maximum
129
- 2. Only create todos for complex, multi-step tasks that truly need tracking
130
- 3. Break down work into clear, actionable items without over-fragmenting
131
- 4. For simple tasks (1-2 steps), just do them directly without creating todos
132
- 5. When first creating a todo list for a task, ALWAYS ask the user if the plan looks good before starting work
133
- - Create the todos, let them render, then ask: "Does this plan look good?" or similar
134
- - Wait for the user's response before marking the first todo as in_progress
135
- - If they want changes, adjust the plan accordingly
136
- 6. Update todo status promptly as you complete each item
137
-
138
- The todo list is a planning tool - use it judiciously to avoid overwhelming the user with excessive task tracking."""
139
-
140
-
141
- def create_agent_with_config(model, assistant_id: str, tools: list):
142
- """Create and configure an agent with the specified model and tools."""
143
- shell_middleware = ResumableShellToolMiddleware(
144
- workspace_root=os.getcwd(), execution_policy=HostExecutionPolicy()
145
- )
146
-
147
- # For long-term memory, point to ~/.deepagents/AGENT_NAME/ with /memories/ prefix
148
- agent_dir = Path.home() / ".deepagents" / assistant_id
149
- agent_dir.mkdir(parents=True, exist_ok=True)
150
- agent_md = agent_dir / "agent.md"
151
- if not agent_md.exists():
152
- source_content = get_default_coding_instructions()
153
- agent_md.write_text(source_content)
154
-
155
- # Long-term backend - rooted at agent directory
156
- # This handles both /memories/ files and /agent.md
157
- long_term_backend = FilesystemBackend(root_dir=agent_dir, virtual_mode=True)
158
-
159
- # Composite backend: current working directory for default, agent directory for /memories/
160
- backend = CompositeBackend(
161
- default=FilesystemBackend(), routes={"/memories/": long_term_backend}
162
- )
163
-
164
- # Use the same backend for agent memory middleware
165
- agent_middleware = [
166
- AgentMemoryMiddleware(backend=long_term_backend, memory_path="/memories/"),
167
- shell_middleware,
168
- ]
169
-
170
- # Get the system prompt
171
- system_prompt = get_system_prompt()
172
-
173
- # Helper functions for formatting tool descriptions in HITL prompts
174
- def format_write_file_description(tool_call: dict) -> str:
175
- """Format write_file tool call for approval prompt."""
176
- args = tool_call.get("args", {})
177
- file_path = args.get("file_path", "unknown")
178
- content = args.get("content", "")
179
-
180
- action = "Overwrite" if os.path.exists(file_path) else "Create"
181
- line_count = len(content.splitlines())
182
- size = len(content.encode("utf-8"))
183
-
184
- return f"File: {file_path}\nAction: {action} file\nLines: {line_count} · Bytes: {size}"
185
-
186
- def format_edit_file_description(tool_call: dict) -> str:
187
- """Format edit_file tool call for approval prompt."""
188
- args = tool_call.get("args", {})
189
- file_path = args.get("file_path", "unknown")
190
- old_string = args.get("old_string", "")
191
- new_string = args.get("new_string", "")
192
- replace_all = bool(args.get("replace_all", False))
193
-
194
- delta = len(new_string) - len(old_string)
195
-
196
- return (
197
- f"File: {file_path}\n"
198
- f"Action: Replace text ({'all occurrences' if replace_all else 'single occurrence'})\n"
199
- f"Snippet delta: {delta:+} characters"
200
- )
201
-
202
- def format_web_search_description(tool_call: dict) -> str:
203
- """Format web_search tool call for approval prompt."""
204
- args = tool_call.get("args", {})
205
- query = args.get("query", "unknown")
206
- max_results = args.get("max_results", 5)
207
-
208
- return f"Query: {query}\nMax results: {max_results}\n\n⚠️ This will use Tavily API credits"
209
-
210
- def format_task_description(tool_call: dict) -> str:
211
- """Format task (subagent) tool call for approval prompt."""
212
- args = tool_call.get("args", {})
213
- description = args.get("description", "unknown")
214
- prompt = args.get("prompt", "")
215
-
216
- # Truncate prompt if too long
217
- prompt_preview = prompt[:300]
218
- if len(prompt) > 300:
219
- prompt_preview += "..."
220
-
221
- return (
222
- f"Task: {description}\n\n"
223
- f"Instructions to subagent:\n"
224
- f"{'─' * 40}\n"
225
- f"{prompt_preview}\n"
226
- f"{'─' * 40}\n\n"
227
- f"⚠️ Subagent will have access to file operations and shell commands"
228
- )
229
-
230
- # Configure human-in-the-loop for potentially destructive tools
231
- from langchain.agents.middleware import InterruptOnConfig
232
-
233
- shell_interrupt_config: InterruptOnConfig = {
234
- "allowed_decisions": ["approve", "reject"],
235
- "description": lambda tool_call, state, runtime: (
236
- f"Shell Command: {tool_call['args'].get('command', 'N/A')}\n"
237
- f"Working Directory: {os.getcwd()}"
238
- ),
239
- }
240
-
241
- write_file_interrupt_config: InterruptOnConfig = {
242
- "allowed_decisions": ["approve", "reject"],
243
- "description": lambda tool_call, state, runtime: format_write_file_description(tool_call),
244
- }
245
-
246
- edit_file_interrupt_config: InterruptOnConfig = {
247
- "allowed_decisions": ["approve", "reject"],
248
- "description": lambda tool_call, state, runtime: format_edit_file_description(tool_call),
249
- }
250
-
251
- web_search_interrupt_config: InterruptOnConfig = {
252
- "allowed_decisions": ["approve", "reject"],
253
- "description": lambda tool_call, state, runtime: format_web_search_description(tool_call),
254
- }
255
-
256
- task_interrupt_config: InterruptOnConfig = {
257
- "allowed_decisions": ["approve", "reject"],
258
- "description": lambda tool_call, state, runtime: format_task_description(tool_call),
259
- }
260
-
261
- agent = create_deep_agent(
262
- model=model,
263
- system_prompt=system_prompt,
264
- tools=tools,
265
- backend=backend,
266
- middleware=agent_middleware,
267
- interrupt_on={
268
- "shell": shell_interrupt_config,
269
- "write_file": write_file_interrupt_config,
270
- "edit_file": edit_file_interrupt_config,
271
- "web_search": web_search_interrupt_config,
272
- "task": task_interrupt_config,
273
- },
274
- ).with_config(config)
275
-
276
- agent.checkpointer = InMemorySaver()
277
-
278
- return agent