""" Executes tool calls returned by the model. The `ctx` dict carries runtime state: chat_id, send_fn, etc. """ import json import asyncio from tools import github_tool, search_tool async def execute(tool_name: str, tool_args: dict, ctx: dict) -> str: """ Run a tool and return its string result. ctx must contain: chat_id, send_fn (async callable) """ try: match tool_name: case "web_search": query = tool_args["query"] max_results = tool_args.get("max_results", 5) result = await asyncio.to_thread( search_tool.search, query, max_results ) # Auto-save to research/ await asyncio.to_thread( github_tool.save_research, query, result ) return result case "github_read": path = tool_args["path"] return await asyncio.to_thread(github_tool.read_file, path) case "github_write": path = tool_args["path"] content = tool_args["content"] msg = tool_args.get("commit_message", "") sha = await asyncio.to_thread( github_tool.write_file, path, content, msg ) return f"Written to {path} (commit {sha[:7]})" case "github_list": folder = tool_args["folder"] files = await asyncio.to_thread(github_tool.list_files, folder) return "\n".join(files) if files else "(empty)" case "quick_reply": message = tool_args["message"] send_fn = ctx.get("send_fn") if send_fn: await send_fn(f"⚡ {message}") return f"Quick reply sent: {message}" case _: return f"Unknown tool: {tool_name}" except Exception as e: return f"Tool error ({tool_name}): {e}" def parse_tool_calls(response_message) -> list[dict]: """ Extract tool calls from a model response message. Returns list of dicts: {id, name, args} """ calls = [] if not hasattr(response_message, "tool_calls") or not response_message.tool_calls: return calls for tc in response_message.tool_calls: try: args = json.loads(tc.function.arguments) except (json.JSONDecodeError, AttributeError): args = {} calls.append({ "id": tc.id, "name": tc.function.name, "args": args }) return calls def tool_result_message(call_id: str, tool_name: str, result: str) -> dict: """Format a tool result as a message for the model's context.""" return { "role": "tool", "tool_call_id": call_id, "name": tool_name, "content": result }