deepagents-cli 0.0.3__py3-none-any.whl → 0.0.5__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.
Potentially problematic release.
This version of deepagents-cli might be problematic. Click here for more details.
- deepagents_cli/__init__.py +5 -0
- deepagents_cli/__main__.py +6 -0
- deepagents_cli/agent.py +278 -0
- deepagents_cli/cli.py +13 -0
- deepagents_cli/commands.py +89 -0
- deepagents_cli/config.py +138 -0
- deepagents_cli/execution.py +644 -0
- deepagents_cli/file_ops.py +347 -0
- deepagents_cli/input.py +249 -0
- deepagents_cli/main.py +226 -0
- deepagents_cli/py.typed +0 -0
- deepagents_cli/token_utils.py +63 -0
- deepagents_cli/tools.py +140 -0
- deepagents_cli/ui.py +489 -0
- deepagents_cli-0.0.5.dist-info/METADATA +18 -0
- deepagents_cli-0.0.5.dist-info/RECORD +19 -0
- deepagents_cli-0.0.5.dist-info/entry_points.txt +3 -0
- deepagents_cli-0.0.5.dist-info/top_level.txt +1 -0
- deepagents/__init__.py +0 -7
- deepagents/cli.py +0 -567
- deepagents/default_agent_prompt.md +0 -64
- deepagents/graph.py +0 -144
- deepagents/memory/__init__.py +0 -17
- deepagents/memory/backends/__init__.py +0 -15
- deepagents/memory/backends/composite.py +0 -250
- deepagents/memory/backends/filesystem.py +0 -330
- deepagents/memory/backends/state.py +0 -206
- deepagents/memory/backends/store.py +0 -351
- deepagents/memory/backends/utils.py +0 -319
- deepagents/memory/protocol.py +0 -164
- deepagents/middleware/__init__.py +0 -13
- deepagents/middleware/agent_memory.py +0 -207
- deepagents/middleware/filesystem.py +0 -615
- deepagents/middleware/patch_tool_calls.py +0 -44
- deepagents/middleware/subagents.py +0 -481
- deepagents/pretty_cli.py +0 -289
- deepagents_cli-0.0.3.dist-info/METADATA +0 -551
- deepagents_cli-0.0.3.dist-info/RECORD +0 -24
- deepagents_cli-0.0.3.dist-info/entry_points.txt +0 -2
- deepagents_cli-0.0.3.dist-info/licenses/LICENSE +0 -21
- deepagents_cli-0.0.3.dist-info/top_level.txt +0 -1
- {deepagents_cli-0.0.3.dist-info → deepagents_cli-0.0.5.dist-info}/WHEEL +0 -0
deepagents_cli/main.py
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""Main entry point and CLI loop for deepagents."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import asyncio
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from .agent import create_agent_with_config, list_agents, reset_agent
|
|
9
|
+
from .commands import execute_bash_command, handle_command
|
|
10
|
+
from .config import COLORS, DEEP_AGENTS_ASCII, SessionState, console, create_model
|
|
11
|
+
from .execution import execute_task
|
|
12
|
+
from .input import create_prompt_session
|
|
13
|
+
from .tools import http_request, tavily_client, web_search
|
|
14
|
+
from .ui import TokenTracker, show_help
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def check_cli_dependencies():
|
|
18
|
+
"""Check if CLI optional dependencies are installed."""
|
|
19
|
+
missing = []
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
import rich
|
|
23
|
+
except ImportError:
|
|
24
|
+
missing.append("rich")
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
import requests
|
|
28
|
+
except ImportError:
|
|
29
|
+
missing.append("requests")
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
import dotenv
|
|
33
|
+
except ImportError:
|
|
34
|
+
missing.append("python-dotenv")
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
import tavily
|
|
38
|
+
except ImportError:
|
|
39
|
+
missing.append("tavily-python")
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
import prompt_toolkit
|
|
43
|
+
except ImportError:
|
|
44
|
+
missing.append("prompt-toolkit")
|
|
45
|
+
|
|
46
|
+
if missing:
|
|
47
|
+
print("\n❌ Missing required CLI dependencies!")
|
|
48
|
+
print("\nThe following packages are required to use the deepagents CLI:")
|
|
49
|
+
for pkg in missing:
|
|
50
|
+
print(f" - {pkg}")
|
|
51
|
+
print("\nPlease install them with:")
|
|
52
|
+
print(" pip install deepagents[cli]")
|
|
53
|
+
print("\nOr install all dependencies:")
|
|
54
|
+
print(" pip install 'deepagents[cli]'")
|
|
55
|
+
sys.exit(1)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def parse_args():
|
|
59
|
+
"""Parse command line arguments."""
|
|
60
|
+
parser = argparse.ArgumentParser(
|
|
61
|
+
description="DeepAgents - AI Coding Assistant",
|
|
62
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
63
|
+
add_help=False,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
subparsers = parser.add_subparsers(dest="command", help="Command to run")
|
|
67
|
+
|
|
68
|
+
# List command
|
|
69
|
+
subparsers.add_parser("list", help="List all available agents")
|
|
70
|
+
|
|
71
|
+
# Help command
|
|
72
|
+
subparsers.add_parser("help", help="Show help information")
|
|
73
|
+
|
|
74
|
+
# Reset command
|
|
75
|
+
reset_parser = subparsers.add_parser("reset", help="Reset an agent")
|
|
76
|
+
reset_parser.add_argument("--agent", required=True, help="Name of agent to reset")
|
|
77
|
+
reset_parser.add_argument(
|
|
78
|
+
"--target", dest="source_agent", help="Copy prompt from another agent"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Default interactive mode
|
|
82
|
+
parser.add_argument(
|
|
83
|
+
"--agent",
|
|
84
|
+
default="agent",
|
|
85
|
+
help="Agent identifier for separate memory stores (default: agent).",
|
|
86
|
+
)
|
|
87
|
+
parser.add_argument(
|
|
88
|
+
"--auto-approve",
|
|
89
|
+
action="store_true",
|
|
90
|
+
help="Auto-approve tool usage without prompting (disables human-in-the-loop)",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return parser.parse_args()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
async def simple_cli(agent, assistant_id: str | None, session_state, baseline_tokens: int = 0):
|
|
97
|
+
"""Main CLI loop."""
|
|
98
|
+
console.clear()
|
|
99
|
+
console.print(DEEP_AGENTS_ASCII, style=f"bold {COLORS['primary']}")
|
|
100
|
+
console.print()
|
|
101
|
+
|
|
102
|
+
if tavily_client is None:
|
|
103
|
+
console.print(
|
|
104
|
+
"[yellow]⚠ Web search disabled:[/yellow] TAVILY_API_KEY not found.",
|
|
105
|
+
style=COLORS["dim"],
|
|
106
|
+
)
|
|
107
|
+
console.print(" To enable web search, set your Tavily API key:", style=COLORS["dim"])
|
|
108
|
+
console.print(" export TAVILY_API_KEY=your_api_key_here", style=COLORS["dim"])
|
|
109
|
+
console.print(
|
|
110
|
+
" Or add it to your .env file. Get your key at: https://tavily.com",
|
|
111
|
+
style=COLORS["dim"],
|
|
112
|
+
)
|
|
113
|
+
console.print()
|
|
114
|
+
|
|
115
|
+
console.print("... Ready to code! What would you like to build?", style=COLORS["agent"])
|
|
116
|
+
console.print(f" [dim]Working directory: {Path.cwd()}[/dim]")
|
|
117
|
+
console.print()
|
|
118
|
+
|
|
119
|
+
if session_state.auto_approve:
|
|
120
|
+
console.print(
|
|
121
|
+
" [yellow]⚡ Auto-approve: ON[/yellow] [dim](tools run without confirmation)[/dim]"
|
|
122
|
+
)
|
|
123
|
+
console.print()
|
|
124
|
+
|
|
125
|
+
console.print(
|
|
126
|
+
" Tips: Enter to submit, Alt+Enter for newline, Ctrl+E for editor, Ctrl+T to toggle auto-approve, Ctrl+C to interrupt",
|
|
127
|
+
style=f"dim {COLORS['dim']}",
|
|
128
|
+
)
|
|
129
|
+
console.print()
|
|
130
|
+
|
|
131
|
+
# Create prompt session and token tracker
|
|
132
|
+
session = create_prompt_session(assistant_id, session_state)
|
|
133
|
+
token_tracker = TokenTracker()
|
|
134
|
+
token_tracker.set_baseline(baseline_tokens)
|
|
135
|
+
|
|
136
|
+
while True:
|
|
137
|
+
try:
|
|
138
|
+
user_input = await session.prompt_async()
|
|
139
|
+
user_input = user_input.strip()
|
|
140
|
+
except EOFError:
|
|
141
|
+
break
|
|
142
|
+
except KeyboardInterrupt:
|
|
143
|
+
# Ctrl+C at prompt - exit the program
|
|
144
|
+
console.print("\n\nGoodbye!", style=COLORS["primary"])
|
|
145
|
+
break
|
|
146
|
+
|
|
147
|
+
if not user_input:
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
# Check for slash commands first
|
|
151
|
+
if user_input.startswith("/"):
|
|
152
|
+
result = handle_command(user_input, agent, token_tracker)
|
|
153
|
+
if result == "exit":
|
|
154
|
+
console.print("\nGoodbye!", style=COLORS["primary"])
|
|
155
|
+
break
|
|
156
|
+
if result:
|
|
157
|
+
# Command was handled, continue to next input
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
# Check for bash commands (!)
|
|
161
|
+
if user_input.startswith("!"):
|
|
162
|
+
execute_bash_command(user_input)
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
# Handle regular quit keywords
|
|
166
|
+
if user_input.lower() in ["quit", "exit", "q"]:
|
|
167
|
+
console.print("\nGoodbye!", style=COLORS["primary"])
|
|
168
|
+
break
|
|
169
|
+
|
|
170
|
+
execute_task(user_input, agent, assistant_id, session_state, token_tracker)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
async def main(assistant_id: str, session_state):
|
|
174
|
+
"""Main entry point."""
|
|
175
|
+
# Create the model (checks API keys)
|
|
176
|
+
model = create_model()
|
|
177
|
+
|
|
178
|
+
# Create agent with conditional tools
|
|
179
|
+
tools = [http_request]
|
|
180
|
+
if tavily_client is not None:
|
|
181
|
+
tools.append(web_search)
|
|
182
|
+
|
|
183
|
+
agent = create_agent_with_config(model, assistant_id, tools)
|
|
184
|
+
|
|
185
|
+
# Calculate baseline token count for accurate token tracking
|
|
186
|
+
from .agent import get_system_prompt
|
|
187
|
+
from .token_utils import calculate_baseline_tokens
|
|
188
|
+
|
|
189
|
+
agent_dir = Path.home() / ".deepagents" / assistant_id
|
|
190
|
+
system_prompt = get_system_prompt()
|
|
191
|
+
baseline_tokens = calculate_baseline_tokens(model, agent_dir, system_prompt)
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
await simple_cli(agent, assistant_id, session_state, baseline_tokens)
|
|
195
|
+
except Exception as e:
|
|
196
|
+
console.print(f"\n[bold red]❌ Error:[/bold red] {e}\n")
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def cli_main():
|
|
200
|
+
"""Entry point for console script."""
|
|
201
|
+
# Check dependencies first
|
|
202
|
+
check_cli_dependencies()
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
args = parse_args()
|
|
206
|
+
|
|
207
|
+
if args.command == "help":
|
|
208
|
+
show_help()
|
|
209
|
+
elif args.command == "list":
|
|
210
|
+
list_agents()
|
|
211
|
+
elif args.command == "reset":
|
|
212
|
+
reset_agent(args.agent, args.source_agent)
|
|
213
|
+
else:
|
|
214
|
+
# Create session state from args
|
|
215
|
+
session_state = SessionState(auto_approve=args.auto_approve)
|
|
216
|
+
|
|
217
|
+
# API key validation happens in create_model()
|
|
218
|
+
asyncio.run(main(args.agent, session_state))
|
|
219
|
+
except KeyboardInterrupt:
|
|
220
|
+
# Clean exit on Ctrl+C - suppress ugly traceback
|
|
221
|
+
console.print("\n\n[yellow]Interrupted[/yellow]")
|
|
222
|
+
sys.exit(0)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
if __name__ == "__main__":
|
|
226
|
+
cli_main()
|
deepagents_cli/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Utilities for accurate token counting using LangChain models."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from langchain_core.messages import SystemMessage
|
|
6
|
+
|
|
7
|
+
from .config import console
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def calculate_baseline_tokens(model, agent_dir: Path, system_prompt: str) -> int:
|
|
11
|
+
"""Calculate baseline context tokens using the model's official tokenizer.
|
|
12
|
+
|
|
13
|
+
This uses the model's get_num_tokens_from_messages() method to get
|
|
14
|
+
accurate token counts for the initial context (system prompt + agent.md).
|
|
15
|
+
|
|
16
|
+
Note: Tool definitions cannot be accurately counted before the first API call
|
|
17
|
+
due to LangChain limitations. They will be included in the total after the
|
|
18
|
+
first message is sent (~5,000 tokens).
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
model: LangChain model instance (ChatAnthropic or ChatOpenAI)
|
|
22
|
+
agent_dir: Path to agent directory containing agent.md
|
|
23
|
+
system_prompt: The base system prompt string
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Token count for system prompt + agent.md (tools not included)
|
|
27
|
+
"""
|
|
28
|
+
# Load agent.md content
|
|
29
|
+
agent_md_path = agent_dir / "agent.md"
|
|
30
|
+
agent_memory = ""
|
|
31
|
+
if agent_md_path.exists():
|
|
32
|
+
agent_memory = agent_md_path.read_text()
|
|
33
|
+
|
|
34
|
+
# Build the complete system prompt as it will be sent
|
|
35
|
+
# This mimics what AgentMemoryMiddleware.wrap_model_call() does
|
|
36
|
+
memory_section = f"<agent_memory>\n{agent_memory}\n</agent_memory>"
|
|
37
|
+
|
|
38
|
+
# Get the long-term memory system prompt
|
|
39
|
+
memory_system_prompt = get_memory_system_prompt()
|
|
40
|
+
|
|
41
|
+
# Combine all parts in the same order as the middleware
|
|
42
|
+
full_system_prompt = memory_section + "\n\n" + system_prompt + "\n\n" + memory_system_prompt
|
|
43
|
+
|
|
44
|
+
# Count tokens using the model's official method
|
|
45
|
+
messages = [SystemMessage(content=full_system_prompt)]
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
# Note: tools parameter is not supported by LangChain's token counting
|
|
49
|
+
# Tool tokens will be included in the API response after first message
|
|
50
|
+
token_count = model.get_num_tokens_from_messages(messages)
|
|
51
|
+
return token_count
|
|
52
|
+
except Exception as e:
|
|
53
|
+
# Fallback if token counting fails
|
|
54
|
+
console.print(f"[yellow]Warning: Could not calculate baseline tokens: {e}[/yellow]")
|
|
55
|
+
return 0
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_memory_system_prompt() -> str:
|
|
59
|
+
"""Get the long-term memory system prompt text."""
|
|
60
|
+
# Import from agent_memory middleware
|
|
61
|
+
from deepagents.middleware.agent_memory import LONGTERM_MEMORY_SYSTEM_PROMPT
|
|
62
|
+
|
|
63
|
+
return LONGTERM_MEMORY_SYSTEM_PROMPT.format(memory_path="/memories/")
|
deepagents_cli/tools.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Custom tools for the CLI agent."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any, Literal
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
from tavily import TavilyClient
|
|
8
|
+
|
|
9
|
+
# Initialize Tavily client if API key is available
|
|
10
|
+
tavily_client = (
|
|
11
|
+
TavilyClient(api_key=os.environ.get("TAVILY_API_KEY"))
|
|
12
|
+
if os.environ.get("TAVILY_API_KEY")
|
|
13
|
+
else None
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def http_request(
|
|
18
|
+
url: str,
|
|
19
|
+
method: str = "GET",
|
|
20
|
+
headers: dict[str, str] = None,
|
|
21
|
+
data: str | dict = None,
|
|
22
|
+
params: dict[str, str] = None,
|
|
23
|
+
timeout: int = 30,
|
|
24
|
+
) -> dict[str, Any]:
|
|
25
|
+
"""Make HTTP requests to APIs and web services.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
url: Target URL
|
|
29
|
+
method: HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
30
|
+
headers: HTTP headers to include
|
|
31
|
+
data: Request body data (string or dict)
|
|
32
|
+
params: URL query parameters
|
|
33
|
+
timeout: Request timeout in seconds
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Dictionary with response data including status, headers, and content
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
kwargs = {"url": url, "method": method.upper(), "timeout": timeout}
|
|
40
|
+
|
|
41
|
+
if headers:
|
|
42
|
+
kwargs["headers"] = headers
|
|
43
|
+
if params:
|
|
44
|
+
kwargs["params"] = params
|
|
45
|
+
if data:
|
|
46
|
+
if isinstance(data, dict):
|
|
47
|
+
kwargs["json"] = data
|
|
48
|
+
else:
|
|
49
|
+
kwargs["data"] = data
|
|
50
|
+
|
|
51
|
+
response = requests.request(**kwargs)
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
content = response.json()
|
|
55
|
+
except:
|
|
56
|
+
content = response.text
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
"success": response.status_code < 400,
|
|
60
|
+
"status_code": response.status_code,
|
|
61
|
+
"headers": dict(response.headers),
|
|
62
|
+
"content": content,
|
|
63
|
+
"url": response.url,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
except requests.exceptions.Timeout:
|
|
67
|
+
return {
|
|
68
|
+
"success": False,
|
|
69
|
+
"status_code": 0,
|
|
70
|
+
"headers": {},
|
|
71
|
+
"content": f"Request timed out after {timeout} seconds",
|
|
72
|
+
"url": url,
|
|
73
|
+
}
|
|
74
|
+
except requests.exceptions.RequestException as e:
|
|
75
|
+
return {
|
|
76
|
+
"success": False,
|
|
77
|
+
"status_code": 0,
|
|
78
|
+
"headers": {},
|
|
79
|
+
"content": f"Request error: {e!s}",
|
|
80
|
+
"url": url,
|
|
81
|
+
}
|
|
82
|
+
except Exception as e:
|
|
83
|
+
return {
|
|
84
|
+
"success": False,
|
|
85
|
+
"status_code": 0,
|
|
86
|
+
"headers": {},
|
|
87
|
+
"content": f"Error making request: {e!s}",
|
|
88
|
+
"url": url,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def web_search(
|
|
93
|
+
query: str,
|
|
94
|
+
max_results: int = 5,
|
|
95
|
+
topic: Literal["general", "news", "finance"] = "general",
|
|
96
|
+
include_raw_content: bool = False,
|
|
97
|
+
):
|
|
98
|
+
"""Search the web using Tavily for current information and documentation.
|
|
99
|
+
|
|
100
|
+
This tool searches the web and returns relevant results. After receiving results,
|
|
101
|
+
you MUST synthesize the information into a natural, helpful response for the user.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
query: The search query (be specific and detailed)
|
|
105
|
+
max_results: Number of results to return (default: 5)
|
|
106
|
+
topic: Search topic type - "general" for most queries, "news" for current events
|
|
107
|
+
include_raw_content: Include full page content (warning: uses more tokens)
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Dictionary containing:
|
|
111
|
+
- results: List of search results, each with:
|
|
112
|
+
- title: Page title
|
|
113
|
+
- url: Page URL
|
|
114
|
+
- content: Relevant excerpt from the page
|
|
115
|
+
- score: Relevance score (0-1)
|
|
116
|
+
- query: The original search query
|
|
117
|
+
|
|
118
|
+
IMPORTANT: After using this tool:
|
|
119
|
+
1. Read through the 'content' field of each result
|
|
120
|
+
2. Extract relevant information that answers the user's question
|
|
121
|
+
3. Synthesize this into a clear, natural language response
|
|
122
|
+
4. Cite sources by mentioning the page titles or URLs
|
|
123
|
+
5. NEVER show the raw JSON to the user - always provide a formatted response
|
|
124
|
+
"""
|
|
125
|
+
if tavily_client is None:
|
|
126
|
+
return {
|
|
127
|
+
"error": "Tavily API key not configured. Please set TAVILY_API_KEY environment variable.",
|
|
128
|
+
"query": query,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
search_docs = tavily_client.search(
|
|
133
|
+
query,
|
|
134
|
+
max_results=max_results,
|
|
135
|
+
include_raw_content=include_raw_content,
|
|
136
|
+
topic=topic,
|
|
137
|
+
)
|
|
138
|
+
return search_docs
|
|
139
|
+
except Exception as e:
|
|
140
|
+
return {"error": f"Web search error: {e!s}", "query": query}
|