diff --git a/backend/agent/tools/agent_builder_tools/agent_config_tool.py b/backend/agent/tools/agent_builder_tools/agent_config_tool.py index d6c7d5f3..6448f0fa 100644 --- a/backend/agent/tools/agent_builder_tools/agent_config_tool.py +++ b/backend/agent/tools/agent_builder_tools/agent_config_tool.py @@ -145,7 +145,20 @@ class AgentConfigTool(AgentBuilderBaseTool): current_system_prompt = system_prompt if system_prompt is not None else current_agent.get('system_prompt', '') current_agentpress_tools = update_data.get('agentpress_tools', current_agent.get('agentpress_tools', {})) current_configured_mcps = configured_mcps if configured_mcps is not None else current_agent.get('configured_mcps', []) - current_custom_mcps = current_agent.get('custom_mcps', []) # Preserve custom MCPs + + raw_custom_mcps = current_agent.get('custom_mcps', []) + import re + sanitized_custom_mcps = [] + for mcp in raw_custom_mcps: + headers = mcp.get('config', {}).get('headers', {}) + slug_val = headers.get('x-pd-app-slug') + if isinstance(slug_val, str): + match = re.match(r"AppSlug\(value='(.+)'\)", slug_val) + if match: + headers['x-pd-app-slug'] = match.group(1) + sanitized_custom_mcps.append(mcp) + current_custom_mcps = sanitized_custom_mcps + current_avatar = avatar if avatar is not None else current_agent.get('avatar') current_avatar_color = avatar_color if avatar_color is not None else current_agent.get('avatar_color') diff --git a/backend/agent/tools/agent_builder_tools/credential_profile_tool.py b/backend/agent/tools/agent_builder_tools/credential_profile_tool.py index df8a03e7..ef42ace7 100644 --- a/backend/agent/tools/agent_builder_tools/credential_profile_tool.py +++ b/backend/agent/tools/agent_builder_tools/credential_profile_tool.py @@ -45,22 +45,19 @@ class CredentialProfileTool(AgentBuilderBaseTool): ''' ) async def get_credential_profiles(self, app_slug: Optional[str] = None) -> ToolResult: - """Get all existing credential profiles for the current user.""" try: account_id = await self._get_current_account_id() - profile_manager = get_profile_manager(self.db) - - profiles = await profile_manager.get_profiles(account_id, app_slug) + profiles = await self.pipedream_manager.get_profiles(account_id, app_slug) formatted_profiles = [] for profile in profiles: formatted_profiles.append({ "profile_id": str(profile.profile_id), - "profile_name": profile.profile_name, + "profile_name": profile.profile_name.value if hasattr(profile.profile_name, 'value') else str(profile.profile_name), "display_name": profile.display_name, - "app_slug": profile.app_slug, + "app_slug": profile.app_slug.value if hasattr(profile.app_slug, 'value') else str(profile.app_slug), "app_name": profile.app_name, - "external_user_id": profile.external_user_id, + "external_user_id": profile.external_user_id.value if hasattr(profile.external_user_id, 'value') else str(profile.external_user_id), "is_connected": profile.is_connected, "is_active": profile.is_active, "is_default": profile.is_default, @@ -121,40 +118,36 @@ class CredentialProfileTool(AgentBuilderBaseTool): ''' ) async def create_credential_profile( - self, - app_slug: str, - profile_name: str, + self, + app_slug: str, + profile_name: str, display_name: Optional[str] = None ) -> ToolResult: try: account_id = await self._get_current_account_id() - profile_manager = get_profile_manager(self.db) - - app_result = await self.pipedream_search.get_app_details(app_slug) - if not app_result["success"]: - return self.fail_response(f"Could not find app details for '{app_slug}': {app_result.get('error', 'Unknown error')}") - - app_data = app_result["app"] - - account_id = await self._get_current_account_id() + # fetch app domain object directly + app_obj = await self.pipedream_manager.get_app_by_slug(app_slug) + if not app_obj: + return self.fail_response(f"Could not find app for slug '{app_slug}'") + # create credential profile using the app name profile = await self.pipedream_manager.create_profile( account_id=account_id, profile_name=profile_name, app_slug=app_slug, - app_name=app_data.get("name", app_slug), + app_name=app_obj.name, description=display_name or profile_name, enabled_tools=[] ) return self.success_response({ - "message": f"Successfully created credential profile '{profile_name}' for {app_data.get('name', app_slug)}", + "message": f"Successfully created credential profile '{profile_name}' for {app_obj.name}", "profile": { "profile_id": str(profile.profile_id), - "profile_name": profile.profile_name, + "profile_name": profile.profile_name.value if hasattr(profile.profile_name, 'value') else str(profile.profile_name), "display_name": profile.display_name, - "app_slug": profile.app_slug, + "app_slug": profile.app_slug.value if hasattr(profile.app_slug, 'value') else str(profile.app_slug), "app_name": profile.app_name, - "external_user_id": profile.external_user_id, + "external_user_id": profile.external_user_id.value if hasattr(profile.external_user_id, 'value') else str(profile.external_user_id), "is_connected": profile.is_connected, "created_at": profile.created_at.isoformat() } @@ -196,20 +189,23 @@ class CredentialProfileTool(AgentBuilderBaseTool): async def connect_credential_profile(self, profile_id: str) -> ToolResult: try: account_id = await self._get_current_account_id() - profile_manager = get_profile_manager(self.db) - profile = await profile_manager.get_profile(account_id, profile_id) + profile = await self.pipedream_manager.get_profile(account_id, profile_id) if not profile: return self.fail_response("Credential profile not found") - connection_result = await profile_manager.connect_profile(account_id, profile_id, profile.app_slug) + # generate connection token using primitive values + connection_result = await self.pipedream_manager.create_connection_token( + profile.external_user_id.value if hasattr(profile.external_user_id, 'value') else str(profile.external_user_id), + profile.app_slug.value if hasattr(profile.app_slug, 'value') else str(profile.app_slug) + ) return self.success_response({ "message": f"Generated connection link for '{profile.display_name}'", "profile_name": profile.display_name, "app_name": profile.app_name, - "connection_link": connection_result.get("link"), - "external_user_id": profile.external_user_id, + "connection_link": connection_result.get("connect_link_url"), + "external_user_id": profile.external_user_id.value if hasattr(profile.external_user_id, 'value') else str(profile.external_user_id), "expires_at": connection_result.get("expires_at"), "instructions": f"Please visit the connection link to connect your {profile.app_name} account to this profile. After connecting, you'll be able to use {profile.app_name} tools in your agent." }) @@ -250,19 +246,31 @@ class CredentialProfileTool(AgentBuilderBaseTool): async def check_profile_connection(self, profile_id: str) -> ToolResult: try: account_id = await self._get_current_account_id() - profile_manager = get_profile_manager(self.db) - profile = await profile_manager.get_profile(account_id, profile_id) + profile = await self.pipedream_manager.get_profile(account_id, profile_id) if not profile: return self.fail_response("Credential profile not found") - connections = await profile_manager.get_profile_connections(account_id, profile_id) + # fetch and serialize connection objects + raw_connections = await self.pipedream_manager.get_connections( + profile.external_user_id.value if hasattr(profile.external_user_id, 'value') else str(profile.external_user_id) + ) + connections = [] + for conn in raw_connections: + connections.append({ + "external_user_id": conn.external_user_id.value if hasattr(conn.external_user_id, 'value') else str(conn.external_user_id), + "app_slug": conn.app.slug.value if hasattr(conn.app.slug, 'value') else str(conn.app.slug), + "app_name": conn.app.name, + "created_at": conn.created_at.isoformat() if conn.created_at else None, + "updated_at": conn.updated_at.isoformat() if conn.updated_at else None, + "is_active": conn.is_active + }) response_data = { "profile_name": profile.display_name, "app_name": profile.app_name, - "app_slug": profile.app_slug, - "external_user_id": profile.external_user_id, + "app_slug": profile.app_slug.value if hasattr(profile.app_slug, 'value') else str(profile.app_slug), + "external_user_id": profile.external_user_id.value if hasattr(profile.external_user_id, 'value') else str(profile.external_user_id), "is_connected": profile.is_connected, "connections": connections, "connection_count": len(connections) @@ -270,23 +278,21 @@ class CredentialProfileTool(AgentBuilderBaseTool): if profile.is_connected and connections: try: - mcp_result = await self.pipedream_search.discover_user_mcp_servers( - user_id=profile.external_user_id, - app_slug=profile.app_slug + # directly discover MCP servers via the facade + from pipedream.domain.entities import ConnectionStatus + servers = await self.pipedream_manager.discover_mcp_servers( + external_user_id=profile.external_user_id.value if hasattr(profile.external_user_id, 'value') else str(profile.external_user_id), + app_slug=profile.app_slug.value if hasattr(profile.app_slug, 'value') else str(profile.app_slug) ) - - if mcp_result["success"]: - connected_servers = [s for s in mcp_result["servers"] if s["status"] == "connected"] - if connected_servers: - tools = connected_servers[0].get("available_tools", []) - response_data["available_tools"] = tools - response_data["tool_count"] = len(tools) - response_data["message"] = f"Profile '{profile.display_name}' is connected with {len(tools)} available tools" - else: - response_data["message"] = f"Profile '{profile.display_name}' is connected but no MCP tools are available yet" + # filter connected servers + connected_servers = [s for s in servers if s.status == ConnectionStatus.CONNECTED] + if connected_servers: + tools = [t.name for t in connected_servers[0].available_tools] + response_data["available_tools"] = tools + response_data["tool_count"] = len(tools) + response_data["message"] = f"Profile '{profile.display_name}' is connected with {len(tools)} available tools" else: - response_data["message"] = f"Profile '{profile.display_name}' is connected but could not retrieve MCP tools" - + response_data["message"] = f"Profile '{profile.display_name}' is connected but no MCP tools are available yet" except Exception as mcp_error: logger.error(f"Error getting MCP tools for profile: {mcp_error}") response_data["message"] = f"Profile '{profile.display_name}' is connected but could not retrieve MCP tools" @@ -349,61 +355,29 @@ class CredentialProfileTool(AgentBuilderBaseTool): ) -> ToolResult: try: account_id = await self._get_current_account_id() - profile_manager = get_profile_manager(self.db) - client = await self.db.client - - profile = await profile_manager.get_profile(account_id, profile_id) + + profile = await self.pipedream_manager.get_profile(account_id, profile_id) if not profile: return self.fail_response("Credential profile not found") - if not profile.is_connected: return self.fail_response("Profile is not connected yet. Please connect the profile first.") - - agent_result = await client.table('agents').select('custom_mcps').eq('agent_id', self.agent_id).execute() - if not agent_result.data: - return self.fail_response("Agent not found") - - current_custom_mcps = agent_result.data[0].get('custom_mcps', []) - - custom_mcp_config = { - "name": display_name or f"{profile.app_name} ({profile.profile_name})", - "customType": "pipedream", - "type": "pipedream", - "config": { - "app_slug": profile.app_slug, - "profile_id": str(profile.profile_id) - }, - "enabledTools": enabled_tools, - "instructions": f"Use this to interact with {profile.app_name} via the {profile.profile_name} profile." - } - - existing_index = None - for i, mcp in enumerate(current_custom_mcps): - if mcp.get('config', {}).get('profile_id') == str(profile.profile_id): - existing_index = i - break - - if existing_index is not None: - current_custom_mcps[existing_index] = custom_mcp_config - action = "updated" - else: - current_custom_mcps.append(custom_mcp_config) - action = "added" - - update_result = await client.table('agents').update({ - 'custom_mcps': current_custom_mcps - }).eq('agent_id', self.agent_id).execute() - - if not update_result.data: - return self.fail_response("Failed to save agent configuration") - + + result = await self.pipedream_manager.update_agent_profile_tools( + self.agent_id, + profile_id, + account_id, + enabled_tools + ) + if not result.get("success", False): + return self.fail_response("Failed to update agent profile tools") + + version_msg = f"Profile '{profile.profile_name.value if hasattr(profile.profile_name, 'value') else str(profile.profile_name)}' updated with {len(enabled_tools)} tools" return self.success_response({ - "message": f"Successfully {action} {profile.app_name} profile '{profile.profile_name}' with {len(enabled_tools)} tools", - "profile_name": profile.profile_name, - "app_name": profile.app_name, - "enabled_tools": enabled_tools, - "total_custom_mcps": len(current_custom_mcps), - "action": action + "message": version_msg, + "enabled_tools": result.get("enabled_tools", []), + "total_tools": result.get("total_tools", 0), + "version_id": result.get("version_id"), + "version_name": result.get("version_name") }) except Exception as e: @@ -442,10 +416,9 @@ class CredentialProfileTool(AgentBuilderBaseTool): async def delete_credential_profile(self, profile_id: str) -> ToolResult: try: account_id = await self._get_current_account_id() - profile_manager = get_profile_manager(self.db) client = await self.db.client - profile = await profile_manager.get_profile(account_id, profile_id) + profile = await self.pipedream_manager.get_profile(account_id, profile_id) if not profile: return self.fail_response("Credential profile not found") @@ -459,7 +432,7 @@ class CredentialProfileTool(AgentBuilderBaseTool): 'custom_mcps': updated_mcps }).eq('agent_id', self.agent_id).execute() - await profile_manager.delete_profile(account_id, profile_id) + await self.pipedream_manager.delete_profile(account_id, profile_id) return self.success_response({ "message": f"Successfully deleted credential profile '{profile.display_name}' for {profile.app_name}", diff --git a/backend/agent/tools/agent_builder_tools/mcp_search_tool.py b/backend/agent/tools/agent_builder_tools/mcp_search_tool.py index ab21296a..d7824596 100644 --- a/backend/agent/tools/agent_builder_tools/mcp_search_tool.py +++ b/backend/agent/tools/agent_builder_tools/mcp_search_tool.py @@ -25,10 +25,6 @@ class MCPSearchTool(AgentBuilderBaseTool): "type": "string", "description": "Search query for finding relevant Pipedream apps (e.g., 'linear', 'github', 'database', 'search')" }, - "category": { - "type": "string", - "description": "Optional category filter for Pipedream apps" - }, "limit": { "type": "integer", "description": "Maximum number of apps to return (default: 10)", @@ -43,7 +39,6 @@ class MCPSearchTool(AgentBuilderBaseTool): tag_name="search-mcp-servers", mappings=[ {"param_name": "query", "node_type": "attribute", "path": "."}, - {"param_name": "category", "node_type": "attribute", "path": "."}, {"param_name": "limit", "node_type": "attribute", "path": "."} ], example=''' @@ -73,30 +68,16 @@ class MCPSearchTool(AgentBuilderBaseTool): formatted_apps = [] for app in apps: - if hasattr(app, '__dict__'): - formatted_apps.append({ - "name": app.name, - "app_slug": app.app_slug.value if hasattr(app.app_slug, 'value') else str(app.app_slug), - "description": app.description, - "category": app.categories[0] if app.categories else "Other", - "logo_url": getattr(app, 'logo_url', ''), - "auth_type": app.auth_type.value if app.auth_type else '', - "is_verified": getattr(app, 'is_verified', False), - "url": getattr(app, 'url', ''), - "tags": getattr(app, 'tags', []) - }) - else: - formatted_apps.append({ - "name": app.get("name", "Unknown"), - "app_slug": app.get("app_slug", ""), - "description": app.get("description", "No description available"), - "category": app.get("category", "Other"), - "logo_url": app.get("logo_url", ""), - "auth_type": app.get("auth_type", ""), - "is_verified": app.get("is_verified", False), - "url": app.get("url", ""), - "tags": app.get("tags", []) - }) + formatted_apps.append({ + "name": app.name, + "app_slug": app.slug.value if hasattr(app.slug, 'value') else str(app.slug), + "description": app.description, + "logo_url": getattr(app, 'logo_url', ''), + "auth_type": app.auth_type.value if app.auth_type else '', + "is_verified": getattr(app, 'is_verified', False), + "url": getattr(app, 'url', ''), + "tags": getattr(app, 'tags', []) + }) if not formatted_apps: return ToolResult( @@ -149,37 +130,19 @@ class MCPSearchTool(AgentBuilderBaseTool): if not app_data: return self.fail_response(f"Could not find app details for '{app_slug}'") - if hasattr(app_data, '__dict__'): - app_data = { - "name": app_data.name, - "app_slug": app_data.app_slug.value, - "description": app_data.description, - "category": app_data.categories[0] if app_data.categories else "Other", - "logo_url": getattr(app_data, 'logo_url', ''), - "auth_type": app_data.auth_type.value if app_data.auth_type else '', - "is_verified": getattr(app_data, 'is_verified', False), - "url": getattr(app_data, 'url', ''), - "tags": getattr(app_data, 'tags', []), - "pricing": getattr(app_data, 'pricing', ''), - "setup_instructions": getattr(app_data, 'setup_instructions', ''), - "available_actions": getattr(app_data, 'available_actions', []), - "available_triggers": getattr(app_data, 'available_triggers', []) - } - formatted_app = { - "name": app_data.get("name", "Unknown"), - "app_slug": app_data.get("app_slug", app_slug), - "description": app_data.get("description", "No description available"), - "category": app_data.get("category", "Other"), - "logo_url": app_data.get("logo_url", ""), - "auth_type": app_data.get("auth_type", ""), - "is_verified": app_data.get("is_verified", False), - "url": app_data.get("url", ""), - "tags": app_data.get("tags", []), - "pricing": app_data.get("pricing", ""), - "setup_instructions": app_data.get("setup_instructions", ""), - "available_actions": app_data.get("available_actions", []), - "available_triggers": app_data.get("available_triggers", []) + "name": app_data.name, + "app_slug": app_data.slug.value if hasattr(app_data.slug, 'value') else str(app_data.slug), + "description": app_data.description, + "logo_url": getattr(app_data, 'logo_url', ''), + "auth_type": app_data.auth_type.value if app_data.auth_type else '', + "is_verified": getattr(app_data, 'is_verified', False), + "url": getattr(app_data, 'url', ''), + "tags": getattr(app_data, 'tags', []), + "pricing": getattr(app_data, 'pricing', ''), + "setup_instructions": getattr(app_data, 'setup_instructions', ''), + "available_actions": getattr(app_data, 'available_actions', []), + "available_triggers": getattr(app_data, 'available_triggers', []) } return self.success_response({ @@ -235,26 +198,15 @@ class MCPSearchTool(AgentBuilderBaseTool): formatted_servers = [] for server in servers: - if hasattr(server, '__dict__'): - formatted_servers.append({ - "server_id": getattr(server, 'server_id', ''), - "name": getattr(server, 'name', 'Unknown'), - "app_slug": getattr(server, 'app_slug', app_slug), - "status": getattr(server, 'status', 'unknown'), - "available_tools": getattr(server, 'available_tools', []), - "last_ping": getattr(server, 'last_ping', ''), - "created_at": getattr(server, 'created_at', '') - }) - else: - formatted_servers.append({ - "server_id": server.get("server_id", ""), - "name": server.get("name", "Unknown"), - "app_slug": server.get("app_slug", app_slug), - "status": server.get("status", "unknown"), - "available_tools": server.get("available_tools", []), - "last_ping": server.get("last_ping", ""), - "created_at": server.get("created_at", "") - }) + formatted_servers.append({ + "server_id": getattr(server, 'server_id', ''), + "name": getattr(server, 'name', 'Unknown'), + "app_slug": getattr(server, 'app_slug', app_slug), + "status": getattr(server, 'status', 'unknown'), + "available_tools": getattr(server, 'available_tools', []), + "last_ping": getattr(server, 'last_ping', ''), + "created_at": getattr(server, 'created_at', '') + }) connected_servers = [s for s in formatted_servers if s["status"] == "connected"] total_tools = sum(len(s["available_tools"]) for s in connected_servers) diff --git a/backend/agent/tools/agent_builder_tools/trigger_tool.py b/backend/agent/tools/agent_builder_tools/trigger_tool.py index d376ca8d..75f1d876 100644 --- a/backend/agent/tools/agent_builder_tools/trigger_tool.py +++ b/backend/agent/tools/agent_builder_tools/trigger_tool.py @@ -6,7 +6,7 @@ from .base_tool import AgentBuilderBaseTool from utils.logger import logger from datetime import datetime from services.supabase import DBConnection -from triggers.core import TriggerManager +from triggers.support.factory import TriggerModuleFactory class TriggerTool(AgentBuilderBaseTool): @@ -125,11 +125,10 @@ class TriggerTool(AgentBuilderBaseTool): trigger_config["agent_prompt"] = agent_prompt trigger_db = DBConnection() - trigger_manager = TriggerManager(trigger_db) - await trigger_manager.load_provider_definitions() + trigger_svc, _, _ = await TriggerModuleFactory.create_trigger_module(trigger_db) try: - trigger_config_obj = await trigger_manager.create_trigger( + trigger = await trigger_svc.create_trigger( agent_id=self.agent_id, provider_id="schedule", name=name, @@ -153,13 +152,13 @@ class TriggerTool(AgentBuilderBaseTool): return self.success_response({ "message": result_message, "trigger": { - "id": trigger_config_obj.trigger_id, - "name": trigger_config_obj.name, - "description": trigger_config_obj.description, + "id": trigger.trigger_id, + "name": trigger.config.name, + "description": trigger.config.description, "cron_expression": cron_expression, "execution_type": execution_type, - "is_active": trigger_config_obj.is_active, - "created_at": trigger_config_obj.created_at.isoformat() + "is_active": trigger.config.is_active, + "created_at": trigger.metadata.created_at.isoformat() } }) except ValueError as ve: diff --git a/backend/pipedream/facade.py b/backend/pipedream/facade.py index 81e0b838..3ee52eb8 100644 --- a/backend/pipedream/facade.py +++ b/backend/pipedream/facade.py @@ -338,6 +338,9 @@ class PipedreamManager: import copy db = DBConnection() + + from agent.versioning.infrastructure.dependencies import set_db_connection + set_db_connection(db) client = await db.client agent_result = await client.table('agents').select('*').eq('agent_id', agent_id).eq('account_id', user_id).execute() @@ -391,7 +394,7 @@ class PipedreamManager: "config": { "url": "https://remote.mcp.pipedream.net", "headers": { - "x-pd-app-slug": str(profile.app_slug) + "x-pd-app-slug": profile.app_slug.value }, "profile_id": profile_id }, @@ -399,6 +402,7 @@ class PipedreamManager: } updated_custom_mcps.append(new_mcp_config) + new_version = await version_manager.create_version( agent_id=agent_id, diff --git a/backend/triggers/core.py b/backend/triggers/core.py index b3dd7036..54be0687 100644 --- a/backend/triggers/core.py +++ b/backend/triggers/core.py @@ -259,7 +259,7 @@ class TriggerManager: async def _load_custom_providers(self): client = await self.db.client - result = await client.table('trigger_providers').select('*').execute() + result = await client.table('custom_trigger_providers').select('*').execute() for provider_data in result.data: provider_def = ProviderDefinition(**provider_data)