cua-agent 0.1.1__tar.gz → 0.1.3__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 (70) hide show
  1. {cua_agent-0.1.1 → cua_agent-0.1.3}/PKG-INFO +2 -1
  2. {cua_agent-0.1.1 → cua_agent-0.1.3}/README.md +6 -6
  3. cua_agent-0.1.3/agent/__init__.py +22 -0
  4. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/core/README.md +2 -2
  5. cua_agent-0.1.3/agent/core/agent.py +259 -0
  6. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/core/base_agent.py +1 -1
  7. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/core/experiment.py +11 -1
  8. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/core/loop.py +1 -1
  9. cua_agent-0.1.3/agent/core/telemetry.py +138 -0
  10. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/anthropic/__init__.py +2 -2
  11. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/anthropic/api/client.py +43 -46
  12. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/anthropic/loop.py +2 -2
  13. cua_agent-0.1.3/agent/providers/anthropic/types.py +16 -0
  14. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/__init__.py +2 -2
  15. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/loop.py +14 -14
  16. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/parser.py +1 -1
  17. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/prompts.py +0 -14
  18. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/types.py +3 -10
  19. cua_agent-0.1.3/agent/telemetry.py +21 -0
  20. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/types/base.py +2 -1
  21. {cua_agent-0.1.1 → cua_agent-0.1.3}/pyproject.toml +4 -3
  22. {cua_agent-0.1.1 → cua_agent-0.1.3}/tests/test_agent.py +3 -3
  23. cua_agent-0.1.1/agent/__init__.py +0 -10
  24. cua_agent-0.1.1/agent/core/agent.py +0 -330
  25. cua_agent-0.1.1/agent/providers/anthropic/types.py +0 -16
  26. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/README.md +0 -0
  27. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/core/__init__.py +0 -0
  28. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/core/callbacks.py +0 -0
  29. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/core/computer_agent.py +0 -0
  30. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/core/factory.py +0 -0
  31. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/core/messages.py +0 -0
  32. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/core/tools/__init__.py +0 -0
  33. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/core/tools/base.py +0 -0
  34. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/core/tools/bash.py +0 -0
  35. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/core/tools/collection.py +0 -0
  36. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/core/tools/computer.py +0 -0
  37. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/core/tools/edit.py +0 -0
  38. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/core/tools/manager.py +0 -0
  39. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/__init__.py +0 -0
  40. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/anthropic/api/logging.py +0 -0
  41. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/anthropic/callbacks/manager.py +0 -0
  42. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/anthropic/messages/manager.py +0 -0
  43. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/anthropic/prompts.py +0 -0
  44. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/anthropic/tools/__init__.py +0 -0
  45. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/anthropic/tools/base.py +0 -0
  46. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/anthropic/tools/bash.py +0 -0
  47. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/anthropic/tools/collection.py +0 -0
  48. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/anthropic/tools/computer.py +0 -0
  49. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/anthropic/tools/edit.py +0 -0
  50. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/anthropic/tools/manager.py +0 -0
  51. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/anthropic/tools/run.py +0 -0
  52. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/callbacks.py +0 -0
  53. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/clients/anthropic.py +0 -0
  54. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/clients/base.py +0 -0
  55. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/clients/groq.py +0 -0
  56. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/clients/openai.py +0 -0
  57. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/clients/utils.py +0 -0
  58. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/experiment.py +0 -0
  59. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/image_utils.py +0 -0
  60. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/messages.py +0 -0
  61. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/tool_manager.py +0 -0
  62. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/tools/__init__.py +0 -0
  63. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/tools/bash.py +0 -0
  64. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/tools/computer.py +0 -0
  65. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/tools/manager.py +0 -0
  66. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/utils.py +0 -0
  67. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/providers/omni/visualization.py +0 -0
  68. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/types/__init__.py +0 -0
  69. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/types/messages.py +0 -0
  70. {cua_agent-0.1.1 → cua_agent-0.1.3}/agent/types/tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cua-agent
3
- Version: 0.1.1
3
+ Version: 0.1.3
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.13,>=3.10
@@ -13,6 +13,7 @@ Requires-Dist: pydantic<3.0.0,>=2.6.4
13
13
  Requires-Dist: rich<14.0.0,>=13.7.1
14
14
  Requires-Dist: python-dotenv<2.0.0,>=1.0.1
15
15
  Requires-Dist: cua-computer<0.2.0,>=0.1.0
