- 新增小红书前端模块(启动服务、内容分析、爆款创作、发布记录、技能卡片、拟人设置、AI 助手) - 新增 xhs-agent-desktop 独立桌面 Agent,对接 OMS 任务调度 - 登录页重构,整合微信登录与 OMS JWT 认证 - 订单、渠道、用户中心等页面与 API 增强 Co-authored-by: Cursor <cursoragent@cursor.com>
150 lines
4.7 KiB
Python
150 lines
4.7 KiB
Python
"""OMS 小红书 Agent HTTP 客户端。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
import requests
|
|
|
|
AGENT_VERSION = "1.0.0"
|
|
|
|
|
|
class OmsClientError(RuntimeError):
|
|
"""OMS API 调用失败。"""
|
|
|
|
|
|
class OmsAgentClient:
|
|
"""对接 OMS /xhs/* Agent 接口。"""
|
|
|
|
def __init__(
|
|
self,
|
|
base_url: str,
|
|
token: str,
|
|
device_id: str,
|
|
*,
|
|
timeout_sec: int = 60,
|
|
) -> None:
|
|
self.base_url = base_url.rstrip("/")
|
|
self.device_id = device_id.strip()
|
|
self.timeout_sec = timeout_sec
|
|
self._session = requests.Session()
|
|
self._session.headers.update(
|
|
{
|
|
"Authorization": f"Bearer {token.strip()}",
|
|
"Content-Type": "application/json",
|
|
}
|
|
)
|
|
self._human_config_version = 0
|
|
self._agent_id: int | None = None
|
|
|
|
@property
|
|
def human_config_version(self) -> int:
|
|
return self._human_config_version
|
|
|
|
@property
|
|
def agent_id(self) -> int | None:
|
|
return self._agent_id
|
|
|
|
def set_human_config_version(self, version: int) -> None:
|
|
self._human_config_version = version
|
|
|
|
def register(self, *, device_name: str | None = None, bridge_port: int = 9333) -> dict[str, Any]:
|
|
data = self._post(
|
|
"/xhs/agent/register",
|
|
{
|
|
"deviceId": self.device_id,
|
|
"deviceName": device_name,
|
|
"agentVersion": AGENT_VERSION,
|
|
"bridgePort": bridge_port,
|
|
},
|
|
)
|
|
self._human_config_version = int(data.get("humanConfigVersion") or 0)
|
|
self._agent_id = data.get("agentId")
|
|
return data
|
|
|
|
def heartbeat(
|
|
self,
|
|
*,
|
|
engine_running: bool,
|
|
extension_connected: bool,
|
|
xhs_logged_in: bool,
|
|
last_error: str | None = None,
|
|
) -> dict[str, Any]:
|
|
data = self._post(
|
|
"/xhs/agent/heartbeat",
|
|
{
|
|
"deviceId": self.device_id,
|
|
"engineRunning": engine_running,
|
|
"extensionConnected": extension_connected,
|
|
"xhsLoggedIn": xhs_logged_in,
|
|
"lastError": last_error,
|
|
},
|
|
)
|
|
self._human_config_version = int(data.get("humanConfigVersion") or self._human_config_version)
|
|
return data
|
|
|
|
def claim_tasks(self, *, limit: int = 1) -> list[dict[str, Any]]:
|
|
data = self._post(
|
|
"/xhs/task/claim",
|
|
{"deviceId": self.device_id, "limit": limit},
|
|
)
|
|
items = data.get("items") or []
|
|
if not isinstance(items, list):
|
|
return []
|
|
for item in items:
|
|
version = item.get("humanConfigVersion")
|
|
if version is not None:
|
|
self._human_config_version = int(version)
|
|
return items
|
|
|
|
def report_task(
|
|
self,
|
|
task_id: int,
|
|
*,
|
|
status: str,
|
|
progress_text: str | None = None,
|
|
result: Any = None,
|
|
error_message: str | None = None,
|
|
) -> None:
|
|
payload: dict[str, Any] = {"taskId": task_id, "status": status}
|
|
if progress_text:
|
|
payload["progressText"] = progress_text
|
|
if result is not None:
|
|
payload["result"] = result
|
|
if error_message:
|
|
payload["errorMessage"] = error_message
|
|
self._post("/xhs/task/report", payload)
|
|
|
|
def pull_human_config(self, known_version: int) -> dict[str, Any]:
|
|
return self._post(
|
|
"/xhs/human-config/pull",
|
|
{"deviceId": self.device_id, "knownVersion": known_version},
|
|
)
|
|
|
|
def _post(self, path: str, body: dict[str, Any]) -> dict[str, Any]:
|
|
url = f"{self.base_url}{path}"
|
|
try:
|
|
resp = self._session.post(url, json=body, timeout=self.timeout_sec)
|
|
except requests.RequestException as exc:
|
|
raise OmsClientError(f"请求失败 {path}: {exc}") from exc
|
|
|
|
if resp.status_code >= 400:
|
|
raise OmsClientError(f"HTTP {resp.status_code} {path}: {resp.text[:500]}")
|
|
|
|
try:
|
|
payload = resp.json()
|
|
except ValueError as exc:
|
|
raise OmsClientError(f"非 JSON 响应 {path}") from exc
|
|
|
|
code = payload.get("code", payload.get("Code"))
|
|
if code not in (None, 0, "0", 200, "200"):
|
|
message = payload.get("message") or payload.get("Message") or "业务错误"
|
|
raise OmsClientError(f"{path}: {message}")
|
|
|
|
data = payload.get("data", payload.get("Data"))
|
|
if isinstance(data, dict):
|
|
return data
|
|
if data is None and isinstance(payload, dict) and "code" not in payload and "Code" not in payload:
|
|
return payload
|
|
return data if isinstance(data, dict) else {}
|