From 388cd79908418d558fff36f938969cdc79fc60c2 Mon Sep 17 00:00:00 2001 From: Jaakko Husso Date: Wed, 29 Apr 2026 17:25:20 +0300 Subject: [PATCH] fix(core): Pass nodeTypesProvider to validate workflows fully at instance AI (#29333) --- .../__tests__/submit-workflow.tool.test.ts | 96 +++++++++++++++++++ .../tools/workflows/build-workflow.tool.ts | 4 +- .../tools/workflows/submit-workflow.tool.ts | 9 +- packages/@n8n/instance-ai/src/types.ts | 6 ++ .../__tests__/parse-validate.test.ts | 29 ++++++ .../src/workflow-builder/parse-validate.ts | 24 ++++- packages/@n8n/workflow-sdk/package.json | 2 +- .../@n8n/workflow-sdk/src/validation/index.ts | 51 ++++++++-- .../src/validation/validation.test.ts | 88 ++++++++++++++++- ...stance-ai.adapter.service.security.test.ts | 3 + .../instance-ai.adapter.service.test.ts | 34 ++++--- .../instance-ai.adapter.service.ts | 3 + 12 files changed, 319 insertions(+), 30 deletions(-) diff --git a/packages/@n8n/instance-ai/src/tools/workflows/__tests__/submit-workflow.tool.test.ts b/packages/@n8n/instance-ai/src/tools/workflows/__tests__/submit-workflow.tool.test.ts index a305c0f6a84..3ca8a422d7c 100644 --- a/packages/@n8n/instance-ai/src/tools/workflows/__tests__/submit-workflow.tool.test.ts +++ b/packages/@n8n/instance-ai/src/tools/workflows/__tests__/submit-workflow.tool.test.ts @@ -1,4 +1,7 @@ import type { Workspace } from '@mastra/core/workspace'; +import { validateWorkflow } from '@n8n/workflow-sdk'; +import { mock } from 'jest-mock-extended'; +import type { INodeTypes } from 'n8n-workflow'; import type { InstanceAiContext } from '../../../types'; import { isTriggerNodeType, type SubmitWorkflowAttempt } from '../submit-workflow.tool'; @@ -7,10 +10,21 @@ jest.mock('@mastra/core/tools', () => ({ createTool: jest.fn((config: Record) => config), })); +jest.mock('@n8n/workflow-sdk', () => ({ + validateWorkflow: jest.fn(() => ({ errors: [], warnings: [] })), + layoutWorkflowJSON: jest.fn((wf: unknown) => wf), +})); + +// `require` (rather than `import`) is needed because `submit-workflow.tool` +// transitively pulls in @mastra/core (ESM-only); the require call here runs +// AFTER the `jest.mock('@mastra/core/tools', …)` above, so the mock is in +// place before the module is evaluated. const { createSubmitWorkflowTool } = // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/consistent-type-imports require('../submit-workflow.tool') as typeof import('../submit-workflow.tool'); +const mockedValidateWorkflow = jest.mocked(validateWorkflow); + type Executable = { execute: (input: Record) => Promise<{ success: boolean; @@ -20,10 +34,12 @@ type Executable = { function makeContext( permissions: InstanceAiContext['permissions'] = {} as InstanceAiContext['permissions'], + overrides: Partial = {}, ): InstanceAiContext { return { permissions, workflowService: {} as InstanceAiContext['workflowService'], + ...overrides, } as unknown as InstanceAiContext; } @@ -46,6 +62,86 @@ function makeWorkspace(): Workspace { } as unknown as Workspace; } +/** Workspace stub that simulates a successful sandbox build by emitting + * parseable build.mjs output for the build command. */ +function makeBuildSuccessWorkspace( + workflowJson: object = { + id: 'wf-1', + name: 'Test', + nodes: [], + connections: {}, + }, +): Workspace { + return { + sandbox: { + executeCommand: async (command: string) => { + await Promise.resolve(); + if (command === 'echo $HOME') { + return { exitCode: 0, stdout: '/home/test\n', stderr: '' }; + } + if (command.startsWith('node --import tsx build.mjs')) { + return { + exitCode: 0, + stdout: JSON.stringify({ + success: true, + workflow: workflowJson, + warnings: [], + }), + stderr: '', + }; + } + return { exitCode: 0, stdout: '', stderr: '' }; + }, + }, + } as unknown as Workspace; +} + +describe('createSubmitWorkflowTool — schema validation wiring', () => { + beforeEach(() => { + mockedValidateWorkflow.mockReset(); + mockedValidateWorkflow.mockReturnValue({ + // One blocking error so we early-return before workflowService.create/update is called. + errors: [{ code: 'INVALID_PARAM', message: 'forced for test', nodeName: 'X' }], + warnings: [], + } as never); + }); + + it('forwards context.nodeTypesProvider into validateWorkflow', async () => { + const nodeTypesProvider = mock(); + const context = makeContext({} as InstanceAiContext['permissions'], { + nodeTypesProvider, + }); + + const tool = createSubmitWorkflowTool( + context, + makeBuildSuccessWorkspace(), + new Map(), + ) as unknown as Executable; + + await tool.execute({ filePath: 'src/workflow.ts', name: 'Test' }); + + expect(mockedValidateWorkflow).toHaveBeenCalledWith(expect.any(Object), { + nodeTypesProvider, + strictMode: true, + }); + }); + + it('passes undefined nodeTypesProvider when context has none, strictMode still on', async () => { + const tool = createSubmitWorkflowTool( + makeContext(), + makeBuildSuccessWorkspace(), + new Map(), + ) as unknown as Executable; + + await tool.execute({ filePath: 'src/workflow.ts', name: 'Test' }); + + expect(mockedValidateWorkflow).toHaveBeenCalledWith(expect.any(Object), { + nodeTypesProvider: undefined, + strictMode: true, + }); + }); +}); + describe('isTriggerNodeType', () => { it.each([ 'n8n-nodes-base.webhook', diff --git a/packages/@n8n/instance-ai/src/tools/workflows/build-workflow.tool.ts b/packages/@n8n/instance-ai/src/tools/workflows/build-workflow.tool.ts index 0850178799b..03261808c97 100644 --- a/packages/@n8n/instance-ai/src/tools/workflows/build-workflow.tool.ts +++ b/packages/@n8n/instance-ai/src/tools/workflows/build-workflow.tool.ts @@ -123,7 +123,9 @@ export function createBuildWorkflowTool(context: InstanceAiContext) { // Parse TypeScript to WorkflowJSON with two-stage validation let result; try { - result = parseAndValidate(finalCode); + result = parseAndValidate(finalCode, { + nodeTypesProvider: context.nodeTypesProvider, + }); } catch (error) { return { success: false, diff --git a/packages/@n8n/instance-ai/src/tools/workflows/submit-workflow.tool.ts b/packages/@n8n/instance-ai/src/tools/workflows/submit-workflow.tool.ts index 0b74bac803e..305f939e967 100644 --- a/packages/@n8n/instance-ai/src/tools/workflows/submit-workflow.tool.ts +++ b/packages/@n8n/instance-ai/src/tools/workflows/submit-workflow.tool.ts @@ -308,8 +308,13 @@ export function createSubmitWorkflowTool( nodeName: w.nodeName, })); - // Server-side schema validation (Zod checks against node type definitions) - const schemaValidation = validateWorkflow(buildOutput.workflow); + // Server-side schema validation (Zod checks against node type definitions). + // strictMode is hardcoded on at AI-builder call sites — we want every + // catchable bug surfaced as a blocking error so the agent can self-correct. + const schemaValidation = validateWorkflow(buildOutput.workflow, { + nodeTypesProvider: context.nodeTypesProvider, + strictMode: true, + }); for (const issue of [...schemaValidation.errors, ...schemaValidation.warnings]) { allWarnings.push({ code: issue.code, diff --git a/packages/@n8n/instance-ai/src/types.ts b/packages/@n8n/instance-ai/src/types.ts index 9191a75552b..bd27a06292a 100644 --- a/packages/@n8n/instance-ai/src/types.ts +++ b/packages/@n8n/instance-ai/src/types.ts @@ -12,6 +12,7 @@ import type { McpToolCallResult, } from '@n8n/api-types'; import type { WorkflowJSON } from '@n8n/workflow-sdk'; +import type { INodeTypes } from 'n8n-workflow'; // Service interfaces — dependency inversion so the package stays decoupled from n8n internals. // The backend module provides concrete implementations via InstanceAiAdapterService. @@ -595,6 +596,11 @@ export interface InstanceAiContext { currentUserAttachments?: InstanceAiAttachment[]; /** Optional logger for diagnostics from domain tools. */ logger?: Logger; + /** Synchronous node-types provider used by host-side schema validation + * (`validateWorkflow` from `@n8n/workflow-sdk`). Plumbed from the CLI + * adapter; absent in pure-package contexts where no NodeTypes instance + * is reachable. */ + nodeTypesProvider?: INodeTypes; } // ── Task storage ───────────────────────────────────────────────────────────── diff --git a/packages/@n8n/instance-ai/src/workflow-builder/__tests__/parse-validate.test.ts b/packages/@n8n/instance-ai/src/workflow-builder/__tests__/parse-validate.test.ts index 8a09938b0b2..2cd6fb702b2 100644 --- a/packages/@n8n/instance-ai/src/workflow-builder/__tests__/parse-validate.test.ts +++ b/packages/@n8n/instance-ai/src/workflow-builder/__tests__/parse-validate.test.ts @@ -8,6 +8,8 @@ jest.mock('../extract-code', () => ({ })); import { parseWorkflowCodeToBuilder, validateWorkflow } from '@n8n/workflow-sdk'; +import { mock } from 'jest-mock-extended'; +import type { INodeTypes } from 'n8n-workflow'; import { stripImportStatements } from '../extract-code'; import { parseAndValidate, partitionWarnings } from '../parse-validate'; @@ -119,6 +121,33 @@ describe('parseAndValidate', () => { 'Failed to parse workflow code: Unknown error', ); }); + + it('forwards nodeTypesProvider to both validators with strictMode on for schema validation', () => { + const builder = makeBuilder(); + mockedParseWorkflowCodeToBuilder.mockReturnValue(builder as never); + const nodeTypesProvider = mock(); + + parseAndValidate('code', { nodeTypesProvider }); + + expect(builder.validate).toHaveBeenCalledWith({ nodeTypesProvider }); + expect(mockedValidateWorkflow).toHaveBeenCalledWith(expect.any(Object), { + nodeTypesProvider, + strictMode: true, + }); + }); + + it('passes undefined provider but keeps strictMode on when no options are supplied', () => { + const builder = makeBuilder(); + mockedParseWorkflowCodeToBuilder.mockReturnValue(builder as never); + + parseAndValidate('code'); + + expect(builder.validate).toHaveBeenCalledWith({ nodeTypesProvider: undefined }); + expect(mockedValidateWorkflow).toHaveBeenCalledWith(expect.any(Object), { + nodeTypesProvider: undefined, + strictMode: true, + }); + }); }); describe('partitionWarnings', () => { diff --git a/packages/@n8n/instance-ai/src/workflow-builder/parse-validate.ts b/packages/@n8n/instance-ai/src/workflow-builder/parse-validate.ts index 9dbe44d1110..49f9c4250f5 100644 --- a/packages/@n8n/instance-ai/src/workflow-builder/parse-validate.ts +++ b/packages/@n8n/instance-ai/src/workflow-builder/parse-validate.ts @@ -7,10 +7,19 @@ */ import { parseWorkflowCodeToBuilder, validateWorkflow } from '@n8n/workflow-sdk'; +import type { INodeTypes } from 'n8n-workflow'; import { stripImportStatements } from './extract-code'; import type { ParseAndValidateResult, ValidationWarning } from './types'; +export interface ParseAndValidateOptions { + /** Synchronous node-types provider used by both graph and schema validators. + * Without it, AI-aware checks (`MISSING_REQUIRED_INPUT`, + * `UNSUPPORTED_SUBNODE_INPUT`, `SUBNODE_PARAMETER_MISMATCH`, + * `INVALID_INPUT_INDEX`) are silently skipped. */ + nodeTypesProvider?: INodeTypes; +} + /** Validation issue from graph or JSON validation */ interface ValidationIssue { code: string; @@ -46,10 +55,15 @@ function collectValidationIssues( * @returns ParseAndValidateResult with workflow JSON and any warnings/errors * @throws Error if parsing fails */ -export function parseAndValidate(code: string): ParseAndValidateResult { +export function parseAndValidate( + code: string, + options: ParseAndValidateOptions = {}, +): ParseAndValidateResult { // Strip import statements before parsing — SDK functions are available as globals const codeToParse = stripImportStatements(code); + const { nodeTypesProvider } = options; + try { // Parse the TypeScript code to WorkflowBuilder const builder = parseWorkflowCodeToBuilder(codeToParse); @@ -60,14 +74,16 @@ export function parseAndValidate(code: string): ParseAndValidateResult { const allWarnings: ValidationWarning[] = []; // Stage 1: Structural validation via graph validators - const graphValidation = builder.validate(); + const graphValidation = builder.validate({ nodeTypesProvider }); collectValidationIssues(graphValidation.errors, allWarnings); collectValidationIssues(graphValidation.warnings, allWarnings); const json = builder.toJSON(); - // Stage 2: Schema validation via Zod schemas from schemaBaseDirs - const schemaValidation = validateWorkflow(json); + // Stage 2: Schema validation via Zod schemas from schemaBaseDirs. + // strictMode is hardcoded on at AI-builder call sites — we want every + // catchable bug surfaced as a blocking error so the agent can self-correct. + const schemaValidation = validateWorkflow(json, { nodeTypesProvider, strictMode: true }); collectValidationIssues(schemaValidation.errors, allWarnings); collectValidationIssues(schemaValidation.warnings, allWarnings); diff --git a/packages/@n8n/workflow-sdk/package.json b/packages/@n8n/workflow-sdk/package.json index f435c3d7fa5..16a9772d3d4 100644 --- a/packages/@n8n/workflow-sdk/package.json +++ b/packages/@n8n/workflow-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@n8n/workflow-sdk", - "version": "0.12.0", + "version": "0.12.1", "description": "TypeScript SDK for programmatically creating n8n workflows", "exports": { ".": { diff --git a/packages/@n8n/workflow-sdk/src/validation/index.ts b/packages/@n8n/workflow-sdk/src/validation/index.ts index dd088e28287..cfe1bbc3773 100644 --- a/packages/@n8n/workflow-sdk/src/validation/index.ts +++ b/packages/@n8n/workflow-sdk/src/validation/index.ts @@ -1,3 +1,4 @@ +import get from 'lodash/get'; import type { INodeTypes, IConnections as N8nIConnections, IDisplayOptions } from 'n8n-workflow'; import { mapConnectionsByDestination } from 'n8n-workflow'; @@ -662,6 +663,16 @@ function validateSubnodeParameters( ); if (!matches) { + // `displayOptions` on `builderHint.inputs[type]` can describe + // either subnode-relative params (e.g. ai_vectorStore wants + // vector-store mode='retrieve-as-tool') or parent-relative + // params (e.g. ai_memory wants chatTrigger mode='hostedChat'). + // If every mismatched param is absent from the subnode, those + // params don't belong to the subnode at all — blaming it is a + // false-positive misdirect. Defer to validateParentSupportsInputs. + const subnodeOwnsAnyParam = mismatches.some((m) => m.actual !== undefined); + if (!subnodeOwnsAnyParam) continue; + const sdkFn = AI_CONNECTION_TO_SDK_FUNCTION[connectionType] || connectionType; // Build error message with actual parameter names from displayOptions @@ -699,7 +710,10 @@ function buildConditionSummary( const parts: string[] = []; for (const [paramName, expectedValues] of Object.entries(displayOptions.show)) { if (!expectedValues) continue; - const actual = parentParams[paramName]; + // Use lodash get so nested paths (e.g. 'options.loadPreviousSession') + // resolve correctly — direct property access would read the literal + // dotted key and report 'undefined' even when the nested value is set. + const actual = get(parentParams, paramName); const expectedStr = (expectedValues as unknown[]).map((v) => `'${String(v)}'`).join(' or '); parts.push(`${paramName} should be ${expectedStr} (currently '${String(actual)}')`); } @@ -707,6 +721,28 @@ function buildConditionSummary( return parts.length > 0 ? `Required: ${parts.join(', ')}.` : ''; } +/** + * Build a description of which parameters TRIGGERED a requirement. + * Used by `MISSING_REQUIRED_INPUT` where the displayOptions conditions are + * already satisfied (that's why the requirement applies) — the agent needs + * to know which params caused it so it can choose between satisfying the + * requirement or backing out by changing those params. + */ +function buildTriggeringConditionSummary( + displayOptions: IDisplayOptions, + parentParams: Record, +): string { + if (!displayOptions.show) return ''; + + const parts: string[] = []; + for (const [paramName, _expectedValues] of Object.entries(displayOptions.show)) { + const actual = get(parentParams, paramName); + parts.push(`${paramName}='${String(actual)}'`); + } + + return parts.join(', '); +} + /** * Validate that parent nodes actually support their connected AI input types * based on the parent's own parameters and builderHint.inputs displayOptions. @@ -775,7 +811,7 @@ function validateParentSupportsInputs( warnings.push( new ValidationWarning( 'UNSUPPORTED_SUBNODE_INPUT', - `'${subnodeName}' is connected to '${parentNode.name}' as ${subnodeField}, but '${parentNode.name}' does not support ${subnodeField} in its current configuration. ${conditionDetails}`, + `'${parentNode.name}' has a ${subnodeField} subnode ('${subnodeName}') connected, but its current configuration does not accept one. ${conditionDetails} These parameters must be set on '${parentNode.name}' itself, NOT on the ${subnodeField} subnode. Alternatively, remove the ${subnodeField} connection if this capability isn't needed.`, parentNode.name, undefined, undefined, @@ -844,17 +880,20 @@ function validateRequiredInputsConnected( if (hasConnection) continue; const subnodeField = AI_CONNECTION_TO_SUBNODE_FIELD[connectionType] || connectionType; - const conditionDetails = inputConfig.displayOptions - ? ` ${buildConditionSummary( + const triggerDetails = inputConfig.displayOptions + ? ` (triggered by ${buildTriggeringConditionSummary( inputConfig.displayOptions, (parentNode.parameters ?? {}) as Record, - )}` + )})` + : ''; + const alternative = inputConfig.displayOptions + ? ` Either connect a ${subnodeField} subnode, or change those parameters to remove the requirement.` : ''; errors.push( new ValidationError( 'MISSING_REQUIRED_INPUT', - `'${parentNode.name}' requires a ${subnodeField} subnode connected to its ${connectionType} input, but none is connected.${conditionDetails}`, + `'${parentNode.name}' requires a ${subnodeField} subnode connected to its ${connectionType} input${triggerDetails}, but none is connected.${alternative}`, parentNode.name, undefined, 'major', diff --git a/packages/@n8n/workflow-sdk/src/validation/validation.test.ts b/packages/@n8n/workflow-sdk/src/validation/validation.test.ts index 01c72582d79..0dfccb919a2 100644 --- a/packages/@n8n/workflow-sdk/src/validation/validation.test.ts +++ b/packages/@n8n/workflow-sdk/src/validation/validation.test.ts @@ -2218,6 +2218,87 @@ describe('Validation', () => { const warnings = result.warnings.filter((w) => w.code === 'UNSUPPORTED_SUBNODE_INPUT'); expect(warnings).toHaveLength(0); }); + + it('does not also fire SUBNODE_PARAMETER_MISMATCH for parent-relative displayOptions (INS-136)', () => { + // Scenario: chat trigger that has loadPreviousSession: memory but no + // `mode` set, so the parent fails its own displayOptions. The memory + // subnode is connected - but `mode` and `options.loadPreviousSession` + // are parent params, not subnode params. Only UNSUPPORTED_SUBNODE_INPUT + // should fire (parent-relative); SUBNODE_PARAMETER_MISMATCH is a false + // positive when the subnode doesn't even own those params. + const provider = { + getByNameAndVersion: (type: string) => { + if (type === '@n8n/n8n-nodes-langchain.chatTrigger') { + return { + description: { + inputs: ['main'], + builderHint: { + inputs: { + ai_memory: { + required: true, + displayOptions: { + show: { + mode: ['hostedChat', 'webhook'], + 'options.loadPreviousSession': ['memory'], + }, + }, + }, + }, + }, + }, + }; + } + return { description: { inputs: ['main'] } }; + }, + getByName: (type: string) => provider.getByNameAndVersion(type), + getKnownTypes: () => ({}), + }; + + const workflowJson = { + id: 'test', + name: 'Test', + nodes: [ + { + id: 'ct-1', + name: 'Chat Trigger', + type: '@n8n/n8n-nodes-langchain.chatTrigger', + typeVersion: 1.4, + position: [0, 0] as [number, number], + // Parent's own `mode` and `options.loadPreviousSession` not set + // -> parent fails displayOptions + parameters: {}, + }, + { + id: 'mem-1', + name: 'Session Memory', + type: '@n8n/n8n-nodes-langchain.memoryBufferWindow', + typeVersion: 1, + position: [0, 200] as [number, number], + parameters: {}, // memory subnode has no `mode` or `options.loadPreviousSession` + }, + ], + connections: { + 'Session Memory': { + ai_memory: [[{ node: 'Chat Trigger', type: 'ai_memory', index: 0 }]], + }, + }, + }; + + const result = validateWorkflow(workflowJson, { + nodeTypesProvider: provider as never, + allowDisconnectedNodes: true, + }); + + const mismatch = result.warnings.filter((w) => w.code === 'SUBNODE_PARAMETER_MISMATCH'); + const unsupported = result.warnings.filter((w) => w.code === 'UNSUPPORTED_SUBNODE_INPUT'); + + expect(mismatch).toHaveLength(0); + expect(unsupported).toHaveLength(1); + expect(unsupported[0].nodeName).toBe('Chat Trigger'); + // Message should direct the LLM to set params on the parent + expect(unsupported[0].message).toContain("must be set on 'Chat Trigger' itself"); + expect(unsupported[0].message).toContain('NOT on the memory subnode'); + }); }); describe('MISSING_REQUIRED_INPUT validation', () => { @@ -2291,7 +2372,12 @@ describe('Validation', () => { expect(errors).toHaveLength(1); expect(errors[0].nodeName).toBe('Chat Trigger'); expect(errors[0].message).toContain('ai_memory'); - expect(errors[0].message).toContain('loadPreviousSession'); + // The triggering condition reports the actual nested value via lodash get, + // not the literal dotted-key lookup that would resolve to 'undefined'. + expect(errors[0].message).toContain("options.loadPreviousSession='memory'"); + expect(errors[0].message).not.toContain("options.loadPreviousSession='undefined'"); + // Message offers the alternative path (change the params, don't connect). + expect(errors[0].message).toContain('change those parameters to remove the requirement'); expect(result.valid).toBe(false); }); diff --git a/packages/cli/src/modules/instance-ai/__tests__/instance-ai.adapter.service.security.test.ts b/packages/cli/src/modules/instance-ai/__tests__/instance-ai.adapter.service.security.test.ts index d4869282a65..b3fb239c6a2 100644 --- a/packages/cli/src/modules/instance-ai/__tests__/instance-ai.adapter.service.security.test.ts +++ b/packages/cli/src/modules/instance-ai/__tests__/instance-ai.adapter.service.security.test.ts @@ -32,6 +32,7 @@ import type { CredentialsFinderService } from '@/credentials/credentials-finder. import type { ActiveExecutions } from '@/active-executions'; import type { WorkflowRunner } from '@/workflow-runner'; import type { LoadNodesAndCredentials } from '@/load-nodes-and-credentials'; +import type { NodeTypes } from '@/node-types'; import type { DataTableService } from '@/modules/data-table/data-table.service'; import type { DataTableRepository } from '@/modules/data-table/data-table.repository'; import type { DynamicNodeParametersService } from '@/services/dynamic-node-parameters.service'; @@ -76,6 +77,7 @@ const credentialsFinderService = mock(); const activeExecutions = mock(); const workflowRunner = mock(); const loadNodesAndCredentials = mock(); +const nodeTypes = mock(); const dataTableService = mock(); const dataTableRepository = mock(); const dynamicNodeParametersService = mock(); @@ -107,6 +109,7 @@ const service = new InstanceAiAdapterService( activeExecutions, workflowRunner, loadNodesAndCredentials, + nodeTypes, mock({ staticCacheDir: '/tmp/test-cache' }), dataTableService, dataTableRepository, diff --git a/packages/cli/src/modules/instance-ai/__tests__/instance-ai.adapter.service.test.ts b/packages/cli/src/modules/instance-ai/__tests__/instance-ai.adapter.service.test.ts index 0fc03250c0c..3b1cd594a5b 100644 --- a/packages/cli/src/modules/instance-ai/__tests__/instance-ai.adapter.service.test.ts +++ b/packages/cli/src/modules/instance-ai/__tests__/instance-ai.adapter.service.test.ts @@ -726,29 +726,30 @@ function createNodeAdapterForTests(nodes: Array>) { {} as unknown as ConstructorParameters[10], {} as unknown as ConstructorParameters[11], {} as unknown as ConstructorParameters[12], + {} as unknown as ConstructorParameters[13], { staticCacheDir: '/tmp' } as unknown as ConstructorParameters< typeof InstanceAiAdapterService - >[13], - {} as unknown as ConstructorParameters[14], + >[14], {} as unknown as ConstructorParameters[15], {} as unknown as ConstructorParameters[16], {} as unknown as ConstructorParameters[17], {} as unknown as ConstructorParameters[18], {} as unknown as ConstructorParameters[19], + {} as unknown as ConstructorParameters[20], { getPreferences: jest.fn().mockReturnValue({ branchReadOnly: false }), - } as unknown as ConstructorParameters[20], - {} as unknown as ConstructorParameters[21], + } as unknown as ConstructorParameters[21], {} as unknown as ConstructorParameters[22], {} as unknown as ConstructorParameters[23], + {} as unknown as ConstructorParameters[24], { isLicensed: jest.fn().mockReturnValue(false) } as unknown as ConstructorParameters< typeof InstanceAiAdapterService - >[24], - {} as unknown as ConstructorParameters[25], + >[25], {} as unknown as ConstructorParameters[26], {} as unknown as ConstructorParameters[27], {} as unknown as ConstructorParameters[28], {} as unknown as ConstructorParameters[29], + {} as unknown as ConstructorParameters[30], ); ( @@ -862,22 +863,23 @@ function createDataTableAdapterForTests(overrides?: { collectTypes: jest.fn().mockResolvedValue({ nodes: [], credentials: [] }), } as unknown as ConstructorParameters[12], {} as unknown as ConstructorParameters[13], + {} as unknown as ConstructorParameters[14], mockDataTableService as unknown as DataTableService, mockDataTableRepository as unknown as DataTableRepository, - {} as unknown as ConstructorParameters[16], {} as unknown as ConstructorParameters[17], {} as unknown as ConstructorParameters[18], {} as unknown as ConstructorParameters[19], + {} as unknown as ConstructorParameters[20], mockSourceControlPreferencesService as unknown as SourceControlPreferencesService, - {} as unknown as ConstructorParameters[21], {} as unknown as ConstructorParameters[22], {} as unknown as ConstructorParameters[23], + {} as unknown as ConstructorParameters[24], { isLicensed: jest.fn().mockReturnValue(false) } as unknown as License, - {} as unknown as ConstructorParameters[25], {} as unknown as ConstructorParameters[26], {} as unknown as ConstructorParameters[27], {} as unknown as ConstructorParameters[28], {} as unknown as ConstructorParameters[29], + {} as unknown as ConstructorParameters[30], ); const adapter = service.createContext(mockUser).dataTableService; @@ -1133,14 +1135,15 @@ function createWorkflowAdapterForTests(overrides?: { {} as unknown as ConstructorParameters[17], {} as unknown as ConstructorParameters[18], {} as unknown as ConstructorParameters[19], + {} as unknown as ConstructorParameters[20], { getPreferences: jest .fn() .mockReturnValue({ branchReadOnly: overrides?.branchReadOnly ?? false }), } as unknown as SourceControlPreferencesService, - {} as unknown as ConstructorParameters[21], {} as unknown as ConstructorParameters[22], {} as unknown as ConstructorParameters[23], + {} as unknown as ConstructorParameters[24], { isLicensed: jest.fn().mockImplementation((feat: string) => { if (feat === 'feat:namedVersions') return overrides?.namedVersionsLicensed ?? false; @@ -1149,10 +1152,10 @@ function createWorkflowAdapterForTests(overrides?: { }), isSharingEnabled: jest.fn().mockReturnValue(false), } as unknown as License, - {} as unknown as ConstructorParameters[25], {} as unknown as ConstructorParameters[26], {} as unknown as ConstructorParameters[27], - { track: jest.fn() } as unknown as ConstructorParameters[28], + {} as unknown as ConstructorParameters[28], + { track: jest.fn() } as unknown as ConstructorParameters[29], mockAiBuilderTemporaryWorkflowRepository as unknown as AiBuilderTemporaryWorkflowRepository, ); @@ -1469,18 +1472,19 @@ function createExecutionAdapterForTests(overrides?: { sharingEnabled?: boolean } {} as unknown as ConstructorParameters[17], {} as unknown as ConstructorParameters[18], {} as unknown as ConstructorParameters[19], + {} as unknown as ConstructorParameters[20], { getPreferences: jest.fn().mockReturnValue({ branchReadOnly: false }), } as unknown as SourceControlPreferencesService, - {} as unknown as ConstructorParameters[21], {} as unknown as ConstructorParameters[22], {} as unknown as ConstructorParameters[23], + {} as unknown as ConstructorParameters[24], mockLicense as unknown as License, - {} as unknown as ConstructorParameters[25], {} as unknown as ConstructorParameters[26], + {} as unknown as ConstructorParameters[27], mockRoleService as unknown as RoleService, - {} as unknown as ConstructorParameters[28], {} as unknown as ConstructorParameters[29], + {} as unknown as ConstructorParameters[30], ); const adapter = service.createContext(mockUser).executionService; diff --git a/packages/cli/src/modules/instance-ai/instance-ai.adapter.service.ts b/packages/cli/src/modules/instance-ai/instance-ai.adapter.service.ts index d9e3d8fc0f7..143aca4eb75 100644 --- a/packages/cli/src/modules/instance-ai/instance-ai.adapter.service.ts +++ b/packages/cli/src/modules/instance-ai/instance-ai.adapter.service.ts @@ -105,6 +105,7 @@ import { EventService } from '@/events/event.service'; import { ExecutionPersistence } from '@/executions/execution-persistence'; import { License } from '@/license'; import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials'; +import { NodeTypes } from '@/node-types'; import { DataTableRepository } from '@/modules/data-table/data-table.repository'; import { DataTableService } from '@/modules/data-table/data-table.service'; import { SourceControlPreferencesService } from '@/modules/source-control.ee/source-control-preferences.service.ee'; @@ -170,6 +171,7 @@ export class InstanceAiAdapterService { private readonly activeExecutions: ActiveExecutions, private readonly workflowRunner: WorkflowRunner, private readonly loadNodesAndCredentials: LoadNodesAndCredentials, + private readonly nodeTypes: NodeTypes, private readonly instanceSettings: InstanceSettings, private readonly dataTableService: DataTableService, private readonly dataTableRepository: DataTableRepository, @@ -212,6 +214,7 @@ export class InstanceAiAdapterService { workspaceService: this.createWorkspaceAdapter(user), licenseHints: this.buildLicenseHints(), logger: this.logger, + nodeTypesProvider: this.nodeTypes, }; }