Skip to content

Commit efa4f27

Browse files
authored
fix(agent): unique tool ids for multi-instance tools + icon updates (#4933)
* fix(agent): resolve canonical resource id when building unique tool ids Multi-instance agent tools (table, knowledge base, workflow) get a unique id by suffixing the resource id (e.g. table_query_rows_<tableId>). The suffix code read the canonical param key (tableId/knowledgeBaseId) directly from stored params, but selector subblocks persist their value under the subblock id (tableSelector/knowledgeBaseSelector). Tools stored in that state never got a suffix, so two of them collapsed to the same name. Resolve the canonical resource id from the source subblock first — mirroring the execution-time paramsTransform — so the unique id is derived the same way the tool actually runs. Non-destructive; no-op when the canonical key is already present. * test(agent): cover knowledge-base selector resolution for unique tool ids * improvement(icons): new 1Password icon on white bg, enlarge Prospeo and Neo4j glyphs
1 parent c7689c0 commit efa4f27

7 files changed

Lines changed: 388 additions & 126 deletions

File tree

apps/docs/components/icons.tsx

Lines changed: 103 additions & 56 deletions
Large diffs are not rendered by default.

apps/docs/content/docs/en/tools/onepassword.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
77

88
<BlockInfoCard
99
type="onepassword"
10-
color="#145FE4"
10+
color="#FFFFFF"
1111
/>
1212

1313
{/* MANUAL-CONTENT-START:intro */}

apps/sim/blocks/blocks/onepassword.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const OnePasswordBlock: BlockConfig = {
1010
docsLink: 'https://docs.sim.ai/tools/onepassword',
1111
category: 'tools',
1212
integrationType: IntegrationType.Security,
13-
bgColor: '#145FE4',
13+
bgColor: '#FFFFFF',
1414
icon: OnePasswordIcon,
1515
authMode: AuthMode.ApiKey,
1616

apps/sim/components/icons.tsx

Lines changed: 103 additions & 56 deletions
Large diffs are not rendered by default.

apps/sim/lib/integrations/integrations.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"name": "1Password",
66
"description": "Manage secrets and items in 1Password vaults",
77
"longDescription": "Access and manage secrets stored in 1Password vaults using the Connect API or Service Account SDK. List vaults, retrieve items with their fields and secrets, create new items, update existing ones, delete items, and resolve secret references.",
8-
"bgColor": "#145FE4",
8+
"bgColor": "#FFFFFF",
99
"iconName": "OnePasswordIcon",
1010
"docsUrl": "https://docs.sim.ai/tools/onepassword",
1111
"operations": [

apps/sim/providers/utils.test.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
supportsThinking,
3939
supportsToolUsageControl,
4040
supportsVerbosity,
41+
transformBlockTool,
4142
updateOllamaProviderModels,
4243
} from '@/providers/utils'
4344

@@ -1514,3 +1515,130 @@ describe('Provider/Model Blacklist', () => {
15141515
})
15151516
})
15161517
})
1518+
1519+
describe('transformBlockTool multi-instance unique IDs', () => {
1520+
const tableBlockDef = {
1521+
type: 'table',
1522+
inputs: {},
1523+
subBlocks: [
1524+
{ id: 'operation', type: 'dropdown' },
1525+
{ id: 'tableSelector', type: 'table-selector', canonicalParamId: 'tableId', mode: 'basic' },
1526+
{
1527+
id: 'manualTableId',
1528+
type: 'short-input',
1529+
canonicalParamId: 'tableId',
1530+
mode: 'advanced',
1531+
},
1532+
],
1533+
tools: {
1534+
access: ['table_query_rows', 'table_insert_row'],
1535+
config: { tool: () => 'table_query_rows' },
1536+
},
1537+
}
1538+
1539+
const getAllBlocks = () => [tableBlockDef]
1540+
const getTool = (id: string) => ({
1541+
id,
1542+
name: 'Query Rows',
1543+
description: 'Query table rows',
1544+
params: {},
1545+
})
1546+
1547+
const transformTable = (
1548+
params: Record<string, unknown>,
1549+
canonicalModes?: Record<string, 'basic' | 'advanced'>
1550+
) =>
1551+
transformBlockTool(
1552+
{ type: 'table', operation: 'query_rows', params },
1553+
{ selectedOperation: 'query_rows', getAllBlocks, getTool, canonicalModes }
1554+
)
1555+
1556+
it('appends the table id when stored under the basic selector subblock key', async () => {
1557+
const result = await transformTable({ tableSelector: 'tbl_abc' })
1558+
expect(result?.id).toBe('table_query_rows_tbl_abc')
1559+
})
1560+
1561+
it('appends the table id resolved from the advanced manual input', async () => {
1562+
const result = await transformTable(
1563+
{ manualTableId: 'tbl_xyz' },
1564+
{ 'table:tableId': 'advanced' }
1565+
)
1566+
expect(result?.id).toBe('table_query_rows_tbl_xyz')
1567+
})
1568+
1569+
it('appends the canonical table id when already present in params', async () => {
1570+
const result = await transformTable({ tableId: 'tbl_direct' })
1571+
expect(result?.id).toBe('table_query_rows_tbl_direct')
1572+
})
1573+
1574+
it('falls back to the base tool id when no table is selected', async () => {
1575+
const result = await transformTable({})
1576+
expect(result?.id).toBe('table_query_rows')
1577+
})
1578+
})
1579+
1580+
describe('transformBlockTool knowledge-base multi-instance unique IDs', () => {
1581+
const knowledgeBlockDef = {
1582+
type: 'knowledge',
1583+
inputs: {},
1584+
subBlocks: [
1585+
{ id: 'operation', type: 'dropdown' },
1586+
{
1587+
id: 'knowledgeBaseSelector',
1588+
type: 'knowledge-base-selector',
1589+
canonicalParamId: 'knowledgeBaseId',
1590+
mode: 'basic',
1591+
},
1592+
{
1593+
id: 'manualKnowledgeBaseId',
1594+
type: 'short-input',
1595+
canonicalParamId: 'knowledgeBaseId',
1596+
mode: 'advanced',
1597+
},
1598+
],
1599+
tools: {
1600+
access: ['knowledge_search', 'knowledge_upload_chunk'],
1601+
config: { tool: () => 'knowledge_search' },
1602+
},
1603+
}
1604+
1605+
const getAllBlocks = () => [knowledgeBlockDef]
1606+
const getTool = (id: string) => ({
1607+
id,
1608+
name: 'Search',
1609+
description: 'Search the knowledge base',
1610+
params: {},
1611+
})
1612+
1613+
const transformKb = (
1614+
params: Record<string, unknown>,
1615+
canonicalModes?: Record<string, 'basic' | 'advanced'>
1616+
) =>
1617+
transformBlockTool(
1618+
{ type: 'knowledge', operation: 'search', params },
1619+
{ selectedOperation: 'search', getAllBlocks, getTool, canonicalModes }
1620+
)
1621+
1622+
it('appends the knowledge base id when stored under the basic selector subblock key', async () => {
1623+
const result = await transformKb({ knowledgeBaseSelector: 'kb_abc' })
1624+
expect(result?.id).toBe('knowledge_search_kb_abc')
1625+
})
1626+
1627+
it('appends the knowledge base id resolved from the advanced manual input', async () => {
1628+
const result = await transformKb(
1629+
{ manualKnowledgeBaseId: 'kb_xyz' },
1630+
{ 'knowledge:knowledgeBaseId': 'advanced' }
1631+
)
1632+
expect(result?.id).toBe('knowledge_search_kb_xyz')
1633+
})
1634+
1635+
it('appends the canonical knowledge base id when already present in params', async () => {
1636+
const result = await transformKb({ knowledgeBaseId: 'kb_direct' })
1637+
expect(result?.id).toBe('knowledge_search_kb_direct')
1638+
})
1639+
1640+
it('falls back to the base tool id when no knowledge base is selected', async () => {
1641+
const result = await transformKb({})
1642+
expect(result?.id).toBe('knowledge_search')
1643+
})
1644+
})

