deepagents-cli 0.0.3__py3-none-any.whl → 0.0.4__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.

Files changed (41) hide show
  1. deepagents_cli/__init__.py +5 -0
  2. deepagents_cli/__main__.py +6 -0
  3. deepagents_cli/agent.py +267 -0
  4. deepagents_cli/cli.py +13 -0
  5. deepagents_cli/commands.py +86 -0
  6. deepagents_cli/config.py +138 -0
  7. deepagents_cli/execution.py +644 -0
  8. deepagents_cli/file_ops.py +347 -0
  9. deepagents_cli/input.py +249 -0
  10. deepagents_cli/main.py +217 -0
  11. deepagents_cli/py.typed +0 -0
  12. deepagents_cli/tools.py +140 -0
  13. deepagents_cli/ui.py +455 -0
  14. deepagents_cli-0.0.4.dist-info/METADATA +18 -0
  15. deepagents_cli-0.0.4.dist-info/RECORD +18 -0
  16. deepagents_cli-0.0.4.dist-info/entry_points.txt +3 -0
  17. deepagents_cli-0.0.4.dist-info/top_level.txt +1 -0
  18. deepagents/__init__.py +0 -7
  19. deepagents/cli.py +0 -567
  20. deepagents/default_agent_prompt.md +0 -64
  21. deepagents/graph.py +0 -144
  22. deepagents/memory/__init__.py +0 -17
  23. deepagents/memory/backends/__init__.py +0 -15
  24. deepagents/memory/backends/composite.py +0 -250
  25. deepagents/memory/backends/filesystem.py +0 -330
  26. deepagents/memory/backends/state.py +0 -206
  27. deepagents/memory/backends/store.py +0 -351
  28. deepagents/memory/backends/utils.py +0 -319
  29. deepagents/memory/protocol.py +0 -164
  30. deepagents/middleware/__init__.py +0 -13
  31. deepagents/middleware/agent_memory.py +0 -207
  32. deepagents/middleware/filesystem.py +0 -615
  33. deepagents/middleware/patch_tool_calls.py +0 -44
  34. deepagents/middleware/subagents.py +0 -481
  35. deepagents/pretty_cli.py +0 -289
  36. deepagents_cli-0.0.3.dist-info/METADATA +0 -551
  37. deepagents_cli-0.0.3.dist-info/RECORD +0 -24
  38. deepagents_cli-0.0.3.dist-info/entry_points.txt +0 -2
  39. deepagents_cli-0.0.3.dist-info/licenses/LICENSE +0 -21
  40. deepagents_cli-0.0.3.dist-info/top_level.txt +0 -1
  41. {deepagents_cli-0.0.3.dist-info → deepagents_cli-0.0.4.dist-info}/WHEEL +0 -0
