cua-agent 0.1.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.
Potentially problematic release.
This version of cua-agent might be problematic. Click here for more details.
- agent/README.md +63 -0
- agent/__init__.py +10 -0
- agent/core/README.md +101 -0
- agent/core/__init__.py +34 -0
- agent/core/agent.py +284 -0
- agent/core/base_agent.py +164 -0
- agent/core/callbacks.py +147 -0
- agent/core/computer_agent.py +69 -0
- agent/core/experiment.py +222 -0
- agent/core/factory.py +102 -0
- agent/core/loop.py +244 -0
- agent/core/messages.py +230 -0
- agent/core/tools/__init__.py +21 -0
- agent/core/tools/base.py +74 -0
- agent/core/tools/bash.py +52 -0
- agent/core/tools/collection.py +46 -0
- agent/core/tools/computer.py +113 -0
- agent/core/tools/edit.py +67 -0
- agent/core/tools/manager.py +56 -0
- agent/providers/__init__.py +4 -0
- agent/providers/anthropic/__init__.py +6 -0
- agent/providers/anthropic/api/client.py +222 -0
- agent/providers/anthropic/api/logging.py +150 -0
- agent/providers/anthropic/callbacks/manager.py +55 -0
- agent/providers/anthropic/loop.py +521 -0
- agent/providers/anthropic/messages/manager.py +110 -0
- agent/providers/anthropic/prompts.py +20 -0
- agent/providers/anthropic/tools/__init__.py +33 -0
- agent/providers/anthropic/tools/base.py +88 -0
- agent/providers/anthropic/tools/bash.py +163 -0
- agent/providers/anthropic/tools/collection.py +34 -0
- agent/providers/anthropic/tools/computer.py +550 -0
- agent/providers/anthropic/tools/edit.py +326 -0
- agent/providers/anthropic/tools/manager.py +54 -0
- agent/providers/anthropic/tools/run.py +42 -0
- agent/providers/anthropic/types.py +16 -0
- agent/providers/omni/__init__.py +27 -0
- agent/providers/omni/callbacks.py +78 -0
- agent/providers/omni/clients/anthropic.py +99 -0
- agent/providers/omni/clients/base.py +44 -0
- agent/providers/omni/clients/groq.py +101 -0
- agent/providers/omni/clients/openai.py +159 -0
- agent/providers/omni/clients/utils.py +25 -0
- agent/providers/omni/experiment.py +273 -0
- agent/providers/omni/image_utils.py +106 -0
- agent/providers/omni/loop.py +961 -0
- agent/providers/omni/messages.py +168 -0
- agent/providers/omni/parser.py +252 -0
- agent/providers/omni/prompts.py +78 -0
- agent/providers/omni/tool_manager.py +91 -0
- agent/providers/omni/tools/__init__.py +13 -0
- agent/providers/omni/tools/bash.py +69 -0
- agent/providers/omni/tools/computer.py +216 -0
- agent/providers/omni/tools/manager.py +83 -0
- agent/providers/omni/types.py +30 -0
- agent/providers/omni/utils.py +155 -0
- agent/providers/omni/visualization.py +130 -0
- agent/types/__init__.py +26 -0
- agent/types/base.py +52 -0
- agent/types/messages.py +36 -0
- agent/types/tools.py +32 -0
- cua_agent-0.1.0.dist-info/METADATA +44 -0
- cua_agent-0.1.0.dist-info/RECORD +65 -0
- cua_agent-0.1.0.dist-info/WHEEL +4 -0
- cua_agent-0.1.0.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Anthropic-specific tool base classes."""
|
|
2
|
+
|
|
3
|
+
from abc import ABCMeta, abstractmethod
|
|
4
|
+
from dataclasses import dataclass, fields, replace
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
from anthropic.types.beta import BetaToolUnionParam
|
|
8
|
+
|
|
9
|
+
from ....core.tools.base import BaseTool, ToolError, ToolResult, ToolFailure, CLIResult
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseAnthropicTool(BaseTool, metaclass=ABCMeta):
|
|
13
|
+
"""Abstract base class for Anthropic-defined tools."""
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
"""Initialize the base Anthropic tool."""
|
|
17
|
+
# No specific initialization needed yet, but included for future extensibility
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
async def __call__(self, **kwargs) -> Any:
|
|
22
|
+
"""Executes the tool with the given arguments."""
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def to_params(self) -> Dict[str, Any]:
|
|
27
|
+
"""Convert tool to Anthropic-specific API parameters.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Dictionary with tool parameters for Anthropic API
|
|
31
|
+
"""
|
|
32
|
+
raise NotImplementedError
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(kw_only=True, frozen=True)
|
|
36
|
+
class ToolResult:
|
|
37
|
+
"""Represents the result of a tool execution."""
|
|
38
|
+
|
|
39
|
+
output: str | None = None
|
|
40
|
+
error: str | None = None
|
|
41
|
+
base64_image: str | None = None
|
|
42
|
+
system: str | None = None
|
|
43
|
+
content: list[dict] | None = None
|
|
44
|
+
|
|
45
|
+
def __bool__(self):
|
|
46
|
+
return any(getattr(self, field.name) for field in fields(self))
|
|
47
|
+
|
|
48
|
+
def __add__(self, other: "ToolResult"):
|
|
49
|
+
def combine_fields(field: str | None, other_field: str | None, concatenate: bool = True):
|
|
50
|
+
if field and other_field:
|
|
51
|
+
if concatenate:
|
|
52
|
+
return field + other_field
|
|
53
|
+
raise ValueError("Cannot combine tool results")
|
|
54
|
+
return field or other_field
|
|
55
|
+
|
|
56
|
+
return ToolResult(
|
|
57
|
+
output=combine_fields(self.output, other.output),
|
|
58
|
+
error=combine_fields(self.error, other.error),
|
|
59
|
+
base64_image=combine_fields(self.base64_image, other.base64_image, False),
|
|
60
|
+
system=combine_fields(self.system, other.system),
|
|
61
|
+
content=self.content or other.content, # Use first non-None content
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def replace(self, **kwargs):
|
|
65
|
+
"""Returns a new ToolResult with the given fields replaced."""
|
|
66
|
+
return replace(self, **kwargs)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class CLIResult(ToolResult):
|
|
70
|
+
"""A ToolResult that can be rendered as a CLI output."""
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ToolFailure(ToolResult):
|
|
74
|
+
"""A ToolResult that represents a failure."""
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ToolError(Exception):
|
|
78
|
+
"""Raised when a tool encounters an error."""
|
|
79
|
+
|
|
80
|
+
def __init__(self, message):
|
|
81
|
+
self.message = message
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# Re-export the core tool classes with Anthropic-specific names for backward compatibility
|
|
85
|
+
AnthropicToolResult = ToolResult
|
|
86
|
+
AnthropicToolError = ToolError
|
|
87
|
+
AnthropicToolFailure = ToolFailure
|
|
88
|
+
AnthropicCLIResult = CLIResult
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
from typing import ClassVar, Literal, Dict, Any
|
|
4
|
+
from computer.computer import Computer
|
|
5
|
+
|
|
6
|
+
from .base import BaseAnthropicTool, CLIResult, ToolError, ToolResult
|
|
7
|
+
from ....core.tools.bash import BaseBashTool
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _BashSession:
|
|
11
|
+
"""A session of a bash shell."""
|
|
12
|
+
|
|
13
|
+
_started: bool
|
|
14
|
+
_process: asyncio.subprocess.Process
|
|
15
|
+
|
|
16
|
+
command: str = "/bin/bash"
|
|
17
|
+
_output_delay: float = 0.2 # seconds
|
|
18
|
+
_timeout: float = 120.0 # seconds
|
|
19
|
+
_sentinel: str = "<<exit>>"
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self._started = False
|
|
23
|
+
self._timed_out = False
|
|
24
|
+
|
|
25
|
+
async def start(self):
|
|
26
|
+
if self._started:
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
self._process = await asyncio.create_subprocess_shell(
|
|
30
|
+
self.command,
|
|
31
|
+
preexec_fn=os.setsid,
|
|
32
|
+
shell=True,
|
|
33
|
+
bufsize=0,
|
|
34
|
+
stdin=asyncio.subprocess.PIPE,
|
|
35
|
+
stdout=asyncio.subprocess.PIPE,
|
|
36
|
+
stderr=asyncio.subprocess.PIPE,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
self._started = True
|
|
40
|
+
|
|
41
|
+
def stop(self):
|
|
42
|
+
"""Terminate the bash shell."""
|
|
43
|
+
if not self._started:
|
|
44
|
+
raise ToolError("Session has not started.")
|
|
45
|
+
if self._process.returncode is not None:
|
|
46
|
+
return
|
|
47
|
+
self._process.terminate()
|
|
48
|
+
|
|
49
|
+
async def run(self, command: str):
|
|
50
|
+
"""Execute a command in the bash shell."""
|
|
51
|
+
if not self._started:
|
|
52
|
+
raise ToolError("Session has not started.")
|
|
53
|
+
if self._process.returncode is not None:
|
|
54
|
+
return ToolResult(
|
|
55
|
+
system="tool must be restarted",
|
|
56
|
+
error=f"bash has exited with returncode {self._process.returncode}",
|
|
57
|
+
)
|
|
58
|
+
if self._timed_out:
|
|
59
|
+
raise ToolError(
|
|
60
|
+
f"timed out: bash has not returned in {self._timeout} seconds and must be restarted",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# we know these are not None because we created the process with PIPEs
|
|
64
|
+
assert self._process.stdin
|
|
65
|
+
assert self._process.stdout
|
|
66
|
+
assert self._process.stderr
|
|
67
|
+
|
|
68
|
+
# send command to the process
|
|
69
|
+
self._process.stdin.write(command.encode() + f"; echo '{self._sentinel}'\n".encode())
|
|
70
|
+
await self._process.stdin.drain()
|
|
71
|
+
|
|
72
|
+
# read output from the process, until the sentinel is found
|
|
73
|
+
try:
|
|
74
|
+
async with asyncio.timeout(self._timeout):
|
|
75
|
+
while True:
|
|
76
|
+
await asyncio.sleep(self._output_delay)
|
|
77
|
+
# if we read directly from stdout/stderr, it will wait forever for
|
|
78
|
+
# EOF. use the StreamReader buffer directly instead.
|
|
79
|
+
output = (
|
|
80
|
+
self._process.stdout._buffer.decode()
|
|
81
|
+
) # pyright: ignore[reportAttributeAccessIssue]
|
|
82
|
+
if self._sentinel in output:
|
|
83
|
+
# strip the sentinel and break
|
|
84
|
+
output = output[: output.index(self._sentinel)]
|
|
85
|
+
break
|
|
86
|
+
except asyncio.TimeoutError:
|
|
87
|
+
self._timed_out = True
|
|
88
|
+
raise ToolError(
|
|
89
|
+
f"timed out: bash has not returned in {self._timeout} seconds and must be restarted",
|
|
90
|
+
) from None
|
|
91
|
+
|
|
92
|
+
if output.endswith("\n"):
|
|
93
|
+
output = output[:-1]
|
|
94
|
+
|
|
95
|
+
error = self._process.stderr._buffer.decode() # pyright: ignore[reportAttributeAccessIssue]
|
|
96
|
+
if error.endswith("\n"):
|
|
97
|
+
error = error[:-1]
|
|
98
|
+
|
|
99
|
+
# clear the buffers so that the next output can be read correctly
|
|
100
|
+
self._process.stdout._buffer.clear() # pyright: ignore[reportAttributeAccessIssue]
|
|
101
|
+
self._process.stderr._buffer.clear() # pyright: ignore[reportAttributeAccessIssue]
|
|
102
|
+
|
|
103
|
+
return CLIResult(output=output, error=error)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class BashTool(BaseBashTool, BaseAnthropicTool):
|
|
107
|
+
"""
|
|
108
|
+
A tool that allows the agent to run bash commands.
|
|
109
|
+
The tool parameters are defined by Anthropic and are not editable.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
name: ClassVar[Literal["bash"]] = "bash"
|
|
113
|
+
api_type: ClassVar[Literal["bash_20250124"]] = "bash_20250124"
|
|
114
|
+
_timeout: float = 120.0 # seconds
|
|
115
|
+
|
|
116
|
+
def __init__(self, computer: Computer):
|
|
117
|
+
"""Initialize the bash tool.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
computer: Computer instance for executing commands
|
|
121
|
+
"""
|
|
122
|
+
# Initialize the base bash tool first
|
|
123
|
+
BaseBashTool.__init__(self, computer)
|
|
124
|
+
# Then initialize the Anthropic tool
|
|
125
|
+
BaseAnthropicTool.__init__(self)
|
|
126
|
+
# Initialize bash session
|
|
127
|
+
self._session = _BashSession()
|
|
128
|
+
|
|
129
|
+
async def __call__(self, command: str | None = None, restart: bool = False, **kwargs):
|
|
130
|
+
"""Execute a bash command.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
command: The command to execute
|
|
134
|
+
restart: Whether to restart the shell (not used with computer interface)
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Tool execution result
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
ToolError: If command execution fails
|
|
141
|
+
"""
|
|
142
|
+
if restart:
|
|
143
|
+
return ToolResult(system="Restart not needed with computer interface.")
|
|
144
|
+
|
|
145
|
+
if command is None:
|
|
146
|
+
raise ToolError("no command provided.")
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
async with asyncio.timeout(self._timeout):
|
|
150
|
+
stdout, stderr = await self.computer.interface.run_command(command)
|
|
151
|
+
return CLIResult(output=stdout or "", error=stderr or "")
|
|
152
|
+
except asyncio.TimeoutError as e:
|
|
153
|
+
raise ToolError(f"Command timed out after {self._timeout} seconds") from e
|
|
154
|
+
except Exception as e:
|
|
155
|
+
raise ToolError(f"Failed to execute command: {str(e)}")
|
|
156
|
+
|
|
157
|
+
def to_params(self) -> Dict[str, Any]:
|
|
158
|
+
"""Convert tool to API parameters.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Dictionary with tool parameters
|
|
162
|
+
"""
|
|
163
|
+
return {"name": self.name, "type": self.api_type}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Collection classes for managing multiple tools."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from anthropic.types.beta import BetaToolUnionParam
|
|
6
|
+
|
|
7
|
+
from .base import (
|
|
8
|
+
BaseAnthropicTool,
|
|
9
|
+
ToolError,
|
|
10
|
+
ToolFailure,
|
|
11
|
+
ToolResult,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ToolCollection:
|
|
16
|
+
"""A collection of anthropic-defined tools."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, *tools: BaseAnthropicTool):
|
|
19
|
+
self.tools = tools
|
|
20
|
+
self.tool_map = {tool.to_params()["name"]: tool for tool in tools}
|
|
21
|
+
|
|
22
|
+
def to_params(
|
|
23
|
+
self,
|
|
24
|
+
) -> list[BetaToolUnionParam]:
|
|
25
|
+
return [tool.to_params() for tool in self.tools]
|
|
26
|
+
|
|
27
|
+
async def run(self, *, name: str, tool_input: dict[str, Any]) -> ToolResult:
|
|
28
|
+
tool = self.tool_map.get(name)
|
|
29
|
+
if not tool:
|
|
30
|
+
return ToolFailure(error=f"Tool {name} is invalid")
|
|
31
|
+
try:
|
|
32
|
+
return await tool(**tool_input)
|
|
33
|
+
except ToolError as e:
|
|
34
|
+
return ToolFailure(error=e.message)
|