16
+ Requires-Dist: cua-core<0.2.0,>=0.1.0
16
17
  Requires-Dist: certifi>=2024.2.2
17
18
  Provides-Extra: anthropic
18
19
  Requires-Dist: anthropic>=0.49.0; extra == "anthropic"
@@ -20,19 +20,19 @@
20
20
  ### Get started with Agent
21
21
 
22
22
  ```python
23
- from agent import ComputerAgent, AgenticLoop, APIProvider
23
+ from agent import ComputerAgent, AgentLoop, LLMProvider
24
24
  from computer import Computer
25
25
 
26
26
  computer = Computer(verbosity=logging.INFO)
27
27
 
28
28
  agent = ComputerAgent(
29
29
  computer=computer,
30
- api_key="<your-anthropic-api-key>",
31
- loop_type=AgenticLoop.ANTHROPIC, # or AgenticLoop.OMNI
32
- ai_provider=APIProvider.ANTHROPIC,
33
- model='claude-3-7-sonnet-20250219',
30
+ loop=AgentLoop.ANTHROPIC,
31
+ # loop=AgentLoop.OMNI,
32
+ model=LLM(provider=LLMProvider.ANTHROPIC, name="claude-3-7-sonnet-20250219"),
33
+ # model=LLM(provider=LLMProvider.OPENAI, name="gpt-4.5-preview"),
34
34
  save_trajectory=True,
35
- trajectory_dir=str(Path("trajectories") / datetime.now().strftime("%Y%m%d_%H%M%S")),
35
+ trajectory_dir=str(Path("trajectories")),
36
36
  only_n_most_recent_images=3,
37
37
  verbosity=logging.INFO,
38
38
  )
@@ -0,0 +1,22 @@
1
+ """CUA (Computer Use) Agent for AI-driven computer interaction."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ # Initialize telemetry when the package is imported
6
+ try:
7
+ from core.telemetry import enable_telemetry, set_dimension
8
+
9
+ # Enable telemetry by default
10
+ enable_telemetry()
11
+ # Set the package version as a dimension
12
+ set_dimension("agent_version", __version__)
13
+ except ImportError:
14
+ # Core telemetry not available
15
+ pass
16
+
17
+ from .core.factory import AgentFactory
18
+ from .core.agent import ComputerAgent
19
+ from .providers.omni.types import LLMProvider, LLM
20
+ from .types.base import Provider, AgentLoop
21
+
22
+ __all__ = ["AgentFactory", "Provider", "ComputerAgent", "AgentLoop", "LLMProvider", "LLM"]
@@ -34,7 +34,7 @@ Here's how to use the unified ComputerAgent:
34
34
  ```python
35
35
  from agent.core.agent import ComputerAgent
36
36
  from agent.types.base import AgenticLoop
37
- from agent.providers.omni.types import APIProvider
37
+ from agent.providers.omni.types import LLMProvider
38
38
  from computer import Computer
39
39
 
40
40
  # Create a Computer instance
