diff --git a/apps/sim/executor/utils/start-block.test.ts b/apps/sim/executor/utils/start-block.test.ts index 758cbe2116c..32b850e4ee1 100644 --- a/apps/sim/executor/utils/start-block.test.ts +++ b/apps/sim/executor/utils/start-block.test.ts @@ -454,7 +454,7 @@ describe('start-block utilities', () => { expect(output.count).toBe(5) expect(output.payload).toEqual({ event: 'push' }) - expect(output.meta).toEqual({ source: 'api' }) + expect(output.meta).toBe('{"source":"api"}') expect(output.extra).toBe('untouched') }) }) diff --git a/apps/sim/executor/utils/start-block.ts b/apps/sim/executor/utils/start-block.ts index c3dea3bd3ab..e2efde6d84a 100644 --- a/apps/sim/executor/utils/start-block.ts +++ b/apps/sim/executor/utils/start-block.ts @@ -7,7 +7,6 @@ import { isInternalFileUrl, parseInternalFileUrl, } from '@/lib/uploads/utils/file' -import { canonicalInputFormatFieldType } from '@/lib/workflows/input-format-schema' import { classifyStartBlockType, resolveStartCandidates, @@ -246,7 +245,7 @@ export function coerceValue(type: string | null | undefined, value: unknown): un return value } - switch (type ? (canonicalInputFormatFieldType(type) ?? type) : type) { + switch (type) { case 'string': return typeof value === 'string' ? value : String(value) case 'number': { diff --git a/apps/sim/lib/workflows/editing/validation.test.ts b/apps/sim/lib/workflows/editing/validation.test.ts index 88e2e942077..916a85f3190 100644 --- a/apps/sim/lib/workflows/editing/validation.test.ts +++ b/apps/sim/lib/workflows/editing/validation.test.ts @@ -345,34 +345,27 @@ describe('validateInputsForBlock', () => { ).toBe(false) }) - describe('input-format', () => { - const config = { id: 'inputFormat', type: 'input-format' as const } - const validate = (value: unknown) => - validateValueForSubBlockType(config, value, 'inputFormat', 'start_trigger', 'start-1') - - it('stores type aliases as their canonical type and keeps untyped rows', () => { - expect( - validate([ - { name: 'payload', type: 'json' }, - { name: 'documents', type: 'files' }, - { name: 'message' }, - ]) - ).toEqual({ - valid: true, - value: [ - { name: 'payload', type: 'object' }, - { name: 'documents', type: 'file[]' }, - { name: 'message' }, - ], - }) - }) - - it('rejects a type no workflow input can declare, listing the allowed types', () => { - const result = validate([{ name: 'count', type: 'integer' }]) - expect(result.valid).toBe(false) - expect(result.error?.error).toBe( - 'Invalid type "integer" for inputFormat[0] - use one of: string, number, boolean, object, array, file[]' + it('stores input-format rows with legacy or aliased types unchanged', () => { + const rows = [ + { name: 'payload', type: 'json' }, + { name: 'title', type: 'short-input' }, + { name: 'message' }, + ] + expect( + validateValueForSubBlockType( + { id: 'inputFormat', type: 'input-format' }, + rows, + 'inputFormat', + 'start_trigger', + 'start-1' ) + ).toEqual({ + valid: true, + value: [ + { name: 'payload', type: 'json' }, + { name: 'title', type: 'short-input' }, + { name: 'message' }, + ], }) }) diff --git a/apps/sim/lib/workflows/editing/validation.ts b/apps/sim/lib/workflows/editing/validation.ts index 5fcab91ce65..a816ec9f8d0 100644 --- a/apps/sim/lib/workflows/editing/validation.ts +++ b/apps/sim/lib/workflows/editing/validation.ts @@ -24,10 +24,6 @@ import { type SelectorReference, validateSelectorIds, } from '@/lib/workflows/editing/selector-validator' -import { - canonicalInputFormatFieldType, - INPUT_FORMAT_FIELD_TYPES, -} from '@/lib/workflows/input-format-schema' import { containsReference } from '@/lib/workflows/sanitization/references' import { getSkillById } from '@/lib/workflows/skills/operations' import { @@ -643,44 +639,7 @@ export function validateValueForSubBlockType( return { valid: true, value } } - case 'input-format': { - if (!Array.isArray(value)) { - return { - valid: false, - error: { - blockId, - blockType, - field: fieldName, - value, - error: `Invalid ${type} value for field "${fieldName}" - expected an array`, - }, - } - } - const rows: unknown[] = [] - for (const [index, row] of value.entries()) { - if (!isPlainRecord(row) || row.type === undefined) { - rows.push(row) - continue - } - const fieldType = - typeof row.type === 'string' ? canonicalInputFormatFieldType(row.type) : null - if (!fieldType) { - return { - valid: false, - error: { - blockId, - blockType, - field: fieldName, - value, - error: `Invalid type ${JSON.stringify(row.type)} for ${fieldName}[${index}] - use one of: ${INPUT_FORMAT_FIELD_TYPES.join(', ')}`, - }, - } - } - rows.push(fieldType === row.type ? row : { ...row, type: fieldType }) - } - return { valid: true, value: rows } - } - + case 'input-format': case 'table': { // Should be an array if (!Array.isArray(value)) { diff --git a/apps/sim/lib/workflows/input-format-schema.ts b/apps/sim/lib/workflows/input-format-schema.ts index 1f22315e482..3b69b674112 100644 --- a/apps/sim/lib/workflows/input-format-schema.ts +++ b/apps/sim/lib/workflows/input-format-schema.ts @@ -1,7 +1,7 @@ import { z } from 'zod' -/** The input field types the editor offers and workflow runs accept. */ -export const INPUT_FORMAT_FIELD_TYPES = [ +/** The input field types the editor offers. */ +const INPUT_FORMAT_FIELD_TYPES = [ 'string', 'number', 'boolean', @@ -12,14 +12,9 @@ export const INPUT_FORMAT_FIELD_TYPES = [ export type InputFormatFieldType = (typeof INPUT_FORMAT_FIELD_TYPES)[number] -/** - * Every spelling a stored field's `type` may use, mapped to its canonical type: - * `json` is the name blocks use for object params and outputs, `files` the - * legacy MCP alias. - */ +/** Every canonical type by name, plus `files`, the legacy MCP alias of `file[]`. */ const INPUT_FORMAT_FIELD_TYPES_BY_NAME = new Map([ ...INPUT_FORMAT_FIELD_TYPES.map((type) => [type, type] as const), - ['json', 'object'], ['files', 'file[]'], ]) diff --git a/apps/sim/lib/workflows/input-schema.test.ts b/apps/sim/lib/workflows/input-schema.test.ts index 77a408be3b7..7e268d6c591 100644 --- a/apps/sim/lib/workflows/input-schema.test.ts +++ b/apps/sim/lib/workflows/input-schema.test.ts @@ -108,25 +108,22 @@ describe('workflow input schemas', () => { expect(z.object(generateWorkflowInputShape(fields)).parse(input)).toEqual(input) }) - it('accepts the json and files aliases as their canonical types', () => { - const shape = z.object( - generateWorkflowInputShape([ - { name: 'payload', type: 'json' }, - { name: 'documents', type: 'files' }, - ]) - ) - expect(shape.safeParse({ payload: { a: 1 }, documents: [file] }).success).toBe(true) - expect(shape.safeParse({ payload: 'text', documents: [file] }).success).toBe(false) + it('accepts the files alias as file[]', () => { + const shape = z.object(generateWorkflowInputShape([{ name: 'documents', type: 'files' }])) + expect(shape.safeParse({ documents: [file] }).success).toBe(true) + expect(shape.safeParse({ documents: 'text' }).success).toBe(false) }) - it('rejects unsupported type names as a validation failure naming the field', () => { - expect(() => generateToolInputSchema([{ name: 'count', type: 'unsupported' }])).toThrow( - expect.objectContaining({ - name: 'OrchestrationError', - code: 'validation', - message: - 'Workflow input "count" has unsupported type "unsupported". Use one of: string, number, boolean, object, array, file[].', + it.each(['short-input', 'json'])( + 'accepts any value for the legacy type %s and advertises no type for it', + (type) => { + const fields = [{ name: 'title', type }] + expect(generateToolInputSchema(fields).properties).toEqual({ + title: { description: 'title' }, }) - ) - }) + const shape = z.object(generateWorkflowInputShape(fields)) + expect(shape.safeParse({ title: 'Quarterly report' }).success).toBe(true) + expect(shape.safeParse({ title: { text: 'Quarterly report' } }).success).toBe(true) + } + ) }) diff --git a/apps/sim/lib/workflows/input-schema.ts b/apps/sim/lib/workflows/input-schema.ts index f4fed3364c0..ac7f7563e7f 100644 --- a/apps/sim/lib/workflows/input-schema.ts +++ b/apps/sim/lib/workflows/input-schema.ts @@ -3,13 +3,16 @@ import { OrchestrationError } from '@/lib/core/orchestration/types' import { workflowFileInputSchema } from '@/lib/workflows/input-file-schema' import { canonicalInputFormatFieldType, - INPUT_FORMAT_FIELD_TYPES, type InputFormatFieldType, } from '@/lib/workflows/input-format-schema' import type { InputFormatField } from '@/lib/workflows/types' import { isSafeKey } from '@/tools/safe-assign' -function fieldTypeToSchema(type: InputFormatFieldType): z.ZodType { +/** + * A stored type outside the editor's list (legacy widget names such as `short-input`) accepts any + * value, as the executor passes it through unchanged. + */ +function fieldTypeToSchema(type: InputFormatFieldType | null): z.ZodType { switch (type) { case 'string': return z.string() @@ -23,6 +26,8 @@ function fieldTypeToSchema(type: InputFormatFieldType): z.ZodType { return z.array(z.unknown()) case 'file[]': return z.array(workflowFileInputSchema) + case null: + return z.unknown() } } @@ -30,8 +35,7 @@ function fieldTypeToSchema(type: InputFormatFieldType): z.ZodType { * Shared by workflow run validation and MCP discovery so the advertised input * types match execution. The executor supplies configured defaults at run time. * - * @throws OrchestrationError `validation` when a field name is reserved or its - * type is not one a workflow input can declare. + * @throws OrchestrationError `validation` when a field name is reserved. */ export function generateWorkflowInputShape(inputFormat: InputFormatField[]): z.ZodRawShape { const shape: Record = {} @@ -46,12 +50,6 @@ export function generateWorkflowInputShape(inputFormat: InputFormatField[]): z.Z ) } const type = canonicalInputFormatFieldType(field.type) - if (!type) { - throw new OrchestrationError( - 'validation', - `Workflow input "${name}" has unsupported type "${field.type}". Use one of: ${INPUT_FORMAT_FIELD_TYPES.join(', ')}.` - ) - } const schema = fieldTypeToSchema(type).describe( field.description?.trim() || diff --git a/apps/sim/lib/workflows/triggers/run-options.test.ts b/apps/sim/lib/workflows/triggers/run-options.test.ts index 327a9d49552..3f710c44aa1 100644 --- a/apps/sim/lib/workflows/triggers/run-options.test.ts +++ b/apps/sim/lib/workflows/triggers/run-options.test.ts @@ -115,8 +115,8 @@ describe('file inputs through workflow run options', () => { }) }) -describe('mock payloads for aliased field types', () => { - it('samples a json field as an object the run validation accepts', () => { +describe('mock payloads for legacy field types', () => { + it('samples a json field with a value the run validation accepts', () => { const [option] = resolveTriggerRunOptions({ start: { type: 'start_trigger', @@ -124,7 +124,6 @@ describe('mock payloads for aliased field types', () => { subBlocks: { inputFormat: { value: [{ name: 'payload', type: 'json' }] } }, }, }) - expect(option.mockPayload).toEqual({ payload: {} }) expect(validateTriggerInput(option, option.mockPayload)).toEqual({ ok: true }) }) })