deepagents/graph.py DELETED
@@ -1,144 +0,0 @@
1
- """Deepagents come with planning, filesystem, and subagents."""
2
-
3
- from collections.abc import Callable, Sequence
4
- from typing import Any
5
-
6
- from langchain.agents import create_agent
7
- from langchain.agents.middleware import HumanInTheLoopMiddleware, InterruptOnConfig, TodoListMiddleware
8
- from langchain.agents.middleware.summarization import SummarizationMiddleware
9
- from langchain.agents.middleware.types import AgentMiddleware
10
- from langchain.agents.structured_output import ResponseFormat
11
- from langchain_anthropic import ChatAnthropic
12
- from langchain_anthropic.middleware import AnthropicPromptCachingMiddleware
13
- from langchain_core.language_models import BaseChatModel
14
- from langchain_core.tools import BaseTool
15
- from langgraph.cache.base import BaseCache
16
- from langgraph.graph.state import CompiledStateGraph
17
- from langgraph.store.base import BaseStore
18
- from langgraph.types import Checkpointer
19
-
20
- from deepagents.memory.protocol import MemoryBackend
21
- from deepagents.middleware.filesystem import FilesystemMiddleware
22
- from deepagents.middleware.patch_tool_calls import PatchToolCallsMiddleware
23
- from deepagents.middleware.subagents import CompiledSubAgent, SubAgent, SubAgentMiddleware
24
-
25
- BASE_AGENT_PROMPT = "In order to complete the objective that the user asks of you, you have access to a number of standard tools."
26
-
27
-
28
- def get_default_model() -> ChatAnthropic:
29
- """Get the default model for deep agents.
30
-
31
- Returns:
32
- ChatAnthropic instance configured with Claude Sonnet 4.
33
- """
34
- return ChatAnthropic(
35
- model_name="claude-sonnet-4-5-20250929",
36
- max_tokens=20000,
37
- )
38
-
39
-
40
- def create_deep_agent(
41
- model: str | BaseChatModel | None = None,
42
- tools: Sequence[BaseTool | Callable | dict[str, Any]] | None = None,
43
- *,
44
- system_prompt: str | None = None,
45
- middleware: Sequence[AgentMiddleware] = (),
46
- subagents: list[SubAgent | CompiledSubAgent] | None = None,
47
- response_format: ResponseFormat | None = None,
48
- context_schema: type[Any] | None = None,
49
- checkpointer: Checkpointer | None = None,
50
- store: BaseStore | None = None,
51
- memory_backend: MemoryBackend | None = None,
52
- interrupt_on: dict[str, bool | InterruptOnConfig] | None = None,
53
- debug: bool = False,
54
- name: str | None = None,
55
- cache: BaseCache | None = None,
56
- ) -> CompiledStateGraph:
57
- """Create a deep agent.
58
-
59
- This agent will by default have access to a tool to write todos (write_todos),
60
- six file editing tools: write_file, ls, read_file, edit_file, glob_search, grep_search,
61
- and a tool to call subagents.
62
-
63
- Args:
64
- model: The model to use. Defaults to Claude Sonnet 4.
65
- tools: The tools the agent should have access to.
66
- system_prompt: The additional instructions the agent should have. Will go in
67
- the system prompt.
68
- middleware: Additional middleware to apply after standard middleware.
69
- subagents: The subagents to use. Each subagent should be a dictionary with the
70
- following keys:
71
- - `name`
72
- - `description` (used by the main agent to decide whether to call the
73
- sub agent)
74
- - `prompt` (used as the system prompt in the subagent)
75
- - (optional) `tools`
76
- - (optional) `model` (either a LanguageModelLike instance or dict
77
- settings)
78
- - (optional) `middleware` (list of AgentMiddleware)
79
- response_format: A structured output response format to use for the agent.
80
- context_schema: The schema of the deep agent.
81
- checkpointer: Optional checkpointer for persisting agent state between runs.
82
- store: Optional store for persistent storage (required if memory_backend uses StoreBackend).
83
- memory_backend: Optional backend for file storage. Defaults to StateBackend (ephemeral
84
- storage in agent state). For persistent or hybrid storage, use CompositeBackend.
85
- Example: CompositeBackend(default=StateBackend(), routes={"/memories/": StoreBackend()})
86
- interrupt_on: Optional Dict[str, bool | InterruptOnConfig] mapping tool names to
87
- interrupt configs.
88
- debug: Whether to enable debug mode. Passed through to create_agent.
89
- name: The name of the agent. Passed through to create_agent.
90
- cache: The cache to use for the agent. Passed through to create_agent.
91
-
92
- Returns:
93
- A configured deep agent.
94
- """
95
- if model is None:
96
- model = get_default_model()
97
-
98
- deepagent_middleware = [
99
- TodoListMiddleware(),
100
- FilesystemMiddleware(memory_backend=memory_backend),
101
- SubAgentMiddleware(
102
- default_model=model,
103
- default_tools=tools,
104
- subagents=subagents if subagents is not None else [],
105
- default_middleware=[
106
- TodoListMiddleware(),
107
- FilesystemMiddleware(memory_backend=memory_backend),
108
- SummarizationMiddleware(
109
- model=model,
110
- max_tokens_before_summary=170000,
111
- messages_to_keep=6,
112
- ),
113
- AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
114
- PatchToolCallsMiddleware(),
115
- ],
116
- default_interrupt_on=interrupt_on,
117
- general_purpose_agent=True,
118
- ),
119
- SummarizationMiddleware(
120
- model=model,
121
- max_tokens_before_summary=170000,
122
- messages_to_keep=6,
123
- ),
124
- AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
125
- PatchToolCallsMiddleware(),
126
- ]
127
- if interrupt_on is not None:
128
- deepagent_middleware.append(HumanInTheLoopMiddleware(interrupt_on=interrupt_on))
129
- if middleware is not None:
130
- deepagent_middleware.extend(middleware)
131
-
132
- return create_agent(
133
- model,
134
- system_prompt=system_prompt + "\n\n" + BASE_AGENT_PROMPT if system_prompt else BASE_AGENT_PROMPT,
135
- tools=tools,
136
- middleware=deepagent_middleware,
137
- response_format=response_format,
138
- context_schema=context_schema,
139
- checkpointer=checkpointer,
140
- store=store,
141
- debug=debug,
142
- name=name,
143
- cache=cache,
144
- ).with_config({"recursion_limit": 1000})
@@ -1,17 +0,0 @@
1
- """Memory backends for pluggable file storage."""
2
-
3
- from deepagents.memory.backends import (
4
- CompositeBackend,
5
- FilesystemBackend,
6
- StateBackend,
7
- StoreBackend,
8
- )
9
- from deepagents.memory.protocol import MemoryBackend
10
-
11
- __all__ = [
12
- "MemoryBackend",
13
- "CompositeBackend",
14
- "FilesystemBackend",
15
- "StateBackend",
16
- "StoreBackend",
17
- ]
@@ -1,15 +0,0 @@
1
- """Backend implementations for pluggable memory storage."""
2
-
3
- from deepagents.memory.backends.composite import CompositeBackend
4
- from deepagents.memory.backends.filesystem import FilesystemBackend
5
- from deepagents.memory.backends.state import StateBackend
6
- from deepagents.memory.backends.store import StoreBackend
7
- from deepagents.memory.backends import utils
8
-
9
- __all__ = [
10
- "CompositeBackend",
11
- "FilesystemBackend",
12
- "StateBackend",
13
- "StoreBackend",
14
- "utils",
15
- ]
@@ -1,250 +0,0 @@
1
- """CompositeBackend: Route operations to different backends based on path prefix."""
2
-
3
- from typing import Any, Literal, Optional, TYPE_CHECKING
4
-
5
- if TYPE_CHECKING:
6
- from langchain.tools import ToolRuntime
7
-
8
- from deepagents.memory.protocol import MemoryBackend
9
- from langgraph.types import Command
10
-
11
-
12
- class CompositeBackend:
13
- """Backend that routes operations to different backends based on path prefix.
14
-
15
- This backend enables hybrid storage strategies, such as:
16
- - Short-term files (/*) → StateBackend (ephemeral)
17
- - Long-term files (/memories/*) → StoreBackend or FilesystemBackend (persistent)
18
-
19
- The routing is transparent to tools - they just call backend.get(path) and
20
- CompositeBackend handles the routing internally.
21
-
22
- Example:
23
- ```python
24
- # Create a factory function that returns CompositeBackend with resolved backends
25
- backend_factory = lambda runtime: CompositeBackend(
26
- default=StateBackend(runtime), # StateBackend needs runtime
27
- routes={"/memories/": FilesystemBackend("/data/memories")} # FilesystemBackend doesn't
28
- )
29
-
30
- # Then pass this factory to the middleware
31
- middleware = FilesystemMiddleware(memory_backend=backend_factory)
32
- ```
33
- """
34
-
35
- def __init__(
36
- self,
37
- default: MemoryBackend,
38
- routes: dict[str, MemoryBackend],
39
- ) -> None:
40
- """Initialize composite backend with routing rules.
41
-
42
- Args:
43
- default: Default backend for paths that don't match any route (must be resolved backend instance).
44
- routes: Dict mapping path prefixes to backends (must be resolved backend instances).
45
- Keys should include trailing slash (e.g., "/memories/").
46
-
47
- Note: If you need backends that require runtime (like StateBackend), wrap the CompositeBackend
48
- itself in a factory function:
49
- lambda runtime: CompositeBackend(
50
- default=StateBackend(runtime),
51
- routes={"/memories/": FilesystemBackend("./data")}
52
- )
53
- """
54
- self.default = default
55
- self.routes = routes
56
-
57
- # Sort routes by length (longest first) for correct prefix matching
58
- self.sorted_routes = sorted(routes.items(), key=lambda x: len(x[0]), reverse=True)
59
-
60
- def _get_backend_and_key(self, key: str) -> tuple[MemoryBackend, str]:
61
- """Determine which backend handles this key and strip prefix.
62
-
63
- Args:
64
- key: Original file path
65
-
66
- Returns:
67
- Tuple of (backend, stripped_key) where stripped_key has the route
68
- prefix removed (but keeps leading slash).
69
- """
70
- # Check routes in order of length (longest first)
71
- for prefix, backend in self.sorted_routes:
72
- if key.startswith(prefix):
73
- # Strip prefix but keep leading slash
74
- # e.g., "/memories/notes.txt" → "/notes.txt"
75
- stripped_key = key[len(prefix) - 1:] if key[len(prefix) - 1:] else "/"
76
- return backend, stripped_key
77
-
78
- return self.default, key
79
-
80
- def ls(self, path: str) -> list[str]:
81
- """List files from backends, with appropriate prefixes.
82
-
83
- Args:
84
- path: Absolute path to directory.
85
-
86
- Returns:
87
- List of file paths with route prefixes added.
88
- """
89
- # Check if path matches a specific route
90
- for route_prefix, backend in self.sorted_routes:
91
- if path.startswith(route_prefix.rstrip("/")):
92
- # Query only the matching routed backend
93
- search_path = path[len(route_prefix) - 1:]
94
- keys = backend.ls(search_path if search_path else "/")
95
- return [f"{route_prefix[:-1]}{key}" for key in keys]
96
-
97
- # Path doesn't match a route: query only default backend
98
- return self.default.ls(path)
99
-
100
- def read(
101
- self,
102
- file_path: str,
103
- offset: int = 0,
104
- limit: int = 2000,
105
- ) -> str:
106
- """Read file content, routing to appropriate backend.
107
-
108
- Args:
109
- file_path: Absolute file path
110
- offset: Line offset to start reading from (0-indexed)
111
- limit: Maximum number of lines to readReturns:
112
- Formatted file content with line numbers, or error message.
113
- """
114
- backend, stripped_key = self._get_backend_and_key(file_path)
115
- return backend.read(stripped_key, offset=offset, limit=limit)
116
-
117
- def write(
118
- self,
119
- file_path: str,
120
- content: str,
121
- ) -> Command | str:
122
- """Create a new file, routing to appropriate backend.
123
-
124
- Args:
125
- file_path: Absolute file path
126
- content: File content as a stringReturns:
127
- Success message or Command object, or error if file already exists.
128
- """
129
- backend, stripped_key = self._get_backend_and_key(file_path)
130
- return backend.write(stripped_key, content)
131
-
132
- def edit(
133
- self,
134
- file_path: str,
135
- old_string: str,
136
- new_string: str,
137
- replace_all: bool = False,
138
- ) -> Command | str:
139
- """Edit a file, routing to appropriate backend.
140
-
141
- Args:
142
- file_path: Absolute file path
143
- old_string: String to find and replace
144
- new_string: Replacement string
145
- replace_all: If True, replace all occurrencesReturns:
146
- Success message or Command object, or error message on failure.
147
- """
148
- backend, stripped_key = self._get_backend_and_key(file_path)
149
- return backend.edit(stripped_key, old_string, new_string, replace_all=replace_all)
150
-
151
- def delete(self, file_path: str) -> Command | None:
152
- """Delete file, routing to appropriate backend.
153
-
154
- Args:
155
- file_path: File path to deleteReturns:
156
- Return value from backend (None or Command).
157
- """
158
- backend, stripped_key = self._get_backend_and_key(file_path)
159
- return backend.delete(stripped_key)
160
-
161
- def grep(
162
- self,
163
- pattern: str,
164
- path: str = "/",
165
- glob: Optional[str] = None,
166
- output_mode: str = "files_with_matches",
167
- ) -> str:
168
- """Search for a pattern in files, routing to appropriate backend(s).
169
-
170
- Args:
171
- pattern: String pattern to search for
172
- path: Path to search in (default "/")
173
- glob: Optional glob pattern to filter files (e.g., "*.py")
174
- output_mode: Output format - "files_with_matches", "content", or "count"Returns:
175
- Formatted search results based on output_mode.
176
- """
177
- for route_prefix, backend in self.sorted_routes:
178
- if path.startswith(route_prefix.rstrip("/")):
179
- search_path = path[len(route_prefix) - 1:]
180
- result = backend.grep(pattern, search_path if search_path else "/", glob, output_mode)
181
- if result.startswith("No matches found"):
182
- return result
183
-
184
- lines = result.split("\n")
185
- prefixed_lines = []
186
- for line in lines:
187
- if output_mode == "files_with_matches" or line.endswith(":") or ": " in line.split(":", 1)[0]:
188
- if line and not line.startswith(" "):
189
- prefixed_lines.append(f"{route_prefix[:-1]}{line}")
190
- else:
191
- prefixed_lines.append(line)
192
- else:
193
- prefixed_lines.append(line)
194
- return "\n".join(prefixed_lines)
195
-
196
- all_results = []
197
-
198
- default_result = self.default.grep(pattern, path, glob, output_mode)
199
- if not default_result.startswith("No matches found"):
200
- all_results.append(default_result)
201
-
202
- for route_prefix, backend in self.routes.items():
203
- result = backend.grep(pattern, "/", glob, output_mode)
204
- if not result.startswith("No matches found"):
205
- lines = result.split("\n")
206
- prefixed_lines = []
207
- for line in lines:
208
- if output_mode == "files_with_matches" or line.endswith(":") or (": " in line and not line.startswith(" ")):
209
- if line and not line.startswith(" "):
210
- prefixed_lines.append(f"{route_prefix[:-1]}{line}")
211
- else:
212
- prefixed_lines.append(line)
213
- else:
214
- prefixed_lines.append(line)
215
- all_results.append("\n".join(prefixed_lines))
216
-
217
- if not all_results:
218
- return f"No matches found for pattern: '{pattern}'"
219
-
220
- return "\n".join(all_results)
221
-
222
- def glob(self, pattern: str, path: str = "/") -> list[str]:
223
- """Find files matching a glob pattern across all backends.
224
-
225
- Args:
226
- pattern: Glob pattern (e.g., "**/*.py", "*.txt", "/subdir/**/*.md")
227
- path: Base path to search from (default "/")Returns:
228
- List of absolute file paths matching the pattern.
229
- """
230
- results = []
231
-
232
- # Route based on path, not pattern
233
- for route_prefix, backend in self.sorted_routes:
234
- if path.startswith(route_prefix.rstrip("/")):
235
- # Path matches a specific route - search only that backend
236
- search_path = path[len(route_prefix) - 1:]
237
- matches = backend.glob(pattern, search_path if search_path else "/")
238
- results.extend(f"{route_prefix[:-1]}{match}" for match in matches)
239
- return sorted(results)
240
-
241
- # Path doesn't match any specific route - search default backend AND all routed backends
242
- default_matches = self.default.glob(pattern, path)
243
- results.extend(default_matches)
244
-
245
- # Also search in all routed backends and prefix results
246
- for route_prefix, backend in self.routes.items():
247
- matches = backend.glob(pattern, "/")
248
- results.extend(f"{route_prefix[:-1]}{match}" for match in matches)
249
-
250
- return sorted(results)