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

This commit is contained in:
Waleed Latif
2025-03-01 01:04:18 -08:00
parent 86e1f2d8dd
commit ae226e977c
3 changed files with 168 additions and 33 deletions
@@ -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) {
</div>
</div>
{tool.isExpanded && !isCustomTool && requiredParams.length > 0 && (
{tool.isExpanded && !isCustomTool && (
<div
className="p-3 space-y-3"
onClick={(e) => {
@@ -418,6 +476,29 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
}
}}
>
{/* Add operation dropdown for tools with multiple operations */}
{hasOperations && operationOptions.length > 0 && (
<div className="space-y-1.5 relative">
<div className="text-xs font-medium text-muted-foreground">Operation</div>
<Select
value={tool.operation || operationOptions[0].id}
onValueChange={(value) => handleOperationChange(toolIndex, value)}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select operation" />
</SelectTrigger>
<SelectContent>
{operationOptions.map((option) => (
<SelectItem key={option.id} value={option.id}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
{/* Existing parameters */}
{requiredParams.map((param) => (
<div key={param.id} className="space-y-1.5 relative">
<div className="text-xs font-medium text-muted-foreground">
+7 -31
View File
@@ -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<typeof t> => t !== null)
: []
+78
View File
@@ -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),
},
}
}