feat(input-format): upload files in file fields via the file uploader#5297
Conversation
The file field in the start-block input format only offered a raw JSON editor expecting hand-written base64 objects, which users routinely filled with junk (local paths, raw text, leftover placeholders) and which never actually fed a run. Reuse the existing FileUpload component for file-typed fields: - Add an opt-in controlled mode (value/onValueChange) to FileUpload so it can be embedded where the value lives outside a subblock; store-bound consumers (stt/vision/agent) are unchanged. - Render the uploader for file fields, with a toggle to fall back to the raw JSON editor for power users / legacy values. - Detect file fields by normalized type (file[]/files/file/image) so copilot/API-authored variants render correctly too. - Wire editor-attached files (already uploaded, run-ready) into manual runs via the executor's files channel; chat/API runs still override. Backwards compatible: the inputFormat array shape is unchanged (file values are stored as a JSON string of run-ready file objects); legacy free-form values open in JSON mode so nothing is lost.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview
New
Manual / run-from-block execution replaces inline input-format coercion with Unit tests cover the new parsers, adapters, and execution input helpers. Reviewed by Cursor Bugbot for commit 47952a7. Configure here. |
Greptile SummaryThis PR adds uploader support for file fields in the start-block input format. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (9): Last reviewed commit: "fix(input-format): uploaded files always..." | Re-trigger Greptile |
…r, harden types Follow-up cleanup from /simplify + /cleanup review: - Extract pure file adapters into input-format-files.ts (filesToControlValue, controlValueToFiles, serializeInputFormatFiles, defaultFileFieldMode) so they are unit-tested without a DOM; add tests. - Derive InputFormatFile from the canonical executor UserFile so the editor and runtime file shapes can't drift. - Consolidate the two manual run-input builders into one buildInputFormatInput helper so manual run and run-from-block handle files identically. - Narrow file-field detection to the canonical file[] (matches the field-type dropdown and the existing execution/webhook file paths) so no pre-existing non-file[] field changes behavior. - defaultFileFieldMode parses once; the file value is only parsed in upload mode. - Add lib/component unit tests (45 passing).
…files input Addresses review (Greptile P1s): - parseInputFormatFiles now requires the full run-ready shape (id/name/url + finite size + type), so a partial file can't open in uploader mode or reach workflowInput.files only to be rejected by normalizeStartFile (which would silently drop every file). - buildInputFormatInput no longer overwrites a user field literally named 'files' with the upload channel; that reserved-name collision is left for the executor to surface.
|
@greptile review |
|
@cursor review |
…N mode Addresses review round 2 (Greptile P1 + Cursor): - parseInputFormatFiles now requires a non-empty key (the uploader always sets one); an external/signed URL with no recoverable key no longer opens uploader mode or reaches workflowInput.files only to be rejected. - defaultFileFieldMode uses the uploader only when EVERY entry is run-ready; a mixed array with any legacy/partial entry stays in JSON mode so the uploader can't drop the entries it can't represent on the next save.
|
@greptile review |
|
@cursor review |
…ss values Addresses review round 3 (Greptile P1 + Cursor): - parseInputFormatFiles accepts a key-less file when its url is an internal /api/files/serve/... url (the executor recovers the key from it, same as normalizeStartFile); only a missing key with a non-internal url is rejected. - The file field offers the uploader only when it can represent the stored value losslessly (empty or all run-ready); for mixed/legacy values it forces JSON mode and hides the toggle, so a sticky 'upload' preference can no longer drop entries the uploader cannot show on save.
|
@greptile review |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit a028114. Configure here.
…rmatFiles Addresses review round 4 (Greptile): normalizeStartFile rejects falsy id/name/url/type and file normalization is all-or-nothing, so an empty-string field (e.g. hand-edited value with an empty id + internal url) would open in uploader mode and drop every file from the run. Require non-empty strings to match the executor.
|
@greptile review |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit f2718e9. Configure here.
…rnalFileUrl Addresses review round 5 (Greptile): isInternalFileUrl only checks the URL prefix, but the executor parses+decodes the key. A malformed internal URL (no extractable key) passed the prefix check, got collected into workflowInput.files, then normalizeStartFile rejected it and dropped the whole files array. Now mirror the executor exactly — attempt parseInternalFileUrl and require a real key. Tests use a realistic recoverable internal URL.
|
@greptile review |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 519917a. Configure here.
Addresses review round 6 (Greptile): files is NOT in the executor's reserved input-format names (EXECUTION_CONTROL_OUTPUT_FIELD_NAMES), so the previous guard silently dropped uploaded file[] values whenever a plain field named files coexisted. files is the start block's canonical file channel (the chat trigger names its own file field files), so uploaded files now always populate it and take precedence — dropping real attachments is the worse outcome, and making files a reserved-name error isn't viable since it's the legitimate file-field name.
|
@greptile review |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 47952a7. Configure here.
|
@greptile review |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 47952a7. Configure here.
|
@greptile review |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 47952a7. Configure here.
Summary
<base64>placeholders). It also never actually fed a run.value/onValueChange) to FileUpload so it can be embedded where the value lives outside a subblock. Store-bound consumers (stt / vision / agent) are untouched.file[]/files/file/image) so copilot/API-authored variants render correctly too.Backwards compatibility
inputFormatarray shape is unchanged — file values are stored as a JSON string of run-ready file objects, so every existing reader stays compatible. No DB migration.Type of Change
Testing
isFileFieldType,parseInputFormatFiles);lib/workflows/input-format.test.tspasses (32 tests).tscclean for all changed files; tested manually.Checklist