cua-agent 0.1.6__py3-none-any.whl → 0.1.18__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 (57) hide show
  1. agent/__init__.py +3 -2
  2. agent/core/__init__.py +1 -6
  3. agent/core/{computer_agent.py → agent.py} +31 -76
  4. agent/core/{loop.py → base.py} +68 -127
  5. agent/core/factory.py +104 -0
  6. agent/core/messages.py +279 -125
  7. agent/core/provider_config.py +15 -0
  8. agent/core/types.py +45 -0
  9. agent/core/visualization.py +197 -0
  10. agent/providers/anthropic/api/client.py +142 -1
  11. agent/providers/anthropic/api_handler.py +140 -0
  12. agent/providers/anthropic/callbacks/__init__.py +5 -0
  13. agent/providers/anthropic/loop.py +207 -221
  14. agent/providers/anthropic/response_handler.py +226 -0
  15. agent/providers/anthropic/tools/bash.py +0 -97
  16. agent/providers/anthropic/utils.py +368 -0
  17. agent/providers/omni/__init__.py +1 -20
  18. agent/providers/omni/api_handler.py +42 -0
  19. agent/providers/omni/clients/anthropic.py +4 -0
  20. agent/providers/omni/image_utils.py +0 -72
  21. agent/providers/omni/loop.py +491 -607
  22. agent/providers/omni/parser.py +58 -4
  23. agent/providers/omni/tools/__init__.py +25 -7
  24. agent/providers/omni/tools/base.py +29 -0
  25. agent/providers/omni/tools/bash.py +43 -38
  26. agent/providers/omni/tools/computer.py +144 -182
  27. agent/providers/omni/tools/manager.py +25 -45
  28. agent/providers/omni/types.py +1 -3
  29. agent/providers/omni/utils.py +224 -145
  30. agent/providers/openai/__init__.py +6 -0
  31. agent/providers/openai/api_handler.py +453 -0
  32. agent/providers/openai/loop.py +440 -0
  33. agent/providers/openai/response_handler.py +205 -0
  34. agent/providers/openai/tools/__init__.py +15 -0
  35. agent/providers/openai/tools/base.py +79 -0
  36. agent/providers/openai/tools/computer.py +319 -0
  37. agent/providers/openai/tools/manager.py +106 -0
  38. agent/providers/openai/types.py +36 -0
  39. agent/providers/openai/utils.py +98 -0
  40. cua_agent-0.1.18.dist-info/METADATA +165 -0
  41. cua_agent-0.1.18.dist-info/RECORD +73 -0
  42. agent/README.md +0 -63
  43. agent/providers/anthropic/messages/manager.py +0 -112
  44. agent/providers/omni/callbacks.py +0 -78
  45. agent/providers/omni/clients/groq.py +0 -101
  46. agent/providers/omni/experiment.py +0 -276
  47. agent/providers/omni/messages.py +0 -171
  48. agent/providers/omni/tool_manager.py +0 -91
  49. agent/providers/omni/visualization.py +0 -130
  50. agent/types/__init__.py +0 -23
  51. agent/types/base.py +0 -41
  52. agent/types/messages.py +0 -36
  53. cua_agent-0.1.6.dist-info/METADATA +0 -120
  54. cua_agent-0.1.6.dist-info/RECORD +0 -64
  55. /agent/{types → core}/tools.py +0 -0
  56. {cua_agent-0.1.6.dist-info → cua_agent-0.1.18.dist-info}/WHEEL +0 -0
  57. {cua_agent-0.1.6.dist-info → cua_agent-0.1.18.dist-info}/entry_points.txt +0 -0
