2222"""
2323
2424import asyncio
25+ import base64
2526import logging
2627import os
28+ import shlex
2729import subprocess
2830import sys
2931import time
@@ -306,10 +308,14 @@ def _resolve_tools(self) -> List:
306308 tools = []
307309 compute_bridged_tools = {"execute_command" , "read_file" , "write_file" , "list_files" }
308310
311+ try :
312+ from praisonaiagents import tools as tool_module
313+ except ImportError :
314+ tool_module = None
315+
309316 for name in resolved_names :
310317 try :
311- from praisonaiagents import tools as tool_module
312- func = getattr (tool_module , name , None )
318+ func = getattr (tool_module , name , None ) if tool_module else None
313319 if func is not None :
314320 # Bridge shell-based tools to compute when available
315321 if self ._compute and name in compute_bridged_tools :
@@ -345,17 +351,12 @@ def _create_compute_bridge_tool(self, tool_name: str, original_func: Callable) -
345351 environment instead of on the host.
346352 """
347353 import inspect
348- import asyncio
349354
350355 def compute_bridged_tool (* args , ** kwargs ):
351356 """Compute-bridged tool wrapper."""
352357 # Auto-provision compute if needed
353358 if self ._compute_instance_id is None :
354- try :
355- loop = asyncio .get_event_loop ()
356- loop .run_until_complete (self .provision_compute ())
357- except RuntimeError :
358- asyncio .run (self .provision_compute ())
359+ self ._run_async_safe (self .provision_compute ())
359360
360361 if tool_name == "execute_command" :
361362 # For execute_command, directly route to compute
@@ -364,15 +365,9 @@ def compute_bridged_tool(*args, **kwargs):
364365 return "Error: No command specified"
365366
366367 try :
367- try :
368- loop = asyncio .get_event_loop ()
369- result = loop .run_until_complete (
370- self ._compute .execute (self ._compute_instance_id , command )
371- )
372- except RuntimeError :
373- result = asyncio .run (
374- self ._compute .execute (self ._compute_instance_id , command )
375- )
368+ result = self ._run_async_safe (
369+ self ._compute .execute (self ._compute_instance_id , command )
370+ )
376371
377372 # Format result similar to local execute_command
378373 if result .get ("exit_code" , 0 ) == 0 :
@@ -406,42 +401,35 @@ def compute_bridged_tool(*args, **kwargs):
406401
407402 def _bridge_file_tool (self , tool_name : str , * args , ** kwargs ) -> str :
408403 """Bridge file operations to compute environment."""
409- import asyncio
410404
411405 if tool_name == "read_file" :
412406 filepath = args [0 ] if args else kwargs .get ("filepath" , "" )
413407 if not filepath :
414408 return "Error: No filepath specified"
415409
416- command = f'cat " { filepath } " '
410+ command = f'cat { shlex . quote ( filepath ) } '
417411
418412 elif tool_name == "write_file" :
419413 filepath = args [0 ] if args else kwargs .get ("filepath" , "" )
420414 content = args [1 ] if len (args ) > 1 else kwargs .get ("content" , "" )
421415 if not filepath :
422416 return "Error: No filepath specified"
423417
424- # Escape content for shell
425- import shlex
426- command = f'cat > " { filepath } " << "EOF" \n { content } \n EOF '
418+ # Use printf with base64 encoding to safely handle any content
419+ content_b64 = base64 . b64encode ( content . encode ( 'utf-8' )). decode ( 'ascii' )
420+ command = f'echo { shlex . quote ( content_b64 ) } | base64 -d > { shlex . quote ( filepath ) } '
427421
428422 elif tool_name == "list_files" :
429423 directory = args [0 ] if args else kwargs .get ("directory" , "." )
430- command = f'ls -la " { directory } " '
424+ command = f'ls -la { shlex . quote ( directory ) } '
431425
432426 else :
433427 return f"Error: Unsupported bridged tool: { tool_name } "
434428
435429 try :
436- try :
437- loop = asyncio .get_event_loop ()
438- result = loop .run_until_complete (
439- self ._compute .execute (self ._compute_instance_id , command )
440- )
441- except RuntimeError :
442- result = asyncio .run (
443- self ._compute .execute (self ._compute_instance_id , command )
444- )
430+ result = self ._run_async_safe (
431+ self ._compute .execute (self ._compute_instance_id , command )
432+ )
445433
446434 if result .get ("exit_code" , 0 ) == 0 :
447435 return result .get ("stdout" , "" )
@@ -514,6 +502,30 @@ def _persist_state(self) -> None:
514502 except Exception as e :
515503 logger .debug ("[local_managed] _persist_state failed: %s" , e )
516504
505+ def _run_async_safe (self , coroutine ):
506+ """Run a coroutine safely, handling various event loop scenarios.
507+
508+ This method handles:
509+ - No event loop (creates one with asyncio.run)
510+ - Event loop exists but not running (use run_until_complete)
511+ - Event loop exists and is running (raises clear error)
512+ """
513+ try :
514+ loop = asyncio .get_event_loop ()
515+ if loop .is_running ():
516+ raise RuntimeError (
517+ "Cannot run async operation from within a running event loop. "
518+ "This operation must be called from a synchronous context."
519+ )
520+ return loop .run_until_complete (coroutine )
521+ except RuntimeError as e :
522+ if "no current event loop" in str (e ).lower () or "no running event loop" in str (e ).lower ():
523+ # No event loop exists, create one
524+ return asyncio .run (coroutine )
525+ else :
526+ # Re-raise other RuntimeErrors (like "event loop is running")
527+ raise
528+
517529 def _restore_state (self ) -> None :
518530 """Restore all mutable state from the session store.
519531
@@ -623,30 +635,16 @@ def _install_packages_in_compute(self, pip_pkgs: List[str]) -> None:
623635
624636 # Auto-provision compute if not done yet
625637 if self ._compute_instance_id is None :
626- try :
627- import asyncio
628- loop = asyncio .get_event_loop ()
629- loop .run_until_complete (self .provision_compute ())
630- except RuntimeError :
631- # No event loop, create one
632- asyncio .run (self .provision_compute ())
638+ self ._run_async_safe (self .provision_compute ())
633639
634- pip_cmd = "python -m pip install -q " + " " .join (f'" { pkg } "' for pkg in pip_pkgs )
640+ pip_cmd = "python -m pip install -q " + " " .join (shlex . quote ( pkg ) for pkg in pip_pkgs )
635641 logger .info ("[local_managed] installing pip packages in compute: %s" , pip_pkgs )
636642
637643 try :
638644 # Run installation synchronously in compute
639- import asyncio
640- try :
641- loop = asyncio .get_event_loop ()
642- result = loop .run_until_complete (
643- self ._compute .execute (self ._compute_instance_id , pip_cmd , timeout = 120 )
644- )
645- except RuntimeError :
646- # No event loop, create one
647- result = asyncio .run (
648- self ._compute .execute (self ._compute_instance_id , pip_cmd , timeout = 120 )
649- )
645+ result = self ._run_async_safe (
646+ self ._compute .execute (self ._compute_instance_id , pip_cmd , timeout = 120 )
647+ )
650648
651649 if result .get ("exit_code" , 0 ) == 0 :
652650 logger .info ("[local_managed] compute pip install completed" )
0 commit comments