# -*- coding: utf-8 -*- """OpenClaw service client — typed async wrapper for openclaw-service REST API.""" from __future__ import annotations import httpx from shared.models.openclaw import ( AgentsList, ApprovalRequest, ApprovalsList, CronJob, CronList, DaemonStatus, HookStatusReport, ModelAliasesList, ModelFallbacksList, ModelsList, OpenClawStatus, PairingListResponse, QrCodeResponse, SecretsAuditReport, SecurityAuditResponse, SessionEntry, SessionHistory, SessionsList, SkillStatusReport, SkillUpdateResult, UpdateStatusResponse, normalize_agents, normalize_approvals, normalize_cron_jobs, normalize_daemon_status, normalize_hooks, normalize_model_aliases, normalize_model_fallbacks, normalize_models, normalize_pairing, normalize_qr, normalize_security_audit, normalize_secrets_audit, normalize_session_history, normalize_sessions, normalize_skill_update, normalize_skills, normalize_status, normalize_update_status, normalize_plugins, PluginsList, ) class OpenClawServiceClient: """Async client for the openclaw-service API surface. All methods return typed Pydantic models. The raw JSON dict is accessible via the `.model_dump()` method on each result. Example:: async with OpenClawServiceClient() as client: status = await client.fetch_status() print(status.runtime_version) # typed print(status.model_dump()["runtimeVersion"]) # raw dict """ def __init__(self, base_url: str = "http://localhost:8004/api/openclaw"): self.base_url = base_url.rstrip("/") self._client: httpx.AsyncClient | None = None async def __aenter__(self) -> "OpenClawServiceClient": self._client = httpx.AsyncClient(base_url=self.base_url, timeout=30.0) return self async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: if self._client: await self._client.aclose() async def fetch_status(self) -> OpenClawStatus: """GET /status — returns parsed OpenClawStatus model.""" response = await self._client.get("/status") response.raise_for_status() raw = response.json() return normalize_status(raw) async def list_sessions(self) -> SessionsList: """GET /sessions — returns parsed SessionsList model.""" response = await self._client.get("/sessions") response.raise_for_status() raw = response.json() return normalize_sessions(raw) async def get_session(self, session_key: str) -> SessionEntry: """GET /sessions/{session_key} — returns parsed SessionEntry model.""" response = await self._client.get(f"/sessions/{session_key}") response.raise_for_status() raw = response.json() session = raw.get("session") or {} return SessionEntry.model_validate(session, strict=False) async def get_session_history(self, session_key: str, *, limit: int = 20) -> SessionHistory: """GET /sessions/{session_key}/history — returns parsed SessionHistory model.""" response = await self._client.get( f"/sessions/{session_key}/history", params={"limit": limit}, ) response.raise_for_status() raw = response.json() return normalize_session_history(raw, session_key=session_key) async def list_cron_jobs(self) -> CronList: """GET /cron — returns parsed CronList model.""" response = await self._client.get("/cron") response.raise_for_status() raw = response.json() return normalize_cron_jobs(raw) async def list_approvals(self) -> ApprovalsList: """GET /approvals — returns parsed ApprovalsList model.""" response = await self._client.get("/approvals") response.raise_for_status() raw = response.json() return normalize_approvals(raw) async def list_agents(self) -> AgentsList: """GET /agents — returns parsed AgentsList model.""" response = await self._client.get("/agents") response.raise_for_status() raw = response.json() return normalize_agents(raw) async def list_skills(self) -> SkillStatusReport: """GET /skills — returns parsed SkillStatusReport model.""" response = await self._client.get("/skills") response.raise_for_status() raw = response.json() return normalize_skills(raw) async def list_models(self) -> ModelsList: """GET /models — returns parsed ModelsList model.""" response = await self._client.get("/models") response.raise_for_status() raw = response.json() return normalize_models(raw) async def list_hooks(self) -> HookStatusReport: response = await self._client.get("/hooks") response.raise_for_status() raw = response.json() return normalize_hooks(raw) async def list_plugins(self) -> PluginsList: response = await self._client.get("/plugins") response.raise_for_status() raw = response.json() return normalize_plugins(raw) async def secrets_audit(self) -> SecretsAuditReport: response = await self._client.get("/secrets-audit") response.raise_for_status() raw = response.json() return normalize_secrets_audit(raw) async def security_audit(self) -> SecurityAuditResponse: response = await self._client.get("/security-audit") response.raise_for_status() raw = response.json() return normalize_security_audit(raw) async def daemon_status(self) -> DaemonStatus: response = await self._client.get("/daemon-status") response.raise_for_status() raw = response.json() return normalize_daemon_status(raw) async def pairing_list(self) -> PairingListResponse: response = await self._client.get("/pairing") response.raise_for_status() raw = response.json() return normalize_pairing(raw) async def qr_code(self) -> QrCodeResponse: response = await self._client.get("/qr") response.raise_for_status() raw = response.json() return normalize_qr(raw) async def update_status(self) -> UpdateStatusResponse: response = await self._client.get("/update-status") response.raise_for_status() raw = response.json() return normalize_update_status(raw) async def list_model_aliases(self) -> ModelAliasesList: response = await self._client.get("/models-aliases") response.raise_for_status() raw = response.json() return normalize_model_aliases(raw) async def list_model_fallbacks(self) -> ModelFallbacksList: response = await self._client.get("/models-fallbacks") response.raise_for_status() raw = response.json() return normalize_model_fallbacks(raw) async def list_model_image_fallbacks(self) -> ModelFallbacksList: response = await self._client.get("/models-image-fallbacks") response.raise_for_status() raw = response.json() return normalize_model_fallbacks(raw) async def skill_update(self, *, slug: str | None = None, all: bool = False) -> SkillUpdateResult: params = {} if slug is not None: params["slug"] = slug if all: params["all"] = "true" response = await self._client.get("/skill-update", params=params) response.raise_for_status() raw = response.json() return normalize_skill_update(raw) async def models_status(self, *, probe: bool = False) -> dict[str, Any]: """GET /models-status — returns parsed models status dict.""" params = {"probe": "true"} if probe else {} response = await self._client.get("/models-status", params=params) response.raise_for_status() return response.json() async def channels_status(self, *, probe: bool = False) -> dict[str, Any]: """GET /channels-status — returns parsed channels status dict.""" params = {"probe": "true"} if probe else {} response = await self._client.get("/channels-status", params=params) response.raise_for_status() return response.json() async def channels_list(self) -> dict[str, Any]: """GET /channels-list — returns parsed channels list dict.""" response = await self._client.get("/channels-list") response.raise_for_status() return response.json() async def hook_info(self, name: str) -> dict[str, Any]: """GET /hooks/info/{name} — returns parsed hook info dict.""" response = await self._client.get(f"/hooks/info/{name}") response.raise_for_status() return response.json() async def hooks_check(self) -> dict[str, Any]: """GET /hooks/check — returns parsed hooks check dict.""" response = await self._client.get("/hooks/check") response.raise_for_status() return response.json() async def plugins_inspect(self, *, plugin_id: str | None = None, all: bool = False) -> dict[str, Any]: """GET /plugins-inspect — returns parsed plugins inspect dict.""" params: dict[str, Any] = {} if all: params["all"] = "true" elif plugin_id: params["plugin_id"] = plugin_id response = await self._client.get("/plugins-inspect", params=params) response.raise_for_status() return response.json() async def agents_bindings(self, *, agent: str | None = None) -> dict[str, Any]: """GET /agents-bindings — returns parsed agents bindings list dict.""" params: dict[str, Any] = {} if agent: params["agent"] = agent response = await self._client.get("/agents-bindings", params=params) response.raise_for_status() return response.json() async def agents_presence(self) -> dict[str, Any]: """GET /agents/presence — returns runtime session presence for all agents.""" response = await self._client.get("/agents/presence") response.raise_for_status() return response.json() async def workspace_files(self, workspace_path: str) -> dict[str, Any]: """GET /workspace-files?workspace= — list .md files in a workspace.""" response = await self._client.get("/workspace-files", params={"workspace": workspace_path}) response.raise_for_status() return response.json() # ------------------------------------------------------------------------- # Write agents operations # ------------------------------------------------------------------------- async def agents_add( self, name: str, *, workspace: str | None = None, model: str | None = None, agent_dir: str | None = None, bind: list[str] | None = None, non_interactive: bool = False, ) -> dict[str, Any]: """POST /agents/add — create a new agent.""" params: dict[str, Any] = {"name": name} if workspace: params["workspace"] = workspace if model: params["model"] = model if agent_dir: params["agent_dir"] = agent_dir if bind: params["bind"] = bind if non_interactive: params["non_interactive"] = "true" response = await self._client.post("/agents/add", params=params) response.raise_for_status() return response.json() async def agents_delete(self, id: str, *, force: bool = False) -> dict[str, Any]: """POST /agents/delete/{id} — delete an agent.""" params: dict[str, Any] = {} if force: params["force"] = "true" response = await self._client.post(f"/agents/delete/{id}", params=params) response.raise_for_status() return response.json() async def agents_bind( self, *, agent: str | None = None, bind: list[str] | None = None, ) -> dict[str, Any]: """POST /agents/bind — add routing bindings to an agent.""" params: dict[str, Any] = {} if agent: params["agent"] = agent if bind: params["bind"] = bind response = await self._client.post("/agents/bind", params=params) response.raise_for_status() return response.json() async def agents_unbind( self, *, agent: str | None = None, bind: list[str] | None = None, all: bool = False, ) -> dict[str, Any]: """POST /agents/unbind — remove routing bindings from an agent.""" params: dict[str, Any] = {} if agent: params["agent"] = agent if bind: params["bind"] = bind if all: params["all"] = "true" response = await self._client.post("/agents/unbind", params=params) response.raise_for_status() return response.json() async def agents_set_identity( self, *, agent: str | None = None, workspace: str | None = None, identity_file: str | None = None, name: str | None = None, emoji: str | None = None, theme: str | None = None, avatar: str | None = None, from_identity: bool = False, ) -> dict[str, Any]: """POST /agents/set-identity — update agent identity.""" params: dict[str, Any] = {} if agent: params["agent"] = agent if workspace: params["workspace"] = workspace if identity_file: params["identity_file"] = identity_file if name: params["name"] = name if emoji: params["emoji"] = emoji if theme: params["theme"] = theme if avatar: params["avatar"] = avatar if from_identity: params["from_identity"] = "true" response = await self._client.post("/agents/set-identity", params=params) response.raise_for_status() return response.json() async def gateway_status(self, *, url: str | None = None, token: str | None = None) -> dict[str, Any]: """GET /gateway-status — returns parsed gateway status dict.""" params: dict[str, Any] = {} if url: params["url"] = url if token: params["token"] = token response = await self._client.get("/gateway-status", params=params) response.raise_for_status() return response.json() async def memory_status(self, *, agent: str | None = None, deep: bool = False) -> list[dict[str, Any]]: """GET /memory-status — returns list of per-agent memory status dicts.""" params: dict[str, Any] = {} if agent: params["agent"] = agent if deep: params["deep"] = "true" response = await self._client.get("/memory-status", params=params) response.raise_for_status() return response.json()