Newer
Older
navi-1 / navi / tools / http_request.py
"""HTTP request tool — make outbound HTTP calls."""

import json

import httpx

from .base import Tool, ToolResult

_TIMEOUT = 30.0


class HttpRequestTool(Tool):
    name = "http_request"
    description = (
        "Make an HTTP request to an external URL. "
        "Supports GET, POST, PUT, PATCH, DELETE. Returns status code and response body."
    )
    parameters = {
        "type": "object",
        "properties": {
            "method": {
                "type": "string",
                "enum": ["GET", "POST", "PUT", "PATCH", "DELETE"],
                "description": "HTTP method",
            },
            "url": {"type": "string", "description": "Full URL to request"},
            "headers": {
                "type": "object",
                "description": "Optional HTTP headers as key-value pairs",
            },
            "body": {
                "type": "object",
                "description": "Optional JSON body for POST/PUT/PATCH requests",
            },
            "params": {
                "type": "object",
                "description": "Optional query parameters",
            },
        },
        "required": ["method", "url"],
    }

    async def execute(self, params: dict) -> ToolResult:
        method = params["method"].upper()
        url = params["url"]
        headers = params.get("headers") or {}
        body = params.get("body")
        query_params = params.get("params")

        try:
            async with httpx.AsyncClient(timeout=_TIMEOUT, follow_redirects=True) as client:
                response = await client.request(
                    method=method,
                    url=url,
                    headers=headers,
                    json=body,
                    params=query_params,
                )

            # Try to decode as JSON for nicer output
            try:
                body_repr = json.dumps(response.json(), ensure_ascii=False, indent=2)
            except Exception:
                body_repr = response.text[:4096]  # cap large responses

            output = f"Status: {response.status_code}\n\n{body_repr}"
            return ToolResult(
                success=response.is_success,
                output=output,
                metadata={"status_code": response.status_code, "headers": dict(response.headers)},
                error=None if response.is_success else f"HTTP {response.status_code}",
            )
        except httpx.TimeoutException:
            return ToolResult(success=False, output=f"Request timed out after {_TIMEOUT}s", error="timeout")
        except Exception as e:
            return ToolResult(success=False, output=f"Request failed: {e}", error=str(e))