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.

Files changed (39) hide show
  1. {cua_agent-0.4.7 → cua_agent-0.4.9}/PKG-INFO +2 -2
  2. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/__init__.py +2 -2
  3. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/adapters/huggingfacelocal_adapter.py +5 -1
  4. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/agent.py +82 -15
  5. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/cli.py +9 -3
  6. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/computer_handler.py +7 -3
  7. cua_agent-0.4.9/agent/decorators.py +52 -0
  8. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/loops/__init__.py +3 -1
  9. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/loops/anthropic.py +200 -84
  10. cua_agent-0.4.9/agent/loops/base.py +76 -0
  11. cua_agent-0.4.9/agent/loops/composed_grounded.py +318 -0
  12. cua_agent-0.4.9/agent/loops/gta1.py +178 -0
  13. cua_agent-0.4.9/agent/loops/model_types.csv +6 -0
  14. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/loops/omniparser.py +178 -84
  15. cua_agent-0.4.9/agent/loops/openai.py +235 -0
  16. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/loops/uitars.py +305 -178
  17. cua_agent-0.4.9/agent/responses.py +683 -0
  18. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/types.py +7 -5
  19. {cua_agent-0.4.7 → cua_agent-0.4.9}/pyproject.toml +2 -2
  20. cua_agent-0.4.7/agent/decorators.py +0 -90
  21. cua_agent-0.4.7/agent/loops/openai.py +0 -95
  22. cua_agent-0.4.7/agent/responses.py +0 -207
  23. {cua_agent-0.4.7 → cua_agent-0.4.9}/README.md +0 -0
  24. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/__main__.py +0 -0
  25. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/adapters/__init__.py +0 -0
  26. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/callbacks/__init__.py +0 -0
  27. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/callbacks/base.py +0 -0
  28. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/callbacks/budget_manager.py +0 -0
  29. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/callbacks/image_retention.py +0 -0
  30. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/callbacks/logging.py +0 -0
  31. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/callbacks/pii_anonymization.py +0 -0
  32. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/callbacks/telemetry.py +0 -0
  33. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/callbacks/trajectory_saver.py +0 -0
  34. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/telemetry.py +0 -0
  35. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/ui/__init__.py +0 -0
  36. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/ui/__main__.py +0 -0
  37. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/ui/gradio/__init__.py +0 -0
  38. {cua_agent-0.4.7 → cua_agent-0.4.9}/agent/ui/gradio/app.py +0 -0
  39. {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.7
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.8
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 agent_loop
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
- "agent_loop",
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(model_name)
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 find_agent_loop
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.agent_loop_info = None
224
+ self.agent_config_info = None
217
225
  else:
218
- loop_info = find_agent_loop(model)
219
- if not loop_info:
220
- raise ValueError(f"No agent loop found for model: {model}")
221
- self.agent_loop = loop_info.func
222
- self.agent_loop_info = loop_info
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.agent_loop.__name__,
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.agent_loop.__name__, container_name)
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"]