From ae226e977c00828ba5d34b0392847d34175a7b30 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 1 Mar 2025 01:04:15 -0800 Subject: [PATCH] fix(agent): add dropdown for specific tool selection for blocks that have more than one tool, give that tool to the agent instead of arbitrarily selecting the first tool --- .../components/tool-input/tool-input.tsx | 85 ++++++++++++++++++- executor/handlers.ts | 38 ++------- providers/utils.ts | 78 +++++++++++++++++ 3 files changed, 168 insertions(+), 33 deletions(-) diff --git a/app/w/[id]/components/workflow-block/components/sub-block/components/tool-input/tool-input.tsx b/app/w/[id]/components/workflow-block/components/sub-block/components/tool-input/tool-input.tsx index b8ee47a9e1..69a2fddf29 100644 --- a/app/w/[id]/components/workflow-block/components/sub-block/components/tool-input/tool-input.tsx +++ b/app/w/[id]/components/workflow-block/components/sub-block/components/tool-input/tool-input.tsx @@ -11,6 +11,13 @@ import { CommandSeparator, } from '@/components/ui/command' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' import { cn } from '@/lib/utils' import { useCustomToolsStore } from '@/stores/custom-tools/store' import { useWorkflowStore } from '@/stores/workflow/store' @@ -32,6 +39,7 @@ interface StoredTool { isExpanded?: boolean schema?: any // For custom tools code?: string // For custom tools implementation + operation?: string // For tools with multiple operations } interface ToolParam { @@ -77,6 +85,37 @@ const getCustomToolParams = (schema: any): ToolParam[] => { })) } +// Check if a block has multiple operations +const hasMultipleOperations = (blockType: string): boolean => { + const block = getAllBlocks().find((block) => block.type === blockType) + return (block?.tools?.access?.length || 0) > 1 +} + +// Get operation options for a block +const getOperationOptions = (blockType: string): { label: string; id: string }[] => { + const block = getAllBlocks().find((block) => block.type === blockType) + if (!block || !block.tools?.access) return [] + + // Look for an operation dropdown in the block's subBlocks + const operationSubBlock = block.subBlocks.find((sb) => sb.id === 'operation') + if ( + operationSubBlock && + operationSubBlock.type === 'dropdown' && + Array.isArray(operationSubBlock.options) + ) { + return operationSubBlock.options as { label: string; id: string }[] + } + + // Fallback: create options from tools.access + return block.tools.access.map((toolId) => { + const tool = getTool(toolId) + return { + id: toolId, + label: tool?.name || toolId, + } + }) +} + export function ToolInput({ blockId, subBlockId }: ToolInputProps) { const [value, setValue] = useSubBlockValue(blockId, subBlockId) const [open, setOpen] = useState(false) @@ -99,12 +138,16 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) { return } - const toolId = getToolIdFromBlock(toolBlock.type) + const hasOperations = hasMultipleOperations(toolBlock.type) + const operationOptions = hasOperations ? getOperationOptions(toolBlock.type) : [] + const defaultOperation = operationOptions.length > 0 ? operationOptions[0].id : undefined + const newTool: StoredTool = { type: toolBlock.type, title: toolBlock.name, params: {}, isExpanded: true, + operation: defaultOperation, } // If isWide, keep tools in the same row expanded @@ -218,6 +261,19 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) { ) } + const handleOperationChange = (toolIndex: number, operation: string) => { + setValue( + selectedTools.map((tool, index) => + index === toolIndex + ? { + ...tool, + operation, + } + : tool + ) + ) + } + const toggleToolExpansion = (toolIndex: number) => { setValue( selectedTools.map((tool, index) => @@ -349,6 +405,8 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) { ? toolBlocks.find((block) => block.type === tool.type) : null const toolId = !isCustomTool ? getToolIdFromBlock(tool.type) : null + const hasOperations = !isCustomTool && hasMultipleOperations(tool.type) + const operationOptions = hasOperations ? getOperationOptions(tool.type) : [] // Get parameters based on tool type const requiredParams = isCustomTool @@ -409,7 +467,7 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) { - {tool.isExpanded && !isCustomTool && requiredParams.length > 0 && ( + {tool.isExpanded && !isCustomTool && (
{ @@ -418,6 +476,29 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) { } }} > + {/* Add operation dropdown for tools with multiple operations */} + {hasOperations && operationOptions.length > 0 && ( +
+
Operation
+ +
+ )} + + {/* Existing parameters */} {requiredParams.map((param) => (
diff --git a/executor/handlers.ts b/executor/handlers.ts index 76ed543592..d52c9ddafb 100644 --- a/executor/handlers.ts +++ b/executor/handlers.ts @@ -3,6 +3,7 @@ import { generateRouterPrompt } from '@/blocks/blocks/router' import { BlockOutput } from '@/blocks/types' import { executeProviderRequest } from '@/providers' import { getProviderFromModel } from '@/providers/utils' +import { transformBlockTool } from '@/providers/utils' import { SerializedBlock } from '@/serializer/types' import { executeTool, getTool } from '@/tools' import { PathTracker } from './path' @@ -84,37 +85,12 @@ export class AgentBlockHandler implements BlockHandler { } } - // Handle regular block tools - const blockFound = getAllBlocks().find((b) => b.type === tool.type) - const toolId = blockFound?.tools.access[0] - if (!toolId) return null - - const toolConfig = getTool(toolId) - if (!toolConfig) return null - - return { - id: toolConfig.id, - name: toolConfig.name, - description: toolConfig.description, - params: tool.params || {}, - parameters: { - type: 'object', - properties: Object.entries(toolConfig.params).reduce( - (acc, [key, config]) => ({ - ...acc, - [key]: { - type: config.type === 'json' ? 'object' : config.type, - description: config.description || '', - ...(key in tool.params && { default: tool.params[key] }), - }, - }), - {} - ), - required: Object.entries(toolConfig.params) - .filter(([_, config]) => config.required) - .map(([key]) => key), - }, - } + // Handle regular block tools with operation selection + return transformBlockTool(tool, { + selectedOperation: tool.operation, + getAllBlocks, + getTool, + }) }) .filter((t): t is NonNullable => t !== null) : [] diff --git a/providers/utils.ts b/providers/utils.ts index 2034f46776..f1c9419d42 100644 --- a/providers/utils.ts +++ b/providers/utils.ts @@ -227,3 +227,81 @@ export function getCustomTools(): ProviderToolConfig[] { // Transform each custom tool into a provider tool config return customTools.map(transformCustomTool) } + +/** + * Transforms a block tool into a provider tool config with operation selection + * + * @param block The block to transform + * @param options Additional options including dependencies and selected operation + * @returns The provider tool config or null if transform fails + */ +export function transformBlockTool( + block: any, + options: { + selectedOperation?: string + getAllBlocks: () => any[] + getTool: (toolId: string) => any + } +): ProviderToolConfig | null { + const { selectedOperation, getAllBlocks, getTool } = options + + // Get the block definition + const blockDef = getAllBlocks().find((b: any) => b.type === block.type) + if (!blockDef) return null + + // If the block has multiple operations, use the selected one or the first one + let toolId: string | null = null + + if ((blockDef.tools?.access?.length || 0) > 1) { + // If we have an operation dropdown in the block and a selected operation + if (selectedOperation && blockDef.tools?.config?.tool) { + // Use the block's tool selection function to get the right tool + try { + toolId = blockDef.tools.config.tool({ + ...block.params, + operation: selectedOperation, + }) + } catch (error) { + console.error('Error selecting tool:', error) + return null + } + } else { + // Default to first tool if no operation specified + toolId = blockDef.tools.access[0] + } + } else { + // Single tool case + toolId = blockDef.tools?.access?.[0] || null + } + + if (!toolId) return null + + // Get the tool config + const toolConfig = getTool(toolId) + if (!toolConfig) return null + + // Return formatted tool config + return { + id: toolConfig.id, + name: toolConfig.name, + description: toolConfig.description, + params: block.params || {}, + parameters: { + type: 'object', + properties: Object.entries(toolConfig.params).reduce( + (acc, [key, config]: [string, any]) => ({ + ...acc, + [key]: { + type: config.type === 'json' ? 'object' : config.type, + description: config.description || '', + ...(key in block.params && { default: block.params[key] }), + }, + }), + {} + ), + required: Object.entries(toolConfig.params) + .filter(([_, config]: [string, any]) => config.required) + .map(([key]) => key), + }, + } +}