Initial commit of integrated agent system
This commit is contained in:
415
shared/client/openclaw_client.py
Normal file
415
shared/client/openclaw_client.py
Normal file
@@ -0,0 +1,415 @@
|
||||
# -*- 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=<path> — 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()
|
||||
Reference in New Issue
Block a user