diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 5e18d3adc9..dc6f5ea509 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -1500,26 +1500,28 @@ export function ProspeoIcon(props: SVGProps) { xmlns='http://www.w3.org/2000/svg' > - - - - - + + + + + + + ) } @@ -4723,14 +4725,16 @@ export function ApolloIcon(props: SVGProps) { export function Neo4jIcon(props: SVGProps) { return ( - - + + + + ) } @@ -7240,34 +7244,77 @@ export function OktaIcon(props: SVGProps) { export function OnePasswordIcon(props: SVGProps) { return ( - - - - - + + + + + + + + + + + + + + + + + + + + ) } diff --git a/apps/docs/content/docs/en/tools/onepassword.mdx b/apps/docs/content/docs/en/tools/onepassword.mdx index c1fac1dab8..c9723cdace 100644 --- a/apps/docs/content/docs/en/tools/onepassword.mdx +++ b/apps/docs/content/docs/en/tools/onepassword.mdx @@ -7,7 +7,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card" {/* MANUAL-CONTENT-START:intro */} diff --git a/apps/sim/blocks/blocks/onepassword.ts b/apps/sim/blocks/blocks/onepassword.ts index 7f1398997e..8bcae49177 100644 --- a/apps/sim/blocks/blocks/onepassword.ts +++ b/apps/sim/blocks/blocks/onepassword.ts @@ -10,7 +10,7 @@ export const OnePasswordBlock: BlockConfig = { docsLink: 'https://docs.sim.ai/tools/onepassword', category: 'tools', integrationType: IntegrationType.Security, - bgColor: '#145FE4', + bgColor: '#FFFFFF', icon: OnePasswordIcon, authMode: AuthMode.ApiKey, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 5e18d3adc9..dc6f5ea509 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -1500,26 +1500,28 @@ export function ProspeoIcon(props: SVGProps) { xmlns='http://www.w3.org/2000/svg' > - - - - - + + + + + + + ) } @@ -4723,14 +4725,16 @@ export function ApolloIcon(props: SVGProps) { export function Neo4jIcon(props: SVGProps) { return ( - - + + + + ) } @@ -7240,34 +7244,77 @@ export function OktaIcon(props: SVGProps) { export function OnePasswordIcon(props: SVGProps) { return ( - - - - - + + + + + + + + + + + + + + + + + + + + ) } diff --git a/apps/sim/lib/integrations/integrations.json b/apps/sim/lib/integrations/integrations.json index 324e27d3ea..84f8cbf67c 100644 --- a/apps/sim/lib/integrations/integrations.json +++ b/apps/sim/lib/integrations/integrations.json @@ -5,7 +5,7 @@ "name": "1Password", "description": "Manage secrets and items in 1Password vaults", "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.", - "bgColor": "#145FE4", + "bgColor": "#FFFFFF", "iconName": "OnePasswordIcon", "docsUrl": "https://docs.sim.ai/tools/onepassword", "operations": [ diff --git a/apps/sim/providers/utils.test.ts b/apps/sim/providers/utils.test.ts index 03e50c78f2..be01f0946c 100644 --- a/apps/sim/providers/utils.test.ts +++ b/apps/sim/providers/utils.test.ts @@ -38,6 +38,7 @@ import { supportsThinking, supportsToolUsageControl, supportsVerbosity, + transformBlockTool, updateOllamaProviderModels, } from '@/providers/utils' @@ -1514,3 +1515,130 @@ describe('Provider/Model Blacklist', () => { }) }) }) + +describe('transformBlockTool multi-instance unique IDs', () => { + const tableBlockDef = { + type: 'table', + inputs: {}, + subBlocks: [ + { id: 'operation', type: 'dropdown' }, + { id: 'tableSelector', type: 'table-selector', canonicalParamId: 'tableId', mode: 'basic' }, + { + id: 'manualTableId', + type: 'short-input', + canonicalParamId: 'tableId', + mode: 'advanced', + }, + ], + tools: { + access: ['table_query_rows', 'table_insert_row'], + config: { tool: () => 'table_query_rows' }, + }, + } + + const getAllBlocks = () => [tableBlockDef] + const getTool = (id: string) => ({ + id, + name: 'Query Rows', + description: 'Query table rows', + params: {}, + }) + + const transformTable = ( + params: Record, + canonicalModes?: Record + ) => + transformBlockTool( + { type: 'table', operation: 'query_rows', params }, + { selectedOperation: 'query_rows', getAllBlocks, getTool, canonicalModes } + ) + + it('appends the table id when stored under the basic selector subblock key', async () => { + const result = await transformTable({ tableSelector: 'tbl_abc' }) + expect(result?.id).toBe('table_query_rows_tbl_abc') + }) + + it('appends the table id resolved from the advanced manual input', async () => { + const result = await transformTable( + { manualTableId: 'tbl_xyz' }, + { 'table:tableId': 'advanced' } + ) + expect(result?.id).toBe('table_query_rows_tbl_xyz') + }) + + it('appends the canonical table id when already present in params', async () => { + const result = await transformTable({ tableId: 'tbl_direct' }) + expect(result?.id).toBe('table_query_rows_tbl_direct') + }) + + it('falls back to the base tool id when no table is selected', async () => { + const result = await transformTable({}) + expect(result?.id).toBe('table_query_rows') + }) +}) + +describe('transformBlockTool knowledge-base multi-instance unique IDs', () => { + const knowledgeBlockDef = { + type: 'knowledge', + inputs: {}, + subBlocks: [ + { id: 'operation', type: 'dropdown' }, + { + id: 'knowledgeBaseSelector', + type: 'knowledge-base-selector', + canonicalParamId: 'knowledgeBaseId', + mode: 'basic', + }, + { + id: 'manualKnowledgeBaseId', + type: 'short-input', + canonicalParamId: 'knowledgeBaseId', + mode: 'advanced', + }, + ], + tools: { + access: ['knowledge_search', 'knowledge_upload_chunk'], + config: { tool: () => 'knowledge_search' }, + }, + } + + const getAllBlocks = () => [knowledgeBlockDef] + const getTool = (id: string) => ({ + id, + name: 'Search', + description: 'Search the knowledge base', + params: {}, + }) + + const transformKb = ( + params: Record, + canonicalModes?: Record + ) => + transformBlockTool( + { type: 'knowledge', operation: 'search', params }, + { selectedOperation: 'search', getAllBlocks, getTool, canonicalModes } + ) + + it('appends the knowledge base id when stored under the basic selector subblock key', async () => { + const result = await transformKb({ knowledgeBaseSelector: 'kb_abc' }) + expect(result?.id).toBe('knowledge_search_kb_abc') + }) + + it('appends the knowledge base id resolved from the advanced manual input', async () => { + const result = await transformKb( + { manualKnowledgeBaseId: 'kb_xyz' }, + { 'knowledge:knowledgeBaseId': 'advanced' } + ) + expect(result?.id).toBe('knowledge_search_kb_xyz') + }) + + it('appends the canonical knowledge base id when already present in params', async () => { + const result = await transformKb({ knowledgeBaseId: 'kb_direct' }) + expect(result?.id).toBe('knowledge_search_kb_direct') + }) + + it('falls back to the base tool id when no knowledge base is selected', async () => { + const result = await transformKb({}) + expect(result?.id).toBe('knowledge_search') + }) +}) diff --git a/apps/sim/providers/utils.ts b/apps/sim/providers/utils.ts index d9fe8cbab3..b2021817b6 100644 --- a/apps/sim/providers/utils.ts +++ b/apps/sim/providers/utils.ts @@ -473,6 +473,39 @@ export function extractAndParseJSON(content: string): any { } } +/** + * Resolves canonical pair ids (e.g. `tableId`, `knowledgeBaseId`) from a tool's + * raw params, filling them in from their basic/advanced selector subblock source + * values when the canonical key isn't already present. + * + * Selector subblocks persist their value under the subblock id (e.g. + * `tableSelector`), not the canonical id, so any lookup that keys off the + * canonical id — like the unique-tool-id suffix below — must resolve it first. + * Mode selection mirrors {@link transformBlockTool}'s execution-time + * `paramsTransform` so the resolved id matches the params the tool actually runs + * with. + * + * @returns The params with canonical resource ids resolved (non-destructive) + */ +function resolveCanonicalResourceParams( + params: Record, + canonicalGroups: CanonicalGroup[], + blockType: string, + canonicalModes?: Record +): Record { + if (canonicalGroups.length === 0) return params + const resolved = { ...params } + for (const group of canonicalGroups) { + const existing = resolved[group.canonicalId] + if (existing !== undefined && existing !== null && existing !== '') continue + const { basicValue, advancedValue } = getCanonicalValues(group, params) + const pairMode = canonicalModes?.[`${blockType}:${group.canonicalId}`] ?? 'basic' + const chosen = pairMode === 'advanced' ? advancedValue : basicValue + if (chosen !== undefined) resolved[group.canonicalId] = chosen + } + return resolved +} + /** * Transforms a block tool into a provider tool config with operation selection * @@ -549,14 +582,25 @@ export async function transformBlockTool( userProvidedParams ) + const canonicalGroups: CanonicalGroup[] = blockDef?.subBlocks + ? Object.values(buildCanonicalIndex(blockDef.subBlocks).groupsById).filter(isCanonicalPair) + : [] + + const resolvedResourceParams = resolveCanonicalResourceParams( + userProvidedParams, + canonicalGroups, + block.type, + canonicalModes + ) + let uniqueToolId = toolConfig.id let toolName = toolConfig.name let toolDescription = enrichedDescription || toolConfig.description - if (toolId === 'workflow_executor' && userProvidedParams.workflowId) { - uniqueToolId = `${toolConfig.id}_${userProvidedParams.workflowId}` + if (toolId === 'workflow_executor' && resolvedResourceParams.workflowId) { + uniqueToolId = `${toolConfig.id}_${resolvedResourceParams.workflowId}` - const workflowMetadata = await fetchWorkflowMetadata(userProvidedParams.workflowId) + const workflowMetadata = await fetchWorkflowMetadata(resolvedResourceParams.workflowId) if (workflowMetadata) { toolName = workflowMetadata.name || toolConfig.name if ( @@ -566,10 +610,10 @@ export async function transformBlockTool( toolDescription = workflowMetadata.description } } - } else if (toolId.startsWith('knowledge_') && userProvidedParams.knowledgeBaseId) { - uniqueToolId = `${toolConfig.id}_${userProvidedParams.knowledgeBaseId}` - } else if (toolId.startsWith('table_') && userProvidedParams.tableId) { - uniqueToolId = `${toolConfig.id}_${userProvidedParams.tableId}` + } else if (toolId.startsWith('knowledge_') && resolvedResourceParams.knowledgeBaseId) { + uniqueToolId = `${toolConfig.id}_${resolvedResourceParams.knowledgeBaseId}` + } else if (toolId.startsWith('table_') && resolvedResourceParams.tableId) { + uniqueToolId = `${toolConfig.id}_${resolvedResourceParams.tableId}` } const blockParamsFn = blockDef?.tools?.config?.params as @@ -577,10 +621,6 @@ export async function transformBlockTool( | undefined const blockInputDefs = blockDef?.inputs as Record | undefined - const canonicalGroups: CanonicalGroup[] = blockDef?.subBlocks - ? Object.values(buildCanonicalIndex(blockDef.subBlocks).groupsById).filter(isCanonicalPair) - : [] - const needsTransform = blockParamsFn || blockInputDefs || canonicalGroups.length > 0 const paramsTransform = needsTransform ? (params: Record): Record => {