apps/sim/providers/utils.ts

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,39 @@ export function extractAndParseJSON(content: string): any {
473473
}
474474
}
475475

476+
/**
477+
* Resolves canonical pair ids (e.g. `tableId`, `knowledgeBaseId`) from a tool's
478+
* raw params, filling them in from their basic/advanced selector subblock source
479+
* values when the canonical key isn't already present.
480+
*
481+
* Selector subblocks persist their value under the subblock id (e.g.
482+
* `tableSelector`), not the canonical id, so any lookup that keys off the
483+
* canonical id — like the unique-tool-id suffix below — must resolve it first.
484+
* Mode selection mirrors {@link transformBlockTool}'s execution-time
485+
* `paramsTransform` so the resolved id matches the params the tool actually runs
486+
* with.
487+
*
488+
* @returns The params with canonical resource ids resolved (non-destructive)
489+
*/
490+
function resolveCanonicalResourceParams(
491+
params: Record<string, any>,
492+
canonicalGroups: CanonicalGroup[],
493+
blockType: string,
494+
canonicalModes?: Record<string, 'basic' | 'advanced'>
495+
): Record<string, any> {
496+
if (canonicalGroups.length === 0) return params
497+
const resolved = { ...params }
498+
for (const group of canonicalGroups) {
499+
const existing = resolved[group.canonicalId]
500+
if (existing !== undefined && existing !== null && existing !== '') continue
501+
const { basicValue, advancedValue } = getCanonicalValues(group, params)
502+
const pairMode = canonicalModes?.[`${blockType}:${group.canonicalId}`] ?? 'basic'
503+
const chosen = pairMode === 'advanced' ? advancedValue : basicValue
504+
if (chosen !== undefined) resolved[group.canonicalId] = chosen
505+
}
506+
return resolved
507+
}
508+
476509
/**
477510
* Transforms a block tool into a provider tool config with operation selection
478511
*
@@ -549,14 +582,25 @@ export async function transformBlockTool(
549582
userProvidedParams
550583
)
551584

585+
const canonicalGroups: CanonicalGroup[] = blockDef?.subBlocks
586+
? Object.values(buildCanonicalIndex(blockDef.subBlocks).groupsById).filter(isCanonicalPair)
587+
: []
588+
589+
const resolvedResourceParams = resolveCanonicalResourceParams(
590+
userProvidedParams,
591+
canonicalGroups,
592+
block.type,
593+
canonicalModes
594+
)
595+
552596
let uniqueToolId = toolConfig.id
553597
let toolName = toolConfig.name
554598
let toolDescription = enrichedDescription || toolConfig.description
555599

556-
if (toolId === 'workflow_executor' && userProvidedParams.workflowId) {
557-
uniqueToolId = `${toolConfig.id}_${userProvidedParams.workflowId}`
600+
if (toolId === 'workflow_executor' && resolvedResourceParams.workflowId) {
601+
uniqueToolId = `${toolConfig.id}_${resolvedResourceParams.workflowId}`
558602

559-
const workflowMetadata = await fetchWorkflowMetadata(userProvidedParams.workflowId)
603+
const workflowMetadata = await fetchWorkflowMetadata(resolvedResourceParams.workflowId)
560604
if (workflowMetadata) {
561605
toolName = workflowMetadata.name || toolConfig.name
562606
if (
@@ -566,21 +610,17 @@ export async function transformBlockTool(
566610
toolDescription = workflowMetadata.description
567611
}
568612
}
569-
} else if (toolId.startsWith('knowledge_') && userProvidedParams.knowledgeBaseId) {
570-
uniqueToolId = `${toolConfig.id}_${userProvidedParams.knowledgeBaseId}`
571-
} else if (toolId.startsWith('table_') && userProvidedParams.tableId) {
572-
uniqueToolId = `${toolConfig.id}_${userProvidedParams.tableId}`
613+
} else if (toolId.startsWith('knowledge_') && resolvedResourceParams.knowledgeBaseId) {
614+
uniqueToolId = `${toolConfig.id}_${resolvedResourceParams.knowledgeBaseId}`
615+
} else if (toolId.startsWith('table_') && resolvedResourceParams.tableId) {
616+
uniqueToolId = `${toolConfig.id}_${resolvedResourceParams.tableId}`
573617
}
574618

575619
const blockParamsFn = blockDef?.tools?.config?.params as
576620
| ((p: Record<string, any>) => Record<string, any>)
577621
| undefined
578622
const blockInputDefs = blockDef?.inputs as Record<string, any> | undefined
579623

580-
const canonicalGroups: CanonicalGroup[] = blockDef?.subBlocks
581-
? Object.values(buildCanonicalIndex(blockDef.subBlocks).groupsById).filter(isCanonicalPair)
582-
: []
583-
584624
const needsTransform = blockParamsFn || blockInputDefs || canonicalGroups.length > 0
585625
const paramsTransform = needsTransform
586626
? (params: Record<string, any>): Record<string, any> => {

0 commit comments

Comments
 (0)