(tool: Tool, tool_input: dict[str, Any], context: ToolContext)
| 531 | |
| 532 | |
| 533 | async def _call_tool(tool: Tool, tool_input: dict[str, Any], context: ToolContext) -> Any: |
| 534 | from src.tool_system.protocol import ToolResult |
| 535 | |
| 536 | call_fn = tool.call |
| 537 | import asyncio |
| 538 | import inspect |
| 539 | |
| 540 | if inspect.iscoroutinefunction(call_fn): |
| 541 | result = await call_fn(tool_input, context) |
| 542 | else: |
| 543 | # Offload sync tools to a worker thread so they don't block the |
| 544 | # event loop. Without this, a long Bash command holds the loop |
| 545 | # for its entire duration — ESC key presses and abort_controller |
| 546 | # listeners can't fire because no other task gets to run. Mirrors |
| 547 | # the responsiveness TS gets "for free" from Node's |
| 548 | # callback-driven I/O. |
| 549 | result = await asyncio.to_thread(call_fn, tool_input, context) |
| 550 | |
| 551 | if not isinstance(result, ToolResult): |
| 552 | result = ToolResult(name=tool.name, output=result) |
| 553 | |
| 554 | return result |
| 555 | |
| 556 | |
| 557 | async def _resolve_permission( |
no test coverage detected