@@ -232,6 +232,43 @@ def _has_component_method(root: Any, component_name: str, method_name: str) -> b
232232 return getattr (component , method_name , None ) is not None
233233
234234
235+ def _resolve_files_components (sandbox : Any ) -> tuple [Any , ...]:
236+ components : list [Any ] = []
237+ seen_ids : set [int ] = set ()
238+ for name in ("files" , "filesystem" ):
239+ component = getattr (sandbox , name , None )
240+ if component is None :
241+ continue
242+ component_id = id (component )
243+ if component_id in seen_ids :
244+ continue
245+ seen_ids .add (component_id )
246+ components .append (component )
247+ return tuple (components )
248+
249+
250+ def _resolve_files_method (
251+ components : tuple [Any , ...],
252+ method_names : str | tuple [str , ...],
253+ ) -> Any | None :
254+ for component in components :
255+ method = _resolve_component_method (component , method_names )
256+ if method is not None :
257+ return method
258+ return None
259+
260+
261+ def _normalize_native_upload_result (raw : Any , file_name : str ) -> dict [str , Any ]:
262+ payload = _maybe_model_dump (raw )
263+ if not payload :
264+ return {"success" : True , "file_path" : file_name }
265+ if "file_path" not in payload and "path" not in payload :
266+ payload ["file_path" ] = file_name
267+ if "success" not in payload :
268+ payload ["success" ] = not bool (payload .get ("error" ) or payload .get ("stderr" ))
269+ return payload
270+
271+
235272class CuaShellComponent (ShellComponent ):
236273 def __init__ (self , sandbox : Any , os_type : str = "linux" ) -> None :
237274 self ._sandbox = sandbox
@@ -360,7 +397,7 @@ def __init__(
360397 self , sandbox : Any , os_type : str = CUA_DEFAULT_CONFIG ["os_type" ]
361398 ) -> None :
362399 self ._shell = CuaShellComponent (sandbox , os_type = os_type )
363- self ._fs = getattr (sandbox , "filesystem" , None )
400+ self ._fs_components = _resolve_files_components (sandbox )
364401 self ._os_type = os_type .lower ()
365402 self ._fallback = _PosixShellFileSystem (self ._shell , self ._os_type )
366403
@@ -382,7 +419,9 @@ async def read_file(
382419 offset : int | None = None ,
383420 limit : int | None = None ,
384421 ) -> dict [str , Any ]:
385- read_file = None if self ._fs is None else getattr (self ._fs , "read_file" , None )
422+ read_file = _resolve_files_method (
423+ self ._fs_components , ("read_file" , "read_text" )
424+ )
386425 if read_file is None :
387426 return await self ._fallback .read_file (path , encoding , offset , limit )
388427 else :
@@ -405,19 +444,19 @@ async def write_file(
405444 encoding : str = "utf-8" ,
406445 ) -> dict [str , Any ]:
407446 _ = mode
408- write_file = None if self ._fs is None else getattr (self ._fs , "write_file" , None )
447+ write_file = _resolve_files_method (
448+ self ._fs_components , ("write_file" , "write_text" )
449+ )
409450 if write_file is None :
410451 return await self ._fallback .write_file (path , content , mode , encoding )
411452 else :
412453 await _maybe_await (write_file (path , content ))
413454 return {"success" : True , "path" : path }
414455
415456 async def delete_file (self , path : str ) -> dict [str , Any ]:
416- delete = None
417- if self ._fs is not None :
418- delete = getattr (self ._fs , "delete" , None ) or getattr (
419- self ._fs , "delete_file" , None
420- )
457+ delete = _resolve_files_method (
458+ self ._fs_components , ("delete" , "delete_file" , "remove" )
459+ )
421460 if delete is None :
422461 return await self ._fallback .delete_file (path )
423462 else :
@@ -429,7 +468,7 @@ async def list_dir(
429468 path : str = "." ,
430469 show_hidden : bool = False ,
431470 ) -> dict [str , Any ]:
432- list_dir = None if self . _fs is None else getattr (self ._fs , "list_dir" , None )
471+ list_dir = _resolve_files_method (self ._fs_components , ( "list_dir" , "list" ) )
433472 if list_dir is not None :
434473 entries = await _maybe_await (list_dir (path ))
435474 return {"success" : True , "path" : path , "entries" : entries }
@@ -802,6 +841,15 @@ async def upload_file(self, path: str, file_name: str) -> dict:
802841 return _maybe_model_dump (
803842 await sandbox .upload_file (str (local_path ), file_name )
804843 )
844+ files_components = () if sandbox is None else _resolve_files_components (sandbox )
845+ upload = _resolve_files_method (files_components , "upload" )
846+ if upload is not None :
847+ result = await _maybe_await (upload (str (local_path ), file_name ))
848+ return _normalize_native_upload_result (result , file_name )
849+ write_bytes = _resolve_files_method (files_components , "write_bytes" )
850+ if write_bytes is not None :
851+ result = await _maybe_await (write_bytes (file_name , local_path .read_bytes ()))
852+ return _normalize_native_upload_result (result , file_name )
805853 if not _is_posix_os_type (self .os_type ):
806854 return _non_posix_filesystem_result (file_name , self .os_type )
807855 result = await _write_base64_via_shell (
0 commit comments