mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix(Call n8n Sub-Workflow Tool Node): Fix json type when using $fromAI (#13102)
This commit is contained in:
parent
577d5a1bff
commit
9e4e1ca1f4
|
@ -319,7 +319,7 @@ describe('createNodeAsTool', () => {
|
||||||
|
|
||||||
const tool = createNodeAsTool(options).response;
|
const tool = createNodeAsTool(options).response;
|
||||||
|
|
||||||
expect(tool.schema.shape.complexJson._def.innerType).toBeInstanceOf(z.ZodRecord);
|
expect(tool.schema.shape.complexJson._def.innerType).toBeInstanceOf(z.ZodEffects);
|
||||||
expect(tool.schema.shape.complexJson.description).toBe('Param with complex JSON default');
|
expect(tool.schema.shape.complexJson.description).toBe('Param with complex JSON default');
|
||||||
expect(tool.schema.shape.complexJson._def.defaultValue()).toEqual({
|
expect(tool.schema.shape.complexJson._def.defaultValue()).toEqual({
|
||||||
nested: { key: 'value' },
|
nested: { key: 'value' },
|
||||||
|
|
|
@ -34,9 +34,61 @@ export function generateZodSchema(placeholder: FromAIArgument): z.ZodTypeAny {
|
||||||
case 'boolean':
|
case 'boolean':
|
||||||
schema = z.boolean();
|
schema = z.boolean();
|
||||||
break;
|
break;
|
||||||
case 'json':
|
case 'json': {
|
||||||
schema = z.record(z.any());
|
interface CustomSchemaDef extends z.ZodTypeDef {
|
||||||
|
jsonSchema?: {
|
||||||
|
anyOf: [
|
||||||
|
{
|
||||||
|
type: 'object';
|
||||||
|
minProperties: number;
|
||||||
|
additionalProperties: boolean;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'array';
|
||||||
|
minItems: number;
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a custom schema to validate that the incoming data is either a non-empty object or a non-empty array.
|
||||||
|
const customSchema = z.custom<Record<string, unknown> | unknown[]>(
|
||||||
|
(data: unknown) => {
|
||||||
|
if (data === null || typeof data !== 'object') return false;
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return data.length > 0;
|
||||||
|
}
|
||||||
|
return Object.keys(data).length > 0;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Value must be a non-empty object or a non-empty array',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Cast the custom schema to a type that includes our JSON metadata.
|
||||||
|
const typedSchema = customSchema as z.ZodType<
|
||||||
|
Record<string, unknown> | unknown[],
|
||||||
|
CustomSchemaDef
|
||||||
|
>;
|
||||||
|
|
||||||
|
// Attach the updated `jsonSchema` metadata to the internal definition.
|
||||||
|
typedSchema._def.jsonSchema = {
|
||||||
|
anyOf: [
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
minProperties: 1,
|
||||||
|
additionalProperties: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'array',
|
||||||
|
minItems: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
schema = typedSchema;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
schema = z.string();
|
schema = z.string();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import {
|
import {
|
||||||
extractFromAICalls,
|
extractFromAICalls,
|
||||||
type FromAIArgument,
|
|
||||||
traverseNodeParameters,
|
traverseNodeParameters,
|
||||||
|
type FromAIArgument,
|
||||||
|
generateZodSchema,
|
||||||
} from '@/FromAIParseUtils';
|
} from '@/FromAIParseUtils';
|
||||||
|
|
||||||
// Note that for historic reasons a lot of testing of this file happens indirectly in `packages/core/test/CreateNodeAsTool.test.ts`
|
// Note that for historic reasons a lot of testing of this file happens indirectly in `packages/core/test/CreateNodeAsTool.test.ts`
|
||||||
|
@ -85,3 +86,61 @@ describe('traverseNodeParameters', () => {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('JSON Type Parsing via generateZodSchema', () => {
|
||||||
|
it('should correctly parse a JSON parameter without default', () => {
|
||||||
|
// Use an actual $fromAI call string via extractFromAICalls:
|
||||||
|
const [arg] = extractFromAICalls(
|
||||||
|
'$fromAI("jsonWithoutDefault", "JSON parameter without default", "json")',
|
||||||
|
);
|
||||||
|
const schema = generateZodSchema(arg);
|
||||||
|
|
||||||
|
// Valid non-empty JSON objects should pass.
|
||||||
|
expect(() => schema.parse({ key: 'value' })).not.toThrow();
|
||||||
|
expect(schema.parse({ key: 'value' })).toEqual({ key: 'value' });
|
||||||
|
|
||||||
|
// Parsing an empty object should throw a validation error.
|
||||||
|
expect(() => schema.parse({})).toThrowError(
|
||||||
|
/Value must be a non-empty object or a non-empty array/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly parse a JSON parameter with a valid default', () => {
|
||||||
|
const [arg] = extractFromAICalls(
|
||||||
|
'$fromAI("jsonWithValidDefault", "JSON parameter with valid default", "json", "{"key": "defaultValue"}")',
|
||||||
|
);
|
||||||
|
const schema = generateZodSchema(arg);
|
||||||
|
|
||||||
|
// The default value is now stored as a parsed object.
|
||||||
|
expect(schema._def.defaultValue()).toEqual({ key: 'defaultValue' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse a JSON parameter with an empty default', () => {
|
||||||
|
const [arg] = extractFromAICalls(
|
||||||
|
'$fromAI("jsonEmptyDefault", "JSON parameter with empty default", "json", "{}")',
|
||||||
|
);
|
||||||
|
const schema = generateZodSchema(arg);
|
||||||
|
|
||||||
|
// The default value is stored as an empty object.
|
||||||
|
expect(schema._def.defaultValue()).toEqual({});
|
||||||
|
|
||||||
|
// Parsing an empty object should throw a validation error.
|
||||||
|
expect(() => schema.parse({})).toThrowError(
|
||||||
|
/Value must be a non-empty object or a non-empty array/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use provided JSON value over the default value', () => {
|
||||||
|
const [arg] = extractFromAICalls(
|
||||||
|
'$fromAI("jsonParamCustom", "JSON parameter with custom default", "json", "{"initial": "value"}")',
|
||||||
|
);
|
||||||
|
const schema = generateZodSchema(arg);
|
||||||
|
|
||||||
|
// Check that the stored default value parses to the expected object.
|
||||||
|
expect(schema._def.defaultValue()).toEqual({ initial: 'value' });
|
||||||
|
|
||||||
|
// When a new valid value is provided, the schema should use it.
|
||||||
|
const newValue = { newKey: 'newValue' };
|
||||||
|
expect(schema.parse(newValue)).toEqual(newValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue