mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
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:
+83
-2
@@ -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
@@ -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)
|
||||
: []
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user