# Tool handler extracted from app.py import json import inspect from .implementations import record_user_details, record_resume_gap class ToolHandler: """Handles tool execution with resilient error handling""" def __init__(self, pushover_service=None): self.pushover_service = pushover_service # Tool implementations with dependency injection self.tool_impl = { "record_user_details": lambda **kwargs: record_user_details(**kwargs, pushover_service=self.pushover_service), "record_resume_gap": lambda **kwargs: record_resume_gap(**kwargs, pushover_service=self.pushover_service), } def _safe_parse_args(self, raw): """Safely parse tool arguments from various formats""" # Some SDKs already hand a dict; otherwise be forgiving with JSON if isinstance(raw, dict): return raw try: return json.loads(raw or "{}") except Exception: try: return json.loads((raw or "{}").replace("'", '"')) except Exception: print(f"[WARN] Unable to parse tool args: {raw}", flush=True) return {} def handle_tool_calls(self, tool_calls): """Execute tool calls and return results""" results = [] for tool_call in tool_calls: tool_name = tool_call.function.name raw_args = tool_call.function.arguments or "{}" print(f"[TOOL] {tool_name} args (raw): {raw_args}", flush=True) args = self._safe_parse_args(raw_args) impl = self.tool_impl.get(tool_name) if not impl: print(f"[WARN] Unknown tool: {tool_name}", flush=True) results.append({ "role": "tool", "content": json.dumps({"error": f"unknown tool {tool_name}"}), "tool_call_id": tool_call.id }) continue try: out = impl(**args) except TypeError as e: # Model sent unexpected params; retry with filtered args sig = inspect.signature(impl) filtered = {k: v for k, v in args.items() if k in sig.parameters} try: out = impl(**filtered) except Exception as e2: print(f"[ERROR] Tool '{tool_name}' failed: {e2}", flush=True) out = {"error": "tool execution failed"} except Exception as e: print(f"[ERROR] Tool '{tool_name}' crashed: {e}", flush=True) out = {"error": "tool execution crashed"} results.append({ "role": "tool", "content": json.dumps(out), "tool_call_id": tool_call.id }) return results