Add bridge API server (GET /session-id, POST /send) on port 8082

This commit is contained in:
Lucy
2026-05-16 12:14:25 +02:00
parent 751584c99d
commit 52b1b59264
4 changed files with 126 additions and 1 deletions

117
api_server.py Normal file
View File

@@ -0,0 +1,117 @@
"""Simple HTTP API for the Matrix Bridge.
Provides endpoints for agents and scheduled tasks to interact
with the bridge without going through the Matrix protocol.
Endpoints:
GET /session-id — Return the current bridge session ID
POST /send — Send a text message to the configured Matrix room
"""
from __future__ import annotations
import json
import logging
import threading
from http.server import HTTPServer, BaseHTTPRequestHandler
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
from bridge import Bridge
log = logging.getLogger("matrix-bridge.api")
class _Handler(BaseHTTPRequestHandler):
"""HTTP handler that delegates to a shared bridge reference."""
# Set once before the server starts — shared across all requests.
bridge: Optional["Bridge"] = None
# ── Helpers ───────────────────────────────────────────────────────
def _send_json(self, status: int, data: dict) -> None:
"""Send a JSON response with the given status code."""
body = json.dumps(data).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def _read_body(self) -> Optional[dict]:
"""Read and parse the request body as JSON."""
try:
length = int(self.headers.get("Content-Length", 0))
except (ValueError, TypeError):
return None
if length <= 0:
return None
raw = self.rfile.read(length)
try:
return json.loads(raw)
except json.JSONDecodeError:
return None
# ── Routes ────────────────────────────────────────────────────────
def do_GET(self) -> None:
if self.path == "/session-id":
if self.bridge is None:
self._send_json(503, {"error": "Bridge not initialized"})
return
sid = self.bridge.state.session_id
self._send_json(200, {"session_id": sid})
else:
self._send_json(404, {"error": f"Not found: {self.path}"})
def do_POST(self) -> None:
if self.path == "/send":
if self.bridge is None:
self._send_json(503, {"error": "Bridge not initialized"})
return
body = self._read_body()
if body is None:
self._send_json(400, {"error": "Invalid or missing JSON body"})
return
message = body.get("message")
if not message or not isinstance(message, str):
self._send_json(400, {"error": "Missing or invalid 'message' field"})
return
success = self.bridge.matrix.send_message(message)
if success:
self._send_json(200, {"ok": True})
else:
self._send_json(500, {"error": "Failed to send Matrix message"})
else:
self._send_json(404, {"error": f"Not found: {self.path}"})
# ── Silence default logging ───────────────────────────────────────
def log_message(self, format: str, *args) -> None:
log.debug(f"HTTP: {format % args}")
def start_api_server(
bridge: "Bridge", host: str = "127.0.0.1", port: int = 8082
) -> None:
"""Start the API HTTP server in a background daemon thread.
The server runs until the main process exits. It provides a
lightweight JSON API on ``host:port`` for other components to
query the bridge state or trigger Matrix messages.
"""
_Handler.bridge = bridge
server = HTTPServer((host, port), _Handler)
server.timeout = 1.0 # Allows clean shutdown without dedicated stop
thread = threading.Thread(
target=server.serve_forever,
name=f"api-{host}:{port}",
daemon=True,
)
thread.start()
log.info(f"API server listening on http://{host}:{port}")

View File

@@ -16,6 +16,8 @@
"agent_retries": 3,
"max_message_length": 40000,
"processed_ids_limit": 200,
"agent_response_timeout": 300
"agent_response_timeout": 300,
"api_host": "127.0.0.1",
"api_port": 8082
}
}

View File

@@ -37,6 +37,8 @@ class BridgeConfig:
max_message_length: int = 40000
processed_ids_limit: int = 200
agent_response_timeout: int = 300
api_host: str = "127.0.0.1"
api_port: int = 8082
@dataclass

View File

@@ -20,6 +20,7 @@ import sys
from config import Config
from bridge import Bridge
from api_server import start_api_server
def setup_logging(debug: bool = False) -> None:
@@ -61,6 +62,9 @@ def main() -> None:
# Create and run the bridge
bridge = Bridge(config)
# Start the API server (daemon thread, non-blocking)
start_api_server(bridge, config.bridge.api_host, config.bridge.api_port)
# Verify agent API is reachable
if bridge.agent._request("GET", "/api/sessions") is not None:
log.info("Agent API is reachable")