@@ -1,276 +0,0 @@
1
- """Experiment management for the Cua provider."""
2
-
3
- import os
4
- import logging
5
- import copy
6
- import base64
7
- from io import BytesIO
8
- from datetime import datetime
9
- from typing import Any, Dict, List, Optional
10
- from PIL import Image
11
- import json
12
- import time
13
-
14
- logger = logging.getLogger(__name__)
15
-
16
-
17
- class ExperimentManager:
18
- """Manages experiment directories and logging for the agent."""
19
-
20
- def __init__(
21
- self,
22
- base_dir: Optional[str] = None,
23
- only_n_most_recent_images: Optional[int] = None,
24
- ):
25
- """Initialize the experiment manager.
26
-
27
- Args:
28
- base_dir: Base directory for saving experiment data
29
- only_n_most_recent_images: Maximum number of recent screenshots to include in API requests
30
- """
31
- self.base_dir = base_dir
32
- self.only_n_most_recent_images = only_n_most_recent_images
33
- self.run_dir = None
34
- self.current_turn_dir = None
35
- self.turn_count = 0
36
- self.screenshot_count = 0
37
- # Track all screenshots for potential API request inclusion
38
- self.screenshot_paths = []
39
-
40
- # Set up experiment directories if base_dir is provided
41
- if self.base_dir:
42
- self.setup_experiment_dirs()
43
-
44
- def setup_experiment_dirs(self) -> None:
45
- """Setup the experiment directory structure."""
46
- if not self.base_dir:
47
- return
48
-
49
- # Create base experiments directory if it doesn't exist
50
- os.makedirs(self.base_dir, exist_ok=True)
51
-
52
- # Use the base_dir directly as the run_dir
53
- self.run_dir = self.base_dir
54
- logger.info(f"Using directory for experiment: {self.run_dir}")
55
-
56
- # Create first turn directory
57
- self.create_turn_dir()
58
-
59
- def create_turn_dir(self) -> None:
60
- """Create a new directory for the current turn."""
61
- if not self.run_dir:
62
- return
63
-
64
- self.turn_count += 1
65
- self.current_turn_dir = os.path.join(self.run_dir, f"turn_{self.turn_count:03d}")
66
- os.makedirs(self.current_turn_dir, exist_ok=True)
67
- logger.info(f"Created turn directory: {self.current_turn_dir}")
68
-
69
- def sanitize_log_data(self, data: Any) -> Any:
70
- """Sanitize data for logging by removing large base64 strings.
71
-
72
- Args:
73
- data: Data to sanitize (dict, list, or primitive)
74
-
75
- Returns:
76
- Sanitized copy of the data
77
- """
78
- if isinstance(data, dict):
79
- result = copy.deepcopy(data)
80
-
81
- # Handle nested dictionaries and lists
82
- for key, value in result.items():
83
- # Process content arrays that contain image data
84
- if key == "content" and isinstance(value, list):
85
- for i, item in enumerate(value):
86
- if isinstance(item, dict):
87
- # Handle Anthropic format
88
- if item.get("type") == "image" and isinstance(item.get("source"), dict):
89
- source = item["source"]
90
- if "data" in source and isinstance(source["data"], str):
91
- # Replace base64 data with a placeholder and length info
92
- data_len = len(source["data"])
93
- source["data"] = f"[BASE64_IMAGE_DATA_LENGTH_{data_len}]"
94
-
95
- # Handle OpenAI format
96
- elif item.get("type") == "image_url" and isinstance(
97
- item.get("image_url"), dict
98
- ):
99
- url_dict = item["image_url"]
100
- if "url" in url_dict and isinstance(url_dict["url"], str):
101
- url = url_dict["url"]
102
- if url.startswith("data:"):
103
- # Replace base64 data with placeholder
104
- data_len = len(url)
105
- url_dict["url"] = f"[BASE64_IMAGE_URL_LENGTH_{data_len}]"
106
-
107
- # Handle other nested structures recursively
108
- if isinstance(value, dict):
109
- result[key] = self.sanitize_log_data(value)
110
- elif isinstance(value, list):
111
- result[key] = [self.sanitize_log_data(item) for item in value]
112
-
113
- return result
114
- elif isinstance(data, list):
115
- return [self.sanitize_log_data(item) for item in data]
116
- else:
117
- return data
118
-
119
- def save_debug_image(self, image_data: str, filename: str) -> None:
120
- """Save a debug image to the experiment directory.
121
-
122
- Args:
123
- image_data: Base64 encoded image data
124
- filename: Filename to save the image as
125
- """
126
- # Since we no longer want to use the images/ folder, we'll skip this functionality
127
- return
128
-
129
- def save_screenshot(self, img_base64: str, action_type: str = "") -> Optional[str]:
130
- """Save a screenshot to the experiment directory.
131
-
132
- Args:
133
- img_base64: Base64 encoded screenshot
134
- action_type: Type of action that triggered the screenshot
135
-
136
- Returns:
137
- Optional[str]: Path to the saved screenshot, or None if saving failed
138
- """
139
- if not self.current_turn_dir:
140
- return None
141
-
142
- try:
143
- # Increment screenshot counter
144
- self.screenshot_count += 1
145
-
146
- # Create a descriptive filename
147
- timestamp = int(time.time() * 1000)
148
- action_suffix = f"_{action_type}" if action_type else ""
149
- filename = f"screenshot_{self.screenshot_count:03d}{action_suffix}_{timestamp}.png"
150
-
151
- # Save directly to the turn directory (no screenshots subdirectory)
152
- filepath = os.path.join(self.current_turn_dir, filename)
153
-
154
- # Save the screenshot
155
- img_data = base64.b64decode(img_base64)
156
- with open(filepath, "wb") as f:
157
- f.write(img_data)
158
-
159
- # Keep track of the file path for reference
160
- self.screenshot_paths.append(filepath)
161
-
162
- return filepath
163
- except Exception as e:
164
- logger.error(f"Error saving screenshot: {str(e)}")
165
- return None
166
-
167
- def should_save_debug_image(self) -> bool:
168
- """Determine if debug images should be saved.
169
-
170
- Returns:
171
- Boolean indicating if debug images should be saved
172
- """
173
- # We no longer need to save debug images, so always return False
174
- return False
175
-
176
- def save_action_visualization(
177
- self, img: Image.Image, action_name: str, details: str = ""
178
- ) -> str:
179
- """Save a visualization of an action.
180
-
181
- Args:
182
- img: Image to save
183
- action_name: Name of the action
184
- details: Additional details about the action
185
-
186
- Returns:
187
- Path to the saved image
188
- """
189
- if not self.current_turn_dir:
190
- return ""
191
-
192
- try:
193
- # Create a descriptive filename
194
- timestamp = int(time.time() * 1000)
195
- details_suffix = f"_{details}" if details else ""
196
- filename = f"vis_{action_name}{details_suffix}_{timestamp}.png"
197
-
198
- # Save directly to the turn directory (no visualizations subdirectory)
199
- filepath = os.path.join(self.current_turn_dir, filename)
200
-
201
- # Save the image
202
- img.save(filepath)
203
-
204
- # Keep track of the file path for cleanup
205
- self.screenshot_paths.append(filepath)
206
-
207
- return filepath
208
- except Exception as e:
209
- logger.error(f"Error saving action visualization: {str(e)}")
210
- return ""
211
-
212
- def extract_and_save_images(self, data: Any, prefix: str) -> None:
213
- """Extract and save images from response data.
214
-
215
- Args:
216
- data: Response data to extract images from
217
- prefix: Prefix for saved image filenames
218
- """
219
- # Since we no longer want to save extracted images separately,
220
- # we'll skip this functionality entirely
221
- return
222
-
223
- def log_api_call(
224
- self,
225
- call_type: str,
226
- request: Any,
227
- provider: str,
228
- model: str,
229
- response: Any = None,
230
- error: Optional[Exception] = None,
231
- ) -> None:
232
- """Log API call details to file.
233
-
234
- Args:
235
- call_type: Type of API call (e.g., 'request', 'response', 'error')
236
- request: The API request data
237
- provider: The AI provider used
238
- model: The AI model used
239
- response: Optional API response data
240
- error: Optional error information
241
- """
242
- if not self.current_turn_dir:
243
- return
244
-
245
- try:
246
- # Create a unique filename with timestamp
247
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
248
- filename = f"api_call_{timestamp}_{call_type}.json"
249
- filepath = os.path.join(self.current_turn_dir, filename)
250
-
251
- # Sanitize data to remove large base64 strings
252
- sanitized_request = self.sanitize_log_data(request)
253
- sanitized_response = self.sanitize_log_data(response) if response is not None else None
254
-
255
- # Prepare log data
256
- log_data = {
257
- "timestamp": timestamp,
258
- "provider": provider,
259
- "model": model,
260
- "type": call_type,
261
- "request": sanitized_request,
262
- }
263
-
264
- if sanitized_response is not None:
265
- log_data["response"] = sanitized_response
266
- if error is not None:
267
- log_data["error"] = str(error)
268
-
269
- # Write to file
270
- with open(filepath, "w") as f:
271
- json.dump(log_data, f, indent=2, default=str)
272
-
273
- logger.info(f"Logged API {call_type} to {filepath}")
274
-
275
- except Exception as e:
276
- logger.error(f"Error logging API call: {str(e)}")
@@ -1,171 +0,0 @@
1
- """Omni message manager implementation."""
2
-
3
- import base64
4
- from typing import Any, Dict, List, Optional
5
- from io import BytesIO
6
- from PIL import Image
7
-
8
- from ...core.messages import BaseMessageManager, ImageRetentionConfig
9
-
10
-
11
- class OmniMessageManager(BaseMessageManager):
12
- """Message manager for multi-provider support."""
13
-
14
- def __init__(self, config: Optional[ImageRetentionConfig] = None):
15
- """Initialize the message manager.
16
-
17
- Args:
18
- config: Optional configuration for image retention
19
- """
20
- super().__init__(config)
21
- self.messages: List[Dict[str, Any]] = []
22
- self.config = config
23
-
24
- def add_user_message(self, content: str, images: Optional[List[bytes]] = None) -> None:
25
- """Add a user message to the history.
26
-
27
- Args:
28
- content: Message content
29
- images: Optional list of image data
30
- """
31
- # Add images if present
32
- if images:
33
- # Initialize with proper typing for mixed content
34
- message_content: List[Dict[str, Any]] = [{"type": "text", "text": content}]
35
-
36
- # Add each image
37
- for img in images:
38
- message_content.append(
39
- {
40
- "type": "image_url",
41
- "image_url": {
42
- "url": f"data:image/png;base64,{base64.b64encode(img).decode()}"
43
- },
44
- }
45
- )
46
-
47
- message = {"role": "user", "content": message_content}
48
- else:
49
- # Simple text message
50
- message = {"role": "user", "content": content}
51
-
52
- self.messages.append(message)
53
-
54
- # Apply retention policy
55
- if self.config and self.config.num_images_to_keep:
56
- self._apply_image_retention_policy()
57
-
58
- def add_assistant_message(self, content: str) -> None:
59
- """Add an assistant message to the history.
60
-
61
- Args:
62
- content: Message content
63
- """
64
- self.messages.append({"role": "assistant", "content": content})
65
-
66
- def add_system_message(self, content: str) -> None:
67
- """Add a system message to the history.
68
-
69
- Args:
70
- content: Message content
71
- """
72
- self.messages.append({"role": "system", "content": content})
73
-
74
- def _apply_image_retention_policy(self) -> None:
75
- """Apply image retention policy to message history."""
76
- if not self.config or not self.config.num_images_to_keep:
77
- return
78
-
79
- # Count images from newest to oldest
80
- image_count = 0
81
- for message in reversed(self.messages):
82
- if message["role"] != "user":
83
- continue
84
-
85
- # Handle multimodal messages
86
- if isinstance(message["content"], list):
87
- new_content = []
88
- for item in message["content"]:
89
- if item["type"] == "text":
90
- new_content.append(item)
91
- elif item["type"] == "image_url":
92
- if image_count < self.config.num_images_to_keep:
93
- new_content.append(item)
94
- image_count += 1
95
- message["content"] = new_content
96
-
97
- def get_formatted_messages(self, provider: str) -> List[Dict[str, Any]]:
98
- """Get messages formatted for specific provider.
99
-
100
- Args:
101
- provider: Provider name to format messages for
102
-
103
- Returns:
104
- List of formatted messages
105
- """
106
- # Set the provider for message formatting
107
- self.set_provider(provider)
108
-
109
- if provider == "anthropic":
110
- return self._format_for_anthropic()
111
- elif provider == "openai":
112
- return self._format_for_openai()
113
- elif provider == "groq":
114
- return self._format_for_groq()
115
- elif provider == "qwen":
116
- return self._format_for_qwen()
117
- else:
118
- raise ValueError(f"Unsupported provider: {provider}")
119
-
120
- def _format_for_anthropic(self) -> List[Dict[str, Any]]:
121
- """Format messages for Anthropic API."""
122
- formatted = []
123
- for msg in self.messages:
124
- formatted_msg = {"role": msg["role"]}
125
-
126
- # Handle multimodal content
127
- if isinstance(msg["content"], list):
128
- formatted_msg["content"] = []
129
- for item in msg["content"]:
130
- if item["type"] == "text":
131
- formatted_msg["content"].append({"type": "text", "text": item["text"]})
132
- elif item["type"] == "image_url":
133
- formatted_msg["content"].append(
134
- {
135
- "type": "image",
136
- "source": {
137
- "type": "base64",
138
- "media_type": "image/png",
139
- "data": item["image_url"]["url"].split(",")[1],
140
- },
141
- }
142
- )
143
- else:
144
- formatted_msg["content"] = msg["content"]
145
-
146
- formatted.append(formatted_msg)
147
- return formatted
148
-
149
- def _format_for_openai(self) -> List[Dict[str, Any]]:
150
- """Format messages for OpenAI API."""
151
- # OpenAI already uses the same format
152
- return self.messages
153
-
154
- def _format_for_groq(self) -> List[Dict[str, Any]]:
155
- """Format messages for Groq API."""
156
- # Groq uses OpenAI-compatible format
157
- return self.messages
158
-
159
- def _format_for_qwen(self) -> List[Dict[str, Any]]:
160
- """Format messages for Qwen API."""
161
- formatted = []
162
- for msg in self.messages:
163
- if isinstance(msg["content"], list):
164
- # Convert multimodal content to text-only
165
- text_content = next(
166
- (item["text"] for item in msg["content"] if item["type"] == "text"), ""
167
- )
168
- formatted.append({"role": msg["role"], "content": text_content})
169
- else:
170
- formatted.append(msg)
171
- return formatted
@@ -1,91 +0,0 @@
1
- # """Omni tool manager implementation."""
2
-
3
- # from typing import Dict, List, Type, Any
4
-
5
- # from computer import Computer
6
- # from ...core.tools import BaseToolManager, BashTool, EditTool
7
-
8
- # class OmniToolManager(BaseToolManager):
9
- # """Tool manager for multi-provider support."""
10
-
11
- # def __init__(self, computer: Computer):
12
- # """Initialize Omni tool manager.
13
-
14
- # Args:
15
- # computer: Computer instance for tools
16
- # """
17
- # super().__init__(computer)
18
-
19
- # def get_anthropic_tools(self) -> List[Dict[str, Any]]:
20
- # """Get tools formatted for Anthropic API.
21
-
22
- # Returns:
23
- # List of tool parameters in Anthropic format
24
- # """
25
- # tools: List[Dict[str, Any]] = []
26
-
27
- # # Map base tools to Anthropic format
28
- # for tool in self.tools.values():
29
- # if isinstance(tool, BashTool):
30
- # tools.append({
31
- # "type": "bash_20241022",
32
- # "name": tool.name
33
- # })
34
- # elif isinstance(tool, EditTool):
35
- # tools.append({
36
- # "type": "text_editor_20241022",
37
- # "name": "str_replace_editor"
38
- # })
39
-
40
- # return tools
41
-
42
- # def get_openai_tools(self) -> List[Dict]:
43
- # """Get tools formatted for OpenAI API.
44
-
45
- # Returns:
46
- # List of tool parameters in OpenAI format
47
- # """
48
- # tools = []
49
-
50
- # # Map base tools to OpenAI format
51
- # for tool in self.tools.values():
52
- # tools.append({
53
- # "type": "function",
54
- # "function": tool.get_schema()
55
- # })
56
-
57
- # return tools
58
-
59
- # def get_groq_tools(self) -> List[Dict]:
60
- # """Get tools formatted for Groq API.
61
-
62
- # Returns:
63
- # List of tool parameters in Groq format
64
- # """
65
- # tools = []
66
-
67
- # # Map base tools to Groq format
68
- # for tool in self.tools.values():
69
- # tools.append({
70
- # "type": "function",
71
- # "function": tool.get_schema()
72
- # })
73
-
74
- # return tools
75
-
76
- # def get_qwen_tools(self) -> List[Dict]:
77
- # """Get tools formatted for Qwen API.
78
-
79
- # Returns:
80
- # List of tool parameters in Qwen format
81
- # """
82
- # tools = []
83
-
84
- # # Map base tools to Qwen format
85
- # for tool in self.tools.values():
86
- # tools.append({
87
- # "type": "function",
88
- # "function": tool.get_schema()
89
- # })
90
-
91
- # return tools
@@ -1,130 +0,0 @@
1
- """Visualization utilities for the Cua provider."""
2
-
3
- import base64
4
- import logging
5
- from io import BytesIO
6
- from typing import Tuple
7
- from PIL import Image, ImageDraw
8
-
9
- logger = logging.getLogger(__name__)
10
-
11
-
12
- def visualize_click(x: int, y: int, img_base64: str) -> Image.Image:
13
- """Visualize a click action by drawing on the screenshot.
14
-
15
- Args:
16
- x: X coordinate of the click
17
- y: Y coordinate of the click
18
- img_base64: Base64 encoded image to draw on
19
-
20
- Returns:
21
- PIL Image with visualization
22
- """
23
- try:
24
- # Decode the base64 image
25
- img_data = base64.b64decode(img_base64)
26
- img = Image.open(BytesIO(img_data))
27
-
28
- # Create a drawing context
29
- draw = ImageDraw.Draw(img)
30
-
31
- # Draw concentric circles at the click position
32
- small_radius = 10
33
- large_radius = 30
34
-
35
- # Draw filled inner circle
36
- draw.ellipse(
37
- [(x - small_radius, y - small_radius), (x + small_radius, y + small_radius)],
38
- fill="red",
39
- )
40
-
41
- # Draw outlined outer circle
42
- draw.ellipse(
43
- [(x - large_radius, y - large_radius), (x + large_radius, y + large_radius)],
44
- outline="red",
45
- width=3,
46
- )
47
-
48
- return img
49
-
50
- except Exception as e:
51
- logger.error(f"Error visualizing click: {str(e)}")
52
- # Return a blank image in case of error
53
- return Image.new("RGB", (800, 600), color="white")
54
-
55
-
56
- def visualize_scroll(direction: str, clicks: int, img_base64: str) -> Image.Image:
57
- """Visualize a scroll action by drawing arrows on the screenshot.
58
-
59
- Args:
60
- direction: 'up' or 'down'
61
- clicks: Number of scroll clicks
62
- img_base64: Base64 encoded image to draw on
63
-
64
- Returns:
65
- PIL Image with visualization
66
- """
67
- try:
68
- # Decode the base64 image
69
- img_data = base64.b64decode(img_base64)
70
- img = Image.open(BytesIO(img_data))
71
-
72
- # Get image dimensions
73
- width, height = img.size
74
-
75
- # Create a drawing context
76
- draw = ImageDraw.Draw(img)
77
-
78
- # Determine arrow direction and positions
79
- center_x = width // 2
80
- arrow_width = 100
81
-
82
- if direction.lower() == "up":
83
- # Draw up arrow in the middle of the screen
84
- arrow_y = height // 2
85
- # Arrow points
86
- points = [
87
- (center_x, arrow_y - 50), # Top point
88
- (center_x - arrow_width // 2, arrow_y + 50), # Bottom left
89
- (center_x + arrow_width // 2, arrow_y + 50), # Bottom right
90
- ]
91
- color = "blue"
92
- else: # down
93
- # Draw down arrow in the middle of the screen
94
- arrow_y = height // 2
95
- # Arrow points
96
- points = [
97
- (center_x, arrow_y + 50), # Bottom point
98
- (center_x - arrow_width // 2, arrow_y - 50), # Top left
99
- (center_x + arrow_width // 2, arrow_y - 50), # Top right
100
- ]
101
- color = "green"
102
-
103
- # Draw filled arrow
104
- draw.polygon(points, fill=color)
105
-
106
- # Add text showing number of clicks
107
- text_y = arrow_y + 70 if direction.lower() == "down" else arrow_y - 70
108
- draw.text((center_x - 40, text_y), f"{clicks} clicks", fill="black")
109
-
110
- return img
111
-
112
- except Exception as e:
113
- logger.error(f"Error visualizing scroll: {str(e)}")
114
- # Return a blank image in case of error
115
- return Image.new("RGB", (800, 600), color="white")
116
-
117
-
118
- def calculate_element_center(box: Tuple[int, int, int, int]) -> Tuple[int, int]:
119
- """Calculate the center coordinates of a bounding box.
120
-
121
- Args:
122
- box: Tuple of (left, top, right, bottom) coordinates
123
-
124
- Returns:
125
- Tuple of (center_x, center_y) coordinates
126
- """
127
- left, top, right, bottom = box
128
- center_x = (left + right) // 2
129
- center_y = (top + bottom) // 2
130
- return center_x, center_y
agent/types/__init__.py DELETED
@@ -1,23 +0,0 @@
1
- """Type definitions for the agent package."""
2
-
3
- from .base import HostConfig, TaskResult, Annotation
4
- from .messages import Message, Request, Response, StepMessage, DisengageMessage
5
- from .tools import ToolInvocation, ToolInvocationState, ClientAttachment, ToolResult
6
-
7
- __all__ = [
8
- # Base types
9
- "HostConfig",
10
- "TaskResult",
11
- "Annotation",
12
- # Message types
13
- "Message",
14
- "Request",
15
- "Response",
16
- "StepMessage",
17
- "DisengageMessage",
18
- # Tool types
19
- "ToolInvocation",
20
- "ToolInvocationState",
21
- "ClientAttachment",
22
- "ToolResult",
23
- ]