From d56df553c610a4992cc6761aff5b9cd69387ae41 Mon Sep 17 00:00:00 2001 From: Tushar <47842976+tusharmagar@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:10:43 +0530 Subject: [PATCH] Fix/composio saml auth scheme (#325) * fix: add SAML to Composio auth scheme enum - Add 'SAML' to ZAuthScheme enum in types.ts to support SAML authentication - Add SAML case to getAuthMethodName in ToolkitAuthModal for proper display - Fixes ZodError when Composio API returns toolkits with SAML auth schemes - Resolves 500 error when fetching Composio toolkits * refactor: update composio tool search response handling in copilot --- .../tools/components/ToolkitAuthModal.tsx | 2 + .../src/application/lib/composio/types.ts | 1 + .../src/application/lib/copilot/copilot.ts | 39 ++++++++++++------- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/apps/rowboat/app/projects/[projectId]/tools/components/ToolkitAuthModal.tsx b/apps/rowboat/app/projects/[projectId]/tools/components/ToolkitAuthModal.tsx index d46bc58a..35532a0b 100644 --- a/apps/rowboat/app/projects/[projectId]/tools/components/ToolkitAuthModal.tsx +++ b/apps/rowboat/app/projects/[projectId]/tools/components/ToolkitAuthModal.tsx @@ -312,6 +312,8 @@ export function ToolkitAuthModal({ return 'Bearer Token'; case 'BASIC': return 'Basic Auth'; + case 'SAML': + return 'SAML'; default: return authScheme.toLowerCase().replace('_', ' '); } diff --git a/apps/rowboat/src/application/lib/composio/types.ts b/apps/rowboat/src/application/lib/composio/types.ts index 224696f3..9beba49f 100644 --- a/apps/rowboat/src/application/lib/composio/types.ts +++ b/apps/rowboat/src/application/lib/composio/types.ts @@ -15,6 +15,7 @@ export const ZAuthScheme = z.enum([ 'NO_AUTH', 'OAUTH1', 'OAUTH2', + 'SAML', ]); export const ZConnectedAccountStatus = z.enum([ diff --git a/apps/rowboat/src/application/lib/copilot/copilot.ts b/apps/rowboat/src/application/lib/copilot/copilot.ts index e667b672..c22fdc70 100644 --- a/apps/rowboat/src/application/lib/copilot/copilot.ts +++ b/apps/rowboat/src/application/lib/copilot/copilot.ts @@ -36,16 +36,10 @@ const openai = createOpenAI({ compatibility: "strict", }); -const composioToolSearchToolSuggestion = z.object({ - toolkit: z.string(), - tool_slug: z.string(), - description: z.string(), -}); - const composioToolSearchResponseSchema = z.object({ - main_tools: z.array(composioToolSearchToolSuggestion).optional(), - related_tools: z.array(composioToolSearchToolSuggestion).optional(), - results: z.array(composioToolSearchToolSuggestion).optional(), // Keep for backward compatibility + results: z.array(z.object({ + primary_tool_slugs: z.array(z.string()).optional(), + }).passthrough()).optional(), }).passthrough(); function getContextPrompt(context: z.infer | null): string { @@ -182,17 +176,19 @@ async function searchRelevantTools(usageTracker: UsageTracker, query: string): P const result = composioToolSearchResponseSchema.safeParse(searchResult.data); if (!result.success) { logger.log(`tool search response is invalid: ${JSON.stringify(result.error)}`); - logger.log(`expected schema: results (array), got: ${JSON.stringify(Object.keys(searchResult.data || {}))}`); return 'No tools found!'; } - const tools = result.data.main_tools || result.data.results || []; - if (!tools.length) { + // Extract tool slugs from results[].primary_tool_slugs[] + const toolSlugs = (result.data.results || []) + .flatMap((item: any) => item.primary_tool_slugs || []) + .filter((slug: string) => slug); + + if (!toolSlugs.length) { logger.log(`tool search yielded no results`); return 'No tools found!'; } - - const toolSlugs = tools.map((item) => item.tool_slug); + logger.log(`found tool slugs: ${toolSlugs.join(', ')}`); console.log("✅ TOOL CALL SUCCESS: COMPOSIO_SEARCH_TOOLS", { toolSlugs, @@ -201,7 +197,20 @@ async function searchRelevantTools(usageTracker: UsageTracker, query: string): P // Enrich tools with full details console.log("🔧 TOOL CALL: getTool (multiple calls)", { toolSlugs }); - const composioTools = await Promise.all(toolSlugs.map(slug => getTool(slug))); + const composioToolsResults = await Promise.allSettled( + toolSlugs.map(slug => getTool(slug)) + ); + + // Filter out failed tool fetches + const composioTools = composioToolsResults + .filter((result): result is PromiseFulfilledResult => result.status === 'fulfilled') + .map(result => result.value); + + if (composioTools.length === 0) { + logger.log('all tool fetches failed'); + return 'No tools found!'; + } + const workflowTools: z.infer[] = composioTools.map(tool => ({ name: tool.name, description: tool.description,