optexity-browser-use 0.9.5__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.
Files changed (147) hide show
  1. browser_use/__init__.py +157 -0
  2. browser_use/actor/__init__.py +11 -0
  3. browser_use/actor/element.py +1175 -0
  4. browser_use/actor/mouse.py +134 -0
  5. browser_use/actor/page.py +561 -0
  6. browser_use/actor/playground/flights.py +41 -0
  7. browser_use/actor/playground/mixed_automation.py +54 -0
  8. browser_use/actor/playground/playground.py +236 -0
  9. browser_use/actor/utils.py +176 -0
  10. browser_use/agent/cloud_events.py +282 -0
  11. browser_use/agent/gif.py +424 -0
  12. browser_use/agent/judge.py +170 -0
  13. browser_use/agent/message_manager/service.py +473 -0
  14. browser_use/agent/message_manager/utils.py +52 -0
  15. browser_use/agent/message_manager/views.py +98 -0
  16. browser_use/agent/prompts.py +413 -0
  17. browser_use/agent/service.py +2316 -0
  18. browser_use/agent/system_prompt.md +185 -0
  19. browser_use/agent/system_prompt_flash.md +10 -0
  20. browser_use/agent/system_prompt_no_thinking.md +183 -0
  21. browser_use/agent/views.py +743 -0
  22. browser_use/browser/__init__.py +41 -0
  23. browser_use/browser/cloud/cloud.py +203 -0
  24. browser_use/browser/cloud/views.py +89 -0
  25. browser_use/browser/events.py +578 -0
  26. browser_use/browser/profile.py +1158 -0
  27. browser_use/browser/python_highlights.py +548 -0
  28. browser_use/browser/session.py +3225 -0
  29. browser_use/browser/session_manager.py +399 -0
  30. browser_use/browser/video_recorder.py +162 -0
  31. browser_use/browser/views.py +200 -0
  32. browser_use/browser/watchdog_base.py +260 -0
  33. browser_use/browser/watchdogs/__init__.py +0 -0
  34. browser_use/browser/watchdogs/aboutblank_watchdog.py +253 -0
  35. browser_use/browser/watchdogs/crash_watchdog.py +335 -0
  36. browser_use/browser/watchdogs/default_action_watchdog.py +2729 -0
  37. browser_use/browser/watchdogs/dom_watchdog.py +817 -0
  38. browser_use/browser/watchdogs/downloads_watchdog.py +1277 -0
  39. browser_use/browser/watchdogs/local_browser_watchdog.py +461 -0
  40. browser_use/browser/watchdogs/permissions_watchdog.py +43 -0
  41. browser_use/browser/watchdogs/popups_watchdog.py +143 -0
  42. browser_use/browser/watchdogs/recording_watchdog.py +126 -0
  43. browser_use/browser/watchdogs/screenshot_watchdog.py +62 -0
  44. browser_use/browser/watchdogs/security_watchdog.py +280 -0
  45. browser_use/browser/watchdogs/storage_state_watchdog.py +335 -0
  46. browser_use/cli.py +2359 -0
  47. browser_use/code_use/__init__.py +16 -0
  48. browser_use/code_use/formatting.py +192 -0
  49. browser_use/code_use/namespace.py +665 -0
  50. browser_use/code_use/notebook_export.py +276 -0
  51. browser_use/code_use/service.py +1340 -0
  52. browser_use/code_use/system_prompt.md +574 -0
  53. browser_use/code_use/utils.py +150 -0
  54. browser_use/code_use/views.py +171 -0
  55. browser_use/config.py +505 -0
  56. browser_use/controller/__init__.py +3 -0
  57. browser_use/dom/enhanced_snapshot.py +161 -0
  58. browser_use/dom/markdown_extractor.py +169 -0
  59. browser_use/dom/playground/extraction.py +312 -0
  60. browser_use/dom/playground/multi_act.py +32 -0
  61. browser_use/dom/serializer/clickable_elements.py +200 -0
  62. browser_use/dom/serializer/code_use_serializer.py +287 -0
  63. browser_use/dom/serializer/eval_serializer.py +478 -0
  64. browser_use/dom/serializer/html_serializer.py +212 -0
  65. browser_use/dom/serializer/paint_order.py +197 -0
  66. browser_use/dom/serializer/serializer.py +1170 -0
  67. browser_use/dom/service.py +825 -0
  68. browser_use/dom/utils.py +129 -0
  69. browser_use/dom/views.py +906 -0
  70. browser_use/exceptions.py +5 -0
  71. browser_use/filesystem/__init__.py +0 -0
  72. browser_use/filesystem/file_system.py +619 -0
  73. browser_use/init_cmd.py +376 -0
  74. browser_use/integrations/gmail/__init__.py +24 -0
  75. browser_use/integrations/gmail/actions.py +115 -0
  76. browser_use/integrations/gmail/service.py +225 -0
  77. browser_use/llm/__init__.py +155 -0
  78. browser_use/llm/anthropic/chat.py +242 -0
  79. browser_use/llm/anthropic/serializer.py +312 -0
  80. browser_use/llm/aws/__init__.py +36 -0
  81. browser_use/llm/aws/chat_anthropic.py +242 -0
  82. browser_use/llm/aws/chat_bedrock.py +289 -0
  83. browser_use/llm/aws/serializer.py +257 -0
  84. browser_use/llm/azure/chat.py +91 -0
  85. browser_use/llm/base.py +57 -0
  86. browser_use/llm/browser_use/__init__.py +3 -0
  87. browser_use/llm/browser_use/chat.py +201 -0
  88. browser_use/llm/cerebras/chat.py +193 -0
  89. browser_use/llm/cerebras/serializer.py +109 -0
  90. browser_use/llm/deepseek/chat.py +212 -0
  91. browser_use/llm/deepseek/serializer.py +109 -0
  92. browser_use/llm/exceptions.py +29 -0
  93. browser_use/llm/google/__init__.py +3 -0
  94. browser_use/llm/google/chat.py +542 -0
  95. browser_use/llm/google/serializer.py +120 -0
  96. browser_use/llm/groq/chat.py +229 -0
  97. browser_use/llm/groq/parser.py +158 -0
  98. browser_use/llm/groq/serializer.py +159 -0
  99. browser_use/llm/messages.py +238 -0
  100. browser_use/llm/models.py +271 -0
  101. browser_use/llm/oci_raw/__init__.py +10 -0
  102. browser_use/llm/oci_raw/chat.py +443 -0
  103. browser_use/llm/oci_raw/serializer.py +229 -0
  104. browser_use/llm/ollama/chat.py +97 -0
  105. browser_use/llm/ollama/serializer.py +143 -0
  106. browser_use/llm/openai/chat.py +264 -0
  107. browser_use/llm/openai/like.py +15 -0
  108. browser_use/llm/openai/serializer.py +165 -0
  109. browser_use/llm/openrouter/chat.py +211 -0
  110. browser_use/llm/openrouter/serializer.py +26 -0
  111. browser_use/llm/schema.py +176 -0
  112. browser_use/llm/views.py +48 -0
  113. browser_use/logging_config.py +330 -0
  114. browser_use/mcp/__init__.py +18 -0
  115. browser_use/mcp/__main__.py +12 -0
  116. browser_use/mcp/client.py +544 -0
  117. browser_use/mcp/controller.py +264 -0
  118. browser_use/mcp/server.py +1114 -0
  119. browser_use/observability.py +204 -0
  120. browser_use/py.typed +0 -0
  121. browser_use/sandbox/__init__.py +41 -0
  122. browser_use/sandbox/sandbox.py +637 -0
  123. browser_use/sandbox/views.py +132 -0
  124. browser_use/screenshots/__init__.py +1 -0
  125. browser_use/screenshots/service.py +52 -0
  126. browser_use/sync/__init__.py +6 -0
  127. browser_use/sync/auth.py +357 -0
  128. browser_use/sync/service.py +161 -0
  129. browser_use/telemetry/__init__.py +51 -0
  130. browser_use/telemetry/service.py +112 -0
  131. browser_use/telemetry/views.py +101 -0
  132. browser_use/tokens/__init__.py +0 -0
  133. browser_use/tokens/custom_pricing.py +24 -0
  134. browser_use/tokens/mappings.py +4 -0
  135. browser_use/tokens/service.py +580 -0
  136. browser_use/tokens/views.py +108 -0
  137. browser_use/tools/registry/service.py +572 -0
  138. browser_use/tools/registry/views.py +174 -0
  139. browser_use/tools/service.py +1675 -0
  140. browser_use/tools/utils.py +82 -0
  141. browser_use/tools/views.py +100 -0
  142. browser_use/utils.py +670 -0
  143. optexity_browser_use-0.9.5.dist-info/METADATA +344 -0
  144. optexity_browser_use-0.9.5.dist-info/RECORD +147 -0
  145. optexity_browser_use-0.9.5.dist-info/WHEEL +4 -0
  146. optexity_browser_use-0.9.5.dist-info/entry_points.txt +3 -0
  147. optexity_browser_use-0.9.5.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,161 @@
1
+ """
2
+ Cloud sync service for sending events to the Browser Use cloud.
3
+ """
4
+
5
+ import logging
6
+
7
+ import httpx
8
+ from bubus import BaseEvent
9
+
10
+ from browser_use.config import CONFIG
11
+ from browser_use.sync.auth import TEMP_USER_ID, DeviceAuthClient
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class CloudSync:
17
+ """Service for syncing events to the Browser Use cloud"""
18
+
19
+ def __init__(self, base_url: str | None = None, allow_session_events_for_auth: bool = False):
20
+ # Backend API URL for all API requests - can be passed directly or defaults to env var
21
+ self.base_url = base_url or CONFIG.BROWSER_USE_CLOUD_API_URL
22
+ self.auth_client = DeviceAuthClient(base_url=self.base_url)
23
+ self.session_id: str | None = None
24
+ self.allow_session_events_for_auth = allow_session_events_for_auth
25
+ self.auth_flow_active = False # Flag to indicate auth flow is running
26
+ # Check if cloud sync is actually enabled - if not, we should remain silent
27
+ self.enabled = CONFIG.BROWSER_USE_CLOUD_SYNC
28
+
29
+ async def handle_event(self, event: BaseEvent) -> None:
30
+ """Handle an event by sending it to the cloud"""
31
+ try:
32
+ # If cloud sync is disabled, don't handle any events
33
+ if not self.enabled:
34
+ return
35
+
36
+ # Extract session ID from CreateAgentSessionEvent
37
+ if event.event_type == 'CreateAgentSessionEvent' and hasattr(event, 'id'):
38
+ self.session_id = str(event.id) # type: ignore
39
+
40
+ # Send events based on authentication status and context
41
+ if self.auth_client.is_authenticated:
42
+ # User is authenticated - send all events
43
+ await self._send_event(event)
44
+ elif self.allow_session_events_for_auth:
45
+ # Special case: allow ALL events during auth flow
46
+ await self._send_event(event)
47
+ # Mark auth flow as active when we see a session event
48
+ if event.event_type == 'CreateAgentSessionEvent':
49
+ self.auth_flow_active = True
50
+ else:
51
+ # User is not authenticated and no auth in progress - don't send anything
52
+ logger.debug(f'Skipping event {event.event_type} - user not authenticated')
53
+
54
+ except Exception as e:
55
+ logger.error(f'Failed to handle {event.event_type} event: {type(e).__name__}: {e}', exc_info=True)
56
+
57
+ async def _send_event(self, event: BaseEvent) -> None:
58
+ """Send event to cloud API"""
59
+ try:
60
+ headers = {}
61
+
62
+ # Override user_id only if it's not already set to a specific value
63
+ # This allows CLI and other code to explicitly set temp user_id when needed
64
+ if self.auth_client and self.auth_client.is_authenticated:
65
+ # Only override if we're fully authenticated and event doesn't have temp user_id
66
+ current_user_id = getattr(event, 'user_id', None)
67
+ if current_user_id != TEMP_USER_ID:
68
+ setattr(event, 'user_id', str(self.auth_client.user_id))
69
+ else:
70
+ # Set temp user_id if not already set
71
+ if not hasattr(event, 'user_id') or not getattr(event, 'user_id', None):
72
+ setattr(event, 'user_id', TEMP_USER_ID)
73
+
74
+ # Add auth headers if available
75
+ if self.auth_client:
76
+ headers.update(self.auth_client.get_headers())
77
+
78
+ # Send event (batch format with direct BaseEvent serialization)
79
+ async with httpx.AsyncClient() as client:
80
+ # Serialize event and add device_id to all events
81
+ event_data = event.model_dump(mode='json')
82
+ if self.auth_client and self.auth_client.device_id:
83
+ event_data['device_id'] = self.auth_client.device_id
84
+
85
+ response = await client.post(
86
+ f'{self.base_url.rstrip("/")}/api/v1/events',
87
+ json={'events': [event_data]},
88
+ headers=headers,
89
+ timeout=10.0,
90
+ )
91
+
92
+ if response.status_code >= 400:
93
+ # Log error but don't raise - we want to fail silently
94
+ logger.debug(
95
+ f'Failed to send sync event: POST {response.request.url} {response.status_code} - {response.text}'
96
+ )
97
+ except httpx.TimeoutException:
98
+ logger.debug(f'Event send timed out after 10 seconds: {event}')
99
+ except httpx.ConnectError as e:
100
+ # logger.warning(f'⚠️ Failed to connect to cloud service at {self.base_url}: {e}')
101
+ pass
102
+ except httpx.HTTPError as e:
103
+ logger.debug(f'HTTP error sending event {event}: {type(e).__name__}: {e}')
104
+ except Exception as e:
105
+ logger.debug(f'Unexpected error sending event {event}: {type(e).__name__}: {e}')
106
+
107
+ # async def _update_wal_user_ids(self, session_id: str) -> None:
108
+ # """Update user IDs in WAL file after authentication"""
109
+ # try:
110
+ # assert self.auth_client, 'Cloud sync must be authenticated to update WAL user ID'
111
+
112
+ # wal_path = CONFIG.BROWSER_USE_CONFIG_DIR / 'events' / f'{session_id}.jsonl'
113
+ # if not await anyio.Path(wal_path).exists():
114
+ # raise FileNotFoundError(
115
+ # f'CloudSync failed to update saved event user_ids after auth: Agent EventBus WAL file not found: {wal_path}'
116
+ # )
117
+
118
+ # # Read all events
119
+ # events = []
120
+ # content = await anyio.Path(wal_path).read_text()
121
+ # for line in content.splitlines():
122
+ # if line.strip():
123
+ # events.append(json.loads(line))
124
+
125
+ # # Update user_id and device_id
126
+ # user_id = self.auth_client.user_id
127
+ # device_id = self.auth_client.device_id
128
+ # for event in events:
129
+ # if 'user_id' in event:
130
+ # event['user_id'] = user_id
131
+ # # Add device_id to all events
132
+ # event['device_id'] = device_id
133
+
134
+ # # Write back
135
+ # updated_content = '\n'.join(json.dumps(event) for event in events) + '\n'
136
+ # await anyio.Path(wal_path).write_text(updated_content)
137
+
138
+ # except Exception as e:
139
+ # logger.warning(f'Failed to update WAL user IDs: {e}')
140
+
141
+ def set_auth_flow_active(self) -> None:
142
+ """Mark auth flow as active to allow all events"""
143
+ self.auth_flow_active = True
144
+
145
+ async def authenticate(self, show_instructions: bool = True) -> bool:
146
+ """Authenticate with the cloud service"""
147
+ # If cloud sync is disabled, don't authenticate
148
+ if not self.enabled:
149
+ return False
150
+
151
+ # Check if already authenticated first
152
+ if self.auth_client.is_authenticated:
153
+ import logging
154
+
155
+ logger = logging.getLogger(__name__)
156
+ if show_instructions:
157
+ logger.info('✅ Already authenticated! Skipping OAuth flow.')
158
+ return True
159
+
160
+ # Not authenticated - run OAuth flow
161
+ return await self.auth_client.authenticate(agent_session_id=self.session_id, show_instructions=show_instructions)
@@ -0,0 +1,51 @@
1
+ """
2
+ Telemetry for Browser Use.
3
+ """
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ # Type stubs for lazy imports
8
+ if TYPE_CHECKING:
9
+ from browser_use.telemetry.service import ProductTelemetry
10
+ from browser_use.telemetry.views import (
11
+ BaseTelemetryEvent,
12
+ CLITelemetryEvent,
13
+ MCPClientTelemetryEvent,
14
+ MCPServerTelemetryEvent,
15
+ )
16
+
17
+ # Lazy imports mapping
18
+ _LAZY_IMPORTS = {
19
+ 'ProductTelemetry': ('browser_use.telemetry.service', 'ProductTelemetry'),
20
+ 'BaseTelemetryEvent': ('browser_use.telemetry.views', 'BaseTelemetryEvent'),
21
+ 'CLITelemetryEvent': ('browser_use.telemetry.views', 'CLITelemetryEvent'),
22
+ 'MCPClientTelemetryEvent': ('browser_use.telemetry.views', 'MCPClientTelemetryEvent'),
23
+ 'MCPServerTelemetryEvent': ('browser_use.telemetry.views', 'MCPServerTelemetryEvent'),
24
+ }
25
+
26
+
27
+ def __getattr__(name: str):
28
+ """Lazy import mechanism for telemetry components."""
29
+ if name in _LAZY_IMPORTS:
30
+ module_path, attr_name = _LAZY_IMPORTS[name]
31
+ try:
32
+ from importlib import import_module
33
+
34
+ module = import_module(module_path)
35
+ attr = getattr(module, attr_name)
36
+ # Cache the imported attribute in the module's globals
37
+ globals()[name] = attr
38
+ return attr
39
+ except ImportError as e:
40
+ raise ImportError(f'Failed to import {name} from {module_path}: {e}') from e
41
+
42
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
43
+
44
+
45
+ __all__ = [
46
+ 'BaseTelemetryEvent',
47
+ 'ProductTelemetry',
48
+ 'CLITelemetryEvent',
49
+ 'MCPClientTelemetryEvent',
50
+ 'MCPServerTelemetryEvent',
51
+ ]
@@ -0,0 +1,112 @@
1
+ import logging
2
+ import os
3
+
4
+ from dotenv import load_dotenv
5
+ from posthog import Posthog
6
+ from uuid_extensions import uuid7str
7
+
8
+ from browser_use.telemetry.views import BaseTelemetryEvent
9
+ from browser_use.utils import singleton
10
+
11
+ load_dotenv()
12
+
13
+ from browser_use.config import CONFIG
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ POSTHOG_EVENT_SETTINGS = {
19
+ 'process_person_profile': True,
20
+ }
21
+
22
+
23
+ @singleton
24
+ class ProductTelemetry:
25
+ """
26
+ Service for capturing anonymized telemetry data.
27
+
28
+ If the environment variable `ANONYMIZED_TELEMETRY=False`, anonymized telemetry will be disabled.
29
+ """
30
+
31
+ USER_ID_PATH = str(CONFIG.BROWSER_USE_CONFIG_DIR / 'device_id')
32
+ PROJECT_API_KEY = 'phc_F8JMNjW1i2KbGUTaW1unnDdLSPCoyc52SGRU0JecaUh'
33
+ HOST = 'https://eu.i.posthog.com'
34
+ UNKNOWN_USER_ID = 'UNKNOWN'
35
+
36
+ _curr_user_id = None
37
+
38
+ def __init__(self) -> None:
39
+ telemetry_disabled = not CONFIG.ANONYMIZED_TELEMETRY
40
+ self.debug_logging = CONFIG.BROWSER_USE_LOGGING_LEVEL == 'debug'
41
+
42
+ if telemetry_disabled:
43
+ self._posthog_client = None
44
+ else:
45
+ logger.info('Using anonymized telemetry, see https://docs.browser-use.com/development/telemetry.')
46
+ self._posthog_client = Posthog(
47
+ project_api_key=self.PROJECT_API_KEY,
48
+ host=self.HOST,
49
+ disable_geoip=False,
50
+ enable_exception_autocapture=True,
51
+ )
52
+
53
+ # Silence posthog's logging
54
+ if not self.debug_logging:
55
+ posthog_logger = logging.getLogger('posthog')
56
+ posthog_logger.disabled = True
57
+
58
+ if self._posthog_client is None:
59
+ logger.debug('Telemetry disabled')
60
+
61
+ def capture(self, event: BaseTelemetryEvent) -> None:
62
+ if self._posthog_client is None:
63
+ return
64
+
65
+ self._direct_capture(event)
66
+
67
+ def _direct_capture(self, event: BaseTelemetryEvent) -> None:
68
+ """
69
+ Should not be thread blocking because posthog magically handles it
70
+ """
71
+ if self._posthog_client is None:
72
+ return
73
+
74
+ try:
75
+ self._posthog_client.capture(
76
+ distinct_id=self.user_id,
77
+ event=event.name,
78
+ properties={**event.properties, **POSTHOG_EVENT_SETTINGS},
79
+ )
80
+ except Exception as e:
81
+ logger.error(f'Failed to send telemetry event {event.name}: {e}')
82
+
83
+ def flush(self) -> None:
84
+ if self._posthog_client:
85
+ try:
86
+ self._posthog_client.flush()
87
+ logger.debug('PostHog client telemetry queue flushed.')
88
+ except Exception as e:
89
+ logger.error(f'Failed to flush PostHog client: {e}')
90
+ else:
91
+ logger.debug('PostHog client not available, skipping flush.')
92
+
93
+ @property
94
+ def user_id(self) -> str:
95
+ if self._curr_user_id:
96
+ return self._curr_user_id
97
+
98
+ # File access may fail due to permissions or other reasons. We don't want to
99
+ # crash so we catch all exceptions.
100
+ try:
101
+ if not os.path.exists(self.USER_ID_PATH):
102
+ os.makedirs(os.path.dirname(self.USER_ID_PATH), exist_ok=True)
103
+ with open(self.USER_ID_PATH, 'w') as f:
104
+ new_user_id = uuid7str()
105
+ f.write(new_user_id)
106
+ self._curr_user_id = new_user_id
107
+ else:
108
+ with open(self.USER_ID_PATH) as f:
109
+ self._curr_user_id = f.read()
110
+ except Exception:
111
+ self._curr_user_id = 'UNKNOWN_USER_ID'
112
+ return self._curr_user_id
@@ -0,0 +1,101 @@
1
+ from abc import ABC, abstractmethod
2
+ from collections.abc import Sequence
3
+ from dataclasses import asdict, dataclass
4
+ from typing import Any, Literal
5
+
6
+ from browser_use.config import is_running_in_docker
7
+
8
+
9
+ @dataclass
10
+ class BaseTelemetryEvent(ABC):
11
+ @property
12
+ @abstractmethod
13
+ def name(self) -> str:
14
+ pass
15
+
16
+ @property
17
+ def properties(self) -> dict[str, Any]:
18
+ props = {k: v for k, v in asdict(self).items() if k != 'name'}
19
+ # Add Docker context if running in Docker
20
+ props['is_docker'] = is_running_in_docker()
21
+ return props
22
+
23
+
24
+ @dataclass
25
+ class AgentTelemetryEvent(BaseTelemetryEvent):
26
+ # start details
27
+ task: str
28
+ model: str
29
+ model_provider: str
30
+ max_steps: int
31
+ max_actions_per_step: int
32
+ use_vision: bool | Literal['auto']
33
+ version: str
34
+ source: str
35
+ cdp_url: str | None
36
+ agent_type: str | None # 'code' for CodeAgent, None for regular Agent
37
+ # step details
38
+ action_errors: Sequence[str | None]
39
+ action_history: Sequence[list[dict] | None]
40
+ urls_visited: Sequence[str | None]
41
+ # end details
42
+ steps: int
43
+ total_input_tokens: int
44
+ total_output_tokens: int
45
+ prompt_cached_tokens: int
46
+ total_tokens: int
47
+ total_duration_seconds: float
48
+ success: bool | None
49
+ final_result_response: str | None
50
+ error_message: str | None
51
+ # judge details
52
+ judge_verdict: bool | None = None
53
+ judge_reasoning: str | None = None
54
+ judge_failure_reason: str | None = None
55
+
56
+ name: str = 'agent_event'
57
+
58
+
59
+ @dataclass
60
+ class MCPClientTelemetryEvent(BaseTelemetryEvent):
61
+ """Telemetry event for MCP client usage"""
62
+
63
+ server_name: str
64
+ command: str
65
+ tools_discovered: int
66
+ version: str
67
+ action: str # 'connect', 'disconnect', 'tool_call'
68
+ tool_name: str | None = None
69
+ duration_seconds: float | None = None
70
+ error_message: str | None = None
71
+
72
+ name: str = 'mcp_client_event'
73
+
74
+
75
+ @dataclass
76
+ class MCPServerTelemetryEvent(BaseTelemetryEvent):
77
+ """Telemetry event for MCP server usage"""
78
+
79
+ version: str
80
+ action: str # 'start', 'stop', 'tool_call'
81
+ tool_name: str | None = None
82
+ duration_seconds: float | None = None
83
+ error_message: str | None = None
84
+ parent_process_cmdline: str | None = None
85
+
86
+ name: str = 'mcp_server_event'
87
+
88
+
89
+ @dataclass
90
+ class CLITelemetryEvent(BaseTelemetryEvent):
91
+ """Telemetry event for CLI usage"""
92
+
93
+ version: str
94
+ action: str # 'start', 'message_sent', 'task_completed', 'error'
95
+ mode: str # 'interactive', 'oneshot', 'mcp_server'
96
+ model: str | None = None
97
+ model_provider: str | None = None
98
+ duration_seconds: float | None = None
99
+ error_message: str | None = None
100
+
101
+ name: str = 'cli_event'
File without changes
@@ -0,0 +1,24 @@
1
+ """
2
+ Custom model pricing for models not available in LiteLLM's pricing data.
3
+
4
+ Prices are per token (not per 1M tokens).
5
+ """
6
+
7
+ from typing import Any
8
+
9
+ # Custom model pricing data
10
+ # Format matches LiteLLM's model_prices_and_context_window.json structure
11
+ CUSTOM_MODEL_PRICING: dict[str, dict[str, Any]] = {
12
+ 'bu-1-0': {
13
+ 'input_cost_per_token': 0.2 / 1_000_000, # $0.50 per 1M tokens
14
+ 'output_cost_per_token': 2.00 / 1_000_000, # $3.00 per 1M tokens
15
+ 'cache_read_input_token_cost': 0.02 / 1_000_000, # $0.10 per 1M tokens
16
+ 'cache_creation_input_token_cost': None, # Not specified
17
+ 'max_tokens': None, # Not specified
18
+ 'max_input_tokens': None, # Not specified
19
+ 'max_output_tokens': None, # Not specified
20
+ }
21
+ }
22
+ CUSTOM_MODEL_PRICING['bu-latest'] = CUSTOM_MODEL_PRICING['bu-1-0']
23
+
24
+ CUSTOM_MODEL_PRICING['smart'] = CUSTOM_MODEL_PRICING['bu-1-0']
@@ -0,0 +1,4 @@
1
+ # Mapping from model_name to LiteLLM model name
2
+ MODEL_TO_LITELLM: dict[str, str] = {
3
+ 'gemini-flash-latest': 'gemini/gemini-flash-latest',
4
+ }