@@ -97,6 +97,27 @@ import {
9797
9898const logger = createLogger ( 'ToolInput' )
9999
100+ /**
101+ * Extracts canonical mode overrides scoped to a specific tool type.
102+ * Canonical modes are stored with `{blockType}:{canonicalId}` keys to prevent
103+ * cross-tool collisions when multiple tools share the same canonicalParamId.
104+ */
105+ function scopeCanonicalOverrides (
106+ overrides : CanonicalModeOverrides | undefined ,
107+ blockType : string | undefined
108+ ) : CanonicalModeOverrides | undefined {
109+ if ( ! overrides || ! blockType ) return undefined
110+ const prefix = `${ blockType } :`
111+ let scoped : CanonicalModeOverrides | undefined
112+ for ( const [ key , val ] of Object . entries ( overrides ) ) {
113+ if ( key . startsWith ( prefix ) && val ) {
114+ if ( ! scoped ) scoped = { }
115+ scoped [ key . slice ( prefix . length ) ] = val
116+ }
117+ }
118+ return scoped
119+ }
120+
100121/**
101122 * Renders the input for workflow_executor's inputMapping parameter.
102123 * This is a special case that doesn't map to any SubBlockConfig, so it's kept here.
@@ -498,7 +519,6 @@ export const ToolInput = memo(function ToolInput({
498519 const openSettingsModal = useSettingsModalStore ( ( state ) => state . openModal )
499520 const mcpDataLoading = mcpLoading || mcpServersLoading
500521
501- // Fetch workflows for the Workflows section in the dropdown
502522 const { data : workflowsList = [ ] } = useWorkflows ( workspaceId , { syncRegistry : false } )
503523 const availableWorkflows = useMemo (
504524 ( ) => workflowsList . filter ( ( w ) => w . id !== workflowId ) ,
@@ -1208,7 +1228,6 @@ export const ToolInput = memo(function ToolInput({
12081228 const toolGroups = useMemo ( ( ) : ComboboxOptionGroup [ ] => {
12091229 const groups : ComboboxOptionGroup [ ] = [ ]
12101230
1211- // Actions group (no section header)
12121231 const actionItems : ComboboxOption [ ] = [ ]
12131232 if ( ! permissionConfig . disableCustomTools ) {
12141233 actionItems . push ( {
@@ -1238,7 +1257,6 @@ export const ToolInput = memo(function ToolInput({
12381257 groups . push ( { items : actionItems } )
12391258 }
12401259
1241- // Custom Tools section
12421260 if ( ! permissionConfig . disableCustomTools && customTools . length > 0 ) {
12431261 groups . push ( {
12441262 section : 'Custom Tools' ,
@@ -1268,7 +1286,6 @@ export const ToolInput = memo(function ToolInput({
12681286 } )
12691287 }
12701288
1271- // MCP Tools section
12721289 if ( ! permissionConfig . disableMcpTools && availableMcpTools . length > 0 ) {
12731290 groups . push ( {
12741291 section : 'MCP Tools' ,
@@ -1306,11 +1323,9 @@ export const ToolInput = memo(function ToolInput({
13061323 } )
13071324 }
13081325
1309- // Split tool blocks into built-in tools and integrations
13101326 const builtInTools = toolBlocks . filter ( ( block ) => BUILT_IN_TOOL_TYPES . has ( block . type ) )
13111327 const integrations = toolBlocks . filter ( ( block ) => ! BUILT_IN_TOOL_TYPES . has ( block . type ) )
13121328
1313- // Built-in Tools section
13141329 if ( builtInTools . length > 0 ) {
13151330 groups . push ( {
13161331 section : 'Built-in Tools' ,
@@ -1328,7 +1343,6 @@ export const ToolInput = memo(function ToolInput({
13281343 } )
13291344 }
13301345
1331- // Integrations section
13321346 if ( integrations . length > 0 ) {
13331347 groups . push ( {
13341348 section : 'Integrations' ,
@@ -1410,7 +1424,6 @@ export const ToolInput = memo(function ToolInput({
14101424
14111425 return (
14121426 < div className = 'w-full space-y-[8px]' >
1413- { /* Add Tool Combobox - always at top */ }
14141427 < Combobox
14151428 options = { [ ] }
14161429 groups = { toolGroups }
@@ -1423,10 +1436,8 @@ export const ToolInput = memo(function ToolInput({
14231436 onOpenChange = { setOpen }
14241437 />
14251438
1426- { /* Selected Tools List */ }
14271439 { selectedTools . length > 0 &&
14281440 selectedTools . map ( ( tool , toolIndex ) => {
1429- // Handle custom tools, MCP tools, and workflow tools differently
14301441 const isCustomTool = tool . type === 'custom-tool'
14311442 const isMcpTool = tool . type === 'mcp'
14321443 const isWorkflowTool = tool . type === 'workflow'
@@ -1435,13 +1446,11 @@ export const ToolInput = memo(function ToolInput({
14351446 ? toolBlocks . find ( ( block ) => block . type === tool . type )
14361447 : null
14371448
1438- // Get the current tool ID (may change based on operation)
14391449 const currentToolId =
14401450 ! isCustomTool && ! isMcpTool
14411451 ? getToolIdForOperation ( tool . type , tool . operation ) || tool . toolId || ''
14421452 : tool . toolId || ''
14431453
1444- // Get tool parameters using the new utility with block type for UI components
14451454 const toolParams =
14461455 ! isCustomTool && ! isMcpTool && currentToolId
14471456 ? getToolParametersConfig ( currentToolId , tool . type , {
@@ -1450,7 +1459,8 @@ export const ToolInput = memo(function ToolInput({
14501459 } )
14511460 : null
14521461
1453- // Get subblocks for tool-input (primary source for registry tools)
1462+ const toolScopedOverrides = scopeCanonicalOverrides ( canonicalModeOverrides , tool . type )
1463+
14541464 const subBlocksResult : SubBlocksForToolInput | null =
14551465 ! isCustomTool && ! isMcpTool && currentToolId
14561466 ? getSubBlocksForToolInput (
@@ -1460,16 +1470,14 @@ export const ToolInput = memo(function ToolInput({
14601470 operation : tool . operation ,
14611471 ...tool . params ,
14621472 } ,
1463- canonicalModeOverrides
1473+ toolScopedOverrides
14641474 )
14651475 : null
14661476
1467- // Build canonical index for proper dependency resolution
14681477 const toolCanonicalIndex : CanonicalIndex | null = toolBlock ?. subBlocks
14691478 ? buildCanonicalIndex ( toolBlock . subBlocks )
14701479 : null
14711480
1472- // Build preview context with canonical resolution
14731481 const toolContextValues = toolCanonicalIndex
14741482 ? buildPreviewContextValues ( tool . params || { } , {
14751483 blockType : tool . type ,
@@ -1479,12 +1487,10 @@ export const ToolInput = memo(function ToolInput({
14791487 } )
14801488 : tool . params || { }
14811489
1482- // For custom tools, resolve from reference (new format) or use inline (legacy)
14831490 const resolvedCustomTool = isCustomTool
14841491 ? resolveCustomToolFromReference ( tool , customTools )
14851492 : null
14861493
1487- // Derive title and schema from resolved tool or inline data
14881494 const customToolTitle = isCustomTool
14891495 ? tool . title || resolvedCustomTool ?. title || 'Unknown Tool'
14901496 : null
@@ -1503,8 +1509,6 @@ export const ToolInput = memo(function ToolInput({
15031509 )
15041510 : [ ]
15051511
1506- // For MCP tools, extract parameters from input schema
1507- // Use cached schema from tool object if available, otherwise fetch from mcpTools
15081512 const mcpTool = isMcpTool ? mcpTools . find ( ( t ) => t . id === tool . toolId ) : null
15091513 const mcpToolSchema = isMcpTool ? tool . schema || mcpTool ?. inputSchema : null
15101514 const mcpToolParams =
@@ -1521,8 +1525,6 @@ export const ToolInput = memo(function ToolInput({
15211525 )
15221526 : [ ]
15231527
1524- // Get all parameters to display
1525- // For registry tools with subBlocks, use the subblock-first approach
15261528 const useSubBlocks = ! isCustomTool && ! isMcpTool && subBlocksResult ?. subBlocks ?. length
15271529 const displayParams : ToolParameterConfig [ ] = isCustomTool
15281530 ? customToolParams
@@ -1533,20 +1535,17 @@ export const ToolInput = memo(function ToolInput({
15331535 ? subBlocksResult ! . subBlocks
15341536 : [ ]
15351537
1536- // Check if tool requires OAuth
15371538 const requiresOAuth =
15381539 ! isCustomTool && ! isMcpTool && currentToolId && toolRequiresOAuth ( currentToolId )
15391540 const oauthConfig =
15401541 ! isCustomTool && ! isMcpTool && currentToolId ? getToolOAuthConfig ( currentToolId ) : null
15411542
1542- // Determine if tool has expandable body content
15431543 const hasOperations = ! isCustomTool && ! isMcpTool && hasMultipleOperations ( tool . type )
15441544 const hasParams = useSubBlocks
15451545 ? displaySubBlocks . length > 0
15461546 : displayParams . filter ( ( param ) => evaluateParameterCondition ( param , tool ) ) . length > 0
15471547 const hasToolBody = hasOperations || ( requiresOAuth && oauthConfig ) || hasParams
15481548
1549- // Only show expansion if tool has body content
15501549 const isExpandedForDisplay = hasToolBody
15511550 ? isPreview
15521551 ? ( previewExpanded [ toolIndex ] ?? ! ! tool . isExpanded )
@@ -1717,7 +1716,6 @@ export const ToolInput = memo(function ToolInput({
17171716
17181717 { ! isCustomTool && isExpandedForDisplay && (
17191718 < div className = 'flex flex-col gap-[10px] overflow-visible rounded-b-[4px] border-[var(--border-1)] border-t bg-[var(--surface-2)] px-[8px] py-[8px]' >
1720- { /* Operation dropdown for tools with multiple operations */ }
17211719 { ( ( ) => {
17221720 const hasOperations = hasMultipleOperations ( tool . type )
17231721 const operationOptions = hasOperations ? getOperationOptions ( tool . type ) : [ ]
@@ -1743,7 +1741,6 @@ export const ToolInput = memo(function ToolInput({
17431741 ) : null
17441742 } ) ( ) }
17451743
1746- { /* OAuth credential selector if required */ }
17471744 { requiresOAuth && oauthConfig && (
17481745 < div className = 'relative min-w-0 space-y-[6px]' >
17491746 < div className = 'font-medium text-[13px] text-[var(--text-primary)]' >
@@ -1768,20 +1765,28 @@ export const ToolInput = memo(function ToolInput({
17681765 </ div >
17691766 ) }
17701767
1771- { /* Tool parameters */ }
17721768 { ( ( ) => {
17731769 const renderedElements : React . ReactNode [ ] = [ ]
17741770
1775- // SubBlock-first rendering for registry tools
17761771 if ( useSubBlocks && displaySubBlocks . length > 0 ) {
17771772 const coveredParamIds = new Set (
1778- displaySubBlocks . map ( ( sb ) => sb . canonicalParamId || sb . id )
1773+ displaySubBlocks . flatMap ( ( sb ) => {
1774+ const ids = [ sb . id ]
1775+ if ( sb . canonicalParamId ) ids . push ( sb . canonicalParamId )
1776+ const cId = toolCanonicalIndex ?. canonicalIdBySubBlockId [ sb . id ]
1777+ if ( cId ) {
1778+ const group = toolCanonicalIndex ?. groupsById [ cId ]
1779+ if ( group ) {
1780+ if ( group . basicId ) ids . push ( group . basicId )
1781+ ids . push ( ...group . advancedIds )
1782+ }
1783+ }
1784+ return ids
1785+ } )
17791786 )
17801787
17811788 displaySubBlocks . forEach ( ( sb ) => {
1782- const effectiveParamId = sb . canonicalParamId || sb . id
1783-
1784- // Compute canonical toggle for basic/advanced mode switching
1789+ const effectiveParamId = sb . id
17851790 const canonicalId = toolCanonicalIndex ?. canonicalIdBySubBlockId [ sb . id ]
17861791 const canonicalGroup = canonicalId
17871792 ? toolCanonicalIndex ?. groupsById [ canonicalId ]
@@ -1792,7 +1797,7 @@ export const ToolInput = memo(function ToolInput({
17921797 ? resolveCanonicalMode (
17931798 canonicalGroup ,
17941799 { operation : tool . operation , ...tool . params } ,
1795- canonicalModeOverrides
1800+ toolScopedOverrides
17961801 )
17971802 : undefined
17981803
@@ -1803,12 +1808,15 @@ export const ToolInput = memo(function ToolInput({
18031808 onToggle : ( ) => {
18041809 const nextMode =
18051810 canonicalMode === 'advanced' ? 'basic' : 'advanced'
1806- collaborativeSetBlockCanonicalMode ( blockId , canonicalId , nextMode )
1811+ collaborativeSetBlockCanonicalMode (
1812+ blockId ,
1813+ `${ tool . type } :${ canonicalId } ` ,
1814+ nextMode
1815+ )
18071816 } ,
18081817 }
18091818 : undefined
18101819
1811- // Ensure title is present for SubBlock's label rendering
18121820 const sbWithTitle = sb . title
18131821 ? sb
18141822 : { ...sb , title : formatParameterLabel ( effectiveParamId ) }
@@ -1829,8 +1837,6 @@ export const ToolInput = memo(function ToolInput({
18291837 )
18301838 } )
18311839
1832- // Render remaining tool params not covered by subblocks
1833- // (e.g. inputMapping for workflow tools with custom UI)
18341840 const uncoveredParams = displayParams . filter (
18351841 ( param ) =>
18361842 ! coveredParamIds . has ( param . id ) && evaluateParameterCondition ( param , tool )
@@ -1867,8 +1873,6 @@ export const ToolInput = memo(function ToolInput({
18671873 )
18681874 }
18691875
1870- // Fallback: legacy ToolParameterConfig-based rendering
1871- // Used for custom tools, MCP tools, and registry tools without subBlocks
18721876 const filteredParams = displayParams . filter ( ( param ) =>
18731877 evaluateParameterCondition ( param , tool )
18741878 )
@@ -1907,7 +1911,6 @@ export const ToolInput = memo(function ToolInput({
19071911 )
19081912 } ) }
19091913
1910- { /* Custom Tool Modal */ }
19111914 < CustomToolModal
19121915 open = { customToolModalOpen }
19131916 onOpenChange = { ( open ) => {
@@ -1921,11 +1924,9 @@ export const ToolInput = memo(function ToolInput({
19211924 editingToolIndex !== null && selectedTools [ editingToolIndex ] ?. type === 'custom-tool'
19221925 ? ( ( ) => {
19231926 const storedTool = selectedTools [ editingToolIndex ]
1924- // Resolve the full tool definition from reference or inline
19251927 const resolved = resolveCustomToolFromReference ( storedTool , customTools )
19261928
19271929 if ( resolved ) {
1928- // Find the database ID
19291930 const dbTool = storedTool . customToolId
19301931 ? customTools . find ( ( t ) => t . id === storedTool . customToolId )
19311932 : customTools . find (
@@ -1939,7 +1940,6 @@ export const ToolInput = memo(function ToolInput({
19391940 }
19401941 }
19411942
1942- // Fallback to inline definition (legacy format)
19431943 return {
19441944 id : customTools . find (
19451945 ( tool ) => tool . schema ?. function ?. name === storedTool . schema ?. function ?. name
0 commit comments