cua-agent 0.4.7__tar.gz → 0.4.9__tar.gz
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.
- {cua_agent-0.4.7 → cua_agent-0.4.9}/PKG-INFO +2 -2
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/__init__.py +2 -2
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/adapters/huggingfacelocal_adapter.py +5 -1
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/agent.py +82 -15
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/cli.py +9 -3
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/computer_handler.py +7 -3
- cua_agent-0.4.9/agent/decorators.py +52 -0
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/loops/__init__.py +3 -1
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/loops/anthropic.py +200 -84
- cua_agent-0.4.9/agent/loops/base.py +76 -0
- cua_agent-0.4.9/agent/loops/composed_grounded.py +318 -0
- cua_agent-0.4.9/agent/loops/gta1.py +178 -0
- cua_agent-0.4.9/agent/loops/model_types.csv +6 -0
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/loops/omniparser.py +178 -84
- cua_agent-0.4.9/agent/loops/openai.py +235 -0
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/loops/uitars.py +305 -178
- cua_agent-0.4.9/agent/responses.py +683 -0
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/types.py +7 -5
- {cua_agent-0.4.7 → cua_agent-0.4.9}/pyproject.toml +2 -2
- cua_agent-0.4.7/agent/decorators.py +0 -90
- cua_agent-0.4.7/agent/loops/openai.py +0 -95
- cua_agent-0.4.7/agent/responses.py +0 -207
- {cua_agent-0.4.7 → cua_agent-0.4.9}/README.md +0 -0
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/__main__.py +0 -0
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/adapters/__init__.py +0 -0
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/callbacks/__init__.py +0 -0
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/callbacks/base.py +0 -0
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/callbacks/budget_manager.py +0 -0
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/callbacks/image_retention.py +0 -0
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/callbacks/logging.py +0 -0
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/callbacks/pii_anonymization.py +0 -0
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/callbacks/telemetry.py +0 -0
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/callbacks/trajectory_saver.py +0 -0
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/telemetry.py +0 -0
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/ui/__init__.py +0 -0
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/ui/__main__.py +0 -0
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/ui/gradio/__init__.py +0 -0
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/ui/gradio/app.py +0 -0
- {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/ui/gradio/ui_components.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cua-agent
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.9
|
|
4
4
|
Summary: CUA (Computer Use) Agent for AI-driven computer interaction
|
|
5
5
|
Author-Email: TryCua <gh@trycua.com>
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -15,7 +15,7 @@ Requires-Dist: python-dotenv>=1.0.1
|
|
|
15
15
|
Requires-Dist: cua-computer<0.5.0,>=0.4.0
|
|
16
16
|
Requires-Dist: cua-core<0.2.0,>=0.1.8
|
|
17
17
|
Requires-Dist: certifi>=2024.2.2
|
|
18
|
-
Requires-Dist: litellm>=1.74.
|
|
18
|
+
Requires-Dist: litellm>=1.74.12
|
|
19
19
|
Provides-Extra: openai
|
|
20
20
|
Provides-Extra: anthropic
|
|
21
21
|
Provides-Extra: omni
|
|
@@ -5,7 +5,7 @@ agent - Decorator-based Computer Use Agent with liteLLM integration
|
|
|
5
5
|
import logging
|
|
6
6
|
import sys
|
|
7
7
|
|
|
8
|
-
from .decorators import
|
|
8
|
+
from .decorators import register_agent
|
|
9
9
|
from .agent import ComputerAgent
|
|
10
10
|
from .types import Messages, AgentResponse
|
|
11
11
|
|
|
@@ -13,7 +13,7 @@ from .types import Messages, AgentResponse
|
|
|
13
13
|
from . import loops
|
|
14
14
|
|
|
15
15
|
__all__ = [
|
|
16
|
-
"
|
|
16
|
+
"register_agent",
|
|
17
17
|
"ComputerAgent",
|
|
18
18
|
"Messages",
|
|
19
19
|
"AgentResponse"
|
|
@@ -48,7 +48,11 @@ class HuggingFaceLocalAdapter(CustomLLM):
|
|
|
48
48
|
)
|
|
49
49
|
|
|
50
50
|
# Load processor
|
|
51
|
-
processor = AutoProcessor.from_pretrained(
|
|
51
|
+
processor = AutoProcessor.from_pretrained(
|
|
52
|
+
model_name,
|
|
53
|
+
min_pixels=3136,
|
|
54
|
+
max_pixels=4096 * 2160
|
|
55
|
+
)
|
|
52
56
|
|
|
53
57
|
# Cache them
|
|
54
58
|
self.models[model_name] = model
|
|
@@ -3,12 +3,12 @@ ComputerAgent - Main agent class that selects and runs agent loops
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
-
from typing import Dict, List, Any, Optional, AsyncGenerator, Union, cast, Callable, Set
|
|
6
|
+
from typing import Dict, List, Any, Optional, AsyncGenerator, Union, cast, Callable, Set, Tuple
|
|
7
7
|
|
|
8
8
|
from litellm.responses.utils import Usage
|
|
9
9
|
|
|
10
|
-
from .types import Messages, Computer
|
|
11
|
-
from .decorators import
|
|
10
|
+
from .types import Messages, Computer, AgentCapability
|
|
11
|
+
from .decorators import find_agent_config
|
|
12
12
|
from .computer_handler import OpenAIComputerHandler, acknowledge_safety_check_callback, check_blocklisted_url
|
|
13
13
|
import json
|
|
14
14
|
import litellm
|
|
@@ -117,6 +117,13 @@ def sanitize_message(msg: Any) -> Any:
|
|
|
117
117
|
return sanitized
|
|
118
118
|
return msg
|
|
119
119
|
|
|
120
|
+
def get_output_call_ids(messages: List[Dict[str, Any]]) -> List[str]:
|
|
121
|
+
call_ids = []
|
|
122
|
+
for message in messages:
|
|
123
|
+
if message.get("type") == "computer_call_output" or message.get("type") == "function_call_output":
|
|
124
|
+
call_ids.append(message.get("call_id"))
|
|
125
|
+
return call_ids
|
|
126
|
+
|
|
120
127
|
class ComputerAgent:
|
|
121
128
|
"""
|
|
122
129
|
Main agent class that automatically selects the appropriate agent loop
|
|
@@ -207,19 +214,21 @@ class ComputerAgent:
|
|
|
207
214
|
litellm.custom_provider_map = [
|
|
208
215
|
{"provider": "huggingface-local", "custom_handler": hf_adapter}
|
|
209
216
|
]
|
|
217
|
+
litellm.suppress_debug_info = True
|
|
210
218
|
|
|
211
219
|
# == Initialize computer agent ==
|
|
212
220
|
|
|
213
221
|
# Find the appropriate agent loop
|
|
214
222
|
if custom_loop:
|
|
215
223
|
self.agent_loop = custom_loop
|
|
216
|
-
self.
|
|
224
|
+
self.agent_config_info = None
|
|
217
225
|
else:
|
|
218
|
-
|
|
219
|
-
if not
|
|
220
|
-
raise ValueError(f"No agent
|
|
221
|
-
|
|
222
|
-
self.
|
|
226
|
+
config_info = find_agent_config(model)
|
|
227
|
+
if not config_info:
|
|
228
|
+
raise ValueError(f"No agent config found for model: {model}")
|
|
229
|
+
# Instantiate the agent config class
|
|
230
|
+
self.agent_loop = config_info.agent_class()
|
|
231
|
+
self.agent_config_info = config_info
|
|
223
232
|
|
|
224
233
|
self.tool_schemas = []
|
|
225
234
|
self.computer_handler = None
|
|
@@ -389,8 +398,10 @@ class ComputerAgent:
|
|
|
389
398
|
# AGENT OUTPUT PROCESSING
|
|
390
399
|
# ============================================================================
|
|
391
400
|
|
|
392
|
-
async def _handle_item(self, item: Any, computer: Optional[Computer] = None) -> List[Dict[str, Any]]:
|
|
401
|
+
async def _handle_item(self, item: Any, computer: Optional[Computer] = None, ignore_call_ids: Optional[List[str]] = None) -> List[Dict[str, Any]]:
|
|
393
402
|
"""Handle each item; may cause a computer action + screenshot."""
|
|
403
|
+
if ignore_call_ids and item.get("call_id") and item.get("call_id") in ignore_call_ids:
|
|
404
|
+
return []
|
|
394
405
|
|
|
395
406
|
item_type = item.get("type", None)
|
|
396
407
|
|
|
@@ -439,7 +450,7 @@ class ComputerAgent:
|
|
|
439
450
|
acknowledged_checks = []
|
|
440
451
|
for check in pending_checks:
|
|
441
452
|
check_message = check.get("message", str(check))
|
|
442
|
-
if acknowledge_safety_check_callback(check_message):
|
|
453
|
+
if acknowledge_safety_check_callback(check_message, allow_always=True): # TODO: implement a callback for safety checks
|
|
443
454
|
acknowledged_checks.append(check)
|
|
444
455
|
else:
|
|
445
456
|
raise ValueError(f"Safety check failed: {check_message}")
|
|
@@ -514,6 +525,12 @@ class ComputerAgent:
|
|
|
514
525
|
Returns:
|
|
515
526
|
AsyncGenerator that yields response chunks
|
|
516
527
|
"""
|
|
528
|
+
if not self.agent_config_info:
|
|
529
|
+
raise ValueError("Agent configuration not found")
|
|
530
|
+
|
|
531
|
+
capabilities = self.get_capabilities()
|
|
532
|
+
if "step" not in capabilities:
|
|
533
|
+
raise ValueError(f"Agent loop {self.agent_config_info.agent_class.__name__} does not support step predictions")
|
|
517
534
|
|
|
518
535
|
await self._initialize_computers()
|
|
519
536
|
|
|
@@ -528,7 +545,7 @@ class ComputerAgent:
|
|
|
528
545
|
"messages": messages,
|
|
529
546
|
"stream": stream,
|
|
530
547
|
"model": self.model,
|
|
531
|
-
"agent_loop": self.
|
|
548
|
+
"agent_loop": self.agent_config_info.agent_class.__name__,
|
|
532
549
|
**merged_kwargs
|
|
533
550
|
}
|
|
534
551
|
await self._on_run_start(run_kwargs, old_items)
|
|
@@ -558,7 +575,7 @@ class ComputerAgent:
|
|
|
558
575
|
}
|
|
559
576
|
|
|
560
577
|
# Run agent loop iteration
|
|
561
|
-
result = await self.agent_loop(
|
|
578
|
+
result = await self.agent_loop.predict_step(
|
|
562
579
|
**loop_kwargs,
|
|
563
580
|
_on_api_start=self._on_api_start,
|
|
564
581
|
_on_api_end=self._on_api_end,
|
|
@@ -579,9 +596,12 @@ class ComputerAgent:
|
|
|
579
596
|
# Add agent response to new_items
|
|
580
597
|
new_items += result.get("output")
|
|
581
598
|
|
|
599
|
+
# Get output call ids
|
|
600
|
+
output_call_ids = get_output_call_ids(result.get("output", []))
|
|
601
|
+
|
|
582
602
|
# Handle computer actions
|
|
583
603
|
for item in result.get("output"):
|
|
584
|
-
partial_items = await self._handle_item(item, self.computer_handler)
|
|
604
|
+
partial_items = await self._handle_item(item, self.computer_handler, ignore_call_ids=output_call_ids)
|
|
585
605
|
new_items += partial_items
|
|
586
606
|
|
|
587
607
|
# Yield partial response
|
|
@@ -594,4 +614,51 @@ class ComputerAgent:
|
|
|
594
614
|
)
|
|
595
615
|
}
|
|
596
616
|
|
|
597
|
-
await self._on_run_end(loop_kwargs, old_items, new_items)
|
|
617
|
+
await self._on_run_end(loop_kwargs, old_items, new_items)
|
|
618
|
+
|
|
619
|
+
async def predict_click(
|
|
620
|
+
self,
|
|
621
|
+
instruction: str,
|
|
622
|
+
image_b64: Optional[str] = None
|
|
623
|
+
) -> Optional[Tuple[int, int]]:
|
|
624
|
+
"""
|
|
625
|
+
Predict click coordinates based on image and instruction.
|
|
626
|
+
|
|
627
|
+
Args:
|
|
628
|
+
instruction: Instruction for where to click
|
|
629
|
+
image_b64: Base64 encoded image (optional, will take screenshot if not provided)
|
|
630
|
+
|
|
631
|
+
Returns:
|
|
632
|
+
None or tuple with (x, y) coordinates
|
|
633
|
+
"""
|
|
634
|
+
if not self.agent_config_info:
|
|
635
|
+
raise ValueError("Agent configuration not found")
|
|
636
|
+
|
|
637
|
+
capabilities = self.get_capabilities()
|
|
638
|
+
if "click" not in capabilities:
|
|
639
|
+
raise ValueError(f"Agent loop {self.agent_config_info.agent_class.__name__} does not support click predictions")
|
|
640
|
+
if hasattr(self.agent_loop, 'predict_click'):
|
|
641
|
+
if not image_b64:
|
|
642
|
+
if not self.computer_handler:
|
|
643
|
+
raise ValueError("Computer tool or image_b64 is required for predict_click")
|
|
644
|
+
image_b64 = await self.computer_handler.screenshot()
|
|
645
|
+
return await self.agent_loop.predict_click(
|
|
646
|
+
model=self.model,
|
|
647
|
+
image_b64=image_b64,
|
|
648
|
+
instruction=instruction
|
|
649
|
+
)
|
|
650
|
+
return None
|
|
651
|
+
|
|
652
|
+
def get_capabilities(self) -> List[AgentCapability]:
|
|
653
|
+
"""
|
|
654
|
+
Get list of capabilities supported by the current agent config.
|
|
655
|
+
|
|
656
|
+
Returns:
|
|
657
|
+
List of capability strings (e.g., ["step", "click"])
|
|
658
|
+
"""
|
|
659
|
+
if not self.agent_config_info:
|
|
660
|
+
raise ValueError("Agent configuration not found")
|
|
661
|
+
|
|
662
|
+
if hasattr(self.agent_loop, 'get_capabilities'):
|
|
663
|
+
return self.agent_loop.get_capabilities()
|
|
664
|
+
return ["step"] # Default capability
|
|
@@ -120,7 +120,7 @@ async def ainput(prompt: str = ""):
|
|
|
120
120
|
|
|
121
121
|
async def chat_loop(agent, model: str, container_name: str, initial_prompt: str = "", show_usage: bool = True):
|
|
122
122
|
"""Main chat loop with the agent."""
|
|
123
|
-
print_welcome(model, agent.
|
|
123
|
+
print_welcome(model, agent.agent_config_info.agent_class.__name__, container_name)
|
|
124
124
|
|
|
125
125
|
history = []
|
|
126
126
|
|
|
@@ -130,7 +130,7 @@ async def chat_loop(agent, model: str, container_name: str, initial_prompt: str
|
|
|
130
130
|
total_cost = 0
|
|
131
131
|
|
|
132
132
|
while True:
|
|
133
|
-
if history[-1].get("role") != "user":
|
|
133
|
+
if len(history) == 0 or history[-1].get("role") != "user":
|
|
134
134
|
# Get user input with prompt
|
|
135
135
|
print_colored("> ", end="")
|
|
136
136
|
user_input = await ainput()
|
|
@@ -260,7 +260,12 @@ Examples:
|
|
|
260
260
|
help="Show total cost of the agent runs"
|
|
261
261
|
)
|
|
262
262
|
|
|
263
|
-
|
|
263
|
+
parser.add_argument(
|
|
264
|
+
"-r", "--max-retries",
|
|
265
|
+
type=int,
|
|
266
|
+
default=3,
|
|
267
|
+
help="Maximum number of retries for the LLM API calls"
|
|
268
|
+
)
|
|
264
269
|
|
|
265
270
|
args = parser.parse_args()
|
|
266
271
|
|
|
@@ -327,6 +332,7 @@ Examples:
|
|
|
327
332
|
"model": args.model,
|
|
328
333
|
"tools": [computer],
|
|
329
334
|
"verbosity": 20 if args.verbose else 30, # DEBUG vs WARNING
|
|
335
|
+
"max_retries": args.max_retries
|
|
330
336
|
}
|
|
331
337
|
|
|
332
338
|
if args.images > 0:
|
|
@@ -3,7 +3,7 @@ Computer handler implementation for OpenAI computer-use-preview protocol.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import base64
|
|
6
|
-
from typing import Dict, List, Any, Literal
|
|
6
|
+
from typing import Dict, List, Any, Literal, Union
|
|
7
7
|
from .types import Computer
|
|
8
8
|
|
|
9
9
|
|
|
@@ -61,8 +61,10 @@ class OpenAIComputerHandler:
|
|
|
61
61
|
"""Move cursor to coordinates."""
|
|
62
62
|
await self.interface.move_cursor(x, y)
|
|
63
63
|
|
|
64
|
-
async def keypress(self, keys: List[str]) -> None:
|
|
64
|
+
async def keypress(self, keys: Union[List[str], str]) -> None:
|
|
65
65
|
"""Press key combination."""
|
|
66
|
+
if isinstance(keys, str):
|
|
67
|
+
keys = [keys]
|
|
66
68
|
if len(keys) == 1:
|
|
67
69
|
await self.interface.press_key(keys[0])
|
|
68
70
|
else:
|
|
@@ -93,8 +95,10 @@ class OpenAIComputerHandler:
|
|
|
93
95
|
return ""
|
|
94
96
|
|
|
95
97
|
|
|
96
|
-
def acknowledge_safety_check_callback(message: str) -> bool:
|
|
98
|
+
def acknowledge_safety_check_callback(message: str, allow_always: bool = False) -> bool:
|
|
97
99
|
"""Safety check callback for user acknowledgment."""
|
|
100
|
+
if allow_always:
|
|
101
|
+
return True
|
|
98
102
|
response = input(
|
|
99
103
|
f"Safety Check Warning: {message}\nDo you want to acknowledge and proceed? (y/n): "
|
|
100
104
|
).lower()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Decorators for agent - agent_loop decorator
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
from .types import AgentConfigInfo
|
|
7
|
+
|
|
8
|
+
# Global registry
|
|
9
|
+
_agent_configs: List[AgentConfigInfo] = []
|
|
10
|
+
|
|
11
|
+
def register_agent(models: str, priority: int = 0):
|
|
12
|
+
"""
|
|
13
|
+
Decorator to register an AsyncAgentConfig class.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
models: Regex pattern to match supported models
|
|
17
|
+
priority: Priority for agent selection (higher = more priority)
|
|
18
|
+
"""
|
|
19
|
+
def decorator(agent_class: type):
|
|
20
|
+
# Validate that the class implements AsyncAgentConfig protocol
|
|
21
|
+
if not hasattr(agent_class, 'predict_step'):
|
|
22
|
+
raise ValueError(f"Agent class {agent_class.__name__} must implement predict_step method")
|
|
23
|
+
if not hasattr(agent_class, 'predict_click'):
|
|
24
|
+
raise ValueError(f"Agent class {agent_class.__name__} must implement predict_click method")
|
|
25
|
+
if not hasattr(agent_class, 'get_capabilities'):
|
|
26
|
+
raise ValueError(f"Agent class {agent_class.__name__} must implement get_capabilities method")
|
|
27
|
+
|
|
28
|
+
# Register the agent config
|
|
29
|
+
config_info = AgentConfigInfo(
|
|
30
|
+
agent_class=agent_class,
|
|
31
|
+
models_regex=models,
|
|
32
|
+
priority=priority
|
|
33
|
+
)
|
|
34
|
+
_agent_configs.append(config_info)
|
|
35
|
+
|
|
36
|
+
# Sort by priority (highest first)
|
|
37
|
+
_agent_configs.sort(key=lambda x: x.priority, reverse=True)
|
|
38
|
+
|
|
39
|
+
return agent_class
|
|
40
|
+
|
|
41
|
+
return decorator
|
|
42
|
+
|
|
43
|
+
def get_agent_configs() -> List[AgentConfigInfo]:
|
|
44
|
+
"""Get all registered agent configs"""
|
|
45
|
+
return _agent_configs.copy()
|
|
46
|
+
|
|
47
|
+
def find_agent_config(model: str) -> Optional[AgentConfigInfo]:
|
|
48
|
+
"""Find the best matching agent config for a model"""
|
|
49
|
+
for config_info in _agent_configs:
|
|
50
|
+
if config_info.matches_model(model):
|
|
51
|
+
return config_info
|
|
52
|
+
return None
|
|
@@ -7,5 +7,7 @@ from . import anthropic
|
|
|
7
7
|
from . import openai
|
|
8
8
|
from . import uitars
|
|
9
9
|
from . import omniparser
|
|
10
|
+
from . import gta1
|
|
11
|
+
from . import composed_grounded
|
|
10
12
|
|
|
11
|
-
__all__ = ["anthropic", "openai", "uitars", "omniparser"]
|
|
13
|
+
__all__ = ["anthropic", "openai", "uitars", "omniparser", "gta1", "composed_grounded"]
|