node/client.py
node/client.pyBrowse 18 files
688 tokens
2,921 bytes
Token encoding: o200k_base
Snapshot 24fd22b
← Back to SKILL.md
1"""Gateway-side RPC client for a remote meet node: one short-lived sync WebSocket per call, so2non-async tool handlers need no persistent connection. ``websockets`` is imported lazily."""3 4from __future__ import annotations5 6from typing import Any, Dict, Optional7 8from plugins.google_meet.node import protocol as _proto9 10 11class NodeClient:12 """Thin synchronous WS client matching the server's request surface."""13 14 def __init__(self, url: str, token: str, timeout: float = 10.0) -> None:15 if not isinstance(url, str) or not url:16 raise ValueError("url must be a non-empty string")17 if not isinstance(token, str) or not token:18 raise ValueError("token must be a non-empty string")19 self.url = url20 self.token = token21 self.timeout = float(timeout)22 23 def _rpc(self, type: str, payload: Dict[str, Any]) -> Dict[str, Any]:24 """Send one request, return its payload dict; RuntimeError on error envelope / id mismatch."""25 try:26 from websockets.sync.client import connect # type: ignore27 except ImportError as exc:28 raise RuntimeError("NodeClient requires the 'websockets' package. "29 "Install it with: pip install websockets") from exc30 req = _proto.make_request(type, self.token, payload)31 with connect(self.url, open_timeout=self.timeout, close_timeout=self.timeout) as ws:32 ws.send(_proto.encode(req))33 resp = _proto.decode(ws.recv(timeout=self.timeout))34 if resp.get("type") == "error":35 raise RuntimeError(f"node error: {resp.get('error', '<unknown>')}")36 if resp.get("id") != req["id"]:37 raise RuntimeError(f"response id mismatch: sent {req['id']}, got {resp.get('id')!r}")38 payload_out = resp.get("payload")39 if not isinstance(payload_out, dict): # pong envelopes also carry a dict payload40 raise RuntimeError("response missing payload dict")41 return payload_out42 43 def start_bot(self, url: str, guest_name: str = "Hermes Agent", duration: Optional[str] = None,44 headed: bool = False, mode: str = "transcribe") -> Dict[str, Any]:45 payload: Dict[str, Any] = {"url": url, "guest_name": guest_name, "headed": bool(headed), "mode": mode}46 if duration is not None:47 payload["duration"] = duration48 return self._rpc("start_bot", payload)49 50 def stop(self) -> Dict[str, Any]:51 return self._rpc("stop", {})52 53 def status(self) -> Dict[str, Any]:54 return self._rpc("status", {})55 56 def transcript(self, last: Optional[int] = None) -> Dict[str, Any]:57 return self._rpc("transcript", {} if last is None else {"last": int(last)})58 59 def say(self, text: str) -> Dict[str, Any]:60 return self._rpc("say", {"text": str(text)})61 62 def ping(self) -> Dict[str, Any]:63 return self._rpc("ping", {})64