@@ -44,7 +44,7 @@ computer = Computer()
44
44
  agent = ComputerAgent(
45
45
  computer=computer,
46
46
  loop_type=AgenticLoop.OMNI,
47
- provider=APIProvider.OPENAI,
47
+ provider=LLMProvider.OPENAI,
48
48
  model="gpt-4o",
49
49
  api_key="your_api_key_here", # Can also use OPENAI_API_KEY environment variable
50
50
  save_trajectory=True,
@@ -0,0 +1,259 @@
1
+ """Unified computer agent implementation that supports multiple loops."""
2
+
3
+ import os
4
+ import logging
5
+ import asyncio
6
+ import time
7
+ import uuid
8
+ from typing import Any, AsyncGenerator, Dict, List, Optional, TYPE_CHECKING, Union, cast
9
+ from datetime import datetime
10
+ from enum import Enum
11
+
12
+ from computer import Computer
13
+
14
+ from ..types.base import Provider, AgentLoop
15
+ from .base_agent import BaseComputerAgent
16
+ from ..core.telemetry import record_agent_initialization
17
+
18
+ # Only import types for type checking to avoid circular imports
19
+ if TYPE_CHECKING:
20
+ from ..providers.anthropic.loop import AnthropicLoop
21
+ from ..providers.omni.loop import OmniLoop
22
+ from ..providers.omni.parser import OmniParser
23
+
24
+ # Import the provider types
25
+ from ..providers.omni.types import LLMProvider, LLM, Model, LLMModel
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ # Default models for different providers
30
+ DEFAULT_MODELS = {
31
+ LLMProvider.OPENAI: "gpt-4o",
32
+ LLMProvider.ANTHROPIC: "claude-3-7-sonnet-20250219",
33
+ }
34
+
35
+ # Map providers to their environment variable names
36
+ ENV_VARS = {
37
+ LLMProvider.OPENAI: "OPENAI_API_KEY",
38
+ LLMProvider.ANTHROPIC: "ANTHROPIC_API_KEY",
39
+ }
40
+
41
+
42
+ class ComputerAgent(BaseComputerAgent):
43
+ """Unified implementation of the computer agent supporting multiple loop types.
44
+
45
+ This class consolidates the previous AnthropicComputerAgent and OmniComputerAgent
46
+ into a single implementation with configurable loop type.
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ computer: Computer,
52
+ loop: AgentLoop = AgentLoop.OMNI,
53
+ model: Optional[Union[LLM, Dict[str, str], str]] = None,
54
+ api_key: Optional[str] = None,
55
+ save_trajectory: bool = True,
56
+ trajectory_dir: Optional[str] = "trajectories",
57
+ only_n_most_recent_images: Optional[int] = None,
58
+ max_retries: int = 3,
59
+ verbosity: int = logging.INFO,
60
+ telemetry_enabled: bool = True,
61
+ **kwargs,
62
+ ):
63
+ """Initialize a ComputerAgent instance.
64
+
65
+ Args:
66
+ computer: The Computer instance to control
67
+ loop: The agent loop to use: ANTHROPIC or OMNI
68
+ model: The model to use. Can be a string, dict or LLM object.
69
+ Defaults to LLM for the loop type.
70
+ api_key: The API key to use. If None, will use environment variables.
71
+ save_trajectory: Whether to save the trajectory.
72
+ trajectory_dir: The directory to save trajectories to.
73
+ only_n_most_recent_images: Only keep this many most recent images.
74
+ max_retries: Maximum number of retries for failed requests.
75
+ verbosity: Logging level (standard Python logging levels).
76
+ telemetry_enabled: Whether to enable telemetry tracking. Defaults to True.
77
+ **kwargs: Additional keyword arguments to pass to the loop.
78
+ """
79
+ super().__init__(computer)
80
+ self._configure_logging(verbosity)
81
+ logger.info(f"Initializing ComputerAgent with {loop} loop")
82
+
83
+ # Store telemetry preference
84
+ self.telemetry_enabled = telemetry_enabled
85
+
86
+ # Pass telemetry preference to computer if available
87
+ if hasattr(computer, "telemetry_enabled"):
88
+ # Computer doesn't have a setter for telemetry_enabled
89
+ # Use disable_telemetry() method if telemetry is disabled
90
+ if not telemetry_enabled and hasattr(computer, "disable_telemetry"):
91
+ computer.disable_telemetry()
92
+
93
+ # Process the model configuration
94
+ self.model = self._process_model_config(model, loop)
95
+ self.loop_type = loop
96
+ self.api_key = api_key
97
+
98
+ # Store computer
99
+ self.computer = computer
100
+
101
+ # Save trajectory settings
102
+ self.save_trajectory = save_trajectory
103
+ self.trajectory_dir = trajectory_dir
104
+ self.only_n_most_recent_images = only_n_most_recent_images
105
+
106
+ # Store the max retries setting
107
+ self.max_retries = max_retries
108
+
109
+ # Initialize message history
110
+ self.messages = []
111
+
112
+ # Extra kwargs for the loop
113
+ self.loop_kwargs = kwargs
114
+
115
+ # Initialize the actual loop implementation
116
+ self.loop = self._init_loop()
117
+
118
+ # Record initialization in telemetry if enabled
119
+ if telemetry_enabled:
120
+ record_agent_initialization()
121
+
122
+ def _process_model_config(
123
+ self, model_input: Optional[Union[LLM, Dict[str, str], str]], loop: AgentLoop
124
+ ) -> LLM:
125
+ """Process and normalize model configuration.
126
+
127
+ Args:
128
+ model_input: Input model configuration (LLM, dict, string, or None)
129
+ loop: The loop type being used
130
+
131
+ Returns:
132
+ Normalized LLM instance
133
+ """
134
+ # Handle case where model_input is None
135
+ if model_input is None:
136
+ # Use Anthropic for Anthropic loop, OpenAI for Omni loop
137
+ default_provider = (
138
+ LLMProvider.ANTHROPIC if loop == AgentLoop.ANTHROPIC else LLMProvider.OPENAI
139
+ )
140
+ return LLM(provider=default_provider)
141
+
142
+ # Handle case where model_input is already a LLM or one of its aliases
143
+ if isinstance(model_input, (LLM, Model, LLMModel)):
144
+ return model_input
145
+
146
+ # Handle case where model_input is a dict
147
+ if isinstance(model_input, dict):
148
+ provider = model_input.get("provider", LLMProvider.OPENAI)
149
+ if isinstance(provider, str):
150
+ provider = LLMProvider(provider)
151
+ return LLM(provider=provider, name=model_input.get("name"))
152
+
153
+ # Handle case where model_input is a string (model name)
154
+ if isinstance(model_input, str):
155
+ default_provider = (
156
+ LLMProvider.ANTHROPIC if loop == AgentLoop.ANTHROPIC else LLMProvider.OPENAI
157
+ )
158
+ return LLM(provider=default_provider, name=model_input)
159
+
160
+ raise ValueError(f"Unsupported model configuration: {model_input}")
161
+
162
+ def _configure_logging(self, verbosity: int):
163
+ """Configure logging based on verbosity level."""
164
+ # Use the logging level directly without mapping
165
+ logger.setLevel(verbosity)
166
+ logging.getLogger("agent").setLevel(verbosity)
167
+
168
+ # Log the verbosity level that was set
169
+ if verbosity <= logging.DEBUG:
170
+ logger.info("Agent logging set to DEBUG level (full debug information)")
171
+ elif verbosity <= logging.INFO:
172
+ logger.info("Agent logging set to INFO level (standard output)")
173
+ elif verbosity <= logging.WARNING:
174
+ logger.warning("Agent logging set to WARNING level (warnings and errors only)")
175
+ elif verbosity <= logging.ERROR:
176
+ logger.warning("Agent logging set to ERROR level (errors only)")
177
+ elif verbosity <= logging.CRITICAL:
178
+ logger.warning("Agent logging set to CRITICAL level (critical errors only)")
179
+
180
+ def _init_loop(self) -> Any:
181
+ """Initialize the loop based on the loop_type.
182
+
183
+ Returns:
184
+ Initialized loop instance
185
+ """
186
+ # Lazy import OmniLoop and OmniParser to avoid circular imports
187
+ from ..providers.omni.loop import OmniLoop
188
+ from ..providers.omni.parser import OmniParser
189
+
190
+ if self.loop_type == AgentLoop.ANTHROPIC:
191
+ from ..providers.anthropic.loop import AnthropicLoop
192
+
193
+ # Ensure we always have a valid model name
194
+ model_name = self.model.name or DEFAULT_MODELS[LLMProvider.ANTHROPIC]
195
+
196
+ return AnthropicLoop(
197
+ api_key=self.api_key,
198
+ model=model_name,
199
+ computer=self.computer,
200
+ save_trajectory=self.save_trajectory,
201
+ base_dir=self.trajectory_dir,
202
+ only_n_most_recent_images=self.only_n_most_recent_images,
203
+ **self.loop_kwargs,
204
+ )
205
+
206
+ # Initialize parser for OmniLoop with appropriate device
207
+ if "parser" not in self.loop_kwargs:
208
+ self.loop_kwargs["parser"] = OmniParser()
209
+
210
+ # Ensure we always have a valid model name
211
+ model_name = self.model.name or DEFAULT_MODELS[self.model.provider]
212
+
213
+ return OmniLoop(
214
+ provider=self.model.provider,
215
+ api_key=self.api_key,
216
+ model=model_name,
217
+ computer=self.computer,
218
+ save_trajectory=self.save_trajectory,
219
+ base_dir=self.trajectory_dir,
220
+ only_n_most_recent_images=self.only_n_most_recent_images,
221
+ **self.loop_kwargs,
222
+ )
223
+
224
+ async def _execute_task(self, task: str) -> AsyncGenerator[Dict[str, Any], None]:
225
+ """Execute a task using the appropriate agent loop.
226
+
227
+ Args:
228
+ task: The task to execute
229
+
230
+ Returns:
231
+ AsyncGenerator yielding task outputs
232
+ """
233
+ logger.info(f"Executing task: {task}")
234
+
235
+ try:
236
+ # Create a message from the task
237
+ task_message = {"role": "user", "content": task}
238
+ messages_with_task = self.messages + [task_message]
239
+
240
+ # Use the run method of the loop
241
+ async for output in self.loop.run(messages_with_task):
242
+ yield output
243
+ except Exception as e:
244
+ logger.error(f"Error executing task: {e}")
245
+ raise
246
+ finally:
247
+ pass
248
+
249
+ async def _execute_action(self, action_type: str, **action_params) -> Any:
250
+ """Execute an action with telemetry tracking."""
251
+ try:
252
+ # Execute the action
253
+ result = await super()._execute_action(action_type, **action_params)
254
+ return result
255
+ except Exception as e:
256
+ logger.exception(f"Error executing action {action_type}: {e}")
257
+ raise
258
+ finally:
259
+ pass
@@ -113,7 +113,7 @@ class BaseComputerAgent(ABC):
113
113
  # Take a test screenshot to verify the computer is working
114
114
  logger.info("Testing computer with a screenshot...")
115
115
  try:
116
- test_screenshot = await self.computer.screenshot()
116
+ test_screenshot = await self.computer.interface.screenshot()
117
117
  # Determine the screenshot size based on its type
118
118
  if isinstance(test_screenshot, bytes):
119
119
  size = len(test_screenshot)
@@ -8,6 +8,7 @@ from datetime import datetime
8
8
  from typing import Any, Dict, List, Optional
9
9
  from PIL import Image
10
10
  import json
11
+ import re
11
12
 
12
13
  logger = logging.getLogger(__name__)
13
14
 
@@ -106,9 +107,18 @@ class ExperimentManager:
106
107
  # Increment screenshot counter
107
108
  self.screenshot_count += 1
108
109
 
110
+ # Sanitize action_type to ensure valid filename
111
+ # Replace characters that are not safe for filenames
112
+ sanitized_action = ""
113
+ if action_type:
114
+ # Replace invalid filename characters with underscores
115
+ sanitized_action = re.sub(r'[\\/*?:"<>|]', "_", action_type)
116
+ # Limit the length to avoid excessively long filenames
117
+ sanitized_action = sanitized_action[:50]
118
+
109
119
  # Create a descriptive filename
110
120
  timestamp = int(datetime.now().timestamp() * 1000)
111
- action_suffix = f"_{action_type}" if action_type else ""
121
+ action_suffix = f"_{sanitized_action}" if sanitized_action else ""
112
122
  filename = f"screenshot_{self.screenshot_count:03d}{action_suffix}_{timestamp}.png"
113
123
 
114
124
  # Save directly to the turn directory
@@ -166,7 +166,7 @@ class BaseLoop(ABC):
166
166
  """
167
167
  try:
168
168
  # Take screenshot
169
- screenshot = await self.computer.screenshot()
169
+ screenshot = await self.computer.interface.screenshot()
170
170
 
171
171
  # Initialize with default values
172
172
  width, height = 1024, 768
@@ -0,0 +1,138 @@
1
+ """Agent telemetry for tracking anonymous usage and feature usage."""
2
+
3
+ import logging
4
+ import os
5
+ import platform
6
+ import sys
7
+ import time
8
+ from typing import Dict, Any, Optional
9
+
10
+ # Import the core telemetry module
11
+ TELEMETRY_AVAILABLE = False
12
+
13
+ try:
14
+ from core.telemetry import (
15
+ record_event,
16
+ increment,
17
+ get_telemetry_client,
18
+ flush,
19
+ is_telemetry_enabled,
20
+ is_telemetry_globally_disabled,
21
+ )
22
+
23
+ def increment_counter(counter_name: str, value: int = 1) -> None:
24
+ """Wrapper for increment to maintain backward compatibility."""
25
+ if is_telemetry_enabled():
26
+ increment(counter_name, value)
27
+
28
+ def set_dimension(name: str, value: Any) -> None:
29
+ """Set a dimension that will be attached to all events."""
30
+ logger = logging.getLogger("cua.agent.telemetry")
31
+ logger.debug(f"Setting dimension {name}={value}")
32
+
33
+ TELEMETRY_AVAILABLE = True
34
+ logger = logging.getLogger("cua.agent.telemetry")
35
+ logger.info("Successfully imported telemetry")
36
+ except ImportError as e:
37
+ logger = logging.getLogger("cua.agent.telemetry")
38
+ logger.warning(f"Could not import telemetry: {e}")
39
+ TELEMETRY_AVAILABLE = False
40
+
41
+
42
+ # Local fallbacks in case core telemetry isn't available
43
+ def _noop(*args: Any, **kwargs: Any) -> None:
44
+ """No-op function for when telemetry is not available."""
45
+ pass
46
+
47
+
48
+ logger = logging.getLogger("cua.agent.telemetry")
49
+
50
+ # If telemetry isn't available, use no-op functions
51
+ if not TELEMETRY_AVAILABLE:
52
+ logger.debug("Telemetry not available, using no-op functions")
53
+ record_event = _noop # type: ignore
54
+ increment_counter = _noop # type: ignore
55
+ set_dimension = _noop # type: ignore
56
+ get_telemetry_client = lambda: None # type: ignore
57
+ flush = _noop # type: ignore
58
+ is_telemetry_enabled = lambda: False # type: ignore
59
+ is_telemetry_globally_disabled = lambda: True # type: ignore
60
+
61
+ # Get system info once to use in telemetry
62
+ SYSTEM_INFO = {
63
+ "os": platform.system().lower(),
64
+ "os_version": platform.release(),
65
+ "python_version": platform.python_version(),
66
+ }
67
+
68
+
69
+ def enable_telemetry() -> bool:
70
+ """Enable telemetry if available.
71
+
72
+ Returns:
73
+ bool: True if telemetry was successfully enabled, False otherwise
74
+ """
75
+ global TELEMETRY_AVAILABLE
76
+
77
+ # Check if globally disabled using core function
78
+ if TELEMETRY_AVAILABLE and is_telemetry_globally_disabled():
79
+ logger.info("Telemetry is globally disabled via environment variable - cannot enable")
80
+ return False
81
+
82
+ # Already enabled
83
+ if TELEMETRY_AVAILABLE:
84
+ return True
85
+
86
+ # Try to import and enable
87
+ try:
88
+ from core.telemetry import (
89
+ record_event,
90
+ increment,
91
+ get_telemetry_client,
92
+ flush,
93
+ is_telemetry_globally_disabled,
94
+ )
95
+
96
+ # Check again after import
97
+ if is_telemetry_globally_disabled():
98
+ logger.info("Telemetry is globally disabled via environment variable - cannot enable")
99
+ return False
100
+
101
+ TELEMETRY_AVAILABLE = True
102
+ logger.info("Telemetry successfully enabled")
103
+ return True
104
+ except ImportError as e:
105
+ logger.warning(f"Could not enable telemetry: {e}")
106
+ return False
107
+
108
+
109
+ def disable_telemetry() -> None:
110
+ """Disable telemetry for this session."""
111
+ global TELEMETRY_AVAILABLE
112
+ TELEMETRY_AVAILABLE = False
113
+ logger.info("Telemetry disabled for this session")
114
+
115
+
116
+ def is_telemetry_enabled() -> bool:
117
+ """Check if telemetry is enabled.
118
+
119
+ Returns:
120
+ bool: True if telemetry is enabled, False otherwise
121
+ """
122
+ # Use the core function if available, otherwise use our local flag
123
+ if TELEMETRY_AVAILABLE:
124
+ from core.telemetry import is_telemetry_enabled as core_is_enabled
125
+
126
+ return core_is_enabled()
127
+ return False
128
+
129
+
130
+ def record_agent_initialization() -> None:
131
+ """Record when an agent instance is initialized."""
132
+ if TELEMETRY_AVAILABLE and is_telemetry_enabled():
133
+ record_event("agent_initialized", SYSTEM_INFO)
134
+
135
+ # Set dimensions that will be attached to all events
136
+ set_dimension("os", SYSTEM_INFO["os"])
137
+ set_dimension("os_version", SYSTEM_INFO["os_version"])
138
+ set_dimension("python_version", SYSTEM_INFO["python_version"])
@@ -1,6 +1,6 @@
1
1
  """Anthropic provider implementation."""
2
2
 
3
3
  from .loop import AnthropicLoop
4
- from .types import APIProvider
4
+ from .types import LLMProvider
5
5
 
6
- __all__ = ["AnthropicLoop", "APIProvider"]
6
+ __all__ = ["AnthropicLoop", "LLMProvider"]