11import { createLogger } from '@sim/logger'
2+ import { resolveChatUpload } from '@/lib/copilot/tools/handlers/upload-file-reader'
23import { decodeVfsPathSegments , encodeVfsPathSegments } from '@/lib/copilot/vfs/path-utils'
34import { resolveWorkflowAliasForWorkspace } from '@/lib/copilot/vfs/workflow-alias-resolver'
45import { isPlanAliasPath , workflowAliasSandboxPath } from '@/lib/copilot/vfs/workflow-aliases'
@@ -29,6 +30,7 @@ const logger = createLogger('CopilotFunctionExecute')
2930const MAX_FILE_SIZE = 10 * 1024 * 1024
3031const MAX_TOTAL_SIZE = 50 * 1024 * 1024
3132const MAX_MOUNTED_FILES = 500
33+ const UPLOADS_PREFIX = 'uploads/'
3234
3335/**
3436 * Below this row count a table mounts via the direct inline CSV path — the version-keyed snapshot
@@ -170,7 +172,8 @@ export async function resolveInputFiles(
170172 workspaceId : string ,
171173 inputFiles ?: unknown [ ] ,
172174 inputTables ?: unknown [ ] ,
173- inputDirectories ?: unknown [ ]
175+ inputDirectories ?: unknown [ ] ,
176+ chatId ?: string
174177) : Promise < SandboxFile [ ] > {
175178 const sandboxFiles : SandboxFile [ ] = [ ]
176179 const mounted : MountedBytes = { buffered : 0 , url : 0 }
@@ -182,9 +185,7 @@ export async function resolveInputFiles(
182185 `Too many input files (${ inputFiles . length } ). Maximum is ${ MAX_MOUNTED_FILES } . Mount fewer files.`
183186 )
184187 }
185- const allFiles = await listWorkspaceFiles ( workspaceId , {
186- includeReservedSystemFiles : betaEnabled ,
187- } )
188+ let allFiles : WorkspaceFileRecord [ ] | undefined
188189 for ( const fileRef of inputFiles ) {
189190 const filePath =
190191 typeof fileRef === 'string'
@@ -193,6 +194,35 @@ export async function resolveInputFiles(
193194 ? ( fileRef as CanonicalFileInput ) . path
194195 : undefined
195196 if ( ! filePath ) continue
197+ const explicitSandboxPath =
198+ typeof fileRef === 'object' && fileRef !== null
199+ ? ( fileRef as CanonicalFileInput ) . sandboxPath
200+ : undefined
201+ if ( filePath . startsWith ( UPLOADS_PREFIX ) ) {
202+ const fileName = filePath . slice ( UPLOADS_PREFIX . length )
203+ if ( ! fileName || fileName . includes ( '/' ) ) {
204+ throw new Error ( `Upload input path must identify one file: "${ filePath } "` )
205+ }
206+ if ( ! chatId ) {
207+ throw new Error ( `Chat context is required to mount upload: "${ filePath } "` )
208+ }
209+ const record = await resolveChatUpload ( fileName , chatId )
210+ if ( ! record ) {
211+ throw new Error (
212+ `Upload not found: "${ filePath } ". The chat or upload may no longer exist.`
213+ )
214+ }
215+ if ( record . workspaceId !== workspaceId ) {
216+ throw new Error ( `Upload does not belong to the current workspace: "${ filePath } "` )
217+ }
218+ await pushWorkspaceFileMount (
219+ sandboxFiles ,
220+ record ,
221+ explicitSandboxPath || `/home/user/${ filePath } ` ,
222+ mounted
223+ )
224+ continue
225+ }
196226 const alias = await resolveWorkflowAliasForWorkspace ( { workspaceId, path : filePath } )
197227 if ( ! alias && isPlanAliasPath ( filePath ) ) {
198228 logger . warn ( 'Unsupported plan alias input file path' , { filePath } )
@@ -202,21 +232,15 @@ export async function resolveInputFiles(
202232 logger . warn ( 'Input file is a plan alias directory' , { filePath } )
203233 continue
204234 }
235+ allFiles ??= await listWorkspaceFiles ( workspaceId , {
236+ includeReservedSystemFiles : betaEnabled ,
237+ } )
205238 const record = findWorkspaceFileRecord ( allFiles , alias ?. backingPath ?? filePath )
206239 if ( ! record ) {
207- if ( filePath . startsWith ( 'uploads/' ) ) {
208- throw new Error (
209- `Cannot mount "${ filePath } ": uploads/ files are not mountable into the sandbox. Use materialize_file to save it to a files/... path first, then mount that canonical path.`
210- )
211- }
212240 throw new Error (
213241 `Input file not found: "${ filePath } ". Pass the exact canonical VFS path copied from glob/read (e.g. "files/Reports/data.csv").`
214242 )
215243 }
216- const explicitSandboxPath =
217- typeof fileRef === 'object' && fileRef !== null
218- ? ( fileRef as CanonicalFileInput ) . sandboxPath
219- : undefined
220244 const mountPath =
221245 explicitSandboxPath ||
222246 ( alias ? workflowAliasSandboxPath ( alias . aliasPath ) : getSandboxWorkspaceFilePath ( record ) )
@@ -441,7 +465,8 @@ export async function executeFunctionExecute(
441465 context . workspaceId ,
442466 inputFiles ,
443467 inputTables ,
444- inputDirectories
468+ inputDirectories ,
469+ context . chatId
445470 )
446471 if ( resolved . length > 0 ) {
447472 const existing = ( enrichedParams . _sandboxFiles as SandboxFile [ ] ) || [ ]
0 commit comments