feat(execution-filesystem): system to pass files between blocks (#866)

* feat(files): pass files between blocks

* presigned URL for downloads

* Remove latest migration before merge

* starter block file upload wasn't getting logged

* checkpoint in human readable form

* checkpoint files / file type outputs

* file downloads working for block outputs

* checkpoint file download

* fix type issues

* remove filereference interface with simpler user file interface

* show files in the tag dropdown for start block

* more migration to simple url object, reduce presigned time to 5 min

* Remove migration 0065_parallel_nightmare and related files

- Deleted apps/sim/db/migrations/0065_parallel_nightmare.sql
- Deleted apps/sim/db/migrations/meta/0065_snapshot.json
- Removed 0065 entry from apps/sim/db/migrations/meta/_journal.json

Preparing for merge with origin/staging and migration regeneration

* add migration files

* fix tests

* Update apps/sim/lib/uploads/setup.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update apps/sim/lib/workflows/execution-file-storage.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update apps/sim/lib/workflows/execution-file-storage.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* cleanup types

* fix lint

* fix logs typing for file refs

* open download in new tab

* fixed

* Update apps/sim/tools/index.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* fix file block

* cleanup unused code

* fix bugs

* remove hacky file id logic

* fix drag and drop

* fix tests

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
Vikhyath Mondreti
2025-08-07 12:51:30 -07:00
committed by GitHub
parent 75963eb851
commit de93e167af
60 changed files with 8487 additions and 455 deletions

View File

@@ -87,6 +87,7 @@ export class Executor {
edges?: Array<{ source: string; target: string }>
onStream?: (streamingExecution: StreamingExecution) => Promise<void>
executionId?: string
workspaceId?: string
}
},
private initialBlockStates: Record<string, BlockOutput> = {},
@@ -675,6 +676,8 @@ export class Executor {
): ExecutionContext {
const context: ExecutionContext = {
workflowId,
workspaceId: this.contextExtensions.workspaceId,
executionId: this.contextExtensions.executionId,
blockStates: new Map(),
blockLogs: [],
metadata: {
@@ -797,6 +800,11 @@ export class Executor {
...finalInput, // Add input fields directly at top level
}
// Add files if present (for all trigger types)
if (this.workflowInput?.files && Array.isArray(this.workflowInput.files)) {
blockOutput.files = this.workflowInput.files
}
logger.info(`[Executor] Starting block output:`, JSON.stringify(blockOutput, null, 2))
context.blockStates.set(initBlock.id, {
@@ -804,6 +812,10 @@ export class Executor {
executed: true,
executionTime: 0,
})
// Create a block log for the starter block if it has files
// This ensures files are captured in trace spans and execution logs
this.createStartedBlockWithFilesLog(initBlock, blockOutput, context)
} else {
// Handle structured input (like API calls or chat messages)
if (this.workflowInput && typeof this.workflowInput === 'object') {
@@ -812,17 +824,26 @@ export class Executor {
Object.hasOwn(this.workflowInput, 'input') &&
Object.hasOwn(this.workflowInput, 'conversationId')
) {
// Chat workflow: extract input and conversationId to root level
const starterOutput = {
// Chat workflow: extract input, conversationId, and files to root level
const starterOutput: any = {
input: this.workflowInput.input,
conversationId: this.workflowInput.conversationId,
}
// Add files if present
if (this.workflowInput.files && Array.isArray(this.workflowInput.files)) {
starterOutput.files = this.workflowInput.files
}
context.blockStates.set(initBlock.id, {
output: starterOutput,
executed: true,
executionTime: 0,
})
// Create a block log for the starter block if it has files
// This ensures files are captured in trace spans and execution logs
this.createStartedBlockWithFilesLog(initBlock, starterOutput, context)
} else {
// API workflow: spread the raw data directly (no wrapping)
const starterOutput = { ...this.workflowInput }
@@ -857,11 +878,16 @@ export class Executor {
Object.hasOwn(this.workflowInput, 'input') &&
Object.hasOwn(this.workflowInput, 'conversationId')
) {
// Chat workflow: extract input and conversationId to root level
// Chat workflow: extract input, conversationId, and files to root level
blockOutput = {
input: this.workflowInput.input,
conversationId: this.workflowInput.conversationId,
}
// Add files if present
if (this.workflowInput.files && Array.isArray(this.workflowInput.files)) {
blockOutput.files = this.workflowInput.files
}
} else {
// API workflow: spread the raw data directly (no wrapping)
blockOutput = { ...this.workflowInput }
@@ -883,6 +909,7 @@ export class Executor {
executed: true,
executionTime: 0,
})
this.createStartedBlockWithFilesLog(initBlock, blockOutput, context)
}
// Ensure the starting block is in the active execution path
context.activeExecutionPath.add(initBlock.id)
@@ -1806,4 +1833,29 @@ export class Executor {
// Fallback to string conversion
return String(error)
}
/**
* Creates a block log for the starter block if it contains files.
* This ensures files are captured in trace spans and execution logs.
*/
private createStartedBlockWithFilesLog(
initBlock: SerializedBlock,
blockOutput: any,
context: ExecutionContext
): void {
if (blockOutput.files && Array.isArray(blockOutput.files) && blockOutput.files.length > 0) {
const starterBlockLog: BlockLog = {
blockId: initBlock.id,
blockName: initBlock.metadata?.name || 'Start',
blockType: initBlock.metadata?.id || 'start',
startedAt: new Date().toISOString(),
endedAt: new Date().toISOString(),
success: true,
input: this.workflowInput,
output: blockOutput,
durationMs: 0,
}
context.blockLogs.push(starterBlockLog)
}
}
}