feat(tools): add OpenClaw remote agent tool integration

- Detect x-openclaw flag in OpenAPI schema and init dedicated config
- Implement multimodal input/output (image download, compress, base64)
- Add OpenClaw connection test and status validation in tool service
- Fix auth_config token check to support both api_key and bearer_token
- Inject runtime context (user_id, conversation_id, files) in chat services
This commit is contained in:
miao
2026-04-07 10:35:14 +08:00
parent 460c86cd94
commit e298b38de9
4 changed files with 467 additions and 6 deletions

View File

@@ -330,6 +330,20 @@ class ToolService:
if config.tool_type == ToolType.MCP.value:
return await self._test_mcp_connection(config)
elif config.tool_type == ToolType.CUSTOM.value:
# ========== 测试工具连接 OpenClaw 特判 ==========
custom_config = self.custom_repo.find_by_tool_id(self.db, config.id)
if custom_config and custom_config.schema_content:
schema = custom_config.schema_content
if isinstance(schema, str):
try:
schema = json.loads(schema)
except json.JSONDecodeError:
schema = {}
#请求头中包含OpenClaw字段
if isinstance(schema, dict) and schema.get("info", {}).get("x-openclaw"):
return await self._test_openclaw_connection(custom_config, schema)
# ========== OpenClaw 特判结束 ==========
#正常自定义工具逻辑
return await self._test_custom_connection(config)
elif config.tool_type == ToolType.BUILTIN.value:
return await self._test_builtin_connection(config)
@@ -339,6 +353,45 @@ class ToolService:
except Exception as e:
return {"success": False, "message": f"测试失败: {str(e)}"}
#=============测试openclaw连接 特判===============
async def _test_openclaw_connection(
self, custom_config: CustomToolConfig, schema: dict
) -> Dict[str, Any]:
"""测试 OpenClaw 连接"""
try:
info = schema.get("info", {})
servers = schema.get("servers", [])
base_url = servers[0]["url"] if servers else ""
token = (custom_config.auth_config or {}).get("token", "")
agent_id = info.get("x-openclaw-agent-id", "main")
model = info.get("x-openclaw-default-model", "openclaw")
url = f"{base_url.rstrip('/')}/v1/responses"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"x-openclaw-agent-id": agent_id
}
body = {
"model": model,
"user": "connection-test",
"input": "hi",
"stream": False
}
timeout_config = aiohttp.ClientTimeout(total=30)
async with aiohttp.ClientSession(timeout=timeout_config) as session:
async with session.post(url, json=body, headers=headers) as resp:
if resp.status < 400:
return {"success": True, "message": "OpenClaw 连接成功"}
error_text = await resp.text()
return {
"success": False,
"message": f"OpenClaw HTTP {resp.status}: {error_text[:200]}"
}
except Exception as e:
return {"success": False, "message": f"OpenClaw 连接失败: {str(e)}"}
#=============测试openclaw连接结束===========
def ensure_builtin_tools_initialized(self, tenant_id: uuid.UUID):
"""确保内置工具已初始化"""
existing = self.tool_repo.exists_builtin_for_tenant(self.db, tenant_id)
@@ -1139,6 +1192,27 @@ class ToolService:
custom_config = self.db.query(CustomToolConfig).filter(
CustomToolConfig.id == tool_config.id
).first()
# ========== 更新工具 OpenClaw 特判 ==========
if custom_config and custom_config.schema_content:
schema = custom_config.schema_content
if isinstance(schema, str):
try:
schema = json.loads(schema)
except json.JSONDecodeError:
schema = {}
info = schema.get("info", {}) if isinstance(schema, dict) else {}
if info.get("x-openclaw"):
servers = schema.get("servers", [])
has_url = bool(servers and servers[0].get("url"))
has_agent_id = bool(info.get("x-openclaw-agent-id"))
has_token = bool(custom_config.auth_config
and custom_config.auth_config.get("api_key"))
if has_url and has_agent_id and has_token:
tool_config.status = ToolStatus.AVAILABLE.value
else:
tool_config.status = ToolStatus.UNCONFIGURED.value
return
# ========== OpenClaw 特判结束 ==========
if custom_config and tool_config.name and (custom_config.schema_content or custom_config.schema_url):
tool_config.status = ToolStatus.AVAILABLE.value