From ca612262285ce59d2a85ccdd90addd0936bb8886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milorad=20FIlipovi=C4=87?= Date: Tue, 21 Apr 2026 09:59:02 +0200 Subject: [PATCH] fix(editor): Allow `name` parameters to be defined by AI --- .../utils/fromAIOverride.utils.test.ts | 46 +++++++++++++++++++ .../parameters/utils/fromAIOverride.utils.ts | 7 ++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/packages/frontend/editor-ui/src/features/ndv/parameters/utils/fromAIOverride.utils.test.ts b/packages/frontend/editor-ui/src/features/ndv/parameters/utils/fromAIOverride.utils.test.ts index 1f5f8962962e2..53a7bbdd562a1 100644 --- a/packages/frontend/editor-ui/src/features/ndv/parameters/utils/fromAIOverride.utils.test.ts +++ b/packages/frontend/editor-ui/src/features/ndv/parameters/utils/fromAIOverride.utils.test.ts @@ -91,6 +91,19 @@ function mockNodeFromType(type: INodeTypeDescription) { } as never); } +function mockAiToolNode(typeName: string, typeVersion: number): INodeUi { + return vi.mocked({ type: typeName, typeVersion } as never); +} + +const AI_TOOL_CODEX: INodeTypeDescription = { + name: '', + codex: { + categories: ['AI'], + subcategories: { AI: ['Tools'] }, + }, + ...MOCK_NODE_TYPE_MIXIN, +}; + describe('makeOverrideValue', () => { test.each<[string, ...Parameters]>([ ['null nodeType', makeContext(''), null], @@ -149,6 +162,39 @@ describe('makeOverrideValue', () => { expect(result).toBeDefined(); expect(result?.extraPropValues.description).not.toBeDefined(); }); + + it('creates an override for a parameter named "name" on an allowed AI tool node', () => { + getNodeType.mockReturnValue(AI_NODE_TYPE); + const result = makeOverrideValue( + makeContext('', 'parameters.name'), + mockNodeFromType(AI_NODE_TYPE), + ); + + expect(result).not.toBeNull(); + expect(result?.type).toEqual('fromAI'); + }); + + describe('legacy tool-name node denylist', () => { + test.each<[string, string, number, boolean]>([ + ['toolWorkflow v2.0 denied', '@n8n/n8n-nodes-langchain.toolWorkflow', 2.0, false], + ['toolWorkflow v2.1 denied', '@n8n/n8n-nodes-langchain.toolWorkflow', 2.1, false], + ['toolWorkflow v2.2 allowed', '@n8n/n8n-nodes-langchain.toolWorkflow', 2.2, true], + ['toolVectorStore v1 denied', '@n8n/n8n-nodes-langchain.toolVectorStore', 1, false], + ['toolVectorStore v1.1 allowed', '@n8n/n8n-nodes-langchain.toolVectorStore', 1.1, true], + ])('%s', (_name, typeName, typeVersion, shouldOverride) => { + getNodeType.mockReturnValue(AI_TOOL_CODEX); + const result = makeOverrideValue( + makeContext('', 'parameters.name'), + mockAiToolNode(typeName, typeVersion), + ); + + if (shouldOverride) { + expect(result).not.toBeNull(); + } else { + expect(result).toBeNull(); + } + }); + }); }); describe('FromAiOverride', () => { diff --git a/packages/frontend/editor-ui/src/features/ndv/parameters/utils/fromAIOverride.utils.ts b/packages/frontend/editor-ui/src/features/ndv/parameters/utils/fromAIOverride.utils.ts index 69537fa2e6148..bc7536f63c26b 100644 --- a/packages/frontend/editor-ui/src/features/ndv/parameters/utils/fromAIOverride.utils.ts +++ b/packages/frontend/editor-ui/src/features/ndv/parameters/utils/fromAIOverride.utils.ts @@ -50,11 +50,14 @@ const NODE_DENYLIST = [ '@n8n/n8n-nodes-langchain.toolCode', '@n8n/n8n-nodes-langchain.toolHttpRequest', '@n8n/n8n-nodes-langchain.mcpClientTool', - ['@n8n/n8n-nodes-langchain.toolWorkflow', 1.2], + // Legacy versions read `parameters.name` at runtime as the tool's identity; + // newer versions derive it from the node name, so $fromAI on that field would + // produce an invalid tool name. Keep these ranges in sync when bumping versions. + ['@n8n/n8n-nodes-langchain.toolWorkflow', 2.1], + ['@n8n/n8n-nodes-langchain.toolVectorStore', 1], ] as const; const PATH_DENYLIST = [ - 'parameters.name', // this is used in vector store tools 'parameters.toolName', 'parameters.description',