mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-08 06:42:39 +02:00
refactor tools UX: part 2
This commit is contained in:
parent
751a86c34d
commit
2e3a7916e9
40 changed files with 1499 additions and 2261 deletions
|
|
@ -60,7 +60,6 @@ export async function scrapeWebpage(url: string): Promise<z.infer<typeof Webpage
|
|||
export async function getAssistantResponseStreamId(
|
||||
projectId: string,
|
||||
workflow: z.infer<typeof Workflow>,
|
||||
projectTools: z.infer<typeof WorkflowTool>[],
|
||||
messages: z.infer<typeof Message>[],
|
||||
): Promise<{ streamId: string } | { billingError: string }> {
|
||||
await projectAuthCheck(projectId);
|
||||
|
|
@ -83,6 +82,6 @@ export async function getAssistantResponseStreamId(
|
|||
return { billingError: error || 'Billing error' };
|
||||
}
|
||||
|
||||
const response = await getAgenticResponseStreamId(projectId, workflow, projectTools, messages);
|
||||
const response = await getAgenticResponseStreamId(projectId, workflow, messages);
|
||||
return response;
|
||||
}
|
||||
|
|
@ -3,9 +3,6 @@ import { z } from "zod";
|
|||
import {
|
||||
listToolkits as libListToolkits,
|
||||
listTools as libListTools,
|
||||
searchTools as libSearchTools,
|
||||
getToolsByIds as libGetToolsByIds,
|
||||
getTool as libGetTool,
|
||||
getConnectedAccount as libGetConnectedAccount,
|
||||
deleteConnectedAccount as libDeleteConnectedAccount,
|
||||
listAuthConfigs as libListAuthConfigs,
|
||||
|
|
@ -23,7 +20,6 @@ import {
|
|||
ZCredentials,
|
||||
} from "@/app/lib/composio/composio";
|
||||
import { ComposioConnectedAccount } from "@/app/lib/types/project_types";
|
||||
import { WorkflowTool } from "@/app/lib/types/workflow_types";
|
||||
import { getProjectConfig, projectAuthCheck } from "./project_actions";
|
||||
import { projectsCollection } from "../lib/mongodb";
|
||||
|
||||
|
|
@ -46,29 +42,11 @@ export async function getToolkit(projectId: string, toolkitSlug: string): Promis
|
|||
return await libGetToolkit(toolkitSlug);
|
||||
}
|
||||
|
||||
export async function listTools(projectId: string, toolkitSlug: string, cursor: string | null = null): Promise<z.infer<ReturnType<typeof ZListResponse<typeof ZTool>>>> {
|
||||
export async function listTools(projectId: string, toolkitSlug: string, searchQuery: string | null, cursor: string | null = null): Promise<z.infer<ReturnType<typeof ZListResponse<typeof ZTool>>>> {
|
||||
await projectAuthCheck(projectId);
|
||||
return await libListTools(toolkitSlug, cursor);
|
||||
return await libListTools(toolkitSlug, searchQuery, cursor);
|
||||
}
|
||||
|
||||
// New efficient search functions
|
||||
|
||||
export async function searchTools(projectId: string, searchQuery: string, cursor: string | null = null, limit?: number): Promise<z.infer<ReturnType<typeof ZListResponse<typeof ZTool>>>> {
|
||||
await projectAuthCheck(projectId);
|
||||
return await libSearchTools(searchQuery, cursor, limit);
|
||||
}
|
||||
|
||||
export async function getToolsByIds(projectId: string, toolSlugs: string[], cursor: string | null = null): Promise<z.infer<ReturnType<typeof ZListResponse<typeof ZTool>>>> {
|
||||
await projectAuthCheck(projectId);
|
||||
return await libGetToolsByIds(toolSlugs, cursor);
|
||||
}
|
||||
|
||||
export async function getTool(projectId: string, toolSlug: string): Promise<z.infer<typeof ZTool>> {
|
||||
await projectAuthCheck(projectId);
|
||||
return await libGetTool(toolSlug);
|
||||
}
|
||||
|
||||
|
||||
export async function createComposioManagedOauth2ConnectedAccount(projectId: string, toolkitSlug: string, callbackUrl: string): Promise<z.infer<typeof ZCreateConnectedAccountResponse>> {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
|
|
@ -237,196 +215,5 @@ export async function deleteConnectedAccount(projectId: string, toolkitSlug: str
|
|||
const key = `composioConnectedAccounts.${toolkitSlug}`;
|
||||
await projectsCollection.updateOne({ _id: projectId }, { $unset: { [key]: "" } });
|
||||
|
||||
// Notify other tabs about the tools update (lightweight refresh)
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(`tools-light-refresh-${projectId}`, Date.now().toString());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Note: composio tools are now stored in workflow.tools array with isComposio: true
|
||||
// This function provides backward compatibility by updating workflow tools
|
||||
export async function getComposioToolsFromWorkflow(projectId: string): Promise<z.infer<typeof ZTool>[]> {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
// Get the project to access draft workflow
|
||||
const project = await projectsCollection.findOne({ _id: projectId });
|
||||
if (!project || !project.draftWorkflow) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Extract composio tools from workflow and convert back to ZTool format
|
||||
const composioTools = project.draftWorkflow.tools
|
||||
.filter(tool => tool.isComposio && tool.composioData)
|
||||
.map(tool => ({
|
||||
slug: tool.composioData!.slug,
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
no_auth: tool.composioData!.noAuth,
|
||||
input_parameters: {
|
||||
type: 'object' as const,
|
||||
properties: tool.parameters.properties,
|
||||
required: tool.parameters.required || []
|
||||
},
|
||||
toolkit: {
|
||||
name: tool.composioData!.toolkitName,
|
||||
slug: tool.composioData!.toolkitSlug,
|
||||
logo: tool.composioData!.logo,
|
||||
}
|
||||
}));
|
||||
|
||||
return composioTools;
|
||||
}
|
||||
|
||||
export async function updateComposioSelectedTools(projectId: string, tools: z.infer<typeof ZTool>[]): Promise<void> {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
// Get the project to access draft workflow
|
||||
const project = await projectsCollection.findOne({ _id: projectId });
|
||||
if (!project || !project.draftWorkflow) {
|
||||
throw new Error(`Project ${projectId} not found or has no draft workflow`);
|
||||
}
|
||||
|
||||
// Convert Composio tools to workflow tool format
|
||||
const composioWorkflowTools: z.infer<typeof WorkflowTool>[] = tools.map(tool => ({
|
||||
name: tool.slug,
|
||||
description: tool.description || "",
|
||||
parameters: {
|
||||
type: 'object' as const,
|
||||
properties: tool.input_parameters?.properties || {},
|
||||
required: tool.input_parameters?.required || []
|
||||
},
|
||||
isComposio: true,
|
||||
composioData: {
|
||||
slug: tool.slug,
|
||||
noAuth: tool.no_auth,
|
||||
toolkitName: tool.toolkit.name,
|
||||
toolkitSlug: tool.toolkit.slug,
|
||||
logo: tool.toolkit.logo,
|
||||
},
|
||||
}));
|
||||
|
||||
// Remove existing composio tools and add new ones
|
||||
const nonComposioTools = project.draftWorkflow.tools.filter(tool => !tool.isComposio);
|
||||
const updatedWorkflow = {
|
||||
...project.draftWorkflow,
|
||||
tools: [...nonComposioTools, ...composioWorkflowTools],
|
||||
lastUpdatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Update the project's draft workflow
|
||||
const result = await projectsCollection.updateOne(
|
||||
{ _id: projectId },
|
||||
{ $set: { draftWorkflow: updatedWorkflow } }
|
||||
);
|
||||
|
||||
if (result.modifiedCount === 0) {
|
||||
throw new Error(`Failed to update workflow for project ${projectId}`);
|
||||
}
|
||||
|
||||
// Notify other tabs about the tools update (lightweight refresh)
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(`tools-light-refresh-${projectId}`, Date.now().toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Note: composio mock states are now stored in workflow.composioMockToolkitStates
|
||||
// This function provides backward compatibility by updating workflow mock states
|
||||
export async function toggleMockToolkitState(projectId: string, toolkitSlug: string, isMocked: boolean, mockInstructions?: string): Promise<void> {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
// Get the project to access draft workflow
|
||||
const project = await projectsCollection.findOne({ _id: projectId });
|
||||
if (!project || !project.draftWorkflow) {
|
||||
throw new Error(`Project ${projectId} not found or has no draft workflow`);
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
let updatedMockToolkitStates = { ...(project.draftWorkflow.composioMockToolkitStates || {}) };
|
||||
|
||||
if (isMocked) {
|
||||
// Enable mock mode
|
||||
updatedMockToolkitStates[toolkitSlug] = {
|
||||
toolkitSlug,
|
||||
isMocked: true,
|
||||
mockInstructions: mockInstructions || 'Mock responses using GPT-4.1 based on tool descriptions.',
|
||||
autoSubmitMockedResponse: false,
|
||||
createdAt: now,
|
||||
lastUpdatedAt: now,
|
||||
};
|
||||
} else {
|
||||
// Disable mock mode - remove the toolkit from the object
|
||||
delete updatedMockToolkitStates[toolkitSlug];
|
||||
}
|
||||
|
||||
// Update the workflow with new mock states
|
||||
const updatedWorkflow = {
|
||||
...project.draftWorkflow,
|
||||
composioMockToolkitStates: updatedMockToolkitStates,
|
||||
lastUpdatedAt: now
|
||||
};
|
||||
|
||||
// Update the project's draft workflow
|
||||
const result = await projectsCollection.updateOne(
|
||||
{ _id: projectId },
|
||||
{ $set: { draftWorkflow: updatedWorkflow } }
|
||||
);
|
||||
|
||||
if (result.modifiedCount === 0) {
|
||||
throw new Error(`Failed to update workflow mock states for project ${projectId}`);
|
||||
}
|
||||
|
||||
// Notify other tabs about the tools update (lightweight refresh)
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(`tools-light-refresh-${projectId}`, Date.now().toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Note: composio mock states are now stored in workflow.composioMockToolkitStates
|
||||
// This function provides backward compatibility by updating workflow mock states
|
||||
export async function updateMockToolkitInstructions(projectId: string, toolkitSlug: string, mockInstructions: string): Promise<void> {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
// Get the project to access draft workflow
|
||||
const project = await projectsCollection.findOne({ _id: projectId });
|
||||
if (!project || !project.draftWorkflow) {
|
||||
throw new Error(`Project ${projectId} not found or has no draft workflow`);
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
let updatedMockToolkitStates = { ...(project.draftWorkflow.composioMockToolkitStates || {}) };
|
||||
|
||||
// Update the mock instructions for the specified toolkit
|
||||
if (updatedMockToolkitStates[toolkitSlug]) {
|
||||
updatedMockToolkitStates[toolkitSlug] = {
|
||||
...updatedMockToolkitStates[toolkitSlug],
|
||||
mockInstructions,
|
||||
lastUpdatedAt: now
|
||||
};
|
||||
|
||||
// Update the workflow with new mock states
|
||||
const updatedWorkflow = {
|
||||
...project.draftWorkflow,
|
||||
composioMockToolkitStates: updatedMockToolkitStates,
|
||||
lastUpdatedAt: now
|
||||
};
|
||||
|
||||
// Update the project's draft workflow
|
||||
const result = await projectsCollection.updateOne(
|
||||
{ _id: projectId },
|
||||
{ $set: { draftWorkflow: updatedWorkflow } }
|
||||
);
|
||||
|
||||
if (result.modifiedCount === 0) {
|
||||
throw new Error(`Failed to update workflow mock instructions for project ${projectId}`);
|
||||
}
|
||||
|
||||
// Notify other tabs about the tools update
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(`tools-updated-${projectId}`, Date.now().toString());
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Mock toolkit state for ${toolkitSlug} not found in project ${projectId}`);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,8 +11,6 @@ import { check_query_limit } from "../lib/rate_limiting";
|
|||
import { QueryLimitError } from "../lib/client_utils";
|
||||
import { projectAuthCheck } from "./project_actions";
|
||||
import { redisClient } from "../lib/redis";
|
||||
import { collectProjectTools } from "../lib/project_tools";
|
||||
import { mergeProjectTools } from "../lib/types/project_types";
|
||||
import { authorizeUserAction, logUsage } from "./billing_actions";
|
||||
import { USE_BILLING } from "../lib/feature_flags";
|
||||
import { WithStringId } from "../lib/types/types";
|
||||
|
|
@ -44,21 +42,12 @@ export async function getCopilotResponseStream(
|
|||
if (!await check_query_limit(projectId)) {
|
||||
throw new QueryLimitError();
|
||||
}
|
||||
|
||||
// Get MCP tools from project and merge with workflow tools
|
||||
const projectTools = await collectProjectTools(projectId);
|
||||
|
||||
// Convert workflow to copilot format with both workflow and project tools
|
||||
const wflow = {
|
||||
...current_workflow_config,
|
||||
tools: mergeProjectTools(current_workflow_config.tools, projectTools)
|
||||
};
|
||||
|
||||
// prepare request
|
||||
const request: z.infer<typeof CopilotAPIRequest> = {
|
||||
projectId,
|
||||
messages,
|
||||
workflow: wflow,
|
||||
workflow: current_workflow_config,
|
||||
context,
|
||||
dataSources: dataSources,
|
||||
};
|
||||
|
|
@ -97,20 +86,11 @@ export async function getCopilotAgentInstructions(
|
|||
return { billingError: authResponse.error || 'Billing error' };
|
||||
}
|
||||
|
||||
// Get MCP tools from project and merge with workflow tools
|
||||
const projectTools = await collectProjectTools(projectId);
|
||||
|
||||
// Convert workflow to copilot format with both workflow and project tools
|
||||
const wflow = {
|
||||
...current_workflow_config,
|
||||
tools: mergeProjectTools(current_workflow_config.tools, projectTools)
|
||||
};
|
||||
|
||||
// prepare request
|
||||
const request: z.infer<typeof CopilotAPIRequest> = {
|
||||
projectId,
|
||||
messages,
|
||||
workflow: wflow,
|
||||
workflow: current_workflow_config,
|
||||
context: {
|
||||
type: 'agent',
|
||||
name: agentName,
|
||||
|
|
|
|||
67
apps/rowboat/app/actions/custom_mcp_server_actions.ts
Normal file
67
apps/rowboat/app/actions/custom_mcp_server_actions.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
'use server';
|
||||
|
||||
import { projectsCollection } from '../lib/mongodb';
|
||||
import { z } from 'zod';
|
||||
import { projectAuthCheck } from './project_actions';
|
||||
import { CustomMcpServer } from '../lib/types/project_types';
|
||||
import { getMcpClient } from '../lib/mcp';
|
||||
import { WorkflowTool } from '../lib/types/workflow_types';
|
||||
import { authCheck } from './auth_actions';
|
||||
|
||||
type McpServerType = z.infer<typeof CustomMcpServer>;
|
||||
|
||||
function validateUrl(url: string): string {
|
||||
try {
|
||||
const parsedUrl = new URL(url);
|
||||
if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
|
||||
throw new Error('Invalid protocol');
|
||||
}
|
||||
return parsedUrl.toString();
|
||||
} catch (error) {
|
||||
throw new Error('Invalid URL');
|
||||
}
|
||||
}
|
||||
|
||||
export async function addServer(projectId: string, name: string, server: McpServerType): Promise<void> {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
// Validate the server URL
|
||||
validateUrl(server.serverUrl);
|
||||
|
||||
// Update the customMcpServers record with the server
|
||||
await projectsCollection.updateOne(
|
||||
{ _id: projectId },
|
||||
{ $set: { [`customMcpServers.${name}`]: server } }
|
||||
);
|
||||
}
|
||||
|
||||
export async function removeServer(projectId: string, name: string): Promise<void> {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
await projectsCollection.updateOne(
|
||||
{ _id: projectId },
|
||||
{ $unset: { [`customMcpServers.${name}`]: "" } }
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchTools(serverUrl: string, serverName: string): Promise<z.infer<typeof WorkflowTool>[]> {
|
||||
await authCheck();
|
||||
|
||||
const client = await getMcpClient(serverUrl, serverName);
|
||||
const result = await client.listTools();
|
||||
return result.tools.map(tool => {
|
||||
return {
|
||||
name: tool.name,
|
||||
description: tool.description || '',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: tool.inputSchema?.properties || {},
|
||||
required: tool.inputSchema?.required || [],
|
||||
additionalProperties: true,
|
||||
},
|
||||
isMcp: true,
|
||||
mcpServerName: serverName,
|
||||
mcpServerURL: serverUrl,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
'use server';
|
||||
|
||||
import { projectsCollection } from '../lib/mongodb';
|
||||
import { MCPServer } from '../lib/types/types';
|
||||
import { z } from 'zod';
|
||||
import { projectAuthCheck } from './project_actions';
|
||||
|
||||
type McpServerType = z.infer<typeof MCPServer>;
|
||||
|
||||
function formatServerUrl(url: string): string {
|
||||
// Ensure URL starts with http:// or https://
|
||||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||
url = 'http://' + url;
|
||||
}
|
||||
// Remove trailing slash if present
|
||||
return url.replace(/\/$/, '');
|
||||
}
|
||||
|
||||
export async function fetchCustomServers(projectId: string) {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
const project = await projectsCollection.findOne({ _id: projectId });
|
||||
return (project?.mcpServers || [])
|
||||
.filter(server => server.serverType === 'custom')
|
||||
.map(server => ({
|
||||
...server,
|
||||
serverType: 'custom' as const,
|
||||
isReady: true // Custom servers are always ready
|
||||
}));
|
||||
}
|
||||
|
||||
export async function addCustomServer(projectId: string, server: McpServerType) {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
// Format the server URL and ensure isReady is true for custom servers
|
||||
const formattedServer = {
|
||||
...server,
|
||||
serverUrl: formatServerUrl(server.serverUrl || ''),
|
||||
isReady: true // Custom servers are always ready
|
||||
};
|
||||
|
||||
await projectsCollection.updateOne(
|
||||
{ _id: projectId },
|
||||
{ $push: { mcpServers: formattedServer } }
|
||||
);
|
||||
|
||||
return formattedServer;
|
||||
}
|
||||
|
||||
export async function removeCustomServer(projectId: string, serverName: string) {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
await projectsCollection.updateOne(
|
||||
{ _id: projectId },
|
||||
{ $pull: { mcpServers: { name: serverName } } }
|
||||
);
|
||||
}
|
||||
|
||||
export async function toggleCustomServer(projectId: string, serverName: string, isActive: boolean) {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
await projectsCollection.updateOne(
|
||||
{ _id: projectId, "mcpServers.name": serverName },
|
||||
{
|
||||
$set: {
|
||||
"mcpServers.$.isActive": isActive,
|
||||
"mcpServers.$.isReady": isActive // Update isReady along with isActive
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function updateCustomServerTools(
|
||||
projectId: string,
|
||||
serverName: string,
|
||||
tools: McpServerType['tools'],
|
||||
availableTools?: McpServerType['availableTools']
|
||||
) {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
const update: Record<string, any> = {
|
||||
"mcpServers.$.tools": tools
|
||||
};
|
||||
|
||||
if (availableTools) {
|
||||
update["mcpServers.$.availableTools"] = availableTools;
|
||||
}
|
||||
|
||||
await projectsCollection.updateOne(
|
||||
{ _id: projectId, "mcpServers.name": serverName },
|
||||
{ $set: update }
|
||||
);
|
||||
}
|
||||
|
|
@ -14,15 +14,6 @@ import { USE_AUTH } from "../lib/feature_flags";
|
|||
import { deleteMcpServerInstance, listActiveServerInstances } from "./klavis_actions";
|
||||
import { authorizeUserAction } from "./billing_actions";
|
||||
import { Workflow } from "../lib/types/workflow_types";
|
||||
import { WorkflowTool } from "../lib/types/workflow_types";
|
||||
import { collectProjectTools as libCollectProjectTools } from "../lib/project_tools";
|
||||
import {
|
||||
searchTools as libSearchTools,
|
||||
getToolsByIds as libGetToolsByIds,
|
||||
getTool as libGetTool,
|
||||
ZTool,
|
||||
ZToolkit
|
||||
} from "../lib/composio/composio";
|
||||
|
||||
const KLAVIS_API_KEY = process.env.KLAVIS_API_KEY || '';
|
||||
|
||||
|
|
@ -313,113 +304,6 @@ export async function createProjectFromPrompt(formData: FormData): Promise<{ id:
|
|||
return { id: projectId };
|
||||
}
|
||||
|
||||
async function detectAndAddComposioTools(projectId: string, workflow: z.infer<typeof Workflow>) {
|
||||
// Extract tool mentions from agent instructions
|
||||
const toolMentionPattern = /\[@tool:([^\]]+)\]\(#mention[^\)]*\)/g;
|
||||
const mentionedToolNames = new Set<string>();
|
||||
|
||||
// Scan all agent instructions for tool mentions
|
||||
for (const agent of workflow.agents || []) {
|
||||
const instructions = agent.instructions || "";
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = toolMentionPattern.exec(instructions))) {
|
||||
mentionedToolNames.add(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (mentionedToolNames.size === 0) {
|
||||
return; // No tool mentions found
|
||||
}
|
||||
|
||||
console.log(`Found ${mentionedToolNames.size} tool mentions in workflow:`, Array.from(mentionedToolNames));
|
||||
|
||||
// Search for these tools in Composio using the new efficient search methods
|
||||
const foundTools: z.infer<typeof ZTool>[] = [];
|
||||
|
||||
try {
|
||||
// Method 1: Try to get tools directly by their exact slugs/names
|
||||
const mentionedToolNamesArray = Array.from(mentionedToolNames);
|
||||
|
||||
try {
|
||||
const directToolsResponse = await libGetToolsByIds(mentionedToolNamesArray);
|
||||
foundTools.push(...directToolsResponse.items);
|
||||
console.log(`Found ${directToolsResponse.items.length} tools by direct lookup`);
|
||||
} catch (error) {
|
||||
console.log('Direct tool lookup failed, trying search approach');
|
||||
}
|
||||
|
||||
// Method 2: For any remaining tools, use search functionality
|
||||
const foundToolSlugs = new Set(foundTools.map(tool => tool.slug));
|
||||
const foundToolNames = new Set(foundTools.map(tool => tool.name));
|
||||
const remainingToolNames = mentionedToolNamesArray.filter(name =>
|
||||
!foundToolSlugs.has(name) && !foundToolNames.has(name)
|
||||
);
|
||||
|
||||
for (const toolName of remainingToolNames) {
|
||||
try {
|
||||
// Search for tools by name/description
|
||||
const searchResponse = await libSearchTools(toolName, null, 10);
|
||||
|
||||
// Find exact matches by name or slug
|
||||
const exactMatches = searchResponse.items.filter(tool =>
|
||||
tool.name === toolName ||
|
||||
tool.slug === toolName ||
|
||||
tool.name.toLowerCase() === toolName.toLowerCase() ||
|
||||
tool.slug.toLowerCase() === toolName.toLowerCase()
|
||||
);
|
||||
|
||||
if (exactMatches.length > 0) {
|
||||
foundTools.push(...exactMatches);
|
||||
console.log(`Found ${exactMatches.length} tools for search term "${toolName}"`);
|
||||
} else {
|
||||
console.log(`No exact matches found for tool "${toolName}"`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error searching for tool "${toolName}":`, error);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error searching for Composio tools:', error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (foundTools.length > 0) {
|
||||
console.log(`Adding ${foundTools.length} Composio tools to workflow`);
|
||||
|
||||
// Remove duplicates based on slug
|
||||
const uniqueTools = foundTools.filter((tool, index, self) =>
|
||||
index === self.findIndex(t => t.slug === tool.slug)
|
||||
);
|
||||
|
||||
// Convert Composio tools to workflow tool format
|
||||
const composioWorkflowTools: z.infer<typeof WorkflowTool>[] = uniqueTools.map(tool => ({
|
||||
name: tool.slug,
|
||||
description: tool.description || "",
|
||||
parameters: {
|
||||
type: 'object' as const,
|
||||
properties: tool.input_parameters?.properties || {},
|
||||
required: tool.input_parameters?.required || []
|
||||
},
|
||||
isComposio: true,
|
||||
composioData: {
|
||||
slug: tool.slug,
|
||||
noAuth: tool.no_auth,
|
||||
toolkitName: tool.toolkit.name,
|
||||
toolkitSlug: tool.toolkit.slug,
|
||||
logo: tool.toolkit.logo,
|
||||
},
|
||||
}));
|
||||
|
||||
// Add these tools to the workflow.tools array
|
||||
workflow.tools = [...workflow.tools, ...composioWorkflowTools];
|
||||
|
||||
console.log(`Added ${composioWorkflowTools.length} Composio tools to workflow`);
|
||||
} else {
|
||||
console.log('No matching Composio tools found for the mentioned tool names');
|
||||
}
|
||||
}
|
||||
|
||||
export async function createProjectFromWorkflowJson(formData: FormData): Promise<{ id: string } | { billingError: string }> {
|
||||
const user = await authCheck();
|
||||
const workflowJson = formData.get('workflowJson') as string;
|
||||
|
|
@ -441,23 +325,9 @@ export async function createProjectFromWorkflowJson(formData: FormData): Promise
|
|||
return response;
|
||||
}
|
||||
const projectId = response.id;
|
||||
|
||||
// Automatically detect and add Composio tools mentioned in agent instructions
|
||||
try {
|
||||
await detectAndAddComposioTools(projectId, workflow);
|
||||
} catch (error) {
|
||||
// Log error but don't fail the import if tool detection fails
|
||||
console.error('Failed to auto-detect Composio tools:', error);
|
||||
}
|
||||
|
||||
return { id: projectId };
|
||||
}
|
||||
|
||||
export async function collectProjectTools(projectId: string): Promise<z.infer<typeof WorkflowTool>[]> {
|
||||
await projectAuthCheck(projectId);
|
||||
return libCollectProjectTools(projectId);
|
||||
}
|
||||
|
||||
export async function saveWorkflow(projectId: string, workflow: z.infer<typeof Workflow>) {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue