cua-agent 0.3.2__py3-none-any.whl → 0.4.0b1__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/__init__.py +15 -51
- agent/__main__.py +21 -0
- agent/adapters/__init__.py +9 -0
- agent/adapters/huggingfacelocal_adapter.py +216 -0
- agent/agent.py +577 -0
- agent/callbacks/__init__.py +17 -0
- agent/callbacks/base.py +153 -0
- agent/callbacks/budget_manager.py +44 -0
- agent/callbacks/image_retention.py +139 -0
- agent/callbacks/logging.py +247 -0
- agent/callbacks/pii_anonymization.py +259 -0
- agent/callbacks/trajectory_saver.py +305 -0
- agent/cli.py +290 -0
- agent/computer_handler.py +107 -0
- agent/decorators.py +90 -0
- agent/loops/__init__.py +11 -0
- agent/loops/anthropic.py +728 -0
- agent/loops/omniparser.py +339 -0
- agent/loops/openai.py +95 -0
- agent/loops/uitars.py +688 -0
- agent/responses.py +207 -0
- agent/types.py +79 -0
- agent/ui/__init__.py +7 -1
- agent/ui/gradio/__init__.py +6 -19
- agent/ui/gradio/app.py +80 -1299
- agent/ui/gradio/ui_components.py +703 -0
- cua_agent-0.4.0b1.dist-info/METADATA +424 -0
- cua_agent-0.4.0b1.dist-info/RECORD +30 -0
- agent/core/__init__.py +0 -27
- agent/core/agent.py +0 -210
- agent/core/base.py +0 -217
- agent/core/callbacks.py +0 -200
- agent/core/experiment.py +0 -249
- agent/core/factory.py +0 -122
- agent/core/messages.py +0 -332
- agent/core/provider_config.py +0 -21
- agent/core/telemetry.py +0 -142
- agent/core/tools/__init__.py +0 -21
- agent/core/tools/base.py +0 -74
- agent/core/tools/bash.py +0 -52
- agent/core/tools/collection.py +0 -46
- agent/core/tools/computer.py +0 -113
- agent/core/tools/edit.py +0 -67
- agent/core/tools/manager.py +0 -56
- agent/core/tools.py +0 -32
- agent/core/types.py +0 -88
- agent/core/visualization.py +0 -197
- agent/providers/__init__.py +0 -4
- agent/providers/anthropic/__init__.py +0 -6
- agent/providers/anthropic/api/client.py +0 -360
- agent/providers/anthropic/api/logging.py +0 -150
- agent/providers/anthropic/api_handler.py +0 -140
- agent/providers/anthropic/callbacks/__init__.py +0 -5
- agent/providers/anthropic/callbacks/manager.py +0 -65
- agent/providers/anthropic/loop.py +0 -568
- agent/providers/anthropic/prompts.py +0 -23
- agent/providers/anthropic/response_handler.py +0 -226
- agent/providers/anthropic/tools/__init__.py +0 -33
- agent/providers/anthropic/tools/base.py +0 -88
- agent/providers/anthropic/tools/bash.py +0 -66
- agent/providers/anthropic/tools/collection.py +0 -34
- agent/providers/anthropic/tools/computer.py +0 -396
- agent/providers/anthropic/tools/edit.py +0 -326
- agent/providers/anthropic/tools/manager.py +0 -54
- agent/providers/anthropic/tools/run.py +0 -42
- agent/providers/anthropic/types.py +0 -16
- agent/providers/anthropic/utils.py +0 -381
- agent/providers/omni/__init__.py +0 -8
- agent/providers/omni/api_handler.py +0 -42
- agent/providers/omni/clients/anthropic.py +0 -103
- agent/providers/omni/clients/base.py +0 -35
- agent/providers/omni/clients/oaicompat.py +0 -195
- agent/providers/omni/clients/ollama.py +0 -122
- agent/providers/omni/clients/openai.py +0 -155
- agent/providers/omni/clients/utils.py +0 -25
- agent/providers/omni/image_utils.py +0 -34
- agent/providers/omni/loop.py +0 -990
- agent/providers/omni/parser.py +0 -307
- agent/providers/omni/prompts.py +0 -64
- agent/providers/omni/tools/__init__.py +0 -30
- agent/providers/omni/tools/base.py +0 -29
- agent/providers/omni/tools/bash.py +0 -74
- agent/providers/omni/tools/computer.py +0 -179
- agent/providers/omni/tools/manager.py +0 -61
- agent/providers/omni/utils.py +0 -236
- agent/providers/openai/__init__.py +0 -6
- agent/providers/openai/api_handler.py +0 -456
- agent/providers/openai/loop.py +0 -472
- agent/providers/openai/response_handler.py +0 -205
- agent/providers/openai/tools/__init__.py +0 -15
- agent/providers/openai/tools/base.py +0 -79
- agent/providers/openai/tools/computer.py +0 -326
- agent/providers/openai/tools/manager.py +0 -106
- agent/providers/openai/types.py +0 -36
- agent/providers/openai/utils.py +0 -98
- agent/providers/uitars/__init__.py +0 -1
- agent/providers/uitars/clients/base.py +0 -35
- agent/providers/uitars/clients/mlxvlm.py +0 -263
- agent/providers/uitars/clients/oaicompat.py +0 -214
- agent/providers/uitars/loop.py +0 -660
- agent/providers/uitars/prompts.py +0 -63
- agent/providers/uitars/tools/__init__.py +0 -1
- agent/providers/uitars/tools/computer.py +0 -283
- agent/providers/uitars/tools/manager.py +0 -60
- agent/providers/uitars/utils.py +0 -264
- agent/telemetry.py +0 -21
- agent/ui/__main__.py +0 -15
- cua_agent-0.3.2.dist-info/METADATA +0 -295
- cua_agent-0.3.2.dist-info/RECORD +0 -87
- {cua_agent-0.3.2.dist-info → cua_agent-0.4.0b1.dist-info}/WHEEL +0 -0
- {cua_agent-0.3.2.dist-info → cua_agent-0.4.0b1.dist-info}/entry_points.txt +0 -0
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
"""API logging functionality."""
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import logging
|
|
5
|
-
from datetime import datetime
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
import httpx
|
|
8
|
-
from typing import Any
|
|
9
|
-
|
|
10
|
-
logger = logging.getLogger(__name__)
|
|
11
|
-
|
|
12
|
-
def _filter_base64_images(content: Any) -> Any:
|
|
13
|
-
"""Filter out base64 image data from content.
|
|
14
|
-
|
|
15
|
-
Args:
|
|
16
|
-
content: Content to filter
|
|
17
|
-
|
|
18
|
-
Returns:
|
|
19
|
-
Filtered content with base64 data replaced by placeholder
|
|
20
|
-
"""
|
|
21
|
-
if isinstance(content, dict):
|
|
22
|
-
filtered = {}
|
|
23
|
-
for key, value in content.items():
|
|
24
|
-
if (
|
|
25
|
-
isinstance(value, dict)
|
|
26
|
-
and value.get("type") == "image"
|
|
27
|
-
and value.get("source", {}).get("type") == "base64"
|
|
28
|
-
):
|
|
29
|
-
# Replace base64 data with placeholder
|
|
30
|
-
filtered[key] = {
|
|
31
|
-
**value,
|
|
32
|
-
"source": {
|
|
33
|
-
**value["source"],
|
|
34
|
-
"data": "<base64_image_data>"
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
else:
|
|
38
|
-
filtered[key] = _filter_base64_images(value)
|
|
39
|
-
return filtered
|
|
40
|
-
elif isinstance(content, list):
|
|
41
|
-
return [_filter_base64_images(item) for item in content]
|
|
42
|
-
return content
|
|
43
|
-
|
|
44
|
-
def log_api_interaction(
|
|
45
|
-
request: httpx.Request | None,
|
|
46
|
-
response: httpx.Response | object | None,
|
|
47
|
-
error: Exception | None,
|
|
48
|
-
log_dir: Path = Path("/tmp/claude_logs")
|
|
49
|
-
) -> None:
|
|
50
|
-
"""Log API request, response, and any errors in a structured way.
|
|
51
|
-
|
|
52
|
-
Args:
|
|
53
|
-
request: The HTTP request if available
|
|
54
|
-
response: The HTTP response or response object
|
|
55
|
-
error: Any error that occurred
|
|
56
|
-
log_dir: Directory to store log files
|
|
57
|
-
"""
|
|
58
|
-
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
|
|
59
|
-
|
|
60
|
-
# Helper function to safely decode JSON content
|
|
61
|
-
def safe_json_decode(content):
|
|
62
|
-
if not content:
|
|
63
|
-
return None
|
|
64
|
-
try:
|
|
65
|
-
if isinstance(content, bytes):
|
|
66
|
-
return json.loads(content.decode())
|
|
67
|
-
elif isinstance(content, str):
|
|
68
|
-
return json.loads(content)
|
|
69
|
-
elif isinstance(content, dict):
|
|
70
|
-
return content
|
|
71
|
-
return None
|
|
72
|
-
except json.JSONDecodeError:
|
|
73
|
-
return {"error": "Could not decode JSON", "raw": str(content)}
|
|
74
|
-
|
|
75
|
-
# Process request content
|
|
76
|
-
request_content = None
|
|
77
|
-
if request and request.content:
|
|
78
|
-
request_content = safe_json_decode(request.content)
|
|
79
|
-
request_content = _filter_base64_images(request_content)
|
|
80
|
-
|
|
81
|
-
# Process response content
|
|
82
|
-
response_content = None
|
|
83
|
-
if response:
|
|
84
|
-
if isinstance(response, httpx.Response):
|
|
85
|
-
try:
|
|
86
|
-
response_content = response.json()
|
|
87
|
-
except json.JSONDecodeError:
|
|
88
|
-
response_content = {"error": "Could not decode JSON", "raw": response.text}
|
|
89
|
-
else:
|
|
90
|
-
response_content = safe_json_decode(response)
|
|
91
|
-
response_content = _filter_base64_images(response_content)
|
|
92
|
-
|
|
93
|
-
log_entry = {
|
|
94
|
-
"timestamp": timestamp,
|
|
95
|
-
"request": {
|
|
96
|
-
"method": request.method if request else None,
|
|
97
|
-
"url": str(request.url) if request else None,
|
|
98
|
-
"headers": dict(request.headers) if request else None,
|
|
99
|
-
"content": request_content,
|
|
100
|
-
} if request else None,
|
|
101
|
-
"response": {
|
|
102
|
-
"status_code": response.status_code if isinstance(response, httpx.Response) else None,
|
|
103
|
-
"headers": dict(response.headers) if isinstance(response, httpx.Response) else None,
|
|
104
|
-
"content": response_content,
|
|
105
|
-
} if response else None,
|
|
106
|
-
"error": {
|
|
107
|
-
"type": type(error).__name__ if error else None,
|
|
108
|
-
"message": str(error) if error else None,
|
|
109
|
-
} if error else None
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
# Log to file with timestamp in filename
|
|
113
|
-
log_dir.mkdir(exist_ok=True)
|
|
114
|
-
log_file = log_dir / f"claude_api_{timestamp.replace(' ', '_').replace(':', '-')}.json"
|
|
115
|
-
|
|
116
|
-
with open(log_file, 'w') as f:
|
|
117
|
-
json.dump(log_entry, f, indent=2)
|
|
118
|
-
|
|
119
|
-
# Also log a summary to the console
|
|
120
|
-
if error:
|
|
121
|
-
logger.error(f"API Error at {timestamp}: {error}")
|
|
122
|
-
else:
|
|
123
|
-
logger.info(
|
|
124
|
-
f"API Call at {timestamp}: "
|
|
125
|
-
f"{request.method if request else 'No request'} -> "
|
|
126
|
-
f"{response.status_code if isinstance(response, httpx.Response) else 'No response'}"
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
# Log if there are any images in the content
|
|
130
|
-
if response_content:
|
|
131
|
-
image_count = count_images(response_content)
|
|
132
|
-
if image_count > 0:
|
|
133
|
-
logger.info(f"Response contains {image_count} images")
|
|
134
|
-
|
|
135
|
-
def count_images(content: dict | list | Any) -> int:
|
|
136
|
-
"""Count the number of images in the content.
|
|
137
|
-
|
|
138
|
-
Args:
|
|
139
|
-
content: Content to search for images
|
|
140
|
-
|
|
141
|
-
Returns:
|
|
142
|
-
Number of images found
|
|
143
|
-
"""
|
|
144
|
-
if isinstance(content, dict):
|
|
145
|
-
if content.get("type") == "image":
|
|
146
|
-
return 1
|
|
147
|
-
return sum(count_images(v) for v in content.values())
|
|
148
|
-
elif isinstance(content, list):
|
|
149
|
-
return sum(count_images(item) for item in content)
|
|
150
|
-
return 0
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
"""API call handling for Anthropic provider."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
import asyncio
|
|
5
|
-
from typing import List
|
|
6
|
-
|
|
7
|
-
from anthropic.types.beta import (
|
|
8
|
-
BetaMessage,
|
|
9
|
-
BetaMessageParam,
|
|
10
|
-
BetaTextBlockParam,
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
from .types import LLMProvider
|
|
14
|
-
from .prompts import SYSTEM_PROMPT
|
|
15
|
-
|
|
16
|
-
# Constants
|
|
17
|
-
COMPUTER_USE_BETA_FLAG = "computer-use-2025-01-24"
|
|
18
|
-
PROMPT_CACHING_BETA_FLAG = "prompt-caching-2024-07-31"
|
|
19
|
-
|
|
20
|
-
logger = logging.getLogger(__name__)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class AnthropicAPIHandler:
|
|
24
|
-
"""Handles API calls to Anthropic's API with structured error handling and retries."""
|
|
25
|
-
|
|
26
|
-
def __init__(self, loop):
|
|
27
|
-
"""Initialize the API handler.
|
|
28
|
-
|
|
29
|
-
Args:
|
|
30
|
-
loop: Reference to the parent loop instance that provides context
|
|
31
|
-
"""
|
|
32
|
-
self.loop = loop
|
|
33
|
-
|
|
34
|
-
async def make_api_call(
|
|
35
|
-
self, messages: List[BetaMessageParam], system_prompt: str = SYSTEM_PROMPT
|
|
36
|
-
) -> BetaMessage:
|
|
37
|
-
"""Make API call to Anthropic with retry logic.
|
|
38
|
-
|
|
39
|
-
Args:
|
|
40
|
-
messages: List of messages to send to the API
|
|
41
|
-
system_prompt: System prompt to use (default: SYSTEM_PROMPT)
|
|
42
|
-
|
|
43
|
-
Returns:
|
|
44
|
-
API response
|
|
45
|
-
|
|
46
|
-
Raises:
|
|
47
|
-
RuntimeError: If API call fails after all retries
|
|
48
|
-
"""
|
|
49
|
-
if self.loop.client is None:
|
|
50
|
-
raise RuntimeError("Client not initialized. Call initialize_client() first.")
|
|
51
|
-
if self.loop.tool_manager is None:
|
|
52
|
-
raise RuntimeError("Tool manager not initialized. Call initialize_client() first.")
|
|
53
|
-
|
|
54
|
-
last_error = None
|
|
55
|
-
|
|
56
|
-
# Add detailed debug logging to examine messages
|
|
57
|
-
logger.info(f"Sending {len(messages)} messages to Anthropic API")
|
|
58
|
-
|
|
59
|
-
# Log tool use IDs and tool result IDs for debugging
|
|
60
|
-
tool_use_ids = set()
|
|
61
|
-
tool_result_ids = set()
|
|
62
|
-
|
|
63
|
-
for i, msg in enumerate(messages):
|
|
64
|
-
logger.info(f"Message {i}: role={msg.get('role')}")
|
|
65
|
-
if isinstance(msg.get("content"), list):
|
|
66
|
-
for content_block in msg.get("content", []):
|
|
67
|
-
if isinstance(content_block, dict):
|
|
68
|
-
block_type = content_block.get("type")
|
|
69
|
-
if block_type == "tool_use" and "id" in content_block:
|
|
70
|
-
tool_id = content_block.get("id")
|
|
71
|
-
tool_use_ids.add(tool_id)
|
|
72
|
-
logger.info(f" - Found tool_use with ID: {tool_id}")
|
|
73
|
-
elif block_type == "tool_result" and "tool_use_id" in content_block:
|
|
74
|
-
result_id = content_block.get("tool_use_id")
|
|
75
|
-
tool_result_ids.add(result_id)
|
|
76
|
-
logger.info(f" - Found tool_result referencing ID: {result_id}")
|
|
77
|
-
|
|
78
|
-
# Check for mismatches
|
|
79
|
-
missing_tool_uses = tool_result_ids - tool_use_ids
|
|
80
|
-
if missing_tool_uses:
|
|
81
|
-
logger.warning(
|
|
82
|
-
f"Found tool_result IDs without matching tool_use IDs: {missing_tool_uses}"
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
for attempt in range(self.loop.max_retries):
|
|
86
|
-
try:
|
|
87
|
-
# Log request
|
|
88
|
-
request_data = {
|
|
89
|
-
"messages": messages,
|
|
90
|
-
"max_tokens": self.loop.max_tokens,
|
|
91
|
-
"system": system_prompt,
|
|
92
|
-
}
|
|
93
|
-
# Let ExperimentManager handle sanitization
|
|
94
|
-
self.loop._log_api_call("request", request_data)
|
|
95
|
-
|
|
96
|
-
# Setup betas and system
|
|
97
|
-
system = BetaTextBlockParam(
|
|
98
|
-
type="text",
|
|
99
|
-
text=system_prompt,
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
betas = [COMPUTER_USE_BETA_FLAG]
|
|
103
|
-
# Add prompt caching if enabled in the message manager's config
|
|
104
|
-
if self.loop.message_manager.config.enable_caching:
|
|
105
|
-
betas.append(PROMPT_CACHING_BETA_FLAG)
|
|
106
|
-
system["cache_control"] = {"type": "ephemeral"}
|
|
107
|
-
|
|
108
|
-
# Make API call
|
|
109
|
-
response = await self.loop.client.create_message(
|
|
110
|
-
messages=messages,
|
|
111
|
-
system=[system],
|
|
112
|
-
tools=self.loop.tool_manager.get_tool_params(),
|
|
113
|
-
max_tokens=self.loop.max_tokens,
|
|
114
|
-
betas=betas,
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
# Let ExperimentManager handle sanitization
|
|
118
|
-
self.loop._log_api_call("response", request_data, response)
|
|
119
|
-
|
|
120
|
-
return response
|
|
121
|
-
except Exception as e:
|
|
122
|
-
last_error = e
|
|
123
|
-
logger.error(
|
|
124
|
-
f"Error in API call (attempt {attempt + 1}/{self.loop.max_retries}): {str(e)}"
|
|
125
|
-
)
|
|
126
|
-
self.loop._log_api_call("error", {"messages": messages}, error=e)
|
|
127
|
-
|
|
128
|
-
if attempt < self.loop.max_retries - 1:
|
|
129
|
-
await asyncio.sleep(
|
|
130
|
-
self.loop.retry_delay * (attempt + 1)
|
|
131
|
-
) # Exponential backoff
|
|
132
|
-
continue
|
|
133
|
-
|
|
134
|
-
# If we get here, all retries failed
|
|
135
|
-
error_message = f"API call failed after {self.loop.max_retries} attempts"
|
|
136
|
-
if last_error:
|
|
137
|
-
error_message += f": {str(last_error)}"
|
|
138
|
-
|
|
139
|
-
logger.error(error_message)
|
|
140
|
-
raise RuntimeError(error_message)
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
from typing import Callable, Protocol
|
|
2
|
-
import httpx
|
|
3
|
-
from anthropic.types.beta import BetaContentBlockParam
|
|
4
|
-
from ..tools import ToolResult
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class APICallback(Protocol):
|
|
8
|
-
"""Protocol for API callbacks."""
|
|
9
|
-
|
|
10
|
-
def __call__(
|
|
11
|
-
self,
|
|
12
|
-
request: httpx.Request | None,
|
|
13
|
-
response: httpx.Response | object | None,
|
|
14
|
-
error: Exception | None,
|
|
15
|
-
) -> None: ...
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class ContentCallback(Protocol):
|
|
19
|
-
"""Protocol for content callbacks."""
|
|
20
|
-
|
|
21
|
-
def __call__(self, content: BetaContentBlockParam) -> None: ...
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class ToolCallback(Protocol):
|
|
25
|
-
"""Protocol for tool callbacks."""
|
|
26
|
-
|
|
27
|
-
def __call__(self, result: ToolResult, tool_id: str) -> None: ...
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class CallbackManager:
|
|
31
|
-
"""Manages various callbacks for the agent system."""
|
|
32
|
-
|
|
33
|
-
def __init__(
|
|
34
|
-
self,
|
|
35
|
-
content_callback: ContentCallback,
|
|
36
|
-
tool_callback: ToolCallback,
|
|
37
|
-
api_callback: APICallback,
|
|
38
|
-
):
|
|
39
|
-
"""Initialize the callback manager.
|
|
40
|
-
|
|
41
|
-
Args:
|
|
42
|
-
content_callback: Callback for content updates
|
|
43
|
-
tool_callback: Callback for tool execution results
|
|
44
|
-
api_callback: Callback for API interactions
|
|
45
|
-
"""
|
|
46
|
-
self.content_callback = content_callback
|
|
47
|
-
self.tool_callback = tool_callback
|
|
48
|
-
self.api_callback = api_callback
|
|
49
|
-
|
|
50
|
-
def on_content(self, content: BetaContentBlockParam) -> None:
|
|
51
|
-
"""Handle content updates."""
|
|
52
|
-
self.content_callback(content)
|
|
53
|
-
|
|
54
|
-
def on_tool_result(self, result: ToolResult, tool_id: str) -> None:
|
|
55
|
-
"""Handle tool execution results."""
|
|
56
|
-
self.tool_callback(result, tool_id)
|
|
57
|
-
|
|
58
|
-
def on_api_interaction(
|
|
59
|
-
self,
|
|
60
|
-
request: httpx.Request | None,
|
|
61
|
-
response: httpx.Response | object | None,
|
|
62
|
-
error: Exception | None,
|
|
63
|
-
) -> None:
|
|
64
|
-
"""Handle API interactions."""
|
|
65
|
-
self.api_callback(request, response, error)
|