refactor: update imports to use provisioning_db and gateway_rpc modules
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"""OpenClaw lifecycle package.
|
||||
|
||||
Import concrete modules directly (for example: ``app.services.openclaw.agent_service``)
|
||||
Import concrete modules directly (for example: ``app.services.openclaw.provisioning_db``)
|
||||
to keep architectural boundaries explicit.
|
||||
"""
|
||||
|
||||
|
||||
@@ -14,11 +14,6 @@ from app.core.agent_tokens import generate_agent_token, hash_agent_token
|
||||
from app.core.auth import AuthContext
|
||||
from app.core.time import utcnow
|
||||
from app.db import crud
|
||||
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
|
||||
from app.integrations.openclaw_gateway import (
|
||||
OpenClawGatewayError,
|
||||
openclaw_call,
|
||||
)
|
||||
from app.models.activity_events import ActivityEvent
|
||||
from app.models.agents import Agent
|
||||
from app.models.approvals import Approval
|
||||
@@ -26,6 +21,8 @@ from app.models.gateways import Gateway
|
||||
from app.models.tasks import Task
|
||||
from app.schemas.gateways import GatewayTemplatesSyncResult
|
||||
from app.services.openclaw.constants import DEFAULT_HEARTBEAT_CONFIG
|
||||
from app.services.openclaw.gateway_rpc import GatewayConfig as GatewayClientConfig
|
||||
from app.services.openclaw.gateway_rpc import OpenClawGatewayError, openclaw_call
|
||||
from app.services.openclaw.provisioning import OpenClawGatewayProvisioner
|
||||
from app.services.openclaw.provisioning_db import (
|
||||
GatewayTemplateSyncOptions,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,8 +14,6 @@ from sqlmodel import col, select
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.time import utcnow
|
||||
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
|
||||
from app.integrations.openclaw_gateway import OpenClawGatewayError, openclaw_call
|
||||
from app.models.agents import Agent
|
||||
from app.models.boards import Board
|
||||
from app.models.gateways import Gateway
|
||||
@@ -34,6 +32,8 @@ from app.services.openclaw.exceptions import (
|
||||
map_gateway_error_message,
|
||||
map_gateway_error_to_http_exception,
|
||||
)
|
||||
from app.services.openclaw.gateway_rpc import GatewayConfig as GatewayClientConfig
|
||||
from app.services.openclaw.gateway_rpc import OpenClawGatewayError, openclaw_call
|
||||
from app.services.openclaw.internal import agent_key, with_coordination_gateway_retry
|
||||
from app.services.openclaw.policies import OpenClawAuthorizationPolicy
|
||||
from app.services.openclaw.provisioning_db import (
|
||||
|
||||
317
backend/app/services/openclaw/gateway_rpc.py
Normal file
317
backend/app/services/openclaw/gateway_rpc.py
Normal file
@@ -0,0 +1,317 @@
|
||||
"""OpenClaw gateway websocket RPC client and protocol constants.
|
||||
|
||||
This is the low-level, DB-free interface for talking to the OpenClaw gateway.
|
||||
Keep gateway RPC protocol details and client helpers here so OpenClaw services
|
||||
operate within a single scope (no `app.integrations.*` plumbing).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from urllib.parse import urlencode, urlparse, urlunparse
|
||||
from uuid import uuid4
|
||||
|
||||
import websockets
|
||||
from websockets.exceptions import WebSocketException
|
||||
|
||||
PROTOCOL_VERSION = 3
|
||||
|
||||
# NOTE: These are the base gateway methods from the OpenClaw gateway repo.
|
||||
# The gateway can expose additional methods at runtime via channel plugins.
|
||||
GATEWAY_METHODS = [
|
||||
"health",
|
||||
"logs.tail",
|
||||
"channels.status",
|
||||
"channels.logout",
|
||||
"status",
|
||||
"usage.status",
|
||||
"usage.cost",
|
||||
"tts.status",
|
||||
"tts.providers",
|
||||
"tts.enable",
|
||||
"tts.disable",
|
||||
"tts.convert",
|
||||
"tts.setProvider",
|
||||
"config.get",
|
||||
"config.set",
|
||||
"config.apply",
|
||||
"config.patch",
|
||||
"config.schema",
|
||||
"exec.approvals.get",
|
||||
"exec.approvals.set",
|
||||
"exec.approvals.node.get",
|
||||
"exec.approvals.node.set",
|
||||
"exec.approval.request",
|
||||
"exec.approval.resolve",
|
||||
"wizard.start",
|
||||
"wizard.next",
|
||||
"wizard.cancel",
|
||||
"wizard.status",
|
||||
"talk.mode",
|
||||
"models.list",
|
||||
"agents.list",
|
||||
"agents.create",
|
||||
"agents.update",
|
||||
"agents.delete",
|
||||
"agents.files.list",
|
||||
"agents.files.get",
|
||||
"agents.files.set",
|
||||
"skills.status",
|
||||
"skills.bins",
|
||||
"skills.install",
|
||||
"skills.update",
|
||||
"update.run",
|
||||
"voicewake.get",
|
||||
"voicewake.set",
|
||||
"sessions.list",
|
||||
"sessions.preview",
|
||||
"sessions.patch",
|
||||
"sessions.reset",
|
||||
"sessions.delete",
|
||||
"sessions.compact",
|
||||
"last-heartbeat",
|
||||
"set-heartbeats",
|
||||
"wake",
|
||||
"node.pair.request",
|
||||
"node.pair.list",
|
||||
"node.pair.approve",
|
||||
"node.pair.reject",
|
||||
"node.pair.verify",
|
||||
"device.pair.list",
|
||||
"device.pair.approve",
|
||||
"device.pair.reject",
|
||||
"device.token.rotate",
|
||||
"device.token.revoke",
|
||||
"node.rename",
|
||||
"node.list",
|
||||
"node.describe",
|
||||
"node.invoke",
|
||||
"node.invoke.result",
|
||||
"node.event",
|
||||
"cron.list",
|
||||
"cron.status",
|
||||
"cron.add",
|
||||
"cron.update",
|
||||
"cron.remove",
|
||||
"cron.run",
|
||||
"cron.runs",
|
||||
"system-presence",
|
||||
"system-event",
|
||||
"send",
|
||||
"agent",
|
||||
"agent.identity.get",
|
||||
"agent.wait",
|
||||
"browser.request",
|
||||
"chat.history",
|
||||
"chat.abort",
|
||||
"chat.send",
|
||||
]
|
||||
|
||||
GATEWAY_EVENTS = [
|
||||
"connect.challenge",
|
||||
"agent",
|
||||
"chat",
|
||||
"presence",
|
||||
"tick",
|
||||
"talk.mode",
|
||||
"shutdown",
|
||||
"health",
|
||||
"heartbeat",
|
||||
"cron",
|
||||
"node.pair.requested",
|
||||
"node.pair.resolved",
|
||||
"node.invoke.request",
|
||||
"device.pair.requested",
|
||||
"device.pair.resolved",
|
||||
"voicewake.changed",
|
||||
"exec.approval.requested",
|
||||
"exec.approval.resolved",
|
||||
]
|
||||
|
||||
GATEWAY_METHODS_SET = frozenset(GATEWAY_METHODS)
|
||||
GATEWAY_EVENTS_SET = frozenset(GATEWAY_EVENTS)
|
||||
|
||||
|
||||
def is_known_gateway_method(method: str) -> bool:
|
||||
"""Return whether a method name is part of the known base gateway methods."""
|
||||
return method in GATEWAY_METHODS_SET
|
||||
|
||||
|
||||
class OpenClawGatewayError(RuntimeError):
|
||||
"""Raised when OpenClaw gateway calls fail."""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GatewayConfig:
|
||||
"""Connection configuration for the OpenClaw gateway."""
|
||||
|
||||
url: str
|
||||
token: str | None = None
|
||||
|
||||
|
||||
def _build_gateway_url(config: GatewayConfig) -> str:
|
||||
base_url: str = (config.url or "").strip()
|
||||
if not base_url:
|
||||
message = "Gateway URL is not configured."
|
||||
raise OpenClawGatewayError(message)
|
||||
token = config.token
|
||||
if not token:
|
||||
return base_url
|
||||
parsed = urlparse(base_url)
|
||||
query = urlencode({"token": token})
|
||||
return str(urlunparse(parsed._replace(query=query)))
|
||||
|
||||
|
||||
async def _await_response(
|
||||
ws: websockets.ClientConnection,
|
||||
request_id: str,
|
||||
) -> object:
|
||||
while True:
|
||||
raw = await ws.recv()
|
||||
data = json.loads(raw)
|
||||
|
||||
if data.get("type") == "res" and data.get("id") == request_id:
|
||||
ok = data.get("ok")
|
||||
if ok is not None and not ok:
|
||||
error = data.get("error", {}).get("message", "Gateway error")
|
||||
raise OpenClawGatewayError(error)
|
||||
return data.get("payload")
|
||||
|
||||
if data.get("id") == request_id:
|
||||
if data.get("error"):
|
||||
message = data["error"].get("message", "Gateway error")
|
||||
raise OpenClawGatewayError(message)
|
||||
return data.get("result")
|
||||
|
||||
|
||||
async def _send_request(
|
||||
ws: websockets.ClientConnection,
|
||||
method: str,
|
||||
params: dict[str, Any] | None,
|
||||
) -> object:
|
||||
request_id = str(uuid4())
|
||||
message = {
|
||||
"type": "req",
|
||||
"id": request_id,
|
||||
"method": method,
|
||||
"params": params or {},
|
||||
}
|
||||
await ws.send(json.dumps(message))
|
||||
return await _await_response(ws, request_id)
|
||||
|
||||
|
||||
def _build_connect_params(config: GatewayConfig) -> dict[str, Any]:
|
||||
params: dict[str, Any] = {
|
||||
"minProtocol": PROTOCOL_VERSION,
|
||||
"maxProtocol": PROTOCOL_VERSION,
|
||||
"client": {
|
||||
"id": "gateway-client",
|
||||
"version": "1.0.0",
|
||||
"platform": "web",
|
||||
"mode": "ui",
|
||||
},
|
||||
}
|
||||
if config.token:
|
||||
params["auth"] = {"token": config.token}
|
||||
return params
|
||||
|
||||
|
||||
async def _ensure_connected(
|
||||
ws: websockets.ClientConnection,
|
||||
first_message: str | bytes | None,
|
||||
config: GatewayConfig,
|
||||
) -> None:
|
||||
if first_message:
|
||||
if isinstance(first_message, bytes):
|
||||
first_message = first_message.decode("utf-8")
|
||||
data = json.loads(first_message)
|
||||
if data.get("type") != "event" or data.get("event") != "connect.challenge":
|
||||
pass
|
||||
connect_id = str(uuid4())
|
||||
response = {
|
||||
"type": "req",
|
||||
"id": connect_id,
|
||||
"method": "connect",
|
||||
"params": _build_connect_params(config),
|
||||
}
|
||||
await ws.send(json.dumps(response))
|
||||
await _await_response(ws, connect_id)
|
||||
|
||||
|
||||
async def openclaw_call(
|
||||
method: str,
|
||||
params: dict[str, Any] | None = None,
|
||||
*,
|
||||
config: GatewayConfig,
|
||||
) -> object:
|
||||
"""Call a gateway RPC method and return the result payload."""
|
||||
gateway_url = _build_gateway_url(config)
|
||||
try:
|
||||
async with websockets.connect(gateway_url, ping_interval=None) as ws:
|
||||
first_message = None
|
||||
try:
|
||||
first_message = await asyncio.wait_for(ws.recv(), timeout=2)
|
||||
except TimeoutError:
|
||||
first_message = None
|
||||
await _ensure_connected(ws, first_message, config)
|
||||
return await _send_request(ws, method, params)
|
||||
except OpenClawGatewayError:
|
||||
raise
|
||||
except (
|
||||
TimeoutError,
|
||||
ConnectionError,
|
||||
OSError,
|
||||
ValueError,
|
||||
WebSocketException,
|
||||
) as exc: # pragma: no cover - network/protocol errors
|
||||
raise OpenClawGatewayError(str(exc)) from exc
|
||||
|
||||
|
||||
async def send_message(
|
||||
message: str,
|
||||
*,
|
||||
session_key: str,
|
||||
config: GatewayConfig,
|
||||
deliver: bool = False,
|
||||
) -> object:
|
||||
"""Send a chat message to a session."""
|
||||
params: dict[str, Any] = {
|
||||
"sessionKey": session_key,
|
||||
"message": message,
|
||||
"deliver": deliver,
|
||||
"idempotencyKey": str(uuid4()),
|
||||
}
|
||||
return await openclaw_call("chat.send", params, config=config)
|
||||
|
||||
|
||||
async def get_chat_history(
|
||||
session_key: str,
|
||||
config: GatewayConfig,
|
||||
limit: int | None = None,
|
||||
) -> object:
|
||||
"""Fetch chat history for a session."""
|
||||
params: dict[str, Any] = {"sessionKey": session_key}
|
||||
if limit is not None:
|
||||
params["limit"] = limit
|
||||
return await openclaw_call("chat.history", params, config=config)
|
||||
|
||||
|
||||
async def delete_session(session_key: str, *, config: GatewayConfig) -> object:
|
||||
"""Delete a session by key."""
|
||||
return await openclaw_call("sessions.delete", {"key": session_key}, config=config)
|
||||
|
||||
|
||||
async def ensure_session(
|
||||
session_key: str,
|
||||
*,
|
||||
config: GatewayConfig,
|
||||
label: str | None = None,
|
||||
) -> object:
|
||||
"""Ensure a session exists and optionally update its label."""
|
||||
params: dict[str, Any] = {"key": session_key}
|
||||
if label:
|
||||
params["label"] = label
|
||||
return await openclaw_call("sessions.patch", params, config=config)
|
||||
@@ -6,7 +6,6 @@ import asyncio
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import TypeVar
|
||||
|
||||
from app.integrations.openclaw_gateway import OpenClawGatewayError
|
||||
from app.services.openclaw.constants import (
|
||||
_COORDINATION_GATEWAY_BASE_DELAY_S,
|
||||
_COORDINATION_GATEWAY_MAX_DELAY_S,
|
||||
@@ -15,6 +14,7 @@ from app.services.openclaw.constants import (
|
||||
_SECURE_RANDOM,
|
||||
_TRANSIENT_GATEWAY_ERROR_MARKERS,
|
||||
)
|
||||
from app.services.openclaw.gateway_rpc import OpenClawGatewayError
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from app.integrations.openclaw_gateway import OpenClawGatewayError
|
||||
from app.models.board_onboarding import BoardOnboardingSession
|
||||
from app.models.boards import Board
|
||||
from app.services.openclaw.coordination_service import AbstractGatewayMessagingService
|
||||
from app.services.openclaw.exceptions import GatewayOperation, map_gateway_error_to_http_exception
|
||||
from app.services.openclaw.gateway_rpc import OpenClawGatewayError
|
||||
from app.services.openclaw.shared import (
|
||||
GatewayAgentIdentity,
|
||||
require_gateway_config_for_board,
|
||||
|
||||
@@ -18,13 +18,6 @@ from uuid import uuid4
|
||||
from jinja2 import Environment, FileSystemLoader, StrictUndefined, select_autoescape
|
||||
|
||||
from app.core.config import settings
|
||||
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
|
||||
from app.integrations.openclaw_gateway import (
|
||||
OpenClawGatewayError,
|
||||
ensure_session,
|
||||
openclaw_call,
|
||||
send_message,
|
||||
)
|
||||
from app.models.agents import Agent
|
||||
from app.models.boards import Board
|
||||
from app.models.gateways import Gateway
|
||||
@@ -40,6 +33,13 @@ from app.services.openclaw.constants import (
|
||||
MAIN_TEMPLATE_MAP,
|
||||
PRESERVE_AGENT_EDITABLE_FILES,
|
||||
)
|
||||
from app.services.openclaw.gateway_rpc import GatewayConfig as GatewayClientConfig
|
||||
from app.services.openclaw.gateway_rpc import (
|
||||
OpenClawGatewayError,
|
||||
ensure_session,
|
||||
openclaw_call,
|
||||
send_message,
|
||||
)
|
||||
from app.services.openclaw.internal import agent_key as _agent_key
|
||||
from app.services.openclaw.shared import GatewayAgentIdentity
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,14 +11,6 @@ from uuid import UUID
|
||||
from fastapi import HTTPException, status
|
||||
from sqlmodel import col
|
||||
|
||||
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
|
||||
from app.integrations.openclaw_gateway import (
|
||||
OpenClawGatewayError,
|
||||
ensure_session,
|
||||
get_chat_history,
|
||||
openclaw_call,
|
||||
send_message,
|
||||
)
|
||||
from app.models.agents import Agent
|
||||
from app.models.boards import Board
|
||||
from app.models.gateways import Gateway
|
||||
@@ -30,6 +22,14 @@ from app.schemas.gateway_api import (
|
||||
GatewaySessionsResponse,
|
||||
GatewaysStatusResponse,
|
||||
)
|
||||
from app.services.openclaw.gateway_rpc import GatewayConfig as GatewayClientConfig
|
||||
from app.services.openclaw.gateway_rpc import (
|
||||
OpenClawGatewayError,
|
||||
ensure_session,
|
||||
get_chat_history,
|
||||
openclaw_call,
|
||||
send_message,
|
||||
)
|
||||
from app.services.openclaw.policies import OpenClawAuthorizationPolicy
|
||||
from app.services.organizations import require_board_access
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ from uuid import UUID, uuid4
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
from app.integrations.openclaw_gateway import GatewayConfig as _GatewayClientConfig
|
||||
from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message
|
||||
from app.models.boards import Board
|
||||
from app.models.gateways import Gateway
|
||||
from app.services.openclaw.constants import (
|
||||
@@ -17,6 +15,8 @@ from app.services.openclaw.constants import (
|
||||
_GATEWAY_AGENT_SUFFIX,
|
||||
_GATEWAY_OPENCLAW_AGENT_PREFIX,
|
||||
)
|
||||
from app.services.openclaw.gateway_rpc import GatewayConfig as _GatewayClientConfig
|
||||
from app.services.openclaw.gateway_rpc import OpenClawGatewayError, ensure_session, send_message
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
Reference in New Issue
Block a user