fix: recognize dynamic AI Tool nodes in validator (Issue #522) (#526)

When n8n connects any node to an AI Agent's tool slot, it creates a
dynamic Tool variant at runtime (e.g., googleDrive → googleDriveTool).
These don't exist in npm packages, causing false "unknown node type"
errors.

Added validation-time inference: when a *Tool node type is not found,
check if the base node exists. If yes, treat as valid with warning.

Changes:
- workflow-validator.ts: Add INFERRED_TOOL_VARIANT logic
- node-similarity-service.ts: Add 98% confidence for valid Tool variants
- Added 7 unit tests for inferred tool variant functionality

Fixes #522

Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Romuald Członkowski <romualdczlonkowski@MacBook-Pro-Romuald.local>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Romuald Członkowski
2026-01-07 18:09:55 +01:00
committed by GitHub
parent 861005eeed
commit ce2c94c1a5
11 changed files with 425 additions and 7 deletions

View File

@@ -236,7 +236,31 @@ class WorkflowValidator {
}
}
const normalizedType = node_type_normalizer_1.NodeTypeNormalizer.normalizeToFullForm(node.type);
const nodeInfo = this.nodeRepository.getNode(normalizedType);
let nodeInfo = this.nodeRepository.getNode(normalizedType);
if (!nodeInfo && tool_variant_generator_1.ToolVariantGenerator.isToolVariantNodeType(normalizedType)) {
const baseNodeType = tool_variant_generator_1.ToolVariantGenerator.getBaseNodeType(normalizedType);
if (baseNodeType) {
const baseNodeInfo = this.nodeRepository.getNode(baseNodeType);
if (baseNodeInfo) {
result.warnings.push({
type: 'warning',
nodeId: node.id,
nodeName: node.name,
message: `Node type "${node.type}" is inferred as a dynamic AI Tool variant of "${baseNodeType}". ` +
`This Tool variant is created by n8n at runtime when connecting "${baseNodeInfo.displayName}" to an AI Agent.`,
code: 'INFERRED_TOOL_VARIANT'
});
nodeInfo = {
...baseNodeInfo,
nodeType: normalizedType,
displayName: `${baseNodeInfo.displayName} Tool`,
isToolVariant: true,
toolVariantOf: baseNodeType,
isInferred: true
};
}
}
}
if (!nodeInfo) {
const suggestions = await this.similarityService.findSimilarNodes(node.type, 3);
let message = `Unknown node type: "${node.type}".`;
@@ -310,6 +334,9 @@ class WorkflowValidator {
if (normalizedType.startsWith('nodes-langchain.')) {
continue;
}
if (nodeInfo.isInferred) {
continue;
}
const paramsWithVersion = {
'@version': node.typeVersion || 1,
...node.parameters