from __future__ import annotations import json import mimetypes import sys import threading from http import HTTPStatus from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path from typing import Any, Callable from urllib.parse import parse_qs, urlparse StatusProvider = Callable[[], dict[str, Any]] def _json_bytes(payload: Any) -> bytes: return json.dumps(payload, ensure_ascii=False, indent=2).encode("utf-8") def make_handler(status_provider: StatusProvider): class AgentLocalServiceHandler(BaseHTTPRequestHandler): server_version = "SystemTestAgentLocalService/0.1" def log_message(self, fmt: str, *args: Any) -> None: sys.stdout.write( "%s - - [%s] %s\n" % (self.client_address[0], self.log_date_time_string(), fmt % args) ) def _send_json(self, status: int, payload: Any) -> None: body = _json_bytes(payload) self.send_response(status) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Content-Length", str(len(body))) self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Access-Control-Allow-Headers", "Content-Type, Authorization") self.send_header("Access-Control-Allow-Methods", "GET, OPTIONS") self.end_headers() self.wfile.write(body) def _send_file(self, file_path: Path) -> None: body = file_path.read_bytes() content_type = mimetypes.guess_type(str(file_path))[0] or "application/octet-stream" self.send_response(HTTPStatus.OK) self.send_header("Content-Type", content_type) self.send_header("Content-Length", str(len(body))) self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() self.wfile.write(body) def _send_screenshot(self, raw_path: str) -> None: status = status_provider() memory_path = ((status.get("agent_core") or {}).get("memory_path") or "") if not memory_path: self._send_json(HTTPStatus.NOT_FOUND, {"ok": False, "error": "Memory path is not available"}) return screenshot_root = (Path(memory_path).parent / "screenshots").resolve() requested = Path(raw_path).resolve() try: requested.relative_to(screenshot_root) except ValueError: self._send_json(HTTPStatus.FORBIDDEN, {"ok": False, "error": "Screenshot path is outside evidence directory"}) return if not requested.exists() or not requested.is_file(): self._send_json(HTTPStatus.NOT_FOUND, {"ok": False, "error": "Screenshot not found"}) return self._send_file(requested) def do_OPTIONS(self) -> None: self._send_json(HTTPStatus.OK, {"ok": True}) def do_GET(self) -> None: parsed = urlparse(self.path) path = parsed.path if path in {"/", "/health"}: self._send_json(HTTPStatus.OK, status_provider()) return if path == "/agent/tools": status = status_provider() self._send_json( HTTPStatus.OK, { "ok": True, "tools": ((status.get("agent_core") or {}).get("tools") or []), }, ) return if path == "/agent/memory": status = status_provider() agent_core = status.get("agent_core") or {} self._send_json( HTTPStatus.OK, { "ok": True, "memory_path": agent_core.get("memory_path"), "memory": agent_core.get("memory"), }, ) return if path == "/agent/screenshot": query = parse_qs(parsed.query) raw_path = (query.get("path") or [""])[0] if not raw_path: self._send_json(HTTPStatus.BAD_REQUEST, {"ok": False, "error": "Missing path"}) return self._send_screenshot(raw_path) return self._send_json( HTTPStatus.NOT_FOUND, {"ok": False, "error": "Route not found"}, ) return AgentLocalServiceHandler class LocalService: def __init__(self, host: str, port: int, status_provider: StatusProvider): self.host = host self.port = port self._server = ThreadingHTTPServer((host, port), make_handler(status_provider)) self._thread = threading.Thread(target=self._server.serve_forever, daemon=True) def start(self) -> None: self._thread.start() def stop(self) -> None: self._server.shutdown() self._server.server_close() self._thread.join(timeout=2)