refactor tools UX: part 1

This commit is contained in:
arkml 2025-07-16 20:13:03 +05:30 committed by Ramnique Singh
parent cccd383b92
commit 751a86c34d
21 changed files with 1462 additions and 258 deletions

View file

@ -3,6 +3,9 @@ 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,
@ -20,6 +23,7 @@ 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";
@ -47,6 +51,24 @@ export async function listTools(projectId: string, toolkitSlug: string, cursor:
return await libListTools(toolkitSlug, 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);
@ -215,12 +237,196 @@ 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);
// update project with new selected tools
await projectsCollection.updateOne({ _id: projectId }, { $set: { composioSelectedTools: tools } });
// 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}`);
}
}

View file

@ -16,6 +16,13 @@ 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 || '';
@ -306,6 +313,113 @@ 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;
@ -327,6 +441,15 @@ 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 };
}