From 07c088a9320492d2011bba19117f53076220e773 Mon Sep 17 00:00:00 2001 From: Saumya Date: Thu, 7 Aug 2025 16:05:33 +0530 Subject: [PATCH] feat: fix agent performance, bugs, ui --- .../credential_profile_tool.py | 10 ++- .../thread/content/composio-url-detector.tsx | 80 +++++++++++++++---- 2 files changed, 74 insertions(+), 16 deletions(-) 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 e4dc5f84..4eacfefc 100644 --- a/backend/agent/tools/agent_builder_tools/credential_profile_tool.py +++ b/backend/agent/tools/agent_builder_tools/credential_profile_tool.py @@ -140,7 +140,15 @@ class CredentialProfileTool(AgentBuilderBaseTool): if result.connected_account.redirect_url: response_data["connection_link"] = result.connected_account.redirect_url - response_data["instructions"] = f"🔗 **IMPORTANT: Please visit the connection link to authenticate your {result.toolkit.name} account with this profile. After connecting, you'll be able to use {result.toolkit.name} tools in your agent.**" + # Include both the toolkit name and slug in a parseable format + # Format: [toolkit:slug:name] to help frontend identify the service accurately + response_data["instructions"] = f"""🔗 **{result.toolkit.name} Authentication Required** + +Please authenticate your {result.toolkit.name} account by clicking the link below: + +[toolkit:{toolkit_slug}:{result.toolkit.name}] Authentication: {result.connected_account.redirect_url} + +After connecting, you'll be able to use {result.toolkit.name} tools in your agent.""" else: response_data["instructions"] = f"This {result.toolkit.name} profile has been created and is ready to use." diff --git a/frontend/src/components/thread/content/composio-url-detector.tsx b/frontend/src/components/thread/content/composio-url-detector.tsx index b7a711ac..a64fe1fb 100644 --- a/frontend/src/components/thread/content/composio-url-detector.tsx +++ b/frontend/src/components/thread/content/composio-url-detector.tsx @@ -87,34 +87,67 @@ function formatToolkitName(toolkitSlug: string): string { } function extractToolkitInfoFromContext(content: string, urlStartIndex: number): { toolkitName: string | null; toolkitSlug: string | null } { - // Look for toolkit information in the surrounding context const contextBefore = content.substring(Math.max(0, urlStartIndex - 500), urlStartIndex); const contextAfter = content.substring(urlStartIndex, Math.min(content.length, urlStartIndex + 200)); const fullContext = contextBefore + contextAfter; + let match = contextBefore.match(/\[toolkit:([^:]+):([^\]]+)\]\s+Authentication:\s*$/i); + if (match) { + const toolkitSlug = match[1].trim(); + const toolkitName = match[2].trim(); + return { toolkitName, toolkitSlug }; + } - // Try to extract toolkit name from various patterns + match = contextBefore.match(/([A-Za-z]+(?:\s+[A-Za-z]+)*)\s+Authentication:\s*$/i); + if (match) { + const serviceName = match[1].trim(); + const slug = serviceName.toLowerCase().replace(/\s+/g, '_'); + return { toolkitName: serviceName, toolkitSlug: slug }; + } - // Pattern 1: "Successfully created credential profile 'ProfileName' for ToolkitName" - let match = fullContext.match(/Successfully created credential profile[^f]*for\s+([^.!?\n]+)/i); + match = contextBefore.match(/\d+\.\s*([A-Za-z]+)\s+Authentication(?:\s+\([^)]*\))?\s*:?\s*$/i); + if (match) { + const serviceName = match[1].trim(); + const slug = serviceName.toLowerCase().replace(/\s+/g, '_'); + return { toolkitName: serviceName, toolkitSlug: slug }; + } + + match = contextBefore.match(/([A-Za-z]+)\s+Authentication\s+\(for[^)]*\)\s*:?\s*$/i); + if (match) { + const serviceName = match[1].trim(); + const slug = serviceName.toLowerCase().replace(/\s+/g, '_'); + return { toolkitName: serviceName, toolkitSlug: slug }; + } + + match = fullContext.match(/Successfully created credential profile[^f]*for\s+([^.!?\n]+)/i); if (match) { return { toolkitName: match[1].trim(), toolkitSlug: match[1].toLowerCase().replace(/\s+/g, '_') }; } - // Pattern 2: "connect your ToolkitName account" match = fullContext.match(/connect your\s+([^a]+)\s+account/i); if (match) { const name = match[1].trim(); return { toolkitName: name, toolkitSlug: name.toLowerCase().replace(/\s+/g, '_') }; } - // Pattern 3: "authorize access to your ToolkitName account" match = fullContext.match(/authorize access to your\s+([^a]+)\s+account/i); if (match) { const name = match[1].trim(); return { toolkitName: name, toolkitSlug: name.toLowerCase().replace(/\s+/g, '_') }; } - // Pattern 4: Look for common toolkit names in the context + match = fullContext.match(/Sign in to\s+([^.!?\n]+)/i); + if (match) { + const name = match[1].trim(); + return { toolkitName: name, toolkitSlug: name.toLowerCase().replace(/\s+/g, '_') }; + } + + match = contextBefore.match(/([A-Za-z]+)\s+authentication\s*(?:link|url)?:?\s*$/i); + if (match) { + const serviceName = match[1].trim(); + const slug = serviceName.toLowerCase().replace(/\s+/g, '_'); + return { toolkitName: serviceName, toolkitSlug: slug }; + } + const commonToolkits = Object.keys(TOOLKIT_NAME_MAPPINGS); for (const toolkit of commonToolkits) { const toolkitName = TOOLKIT_NAME_MAPPINGS[toolkit]; @@ -127,26 +160,37 @@ function extractToolkitInfoFromContext(content: string, urlStartIndex: number): } function detectComposioUrls(content: string): ComposioUrl[] { - // Detect Composio authentication URLs (these are typically OAuth URLs from various providers) const authUrlPatterns = [ - // Google OAuth /https:\/\/accounts\.google\.com\/oauth\/authorize\?[^\s)]+/g, - // GitHub OAuth + /https:\/\/accounts\.google\.com\/o\/oauth2\/[^\s)]+/g, /https:\/\/github\.com\/login\/oauth\/authorize\?[^\s)]+/g, - // Generic OAuth pattern for other providers + /https:\/\/api\.notion\.com\/v1\/oauth\/authorize\?[^\s)]+/g, + /https:\/\/slack\.com\/oauth\/[^\s)]+/g, + /https:\/\/[^\/\s]+\.slack\.com\/oauth\/[^\s)]+/g, + /https:\/\/login\.microsoftonline\.com\/[^\s)]+/g, /https:\/\/[^\/\s]+\/oauth2?\/authorize\?[^\s)]+/g, - // Composio backend URLs /https:\/\/backend\.composio\.dev\/[^\s)]+/g, - // Any HTTPS URL that looks like an auth callback /https:\/\/[^\/\s]+\/auth\/[^\s)]+/g, + /https:\/\/[^\/\s]+\/authorize\?[^\s)]+/g, + /https:\/\/[^\/\s]+\/connect\/[^\s)]+/g, + /https:\/\/[^\s)]+[?&](client_id|redirect_uri|response_type|scope)=[^\s)]+/g, ]; const urls: ComposioUrl[] = []; + const processedUrls = new Set(); // To avoid duplicates for (const pattern of authUrlPatterns) { let match; + pattern.lastIndex = 0; // Reset regex state while ((match = pattern.exec(content)) !== null) { const url = match[0]; + + // Skip if we've already processed this URL + if (processedUrls.has(url)) { + continue; + } + + processedUrls.add(url); const { toolkitName, toolkitSlug } = extractToolkitInfoFromContext(content, match.index); urls.push({ @@ -164,7 +208,8 @@ function detectComposioUrls(content: string): ComposioUrl[] { function hasAuthUrlPattern(content: string, url: ComposioUrl): boolean { const beforeUrl = content.substring(Math.max(0, url.startIndex - 100), url.startIndex); - return /(?:authentication|auth|connect|visit)\s+(?:url|link):\s*$/i.test(beforeUrl); + // Updated pattern to also match [toolkit:slug:name] Authentication: format + return /(?:(?:\[toolkit:[^:]+:[^\]]+\]|[A-Za-z]+(?:\s+[A-Za-z]+)*)\s+)?(?:authentication|auth|connect|visit)\s+(?:url|link)?:\s*$/i.test(beforeUrl); } interface ComposioConnectButtonProps { @@ -259,7 +304,12 @@ export const ComposioUrlDetector: React.FC = ({ const textBefore = content.substring(lastIndex, composioUrl.startIndex); const cleanedTextBefore = hasAuthUrlPattern(content, composioUrl) - ? textBefore.replace(/(?:authentication|auth|connect|visit)\s+(?:url|link):\s*$/i, '').trim() + ? textBefore + // Remove [toolkit:slug:name] pattern + .replace(/\[toolkit:[^:]+:[^\]]+\]\s+/gi, '') + // Remove authentication/auth/connect/visit url/link patterns + .replace(/(?:authentication|auth|connect|visit)\s+(?:url|link)?:\s*$/i, '') + .trim() : textBefore; if (cleanedTextBefore.trim()) {