1717from astrbot .core .astr_agent_context import AstrAgentContext
1818from astrbot .core .astr_main_agent_resources import (
1919 BACKGROUND_TASK_RESULT_WOKE_SYSTEM_PROMPT ,
20+ EXECUTE_SHELL_TOOL ,
21+ FILE_DOWNLOAD_TOOL ,
22+ FILE_UPLOAD_TOOL ,
23+ LOCAL_EXECUTE_SHELL_TOOL ,
24+ LOCAL_PYTHON_TOOL ,
25+ PYTHON_TOOL ,
2026 SEND_MESSAGE_TO_USER_TOOL ,
2127)
2228from astrbot .core .cron .events import CronMessageEvent
@@ -91,6 +97,65 @@ async def _run_in_background() -> None:
9197 yield r
9298 return
9399
100+ @classmethod
101+ def _get_runtime_computer_tools (cls , runtime : str ) -> dict [str , FunctionTool ]:
102+ if runtime == "sandbox" :
103+ return {
104+ EXECUTE_SHELL_TOOL .name : EXECUTE_SHELL_TOOL ,
105+ PYTHON_TOOL .name : PYTHON_TOOL ,
106+ FILE_UPLOAD_TOOL .name : FILE_UPLOAD_TOOL ,
107+ FILE_DOWNLOAD_TOOL .name : FILE_DOWNLOAD_TOOL ,
108+ }
109+ if runtime == "local" :
110+ return {
111+ LOCAL_EXECUTE_SHELL_TOOL .name : LOCAL_EXECUTE_SHELL_TOOL ,
112+ LOCAL_PYTHON_TOOL .name : LOCAL_PYTHON_TOOL ,
113+ }
114+ return {}
115+
116+ @classmethod
117+ def _build_handoff_toolset (
118+ cls ,
119+ run_context : ContextWrapper [AstrAgentContext ],
120+ tools : list [str | FunctionTool ] | None ,
121+ ) -> ToolSet | None :
122+ ctx = run_context .context .context
123+ event = run_context .context .event
124+ cfg = ctx .get_config (umo = event .unified_msg_origin )
125+ provider_settings = cfg .get ("provider_settings" , {})
126+ runtime = str (provider_settings .get ("computer_use_runtime" , "local" ))
127+ runtime_computer_tools = cls ._get_runtime_computer_tools (runtime )
128+
129+ # Keep persona semantics aligned with the main agent: tools=None means
130+ # "all tools", including runtime computer-use tools.
131+ if tools is None :
132+ toolset = ToolSet ()
133+ for registered_tool in llm_tools .func_list :
134+ if isinstance (registered_tool , HandoffTool ):
135+ continue
136+ if registered_tool .active :
137+ toolset .add_tool (registered_tool )
138+ for runtime_tool in runtime_computer_tools .values ():
139+ toolset .add_tool (runtime_tool )
140+ return None if toolset .empty () else toolset
141+
142+ if not tools :
143+ return None
144+
145+ toolset = ToolSet ()
146+ for tool_name_or_obj in tools :
147+ if isinstance (tool_name_or_obj , str ):
148+ registered_tool = llm_tools .get_func (tool_name_or_obj )
149+ if registered_tool and registered_tool .active :
150+ toolset .add_tool (registered_tool )
151+ continue
152+ runtime_tool = runtime_computer_tools .get (tool_name_or_obj )
153+ if runtime_tool :
154+ toolset .add_tool (runtime_tool )
155+ elif isinstance (tool_name_or_obj , FunctionTool ):
156+ toolset .add_tool (tool_name_or_obj )
157+ return None if toolset .empty () else toolset
158+
94159 @classmethod
95160 async def _execute_handoff (
96161 cls ,
@@ -101,19 +166,8 @@ async def _execute_handoff(
101166 input_ = tool_args .get ("input" )
102167 image_urls = tool_args .get ("image_urls" )
103168
104- # make toolset for the agent
105- tools = tool .agent .tools
106- if tools :
107- toolset = ToolSet ()
108- for t in tools :
109- if isinstance (t , str ):
110- _t = llm_tools .get_func (t )
111- if _t :
112- toolset .add_tool (_t )
113- elif isinstance (t , FunctionTool ):
114- toolset .add_tool (t )
115- else :
116- toolset = None
169+ # Build handoff toolset from registered tools plus runtime computer tools.
170+ toolset = cls ._build_handoff_toolset (run_context , tool .agent .tools )
117171
118172 ctx = run_context .context .context
119173 event = run_context .context .event
0 commit comments