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.

Files changed (65) hide show
  1. agent/README.md +63 -0
  2. agent/__init__.py +10 -0
  3. agent/core/README.md +101 -0
  4. agent/core/__init__.py +34 -0
  5. agent/core/agent.py +284 -0
  6. agent/core/base_agent.py +164 -0
  7. agent/core/callbacks.py +147 -0
  8. agent/core/computer_agent.py +69 -0
  9. agent/core/experiment.py +222 -0
  10. agent/core/factory.py +102 -0
  11. agent/core/loop.py +244 -0
  12. agent/core/messages.py +230 -0
  13. agent/core/tools/__init__.py +21 -0
  14. agent/core/tools/base.py +74 -0
  15. agent/core/tools/bash.py +52 -0
  16. agent/core/tools/collection.py +46 -0
  17. agent/core/tools/computer.py +113 -0
  18. agent/core/tools/edit.py +67 -0
  19. agent/core/tools/manager.py +56 -0
  20. agent/providers/__init__.py +4 -0
  21. agent/providers/anthropic/__init__.py +6 -0
  22. agent/providers/anthropic/api/client.py +222 -0
  23. agent/providers/anthropic/api/logging.py +150 -0
  24. agent/providers/anthropic/callbacks/manager.py +55 -0
  25. agent/providers/anthropic/loop.py +521 -0
  26. agent/providers/anthropic/messages/manager.py +110 -0
  27. agent/providers/anthropic/prompts.py +20 -0
  28. agent/providers/anthropic/tools/__init__.py +33 -0
  29. agent/providers/anthropic/tools/base.py +88 -0
  30. agent/providers/anthropic/tools/bash.py +163 -0
  31. agent/providers/anthropic/tools/collection.py +34 -0
  32. agent/providers/anthropic/tools/computer.py +550 -0
  33. agent/providers/anthropic/tools/edit.py +326 -0
  34. agent/providers/anthropic/tools/manager.py +54 -0
  35. agent/providers/anthropic/tools/run.py +42 -0
  36. agent/providers/anthropic/types.py +16 -0
  37. agent/providers/omni/__init__.py +27 -0
  38. agent/providers/omni/callbacks.py +78 -0
  39. agent/providers/omni/clients/anthropic.py +99 -0
  40. agent/providers/omni/clients/base.py +44 -0
  41. agent/providers/omni/clients/groq.py +101 -0
  42. agent/providers/omni/clients/openai.py +159 -0
  43. agent/providers/omni/clients/utils.py +25 -0
  44. agent/providers/omni/experiment.py +273 -0
  45. agent/providers/omni/image_utils.py +106 -0
  46. agent/providers/omni/loop.py +961 -0
  47. agent/providers/omni/messages.py +168 -0
  48. agent/providers/omni/parser.py +252 -0
  49. agent/providers/omni/prompts.py +78 -0
  50. agent/providers/omni/tool_manager.py +91 -0
  51. agent/providers/omni/tools/__init__.py +13 -0
  52. agent/providers/omni/tools/bash.py +69 -0
  53. agent/providers/omni/tools/computer.py +216 -0
  54. agent/providers/omni/tools/manager.py +83 -0
  55. agent/providers/omni/types.py +30 -0
  56. agent/providers/omni/utils.py +155 -0
  57. agent/providers/omni/visualization.py +130 -0
  58. agent/types/__init__.py +26 -0
  59. agent/types/base.py +52 -0
  60. agent/types/messages.py +36 -0
  61. agent/types/tools.py +32 -0
  62. cua_agent-0.1.0.dist-info/METADATA +44 -0
  63. cua_agent-0.1.0.dist-info/RECORD +65 -0
  64. cua_agent-0.1.0.dist-info/WHEEL +4 -0
  65. 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)