dex-agents-deepagents-cli 0.6.0__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.
- dex_agents_deepagents_cli/__init__.py +112 -0
- dex_agents_deepagents_cli/__main__.py +14 -0
- dex_agents_deepagents_cli/connector_tool_names.py +138 -0
- dex_agents_deepagents_cli/harness.py +443 -0
- dex_agents_deepagents_cli/py.typed +0 -0
- dex_agents_deepagents_cli/workspace_backend.py +382 -0
- dex_agents_deepagents_cli-0.6.0.dist-info/METADATA +199 -0
- dex_agents_deepagents_cli-0.6.0.dist-info/RECORD +11 -0
- dex_agents_deepagents_cli-0.6.0.dist-info/WHEEL +4 -0
- dex_agents_deepagents_cli-0.6.0.dist-info/entry_points.txt +3 -0
- dex_agents_deepagents_cli-0.6.0.dist-info/licenses/LICENSE +105 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Copyright (c) 2026 Super Durable, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Sustainable Use License 1.0.
|
|
4
|
+
# You may not use this file except in compliance with the License.
|
|
5
|
+
# See the LICENSE file in the repository root.
|
|
6
|
+
#
|
|
7
|
+
# SPDX-License-Identifier: LicenseRef-Sustainable-Use-1.0
|
|
8
|
+
|
|
9
|
+
"""The ``dex-agents-deepagents`` command: a Deep Agents agent in the terminal.
|
|
10
|
+
|
|
11
|
+
It builds the agent session Flow from ``create_deep_agent`` with the Dex
|
|
12
|
+
LangGraph adapter and runs it with the shared Dex Agents CLI core. See the
|
|
13
|
+
package README.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import dex_agents_cli_core # noqa: F401 # first: it configures gRPC before anything imports it
|
|
17
|
+
|
|
18
|
+
# isort: split
|
|
19
|
+
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
from dex_agents_cli_core import ConfigError, command_name, run_agent_cli
|
|
23
|
+
from dex_agents_deepagents_cli.connector_tool_names import (
|
|
24
|
+
COLLIDING_CONNECTOR_TOOL_PREFIX,
|
|
25
|
+
PrefixedConnectorToolSource,
|
|
26
|
+
)
|
|
27
|
+
from dex_agents_deepagents_cli.harness import (
|
|
28
|
+
DEEP_AGENTS_TOOL_RULES,
|
|
29
|
+
DEEPAGENTS_CLI_FLOW_TYPE,
|
|
30
|
+
DEFAULT_DEEPAGENTS_MODEL,
|
|
31
|
+
DEFAULT_INSTRUCTIONS,
|
|
32
|
+
EXECUTE_TOOL_NAME,
|
|
33
|
+
TASK_TOOL_NAME,
|
|
34
|
+
WRITE_TODOS_TOOL_NAME,
|
|
35
|
+
DeepAgentsAgentRunner,
|
|
36
|
+
DeepAgentsHarness,
|
|
37
|
+
DeepAgentsToolRule,
|
|
38
|
+
LocalToolSwitchesMiddleware,
|
|
39
|
+
TurnChatModelSelector,
|
|
40
|
+
TurnInstructionsMiddleware,
|
|
41
|
+
build_deep_agent,
|
|
42
|
+
deep_agents_chat_model,
|
|
43
|
+
deep_agents_tool_sources,
|
|
44
|
+
validate_deepagents_section,
|
|
45
|
+
)
|
|
46
|
+
from dex_agents_deepagents_cli.workspace_backend import (
|
|
47
|
+
DEEP_AGENTS_ARTIFACT_PREFIXES,
|
|
48
|
+
SESSION_ARTIFACTS_RETENTION,
|
|
49
|
+
SessionArtifactsBackend,
|
|
50
|
+
WorkspaceShellBackend,
|
|
51
|
+
bind_artifacts_session,
|
|
52
|
+
build_workspace_backend,
|
|
53
|
+
default_artifacts_directory,
|
|
54
|
+
ensure_artifacts_directory,
|
|
55
|
+
remove_stale_session_artifacts,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
__all__ = [
|
|
59
|
+
"COLLIDING_CONNECTOR_TOOL_PREFIX",
|
|
60
|
+
"DEEPAGENTS_CLI_FLOW_TYPE",
|
|
61
|
+
"DEEP_AGENTS_ARTIFACT_PREFIXES",
|
|
62
|
+
"DEEP_AGENTS_TOOL_RULES",
|
|
63
|
+
"DEFAULT_DEEPAGENTS_MODEL",
|
|
64
|
+
"DEFAULT_INSTRUCTIONS",
|
|
65
|
+
"EXECUTE_TOOL_NAME",
|
|
66
|
+
"SESSION_ARTIFACTS_RETENTION",
|
|
67
|
+
"TASK_TOOL_NAME",
|
|
68
|
+
"WRITE_TODOS_TOOL_NAME",
|
|
69
|
+
"DeepAgentsAgentRunner",
|
|
70
|
+
"DeepAgentsHarness",
|
|
71
|
+
"DeepAgentsToolRule",
|
|
72
|
+
"LocalToolSwitchesMiddleware",
|
|
73
|
+
"PrefixedConnectorToolSource",
|
|
74
|
+
"SessionArtifactsBackend",
|
|
75
|
+
"TurnChatModelSelector",
|
|
76
|
+
"TurnInstructionsMiddleware",
|
|
77
|
+
"WorkspaceShellBackend",
|
|
78
|
+
"bind_artifacts_session",
|
|
79
|
+
"build_deep_agent",
|
|
80
|
+
"build_workspace_backend",
|
|
81
|
+
"deep_agents_chat_model",
|
|
82
|
+
"deep_agents_tool_sources",
|
|
83
|
+
"default_artifacts_directory",
|
|
84
|
+
"ensure_artifacts_directory",
|
|
85
|
+
"main",
|
|
86
|
+
"remove_stale_session_artifacts",
|
|
87
|
+
"run",
|
|
88
|
+
"validate_deepagents_section",
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def main(argv: list[str] | None = None) -> int:
|
|
93
|
+
"""Run the command and return its exit code.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
argv: The arguments without the program name; ``None`` reads ``sys.argv``.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
0 on success, 1 for a failure at run time, and 2 for a usage or
|
|
100
|
+
configuration error, such as a key in the ``deepagents`` section.
|
|
101
|
+
"""
|
|
102
|
+
harness = DeepAgentsHarness()
|
|
103
|
+
try:
|
|
104
|
+
return run_agent_cli(harness, argv)
|
|
105
|
+
except ConfigError as error:
|
|
106
|
+
print(f"{command_name(harness)}: {error}", file=sys.stderr)
|
|
107
|
+
return 2
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def run() -> None:
|
|
111
|
+
"""Run the command from its console script and exit with its code."""
|
|
112
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copyright (c) 2026 Super Durable, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Sustainable Use License 1.0.
|
|
4
|
+
# You may not use this file except in compliance with the License.
|
|
5
|
+
# See the LICENSE file in the repository root.
|
|
6
|
+
#
|
|
7
|
+
# SPDX-License-Identifier: LicenseRef-Sustainable-Use-1.0
|
|
8
|
+
|
|
9
|
+
"""``python -m dex_agents_deepagents_cli`` runs the ``dex-agents-deepagents`` command."""
|
|
10
|
+
|
|
11
|
+
from dex_agents_deepagents_cli import run
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
run()
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Copyright (c) 2026 Super Durable, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Sustainable Use License 1.0.
|
|
4
|
+
# You may not use this file except in compliance with the License.
|
|
5
|
+
# See the LICENSE file in the repository root.
|
|
6
|
+
#
|
|
7
|
+
# SPDX-License-Identifier: LicenseRef-Sustainable-Use-1.0
|
|
8
|
+
|
|
9
|
+
"""Connector tools next to Deep Agents' own: prefixed on a name collision, writes approved."""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import dataclasses
|
|
14
|
+
from collections.abc import Collection, Sequence
|
|
15
|
+
|
|
16
|
+
from dex_agents import (
|
|
17
|
+
AgentToolCall,
|
|
18
|
+
AgentTurnContext,
|
|
19
|
+
DeferredToolCall,
|
|
20
|
+
DeferredToolExecution,
|
|
21
|
+
DeferredToolOutcome,
|
|
22
|
+
ToolCallStepOptions,
|
|
23
|
+
ToolEffect,
|
|
24
|
+
ToolPolicy,
|
|
25
|
+
ToolTaskCancellation,
|
|
26
|
+
ToolTaskPoll,
|
|
27
|
+
ToolTaskPollOutcome,
|
|
28
|
+
)
|
|
29
|
+
from dex_agents.builtin_loop import SourceToolDefinition, ToolSource
|
|
30
|
+
|
|
31
|
+
#: The prefix of a connector tool whose name a Deep Agents tool already uses.
|
|
32
|
+
COLLIDING_CONNECTOR_TOOL_PREFIX = "connector_"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PrefixedConnectorToolSource:
|
|
36
|
+
"""Offer a connectors tool source's tools beside Deep Agents' tools.
|
|
37
|
+
|
|
38
|
+
A connector tool keeps its name unless a Deep Agents tool has it; then
|
|
39
|
+
the model sees it as ``connector_<name>``, and ``approval.always`` names
|
|
40
|
+
it that way. When the source also lists that name, the prefix repeats
|
|
41
|
+
until the name is free, so every offered name is unique. The source
|
|
42
|
+
still runs the connector operation it listed. Every call that is not a
|
|
43
|
+
read waits for approval.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self, source: ToolSource, reserved_tool_names: Collection[str]) -> None:
|
|
47
|
+
"""Wrap one connectors tool source.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
source: The CLI core's connectors tool source.
|
|
51
|
+
reserved_tool_names: The Deep Agents tool names. They are fixed, so
|
|
52
|
+
every Worker prefixes the same tools.
|
|
53
|
+
"""
|
|
54
|
+
self._source = source
|
|
55
|
+
self._reserved_tool_names = frozenset(reserved_tool_names)
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def source_name(self) -> str:
|
|
59
|
+
"""Return the wrapped source's name, so stored calls reach it on any Worker."""
|
|
60
|
+
return self._source.source_name
|
|
61
|
+
|
|
62
|
+
async def list_tools(self) -> Sequence[SourceToolDefinition]:
|
|
63
|
+
"""Return the source's listing, each colliding name prefixed."""
|
|
64
|
+
return self._agent_visible_listing(await self._source.list_tools())
|
|
65
|
+
|
|
66
|
+
def cached_tools(self) -> Sequence[SourceToolDefinition]:
|
|
67
|
+
"""Return the source's last listing, each colliding name prefixed."""
|
|
68
|
+
read_cache = getattr(self._source, "cached_tools", None)
|
|
69
|
+
cached: Sequence[SourceToolDefinition] = read_cache() if callable(read_cache) else ()
|
|
70
|
+
return self._agent_visible_listing(cached)
|
|
71
|
+
|
|
72
|
+
def deferred_tool_call(
|
|
73
|
+
self,
|
|
74
|
+
turn: AgentTurnContext,
|
|
75
|
+
*,
|
|
76
|
+
tool_call_id: str,
|
|
77
|
+
tool_name: str,
|
|
78
|
+
arguments: str,
|
|
79
|
+
listed_tool: SourceToolDefinition,
|
|
80
|
+
) -> DeferredToolCall:
|
|
81
|
+
"""Build the source's call under the agent's name, with approval for every write."""
|
|
82
|
+
built = self._source.deferred_tool_call(
|
|
83
|
+
turn,
|
|
84
|
+
tool_call_id=tool_call_id,
|
|
85
|
+
tool_name=tool_name,
|
|
86
|
+
arguments=arguments,
|
|
87
|
+
listed_tool=listed_tool,
|
|
88
|
+
)
|
|
89
|
+
if built.policy.effect is ToolEffect.READ or built.policy.is_approval_required:
|
|
90
|
+
return built
|
|
91
|
+
return dataclasses.replace(
|
|
92
|
+
built, policy=ToolPolicy(built.policy.effect, requires_approval=True)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
async def execute_deferred_tool_call(self, call: DeferredToolExecution) -> DeferredToolOutcome:
|
|
96
|
+
"""Run one call through the source, which reads the operation from its own reference."""
|
|
97
|
+
return await self._source.execute_deferred_tool_call(call)
|
|
98
|
+
|
|
99
|
+
async def poll_tool_task(self, poll: ToolTaskPoll) -> ToolTaskPollOutcome:
|
|
100
|
+
"""Poll one of the source's tool tasks."""
|
|
101
|
+
return await self._source.poll_tool_task(poll)
|
|
102
|
+
|
|
103
|
+
async def cancel_tool_task(self, cancellation: ToolTaskCancellation) -> None:
|
|
104
|
+
"""Cancel one of the source's abandoned tool tasks, when the source can."""
|
|
105
|
+
cancel = getattr(self._source, "cancel_tool_task", None)
|
|
106
|
+
if callable(cancel):
|
|
107
|
+
await cancel(cancellation)
|
|
108
|
+
|
|
109
|
+
def tool_call_step_options(self, tool_call: AgentToolCall) -> ToolCallStepOptions | None:
|
|
110
|
+
"""Return the source's options for one call's tool Step, or ``None`` for the defaults."""
|
|
111
|
+
provide = getattr(self._source, "tool_call_step_options", None)
|
|
112
|
+
options: ToolCallStepOptions | None = provide(tool_call) if callable(provide) else None
|
|
113
|
+
return options
|
|
114
|
+
|
|
115
|
+
def _agent_visible_listing(
|
|
116
|
+
self, tools: Sequence[SourceToolDefinition]
|
|
117
|
+
) -> list[SourceToolDefinition]:
|
|
118
|
+
# Names the source lists and Deep Agents' names stay taken; renamed tools avoid them all.
|
|
119
|
+
taken = set(self._reserved_tool_names)
|
|
120
|
+
taken.update(tool.name for tool in tools if tool.name not in self._reserved_tool_names)
|
|
121
|
+
visible: list[SourceToolDefinition] = []
|
|
122
|
+
for tool in tools:
|
|
123
|
+
if tool.name not in self._reserved_tool_names:
|
|
124
|
+
visible.append(tool)
|
|
125
|
+
continue
|
|
126
|
+
name = f"{COLLIDING_CONNECTOR_TOOL_PREFIX}{tool.name}"
|
|
127
|
+
while name in taken:
|
|
128
|
+
name = f"{COLLIDING_CONNECTOR_TOOL_PREFIX}{name}"
|
|
129
|
+
taken.add(name)
|
|
130
|
+
visible.append(_renamed(tool, name))
|
|
131
|
+
return visible
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _renamed(tool: SourceToolDefinition, name: str) -> SourceToolDefinition:
|
|
135
|
+
if not dataclasses.is_dataclass(tool) or isinstance(tool, type):
|
|
136
|
+
raise TypeError(f"cannot rename the listed tool {tool.name}: it is not a dataclass")
|
|
137
|
+
renamed: SourceToolDefinition = dataclasses.replace(tool, name=name)
|
|
138
|
+
return renamed
|