deepagents 0.2.5__py3-none-any.whl → 0.2.7__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/__init__.py +0 -2
- deepagents/middleware/filesystem.py +224 -21
- deepagents/middleware/subagents.py +7 -4
- {deepagents-0.2.5.dist-info → deepagents-0.2.7.dist-info}/METADATA +5 -4
- deepagents-0.2.7.dist-info/RECORD +18 -0
- deepagents-0.2.7.dist-info/top_level.txt +1 -0
- deepagents/middleware/resumable_shell.py +0 -86
- 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.7.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,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
|