Skip to content

Commit 0b46ca7

Browse files
authored
feat: enable computer-use tools for subagent handoff (#5399)
1 parent 9294b44 commit 0b46ca7

File tree

1 file changed

+67
-13
lines changed

1 file changed

+67
-13
lines changed

astrbot/core/astr_agent_tool_exec.py

Lines changed: 67 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717
from astrbot.core.astr_agent_context import AstrAgentContext
1818
from 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
)
2228
from 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

Comments
 (0)