oclaw/interfaces/ws/runtime_helpers.py
oliver ba3836f00f 初始化:独立 oclaw 仓库首提交
- 在 oclaw/ 下重新初始化 Git 仓库
- 补齐子仓库 .gitignore,避免提交本地运行态数据(_local、node_modules、logs 等)
- 提交当前工程代码与配置

Made-with: Cursor
2026-04-24 22:31:22 +08:00

137 lines
4.7 KiB
Python

from __future__ import annotations
import asyncio
import json
from typing import Any
from starlette.websockets import WebSocketDisconnect
async def recv_frame(
*,
conn: Any,
preauth: bool,
handshake_timeout_ms: int,
validate_or_errors: Any,
format_validation_errors: Any,
error_shape: Any,
) -> dict[str, Any] | None:
try:
if preauth:
raw = await asyncio.wait_for(conn.ws.receive_text(), timeout=handshake_timeout_ms / 1000.0)
else:
raw = await conn.ws.receive_text()
except asyncio.TimeoutError:
conn.handshake_failed = True
return None
except WebSocketDisconnect:
return None
try:
frame = json.loads(raw)
except Exception:
await conn.send_res("invalid", ok=False, error=error_shape("INVALID_REQUEST", "invalid json"))
if preauth:
conn.handshake_failed = True
return {}
frame_errs = validate_or_errors(conn.schemas.frame, frame)
if frame_errs:
fid = str(frame.get("id") or "invalid") if isinstance(frame, dict) else "invalid"
await conn.send_res(
fid,
ok=False,
error=error_shape("INVALID_REQUEST", f"invalid frame: {format_validation_errors(frame_errs)}"),
)
if preauth:
conn.handshake_failed = True
return {}
return frame if isinstance(frame, dict) else {}
async def handle_connect(
*,
conn: Any,
req_id: str,
method: str,
params: Any,
protocol_version: int,
validate_or_errors: Any,
format_validation_errors: Any,
error_shape: Any,
) -> None:
if method != "connect":
await conn.send_res(req_id, ok=False, error=error_shape("INVALID_REQUEST", "first request must be connect"))
conn.handshake_failed = True
return
p_errs = validate_or_errors(conn.schemas.connect_params, params)
if p_errs:
await conn.send_res(
req_id,
ok=False,
error=error_shape("INVALID_REQUEST", f"invalid connect params: {format_validation_errors(p_errs)}"),
)
conn.handshake_failed = True
return
p = dict(params or {})
device = p.get("device") if isinstance(p.get("device"), dict) else None
if device is not None:
nonce = str(device.get("nonce") or "").strip()
if not nonce or nonce != conn.connect_nonce:
await conn.send_res(
req_id,
ok=False,
error=error_shape("INVALID_REQUEST", "device nonce mismatch", details={"code": "DEVICE_NONCE_MISMATCH"}),
)
conn.handshake_failed = True
return
min_protocol = int(p.get("minProtocol") or 0)
max_protocol = int(p.get("maxProtocol") or 0)
if max_protocol < protocol_version or min_protocol > protocol_version:
await conn.send_res(
req_id,
ok=False,
error=error_shape("INVALID_REQUEST", "protocol mismatch", details={"expectedProtocol": protocol_version}),
)
conn.handshake_failed = True
return
hello = conn.build_hello_ok(params)
hello_errs = validate_or_errors(conn.schemas.hello_ok, hello)
if hello_errs:
await conn.send_res(
req_id,
ok=False,
error=error_shape("UNAVAILABLE", f"server hello-ok schema mismatch: {format_validation_errors(hello_errs)}"),
)
conn.handshake_failed = True
return
conn.connected = True
conn.client_meta = dict(params or {})
try:
c = (conn.client_meta or {}).get("client") if isinstance((conn.client_meta or {}).get("client"), dict) else {}
mode = str(c.get("mode") or "").strip().lower()
cid = str(c.get("id") or "").strip().lower()
conn._is_webchat_client = mode == "webchat" or cid in ("webchat-ui", "webchat", "control-ui-webchat")
except Exception:
conn._is_webchat_client = False
conn.role = str((params or {}).get("role") or "operator")
conn.scopes = list((params or {}).get("scopes") or [])
conn.auth_ctx = conn.resolve_ws_auth(params)
auth = p.get("auth") if isinstance(p.get("auth"), dict) else {}
auth_provided = bool(
str(auth.get("token") or "").strip()
or str(auth.get("password") or "").strip()
or str(auth.get("bootstrapToken") or "").strip()
or str(auth.get("deviceToken") or "").strip()
)
if auth_provided and not conn.auth_ctx:
await conn.send_res(
req_id,
ok=False,
error=error_shape("INVALID_REQUEST", "unauthorized", details={"code": "AUTH_UNAUTHORIZED"}),
)
conn.handshake_failed = True
return
await conn.send_res(req_id, ok=True, payload=hello)
__all__ = ["recv_frame", "handle_connect"]