fix(core)!: Type coercion of $fromAI default values (#19128)

This commit is contained in:
Eugene
2025-09-03 13:36:05 +02:00
committed by GitHub
parent e4fb6e5f31
commit d8eb1a97e6
3 changed files with 97 additions and 8 deletions

View File

@@ -104,19 +104,32 @@ export function generateZodSchema(placeholder: FromAIArgument): z.ZodTypeAny {
return schema;
}
function isFromAIArgumentType(value: string): value is FromAIArgumentType {
return ['string', 'number', 'boolean', 'json'].includes(value.toLowerCase());
}
/**
* Parses the default value, preserving its original type.
* @param value The default value as a string.
* @param type The expected type of the default value.
* @returns The parsed default value in its appropriate type.
*/
function parseDefaultValue(
value: string | undefined,
type: FromAIArgumentType = 'string',
): string | number | boolean | Record<string, unknown> | undefined {
if (value === undefined || value === '') return undefined;
if (value === undefined) return value;
const lowerValue = value.toLowerCase();
if (lowerValue === 'true') return true;
if (lowerValue === 'false') return false;
if (!isNaN(Number(value))) return Number(value);
if (type === 'string') {
return value.toString();
}
if (type === 'boolean' && (lowerValue === 'true' || lowerValue === 'false'))
return lowerValue === 'true';
if (type === 'number' && !isNaN(Number(value))) return Number(value);
// For type 'json' or any other case, attempt to parse as JSON
try {
return jsonParse(value);
} catch {
@@ -197,17 +210,17 @@ function parseArguments(argsString: string): FromAIArgument {
return trimmed;
});
const type = cleanArgs?.[2] || 'string';
const type = cleanArgs?.[2] ?? 'string';
if (!['string', 'number', 'boolean', 'json'].includes(type.toLowerCase())) {
if (!isFromAIArgumentType(type)) {
throw new ParseError(`Invalid type: ${type}`);
}
return {
key: cleanArgs[0] || '',
description: cleanArgs[1],
type: (cleanArgs?.[2] ?? 'string') as FromAIArgumentType,
defaultValue: parseDefaultValue(cleanArgs[3]),
type,
defaultValue: parseDefaultValue(cleanArgs[3], type),
};
}

View File

@@ -11,10 +11,19 @@ describe('extractFromAICalls', () => {
test.each<[string, [unknown, unknown, unknown, unknown]]>([
['$fromAI("a", "b", "string")', ['a', 'b', 'string', undefined]],
['$fromAI("a", "b", "number", 5)', ['a', 'b', 'number', 5]],
['$fromAI("a", "b", "number", "5")', ['a', 'b', 'number', 5]],
['$fromAI("a", "`", "number", 5)', ['a', '`', 'number', 5]],
['$fromAI("a", "\\`", "number", 5)', ['a', '`', 'number', 5]], // this is a bit surprising, but intended
['$fromAI("a", "\\n", "number", 5)', ['a', 'n', 'number', 5]], // this is a bit surprising, but intended
['{{ $fromAI("a", "b", "boolean") }}', ['a', 'b', 'boolean', undefined]],
['{{ $fromAI("a", "b", "boolean", "true") }}', ['a', 'b', 'boolean', true]],
['{{ $fromAI("a", "b", "boolean", "false") }}', ['a', 'b', 'boolean', false]],
['{{ $fromAI("a", "b", "boolean", true) }}', ['a', 'b', 'boolean', true]],
['{{ $fromAI("a", "b", "string", "") }}', ['a', 'b', 'string', '']],
['{{ $fromAI("a", "b", "string", "null") }}', ['a', 'b', 'string', 'null']],
['{{ $fromAI("a", "b", "string", "5") }}', ['a', 'b', 'string', '5']],
['{{ $fromAI("a", "b", "string", "true") }}', ['a', 'b', 'string', 'true']],
['{{ $fromAI("a", "b", "string", "{}") }}', ['a', 'b', 'string', '{}']],
])('should parse args as expected for %s', (formula, [key, description, type, defaultValue]) => {
expect(extractFromAICalls(formula)).toEqual([
{