vibesurf 0.1.32__py3-none-any.whl → 0.1.34__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 vibesurf might be problematic. Click here for more details.
- vibe_surf/_version.py +2 -2
- vibe_surf/agents/browser_use_agent.py +1 -1
- vibe_surf/agents/prompts/vibe_surf_prompt.py +6 -0
- vibe_surf/agents/report_writer_agent.py +50 -0
- vibe_surf/agents/vibe_surf_agent.py +55 -0
- vibe_surf/backend/api/composio.py +952 -0
- vibe_surf/backend/database/migrations/v005_add_composio_integration.sql +33 -0
- vibe_surf/backend/database/migrations/v006_add_credentials_table.sql +26 -0
- vibe_surf/backend/database/models.py +53 -1
- vibe_surf/backend/database/queries.py +312 -2
- vibe_surf/backend/main.py +28 -0
- vibe_surf/backend/shared_state.py +123 -9
- vibe_surf/chrome_extension/scripts/api-client.js +32 -0
- vibe_surf/chrome_extension/scripts/settings-manager.js +954 -1
- vibe_surf/chrome_extension/sidepanel.html +190 -0
- vibe_surf/chrome_extension/styles/settings-integrations.css +927 -0
- vibe_surf/chrome_extension/styles/settings-modal.css +7 -3
- vibe_surf/chrome_extension/styles/settings-responsive.css +37 -5
- vibe_surf/cli.py +98 -3
- vibe_surf/telemetry/__init__.py +60 -0
- vibe_surf/telemetry/service.py +112 -0
- vibe_surf/telemetry/views.py +156 -0
- vibe_surf/tools/composio_client.py +456 -0
- vibe_surf/tools/mcp_client.py +21 -2
- vibe_surf/tools/vibesurf_tools.py +290 -87
- vibe_surf/tools/views.py +16 -0
- vibe_surf/tools/website_api/youtube/client.py +35 -13
- vibe_surf/utils.py +13 -0
- {vibesurf-0.1.32.dist-info → vibesurf-0.1.34.dist-info}/METADATA +11 -9
- {vibesurf-0.1.32.dist-info → vibesurf-0.1.34.dist-info}/RECORD +34 -25
- {vibesurf-0.1.32.dist-info → vibesurf-0.1.34.dist-info}/WHEEL +0 -0
- {vibesurf-0.1.32.dist-info → vibesurf-0.1.34.dist-info}/entry_points.txt +0 -0
- {vibesurf-0.1.32.dist-info → vibesurf-0.1.34.dist-info}/licenses/LICENSE +0 -0
- {vibesurf-0.1.32.dist-info → vibesurf-0.1.34.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
"""Composio client integration for VibeSurf tools.
|
|
2
|
+
|
|
3
|
+
This module provides integration between Composio toolkits and VibeSurf's action registry.
|
|
4
|
+
Composio tools are dynamically discovered and registered as VibeSurf actions.
|
|
5
|
+
|
|
6
|
+
Example usage:
|
|
7
|
+
from vibe_surf.tools.composio_client import ComposioClient
|
|
8
|
+
from vibe_surf.tools.vibesurf_tools import VibeSurfTools
|
|
9
|
+
|
|
10
|
+
tools = VibeSurfTools()
|
|
11
|
+
|
|
12
|
+
# Connect to Composio
|
|
13
|
+
composio_client = ComposioClient(
|
|
14
|
+
composio_instance=composio_instance
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# Register all Composio tools as VibeSurf actions
|
|
18
|
+
await composio_client.register_to_tools(tools, toolkit_tools_dict)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import asyncio
|
|
22
|
+
import logging
|
|
23
|
+
import time
|
|
24
|
+
import json
|
|
25
|
+
from typing import Any, Dict, Optional, List
|
|
26
|
+
|
|
27
|
+
from pydantic import BaseModel, ConfigDict, Field, create_model
|
|
28
|
+
|
|
29
|
+
from browser_use.agent.views import ActionResult
|
|
30
|
+
from vibe_surf.logger import get_logger
|
|
31
|
+
from vibe_surf.telemetry.service import ProductTelemetry
|
|
32
|
+
from vibe_surf.telemetry.views import ComposioTelemetryEvent
|
|
33
|
+
from vibe_surf.utils import get_vibesurf_version
|
|
34
|
+
|
|
35
|
+
logger = get_logger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ComposioClient:
|
|
39
|
+
"""Client for connecting to Composio and exposing toolkit tools as VibeSurf actions."""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
composio_instance: Optional[Any] = None,
|
|
44
|
+
):
|
|
45
|
+
"""Initialize Composio client.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
composio_instance: Composio instance (optional, can be set later)
|
|
49
|
+
"""
|
|
50
|
+
self.composio_instance = composio_instance
|
|
51
|
+
self._registered_actions: set[str] = set()
|
|
52
|
+
self._toolkit_tools: Dict[str, List[Dict]] = {}
|
|
53
|
+
self._telemetry = ProductTelemetry()
|
|
54
|
+
|
|
55
|
+
def update_composio_instance(self, composio_instance: Any):
|
|
56
|
+
"""Update the Composio instance"""
|
|
57
|
+
self.composio_instance = composio_instance
|
|
58
|
+
|
|
59
|
+
async def register_to_tools(
|
|
60
|
+
self,
|
|
61
|
+
tools, # VibeSurfTools instance
|
|
62
|
+
toolkit_tools_dict: Dict[str, List[Dict]],
|
|
63
|
+
prefix: str = "cpo.",
|
|
64
|
+
) -> None:
|
|
65
|
+
"""Register Composio tools as actions in the VibeSurf tools.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
tools: VibeSurf tools instance to register actions to
|
|
69
|
+
toolkit_tools_dict: Dict of toolkit_slug -> tools list
|
|
70
|
+
prefix: Prefix to add to action names (e.g., "cpo.")
|
|
71
|
+
"""
|
|
72
|
+
if not self.composio_instance:
|
|
73
|
+
logger.warning("Composio instance not available, skipping registration")
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
self._toolkit_tools = toolkit_tools_dict
|
|
77
|
+
registry = tools.registry
|
|
78
|
+
|
|
79
|
+
for toolkit_slug, tools_list in toolkit_tools_dict.items():
|
|
80
|
+
# Parse tools if it's a JSON string
|
|
81
|
+
if isinstance(tools_list, str):
|
|
82
|
+
try:
|
|
83
|
+
tools_list = json.loads(tools_list)
|
|
84
|
+
except (json.JSONDecodeError, TypeError) as e:
|
|
85
|
+
logger.warning(f"Failed to parse tools for toolkit {toolkit_slug}: {e}")
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
if not isinstance(tools_list, list):
|
|
89
|
+
logger.warning(f"Tools for toolkit {toolkit_slug} is not a list: {type(tools_list)}")
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
for tool_info in tools_list:
|
|
93
|
+
if not isinstance(tool_info, dict):
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
tool_name = tool_info.get('name')
|
|
97
|
+
if not tool_name:
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
# Skip if tool is disabled
|
|
101
|
+
if not tool_info.get('enabled', True):
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
# Apply prefix
|
|
105
|
+
action_name = f'{prefix}{toolkit_slug}.{tool_name}'
|
|
106
|
+
|
|
107
|
+
# Skip if already registered
|
|
108
|
+
if action_name in self._registered_actions:
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
# Register the tool as an action
|
|
112
|
+
self._register_tool_as_action(registry, action_name, toolkit_slug, tool_info)
|
|
113
|
+
self._registered_actions.add(action_name)
|
|
114
|
+
|
|
115
|
+
logger.info(f"✅ Registered {len(self._registered_actions)} Composio tools as VibeSurf actions")
|
|
116
|
+
|
|
117
|
+
# Capture telemetry for registration
|
|
118
|
+
self._telemetry.capture(
|
|
119
|
+
ComposioTelemetryEvent(
|
|
120
|
+
toolkit_slugs=list(toolkit_tools_dict.keys()),
|
|
121
|
+
tools_registered=len(self._registered_actions),
|
|
122
|
+
version=get_vibesurf_version(),
|
|
123
|
+
action='register'
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def _register_tool_as_action(self, registry, action_name: str, toolkit_slug: str, tool_info: Dict) -> None:
|
|
128
|
+
"""Register a single Composio tool as a VibeSurf action.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
registry: VibeSurf registry to register action to
|
|
132
|
+
action_name: Name for the registered action
|
|
133
|
+
toolkit_slug: Toolkit slug
|
|
134
|
+
tool_info: Tool information dictionary
|
|
135
|
+
"""
|
|
136
|
+
# Parse tool parameters to create Pydantic model
|
|
137
|
+
param_fields = {}
|
|
138
|
+
tool_name = tool_info.get('name', '')
|
|
139
|
+
description = tool_info.get('description', f'Composio tool: {tool_name}')
|
|
140
|
+
parameters = tool_info.get('parameters', {})
|
|
141
|
+
|
|
142
|
+
if parameters and isinstance(parameters, dict):
|
|
143
|
+
# Handle JSON Schema parameters
|
|
144
|
+
properties = parameters.get('properties', {})
|
|
145
|
+
required = set(parameters.get('required', []))
|
|
146
|
+
|
|
147
|
+
for param_name, param_schema in properties.items():
|
|
148
|
+
# Convert JSON Schema type to Python type
|
|
149
|
+
param_type = self._json_schema_to_python_type(param_schema, f'{action_name}_{param_name}')
|
|
150
|
+
|
|
151
|
+
# Determine if field is required and handle defaults
|
|
152
|
+
if param_name in required:
|
|
153
|
+
default = ... # Required field
|
|
154
|
+
else:
|
|
155
|
+
# Optional field - make type optional and handle default
|
|
156
|
+
param_type = param_type | None
|
|
157
|
+
if 'default' in param_schema:
|
|
158
|
+
default = param_schema['default']
|
|
159
|
+
else:
|
|
160
|
+
default = None
|
|
161
|
+
|
|
162
|
+
# Add field with description if available
|
|
163
|
+
field_kwargs = {}
|
|
164
|
+
if 'description' in param_schema:
|
|
165
|
+
field_kwargs['description'] = param_schema['description']
|
|
166
|
+
|
|
167
|
+
param_fields[param_name] = (param_type, Field(default, **field_kwargs))
|
|
168
|
+
|
|
169
|
+
# Create Pydantic model for the tool parameters
|
|
170
|
+
if param_fields:
|
|
171
|
+
# Create a BaseModel class with proper configuration
|
|
172
|
+
class ConfiguredBaseModel(BaseModel):
|
|
173
|
+
model_config = ConfigDict(extra='forbid', validate_by_name=True, validate_by_alias=True)
|
|
174
|
+
|
|
175
|
+
param_model = create_model(f'{action_name}_Params', __base__=ConfiguredBaseModel, **param_fields)
|
|
176
|
+
else:
|
|
177
|
+
# No parameters - create empty model
|
|
178
|
+
param_model = None
|
|
179
|
+
|
|
180
|
+
# Create async wrapper function for the Composio tool
|
|
181
|
+
if param_model:
|
|
182
|
+
# Function takes param model as first parameter
|
|
183
|
+
async def composio_action_wrapper(params: param_model) -> ActionResult: # type: ignore[no-redef]
|
|
184
|
+
"""Wrapper function that calls the Composio tool."""
|
|
185
|
+
if not self.composio_instance:
|
|
186
|
+
return ActionResult(error=f"Composio instance not available", success=False)
|
|
187
|
+
|
|
188
|
+
# Convert pydantic model to dict for Composio call
|
|
189
|
+
tool_params = params.model_dump(exclude_none=True)
|
|
190
|
+
|
|
191
|
+
logger.debug(f"🔧 Calling Composio tool '{tool_name}' with params: {tool_params}")
|
|
192
|
+
|
|
193
|
+
start_time = time.time()
|
|
194
|
+
error_msg = None
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
# Call the Composio tool using the tools.execute method
|
|
198
|
+
entity_id = "default" # Use default entity ID
|
|
199
|
+
if 'include_payload' in tool_params:
|
|
200
|
+
tool_params['include_payload'] = False
|
|
201
|
+
result = self.composio_instance.tools.execute(
|
|
202
|
+
slug=tool_name,
|
|
203
|
+
arguments=tool_params,
|
|
204
|
+
user_id=entity_id,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Convert Composio result to ActionResult
|
|
208
|
+
extracted_content = self._format_composio_result(result)
|
|
209
|
+
|
|
210
|
+
return ActionResult(
|
|
211
|
+
extracted_content=extracted_content,
|
|
212
|
+
long_term_memory=f"Used Composio tool '{tool_name}' from {toolkit_slug}",
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
except Exception as e:
|
|
216
|
+
error_msg = f"Composio tool '{tool_name}' failed: {str(e)}"
|
|
217
|
+
logger.error(error_msg)
|
|
218
|
+
return ActionResult(error=error_msg, success=False)
|
|
219
|
+
finally:
|
|
220
|
+
# Log execution time and capture telemetry
|
|
221
|
+
duration = time.time() - start_time
|
|
222
|
+
logger.debug(f"Composio tool '{tool_name}' executed in {duration:.2f}s")
|
|
223
|
+
|
|
224
|
+
# Capture telemetry for tool call
|
|
225
|
+
self._telemetry.capture(
|
|
226
|
+
ComposioTelemetryEvent(
|
|
227
|
+
toolkit_slugs=[toolkit_slug],
|
|
228
|
+
tools_registered=len(self._registered_actions),
|
|
229
|
+
version=get_vibesurf_version(),
|
|
230
|
+
action='tool_call',
|
|
231
|
+
toolkit_slug=toolkit_slug,
|
|
232
|
+
tool_name=tool_name,
|
|
233
|
+
duration_seconds=duration,
|
|
234
|
+
error_message=error_msg
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
else:
|
|
238
|
+
# No parameters - empty function signature
|
|
239
|
+
async def composio_action_wrapper() -> ActionResult: # type: ignore[no-redef]
|
|
240
|
+
"""Wrapper function that calls the Composio tool."""
|
|
241
|
+
if not self.composio_instance:
|
|
242
|
+
return ActionResult(error=f"Composio instance not available", success=False)
|
|
243
|
+
|
|
244
|
+
logger.debug(f"🔧 Calling Composio tool '{tool_name}' with no params")
|
|
245
|
+
|
|
246
|
+
start_time = time.time()
|
|
247
|
+
error_msg = None
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
# Call the Composio tool with empty params
|
|
251
|
+
entity_id = "default" # Use default entity ID
|
|
252
|
+
result = self.composio_instance.tools.execute(
|
|
253
|
+
slug=tool_name,
|
|
254
|
+
arguments={},
|
|
255
|
+
user_id=entity_id,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# Convert Composio result to ActionResult
|
|
259
|
+
extracted_content = self._format_composio_result(result)
|
|
260
|
+
|
|
261
|
+
return ActionResult(
|
|
262
|
+
extracted_content=extracted_content,
|
|
263
|
+
long_term_memory=f"Used Composio tool '{tool_name}' from {toolkit_slug}",
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
except Exception as e:
|
|
267
|
+
error_msg = f"Composio tool '{tool_name}' failed: {str(e)}"
|
|
268
|
+
logger.error(error_msg)
|
|
269
|
+
return ActionResult(error=error_msg, success=False)
|
|
270
|
+
finally:
|
|
271
|
+
# Log execution time and capture telemetry
|
|
272
|
+
duration = time.time() - start_time
|
|
273
|
+
logger.debug(f"Composio tool '{tool_name}' executed in {duration:.2f}s")
|
|
274
|
+
|
|
275
|
+
# Capture telemetry for tool call
|
|
276
|
+
self._telemetry.capture(
|
|
277
|
+
ComposioTelemetryEvent(
|
|
278
|
+
toolkit_slugs=[toolkit_slug],
|
|
279
|
+
tools_registered=len(self._registered_actions),
|
|
280
|
+
version=get_vibesurf_version(),
|
|
281
|
+
action='tool_call',
|
|
282
|
+
toolkit_slug=toolkit_slug,
|
|
283
|
+
tool_name=tool_name,
|
|
284
|
+
duration_seconds=duration,
|
|
285
|
+
error_message=error_msg
|
|
286
|
+
)
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
# Set function metadata for better debugging
|
|
290
|
+
composio_action_wrapper.__name__ = action_name
|
|
291
|
+
composio_action_wrapper.__qualname__ = f'composio.{toolkit_slug}.{action_name}'
|
|
292
|
+
|
|
293
|
+
# Register the action with VibeSurf
|
|
294
|
+
registry.action(description=description, param_model=param_model)(composio_action_wrapper)
|
|
295
|
+
|
|
296
|
+
logger.debug(f"✅ Registered Composio tool '{tool_name}' as action '{action_name}'")
|
|
297
|
+
|
|
298
|
+
def _format_composio_result(self, result: Any) -> str:
|
|
299
|
+
"""Format Composio tool result into a string for ActionResult.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
result: Raw result from Composio tool call
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
Formatted string representation of the result
|
|
306
|
+
"""
|
|
307
|
+
# Handle different Composio result formats
|
|
308
|
+
if isinstance(result, dict) or isinstance(result, list):
|
|
309
|
+
# Dictionary result
|
|
310
|
+
try:
|
|
311
|
+
return f"```json\n{json.dumps(result, indent=2, ensure_ascii=False)}\n```"
|
|
312
|
+
except (TypeError, ValueError):
|
|
313
|
+
return str(result)
|
|
314
|
+
else:
|
|
315
|
+
# Direct result or unknown format
|
|
316
|
+
return str(result)
|
|
317
|
+
|
|
318
|
+
def _json_schema_to_python_type(self, schema: dict, model_name: str = 'NestedModel') -> Any:
|
|
319
|
+
"""Convert JSON Schema type to Python type.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
schema: JSON Schema definition
|
|
323
|
+
model_name: Name for nested models
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Python type corresponding to the schema
|
|
327
|
+
"""
|
|
328
|
+
json_type = schema.get('type', 'string')
|
|
329
|
+
|
|
330
|
+
# Basic type mapping
|
|
331
|
+
type_mapping = {
|
|
332
|
+
'string': str,
|
|
333
|
+
'number': float,
|
|
334
|
+
'integer': int,
|
|
335
|
+
'boolean': bool,
|
|
336
|
+
'array': list,
|
|
337
|
+
'null': type(None),
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
# Handle enums (they're still strings)
|
|
341
|
+
if 'enum' in schema:
|
|
342
|
+
return str
|
|
343
|
+
|
|
344
|
+
# Handle objects with nested properties
|
|
345
|
+
if json_type == 'object':
|
|
346
|
+
properties = schema.get('properties', {})
|
|
347
|
+
if properties:
|
|
348
|
+
# Create nested pydantic model for objects with properties
|
|
349
|
+
nested_fields = {}
|
|
350
|
+
required_fields = set(schema.get('required', []))
|
|
351
|
+
|
|
352
|
+
for prop_name, prop_schema in properties.items():
|
|
353
|
+
# Recursively process nested properties
|
|
354
|
+
prop_type = self._json_schema_to_python_type(prop_schema, f'{model_name}_{prop_name}')
|
|
355
|
+
|
|
356
|
+
# Determine if field is required and handle defaults
|
|
357
|
+
if prop_name in required_fields:
|
|
358
|
+
default = ... # Required field
|
|
359
|
+
else:
|
|
360
|
+
# Optional field - make type optional and handle default
|
|
361
|
+
prop_type = prop_type | None
|
|
362
|
+
if 'default' in prop_schema:
|
|
363
|
+
default = prop_schema['default']
|
|
364
|
+
else:
|
|
365
|
+
default = None
|
|
366
|
+
|
|
367
|
+
# Add field with description if available
|
|
368
|
+
field_kwargs = {}
|
|
369
|
+
if 'description' in prop_schema:
|
|
370
|
+
field_kwargs['description'] = prop_schema['description']
|
|
371
|
+
|
|
372
|
+
nested_fields[prop_name] = (prop_type, Field(default, **field_kwargs))
|
|
373
|
+
|
|
374
|
+
# Create a BaseModel class with proper configuration
|
|
375
|
+
class ConfiguredBaseModel(BaseModel):
|
|
376
|
+
model_config = ConfigDict(extra='forbid', validate_by_name=True, validate_by_alias=True)
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
# Create and return nested pydantic model
|
|
380
|
+
return create_model(model_name, __base__=ConfiguredBaseModel, **nested_fields)
|
|
381
|
+
except Exception as e:
|
|
382
|
+
logger.error(f'Failed to create nested model {model_name}: {e}')
|
|
383
|
+
logger.debug(f'Fields: {nested_fields}')
|
|
384
|
+
# Fallback to basic dict if model creation fails
|
|
385
|
+
return dict
|
|
386
|
+
else:
|
|
387
|
+
# Object without properties - just return dict
|
|
388
|
+
return dict
|
|
389
|
+
|
|
390
|
+
# Handle arrays with specific item types
|
|
391
|
+
if json_type == 'array':
|
|
392
|
+
if 'items' in schema:
|
|
393
|
+
# Get the item type recursively
|
|
394
|
+
item_type = self._json_schema_to_python_type(schema['items'], f'{model_name}_item')
|
|
395
|
+
# Return properly typed list
|
|
396
|
+
return list[item_type]
|
|
397
|
+
else:
|
|
398
|
+
# Array without item type specification
|
|
399
|
+
return list
|
|
400
|
+
|
|
401
|
+
# Get base type for non-object types
|
|
402
|
+
base_type = type_mapping.get(json_type, str)
|
|
403
|
+
|
|
404
|
+
# Handle nullable/optional types
|
|
405
|
+
if schema.get('nullable', False) or json_type == 'null':
|
|
406
|
+
return base_type | None
|
|
407
|
+
|
|
408
|
+
return base_type
|
|
409
|
+
|
|
410
|
+
def unregister_all_tools(self, tools):
|
|
411
|
+
"""Unregister all Composio tools from the registry"""
|
|
412
|
+
try:
|
|
413
|
+
# Get all registered actions
|
|
414
|
+
actions_to_remove = []
|
|
415
|
+
for action_name in list(tools.registry.registry.actions.keys()):
|
|
416
|
+
if action_name.startswith('cpo.'):
|
|
417
|
+
actions_to_remove.append(action_name)
|
|
418
|
+
|
|
419
|
+
# Remove Composio actions from registry
|
|
420
|
+
for action_name in actions_to_remove:
|
|
421
|
+
if action_name in tools.registry.registry.actions:
|
|
422
|
+
del tools.registry.registry.actions[action_name]
|
|
423
|
+
logger.debug(f'Removed Composio action: {action_name}')
|
|
424
|
+
|
|
425
|
+
# Clear the registered actions set
|
|
426
|
+
self._registered_actions.clear()
|
|
427
|
+
self._toolkit_tools.clear()
|
|
428
|
+
|
|
429
|
+
logger.info(f"Unregistered {len(actions_to_remove)} Composio actions")
|
|
430
|
+
|
|
431
|
+
# Capture telemetry for unregistration
|
|
432
|
+
self._telemetry.capture(
|
|
433
|
+
ComposioTelemetryEvent(
|
|
434
|
+
toolkit_slugs=list(self._toolkit_tools.keys()),
|
|
435
|
+
tools_registered=0, # All tools unregistered
|
|
436
|
+
version=get_vibesurf_version(),
|
|
437
|
+
action='unregister'
|
|
438
|
+
)
|
|
439
|
+
)
|
|
440
|
+
self._telemetry.flush()
|
|
441
|
+
|
|
442
|
+
except Exception as e:
|
|
443
|
+
error_msg = str(e)
|
|
444
|
+
logger.error(f'Failed to unregister Composio actions: {error_msg}')
|
|
445
|
+
|
|
446
|
+
# Capture telemetry for unregistration error
|
|
447
|
+
self._telemetry.capture(
|
|
448
|
+
ComposioTelemetryEvent(
|
|
449
|
+
toolkit_slugs=list(self._toolkit_tools.keys()),
|
|
450
|
+
tools_registered=len(self._registered_actions),
|
|
451
|
+
version=get_vibesurf_version(),
|
|
452
|
+
action='unregister',
|
|
453
|
+
error_message=error_msg
|
|
454
|
+
)
|
|
455
|
+
)
|
|
456
|
+
self._telemetry.flush()
|
vibe_surf/tools/mcp_client.py
CHANGED
|
@@ -3,18 +3,37 @@ import logging
|
|
|
3
3
|
import time
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
|
-
from browser_use.telemetry import MCPClientTelemetryEvent
|
|
6
|
+
from browser_use.telemetry import MCPClientTelemetryEvent
|
|
7
7
|
from browser_use.utils import get_browser_use_version
|
|
8
8
|
from browser_use.mcp.client import MCPClient
|
|
9
9
|
from mcp import ClientSession, StdioServerParameters, types
|
|
10
10
|
from mcp.client.stdio import stdio_client
|
|
11
|
-
|
|
11
|
+
from vibe_surf.telemetry.service import ProductTelemetry
|
|
12
12
|
from vibe_surf.logger import get_logger
|
|
13
13
|
|
|
14
14
|
logger = get_logger(__name__)
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class CustomMCPClient(MCPClient):
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
server_name: str,
|
|
21
|
+
command: str,
|
|
22
|
+
args: list[str] | None = None,
|
|
23
|
+
env: dict[str, str] | None = None,
|
|
24
|
+
):
|
|
25
|
+
"""Initialize MCP client.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
server_name: Name of the MCP server (for logging and identification)
|
|
29
|
+
command: Command to start the MCP server (e.g., "npx", "python")
|
|
30
|
+
args: Arguments for the command (e.g., ["@playwright/mcp@latest"])
|
|
31
|
+
env: Environment variables for the server process
|
|
32
|
+
"""
|
|
33
|
+
super().__init__(server_name=server_name, command=command, args=args, env=env)
|
|
34
|
+
|
|
35
|
+
self._telemetry = ProductTelemetry()
|
|
36
|
+
|
|
18
37
|
async def connect(self, timeout: int = 200) -> None:
|
|
19
38
|
"""Connect to the MCP server and discover available tools."""
|
|
20
39
|
if self._connected:
|